Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove pyplot strong dependency #38

Merged
merged 8 commits into from Feb 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
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
@@ -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
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
@@ -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
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