Skip to content

Commit

Permalink
Merge pull request #38 from peterkovesi/remove_pyplot
Browse files Browse the repository at this point in the history
Remove pyplot strong dependency
  • Loading branch information
peterkovesi committed Feb 9, 2024
2 parents 5d63fda + 5084e4c commit c9f9f37
Show file tree
Hide file tree
Showing 9 changed files with 624 additions and 407 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:
fail-fast: false
matrix:
version:
- '1.4' # Min Julia version supported
- '1.9' # Min Julia version supported
- '1' # Expands to latest version
os:
- ubuntu-latest
Expand Down
12 changes: 9 additions & 3 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "ImageProjectiveGeometry"
uuid = "b9d14576-938f-5430-9d4c-b7d7de1409d6"
author = ["Peter Kovesi <peter.kovesi@gmail.com>"]
version = "0.3.6"
version = "0.4.0"

[deps]
DSP = "717857b8-e6f2-59f4-9121-6e50c889abd2"
Expand All @@ -11,16 +11,22 @@ Images = "916415d5-f1e6-5110-898d-aaa5f9f070e0"
Interpolations = "a98d9a8b-a2ab-59e6-89dd-64a1c18fca59"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
PyPlot = "d330b81b-6aea-500a-939a-2ce795aea3ee"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
Requires = "ae029012-a4dd-5104-9daa-d747884805df"
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[weakdeps]
PyPlot = "d330b81b-6aea-500a-939a-2ce795aea3ee"

[extensions]
ImageProjectiveGeometryPyPlotExt = "PyPlot"

[compat]
DSP = "0.5 - 0.7"
FileIO = "1"
ImageFiltering = "0.4 - 0.7"
Images = "0.20 - 0.25"
Interpolations = "0.10 - 0.14"
PyPlot = "2.5 - 2.11"
julia = "1.4"
julia = "1.9"
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ pkg> add ImageProjectiveGeometry
help?> ImageProjectiveGeometry # Lists a summary of the package functions
```

On Julia versions >=1.9, the `PyPlot` package is not added automatically anymore, but used as a weak dependency. To get the plotting functionality back, import `PyPlot` together with `ImageProjectiveGeometry` like this:

```Julia
using PyPlot
using ImageProjectiveGeometry
```

## Summary

This Image Projective Geometry package is intended as a starting point for the development of a library of projective geometry functions for computer vision in Julia.
Expand Down
196 changes: 196 additions & 0 deletions ext/ImageProjectiveGeometryPyPlotExt.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
module ImageProjectiveGeometryPyPlotExt
using LinearAlgebra
using Random
using PyPlot
using ImageProjectiveGeometry

include("ransacdemo.jl")

function plot_briefcoords(S, nPairs, rc)
figure(200); clf
R = (S.-1)/2 .+ 1
axis([-R, R, -R, R])

for n = 1:nPairs
plot(rc[2, 2*n-1:2*n], rc[1, 2*n-1:2*n], color = rand(3),
linewidth = 3)
end

axis("equal")

# Determine distances between pairs and display histogram
sdist = zeros(nPairs)
for n = 1:nPairs
sdist[n] = norm(rc[:,2*n-1] - rc[:,2*n])
end

(e, counts) = hist(sdist, 0:.5:S)
e = e .+ 0.5
figure(30); clf()
PyPlot.bar(x=e, height = counts[2:end], width=e[2]-e[1])
title("Histogram of distances between pairs")
end

function plot_nonmaxsuppts(fig, img, c, r, cols, rows)
# If an image has been supplied display it and overlay corners.
if !isempty(img)
print("Plotting corners")
PyPlot.figure(fig)
PyPlot.clf()
PyPlot.imshow(img)
PyPlot.set_cmap(PyPlot.ColorMap("gray"))
PyPlot.plot(c,r,"r+")
PyPlot.axis([1,cols,rows,1])
PyPlot.title("Corners detected")
end
end


"""
hline - Plot a 2D line defined in homogeneous coordinates.
Function for ploting 2D homogeneous lines defined by 2 points
or a line defined by a single homogeneous vector
```
Usage 1: hline(p1,p2, linestyle="b-")
Arguments: p1, p2 - Two 3-vectors defining points in homogeneous
coordinates
Usage 2: hline(l, linestyle="b-")
Argument: l - A 3-vector defining a line in homogeneous coordinates
```
Note that in the case where a homogeneous line is supplied as the
argument the extent of the line drawn depends on the current axis
limits. This will require you to set the desired limits with a call
to PyPlot.axis() prior to calling this function.
"""
function hline(p1i::Vector, p2i::Vector, linestyle::String="b-")
# Case when 2 homogeneous points are supplied

p1 = p1i./p1i[3] # make sure homogeneous points lie in z=1 plane
p2 = p2i./p2i[3]

# hold(true)
plot([p1[1], p2[1]], [p1[2], p2[2]], linestyle);
end

# Case when homogeneous line is supplied
function hline(li::Vector, linestyle::String="b-")

l = li./li[3] # ensure line in z = 1 plane (not needed?)

if abs(l[1]) > abs(l[2]) # line is more vertical
p1 = hcross(l, [0, -1, PyPlot.ylim()[1]])
p2 = hcross(l, [0, -1, PyPlot.ylim()[2]])

else # line more horizontal
p1 = hcross(l, [-1, 0, PyPlot.xlim()[1]])
p2 = hcross(l, [-1, 0, PyPlot.xlim()[2]])
end

# hold(true)
plot([p1[1], p2[1]], [p1[2], p2[2]], linestyle);
end


"""
plotcamera - Plots graphical representation of camera(s) showing pose.
```
Usage: plotcamera(C, l; col=[0,0,1], plotCamPath=false, fig=nothing)
Arguments:
C - Camera structure (or array of Camera structures)
l - The length of the sides of the rectangular cone indicating
the camera's field of view.
Keyword Arguments:
col - Optional three element vector specifying the RGB colour to
use. Defaults to blue.
plotCamPath - Optional flag true/false to plot line joining camera centre
positions. If omitted or empty defaults to false.
fig - Optional figure number to be used. If not specified a new
figure is created.
```
The function plots into the current figure a graphical representation of one
or more cameras showing their pose. This consists of a rectangular cone,
with its vertex at the camera centre, indicating the camera's field of view.
The camera's coordinate X and Y axes are also plotted at the camera centre.
See also: Camera
"""
function plotcamera(Ci, l, col=[0,0,1], plotCamPath=false, fig=nothing)

if isa(Ci, Array)
C = Ci
else
C = [Ci]
end

figure(fig)
for i = 1:eachindex(C)

if C[i].rows == 0 || C[i].cols == 0
@warn("Camera rows and cols not specified")
continue
end

f = C[i].fx # Use fx as the focal length

if i > 1 && plotCamPath
plot3D([C[i-1].P[1], C[i].P[1]],
[C[i-1].P(2), C[i].P[2]],
[C[i-1].P(3), C[i].P[3]])
end

# Construct transform from camera coordinates to world coords
Tw_c = [C[i].Rc_w' C[i].P
0 0 0 1 ]

# Generate the 4 viewing rays that emanate from the principal point and
# pass through the corners of the image.
ray = zeros(3,4)
ray[:,1] = [-C[i].cols/2, -C[i].rows/2, f]
ray[:,2] = [ C[i].cols/2, -C[i].rows/2, f]
ray[:,3] = [ C[i].cols/2, C[i].rows/2, f]
ray[:,4] = [-C[i].cols/2, C[i].rows/2, f]

# Scale rays to distance l from the focal plane and make homogeneous
ray = makehomogeneous(ray*l/f)
ray = Tw_c*ray # Transform to world coords

for n = 1:4 # Draw the rays
plot3D([C[i].P[1], ray[1,n]],
[C[i].P[2], ray[2,n]],
[C[i].P[3], ray[3,n]],
color=col)
end

# Draw rectangle joining ends of rays
plot3D([ray[1,1], ray[1,2], ray[1,3], ray[1,4], ray[1,1]],
[ray[2,1], ray[2,2], ray[2,3], ray[2,4], ray[2,1]],
[ray[3,1], ray[3,2], ray[3,3], ray[3,4], ray[3,1]],
color=col)

# Draw and label axes
X = Tw_c[1:3,1]*l .+ C[i].P
Y = Tw_c[1:3,2]*l .+ C[i].P
Z = Tw_c[1:3,3]*l .+ C[i].P

plot3D([C[i].P[1], X[1,1]], [C[i].P[2], X[2,1]], [C[i].P[3], X[3,1]],
color=col)
plot3D([C[i].P[1], Y[1,1]], [C[i].P[2], Y[2,1]], [C[i].P[3], Y[3,1]],
color=col)
# plot3D([C[i].P[1], Z(1,1)], [C[i].P[2], Z(2,1)], [C[i].P[3], Z(3,1)],...
# color=col)
text3D(X[1], X[2], X[3], "X", color=col)
text3D(Y[1], Y[2], Y[3], "Y", color=col)

end

end

end # module

37 changes: 15 additions & 22 deletions src/ransacdemo.jl → ext/ransacdemo.jl
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,6 @@ PK March 2016
---------------------------------------------------------------------=#

export fitlinedemo, fitplanedemo
export fitfunddemo, fithomogdemo

using ImageProjectiveGeometry, PyPlot, Printf, FileIO

#-----------------------------------------------------------------------
"""
fitlinedemo - Demonstrates RANSAC line fitting.
Expand Down Expand Up @@ -96,7 +90,7 @@ function fitlinedemo(outliers, sigma, t::Real, feedback::Bool = false)
(V, P, inliers) = ransacfitline(XYZ, t, feedback)

if feedback
@printf("Number of Inliers: %d\n", length(inliers))
println("Number of Inliers: $(length(inliers))")
end

# Plot the inlier points blue, with the outlier points in red.
Expand Down Expand Up @@ -189,10 +183,10 @@ function fitplanedemo(outliers, sigma, t, feedback::Bool = false)
(Bfitted, P, inliers) = ransacfitplane(XYZ, t, feedback)

Bfitted = Bfitted/Bfitted[4]
@printf("Original plane coefficients: ")
@printf("%8.3f %8.3f %8.3f %8.3f \n",B[1], B[2], B[3], B[4])
@printf("Fitted plane coefficients: ")
@printf("%8.3f %8.3f %8.3f %8.3f \n",Bfitted[1], Bfitted[2], Bfitted[3], Bfitted[4])
print("Original plane coefficients: ")
print("%8.3f %8.3f %8.3f %8.3f \n",B[1], B[2], B[3], B[4])
print("Fitted plane coefficients: ")
print("%8.3f %8.3f %8.3f %8.3f \n",Bfitted[1], Bfitted[2], Bfitted[3], Bfitted[4])

# Display the triangular patch formed by the 3 points that gave the
# plane of maximum consensus
Expand All @@ -201,8 +195,8 @@ function fitplanedemo(outliers, sigma, t, feedback::Bool = false)
plot3D(pts[:,1], pts[:,2], pts[:,3], "k-")
# hold(false)

@printf("\nRotate image so that the triangular patch is seen edge on\n")
@printf("These are the points that form the plane of max consensus.\n\n")
print("\nRotate image so that the triangular patch is seen edge on\n")
print("These are the points that form the plane of max consensus.\n\n")

end

Expand Down Expand Up @@ -246,7 +240,7 @@ function fitfunddemo(img1=[], img2=[])
figure(2); axis("off");
keypause()

@printf("Matching features...\n")
print("Matching features...\n")
(m1,m2) = matchbycorrelation(copy(img1), [r1';c1'], copy(img2), [r2';c2'], w, dmax)

# Display putative matches
Expand All @@ -271,9 +265,9 @@ function fitfunddemo(img1=[], img2=[])
(F, inliers) = ransacfitfundmatrix(x1, x2, t, true)
# (F, inliers) = ransacfitaffinefundmatrix(x1, x2, t, true)

@printf("Number of inliers was %d (%d%%) \n",
print("Number of inliers was %d (%d%%) \n",
length(inliers),round(Int, 100*length(inliers)/nMatch))
@printf("Number of putative matches was %d \n", nMatch)
print("Number of putative matches was %d \n", nMatch)

# Display both images overlayed with inlying matched feature points
figure(4); clf(); imshow(img1); axis("off") # hold(true)
Expand All @@ -285,7 +279,7 @@ function fitfunddemo(img1=[], img2=[])
title("Inlying matches")
# hold(false)

@printf("Step through each epipolar line [y/n]?\n")
print("Step through each epipolar line [y/n]?\n")
response = readline()
if response[1] == 'n'
return
Expand Down Expand Up @@ -320,7 +314,7 @@ function fitfunddemo(img1=[], img2=[])
keypause()
end

@printf(" \n")
print(" \n")

end

Expand Down Expand Up @@ -357,7 +351,7 @@ function fithomogdemo(img1=[], img2=[])
(cim1, r1, c1) = shi_tomasi(img1, 1, radius=nonmaxrad, N=100, img=img1, fig=1)
(cim2, r2, c2) = shi_tomasi(img2, 1, radius=nonmaxrad, N=100, img=img2, fig=2)
keypause()
@printf("Matching features...\n")
print("Matching features...\n")
(m1,m2) = matchbycorrelation(img1, [r1';c1'], img2, [r2';c2'], w, dmax)

# Display putative matches
Expand All @@ -377,9 +371,9 @@ function fithomogdemo(img1=[], img2=[])
t = .001 # Distance threshold for deciding outliers
(H, inliers) = ransacfithomography(x1, x2, t)

@printf("Number of inliers was %d (%d%%) \n",
print("Number of inliers was %d (%d%%) \n",
length(inliers),round(Int, 100*length(inliers)/nMatch))
@printf("Number of putative matches was %d \n", nMatch)
print("Number of putative matches was %d \n", nMatch)

# Display both images overlayed with inlying matched feature points
figure(4); clf(); imshow(img1); # hold(true)
Expand All @@ -390,6 +384,5 @@ function fithomogdemo(img1=[], img2=[])
end

title("Inlying matches")
# hold(false)
end

Loading

4 comments on commit c9f9f37

@PaulDebus
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator register()

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request created: JuliaRegistries/General/101515

Tip: Release Notes

Did you know you can add release notes too? Just add markdown formatted text underneath the comment after the text
"Release notes:" and it will be added to the registry PR, and if TagBot is installed it will also be added to the
release that TagBot creates. i.e.

@JuliaRegistrator register

Release notes:

## Breaking changes

- blah

To add them here just re-invoke and the PR will be updated.

Tagging

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v0.4.0 -m "<description of version>" c9f9f374b03da032df9a144966dd8f30485e1eb5
git push origin v0.4.0

@PaulDebus
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator register ()

Release notes:

This release makes use of the Package Extension mechanism introduced in Julia 1.9 to load the plotting code only if the PyPlot is also loaded. This reduces dependencies, especially for depending packages, and improves loading times.

The previous behavior can be restored using

using PyPlot
using ImageProjectiveGeometry

This rework also opens the door for different plotting backends, for example based on Makie.

Breaking Changes

  • Bump minimum Julia version to 1.9
  • Make PyPlot a weak dependency and move the plotting into a package extension

Non-breaking changes

  • Visibility computation in the cameraproject function now includes checking that visible points are in front of the camera 3d45596
  • CI now checks Julia versions 1.9 and latest 5084e4c

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request updated: JuliaRegistries/General/101515

Tagging

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v0.4.0 -m "<description of version>" c9f9f374b03da032df9a144966dd8f30485e1eb5
git push origin v0.4.0

Please sign in to comment.