Skip to content

Commit 9bd856a

Browse files
v0.3.0: allow users to choose between CPU and GPU (#45)
Also consolidated test/Project.toml into src/Project.toml
1 parent 5a1ad85 commit 9bd856a

File tree

10 files changed

+185
-28
lines changed

10 files changed

+185
-28
lines changed

NEWS.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
# version 3.0
2+
3+
## Breaking changes
4+
5+
- RegisterQD now requires an extra step because of enhancements that support either CPU and GPU processing. For CPU processing (formerly the only option), now you must manually load the [RegisterMismatch package](https://github.com/HolyLab/RegisterMismatch.jl): `using RegisterMismatch, RegisterQD`. For GPU processing, you should instead load the [RegisterMismatchCuda package](https://github.com/HolyLab/RegisterMismatchCuda.jl): `using RegisterMismatchCuda, RegisterQD`. *Note that loading both mismatch packages in the same session will cause method conflicts.* Both mismatch packages are registered in the publicly-available [HolyLabRegistry](https://github.com/HolyLab/HolyLabRegistry), and users are advised to add that registry.
6+
17
# version 0.2
28

39
## Breaking changes

Project.toml

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name = "RegisterQD"
22
uuid = "ac24ea0c-1830-11e9-18d4-81f172323054"
3-
version = "0.2.7"
3+
version = "0.3.0"
44

55
[deps]
66
CenterIndexedArrays = "46a7138f-0d70-54e1-8ada-fb8296f91f24"
@@ -14,13 +14,17 @@ PaddedViews = "5432bcbf-9aad-5242-b902-cca2824c8663"
1414
QuadDIRECT = "dae52e8d-d666-5120-a592-9e15c33b8d7a"
1515
RegisterCore = "67712758-55e7-5c3c-8e85-dda1d7758434"
1616
RegisterDeformation = "c19381b7-cf49-59d7-881c-50dfbd227eaf"
17-
RegisterMismatch = "3c0dd727-6833-5f5d-a1e8-c0d421935c74"
17+
RegisterMismatchCommon = "abb2e897-52bf-5d28-a379-6ca321e3b878"
1818
Rotations = "6038ab10-8711-5258-84ad-4b1120ba62dc"
1919
StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
2020

2121
[compat]
22+
AxisArrays = "0.3, 0.4"
2223
CenterIndexedArrays = "0.2"
2324
CoordinateTransformations = "0.5, 0.6"
25+
Distributions = "0.20, 0.21, 0.22, 0.23, 0.24, 0.25"
26+
ImageMagick = "0.7, 1"
27+
ImageMetadata = "0.9"
2428
Images = "0.20, 0.21, 0.22, 0.23, 0.24, 0.25"
2529
Interpolations = "0.12, 0.13"
2630
MappedArrays = "0.2, 0.3, 0.4"
@@ -32,17 +36,21 @@ RegisterDeformation = "0.3, 0.4"
3236
RegisterMismatch = "0.3, 0.4"
3337
Rotations = "0.12, 0.13, 1"
3438
StaticArrays = "0.11, 0.12, 1"
39+
TestImages = "0.5, 0.6, 1"
40+
Unitful = "0.17, 0.18, 1"
3541
julia = "1"
3642

3743
[extras]
3844
AxisArrays = "39de3d68-74b9-583c-8d2d-e117c070f3a9"
3945
Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f"
4046
ImageMagick = "6218d12a-5da1-5696-b52f-db25d2ecc6d1"
4147
ImageMetadata = "bc367c6b-8a6b-528e-b4bd-a4b897500b49"
48+
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
4249
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
50+
RegisterMismatch = "3c0dd727-6833-5f5d-a1e8-c0d421935c74"
4351
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
4452
TestImages = "5e47fb64-e119-507b-a336-dd2b206d9990"
4553
Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d"
4654

4755
[targets]
48-
test = ["Test", "ImageMagick", "TestImages", "Random", "AxisArrays", "ImageMetadata", "Unitful", "Distributions"]
56+
test = ["Test", "ImageMagick", "TestImages", "Random", "AxisArrays", "ImageMetadata", "Unitful", "Distributions", "LinearAlgebra", "RegisterMismatch"]

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
RegisterQD performs image registration using the global optimization routine [QuadDIRECT](https://github.com/timholy/QuadDIRECT.jl).
66
Unlike many other registration packages, this is not "greedy" descent based on an initial guess---it attempts to find the globally-optimal alignment of your images.
77

8+
To use this package, users must choose between using either the CPU or the GPU. For CPU processing, you must manually load the [RegisterMismatch package](https://github.com/HolyLab/RegisterMismatch.jl): `using RegisterMismatch, RegisterQD`. For GPU processing, you should instead load the [RegisterMismatchCuda package](https://github.com/HolyLab/RegisterMismatchCuda.jl): `using RegisterMismatchCuda, RegisterQD`. *Note that loading both mismatch packages in the same session will cause method conflicts.* Both mismatch packages are registered in the publicly-available [HolyLabRegistry](https://github.com/HolyLab/HolyLabRegistry), and users are advised to add that registry.
9+
In the current absense of Github resources for GPU code, "gpu_test.jl" should be run on your personal machine as required.
10+
811
This package exports the following registration functions:
912
- `qd_translate`: register images by shifting one with respect to another (translations only)
1013
- `qd_rigid`: register images using rotations and translations

src/RegisterQD.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
module RegisterQD
22

33
using Images, CoordinateTransformations, QuadDIRECT
4-
using RegisterMismatch
5-
using RegisterCore #just for indmin_mismatch?
4+
using RegisterMismatchCommon
5+
using RegisterCore
66
using RegisterDeformation, PaddedViews, MappedArrays
77
using Rotations
88
using Interpolations, CenterIndexedArrays, StaticArrays, OffsetArrays

src/rigid.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ function qd_rigid(fixed, moving, mxshift::VecLike, mxrot::Union{Number,VecLike};
187187
if initial_tfm == IdentityTransformation() || isrotation(initial_tfm.linear)
188188
else
189189
@show "WARNING: initial_tfm is not a rigid transformation"
190+
# @warn "initial_tfm is not a rigid transformation"
190191
end
191192
fixed, moving = float(fixed), float(moving)
192193
if presmoothed

test/Project.toml

Lines changed: 0 additions & 20 deletions
This file was deleted.

test/gpu_tests.jl

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
using CUDA, RegisterMismatchCuda, RegisterQD
2+
using LinearAlgebra
3+
using ImageMagick
4+
using Distributions #what is this?
5+
using RegisterQD.StaticArrays
6+
using RegisterQD.Interpolations
7+
using RegisterQD.Images
8+
using RegisterQD.CoordinateTransformations
9+
using RegisterQD.Rotations
10+
using RegisterQD.OffsetArrays
11+
using RegisterQD.RegisterMismatchCommon
12+
13+
using TestImages
14+
using Test
15+
16+
# CUDA.GPUArrays.default_scalar_indexing[] = CUDA.GPUArrays.ScalarDisallowed
17+
# CUDA.GPUArrays.default_scalar_indexing[] = CUDA.GPUArrays.ScalarAllowed
18+
19+
20+
#Helper to generate test image pairs
21+
function fixedmov(img, tfm)
22+
img = float(img)
23+
img2 = warp(img,tfm)
24+
inds = OffsetArrays.IdentityUnitRange.(intersect.(axes(img), axes(img2)))
25+
fixed = img[inds...]
26+
moving = img2[inds...]
27+
return fixed, moving
28+
end
29+
30+
#Helpers to convert Transformations to AffineMaps
31+
to_affine(tfm::Translation) = AffineMap(Matrix{Float64}(LinearAlgebra.I, length(tfm.translation), length(tfm.translation)), tfm.translation)
32+
to_affine(tfm::LinearMap) = AffineMap(Matrix{Float64}(LinearAlgebra.I, length(tfm.translation), length(tfm.translation)), tfm.translation)
33+
to_affine(tfm::AffineMap) = tfm
34+
35+
#Helper to test that a found transform is (roughly) the inverse of the original transform.
36+
function tfmtest(tfm, tfminv)
37+
comp = to_affine(tfm tfminv) #should be the identity transform
38+
diagtol = 0.005
39+
offdiagtol = 0.005
40+
vtol = 0.1
41+
@test all(x->(1-diagtol < x < 1+diagtol), diag(comp.linear))
42+
@test all(x->(-offdiagtol < x < offdiagtol), comp.linear.-Matrix(Diagonal(diag(comp.linear))))
43+
@test all(x-> x.<vtol, abs.(comp.translation))
44+
end
45+
46+
# helper for wrapping CuArrays in Offset Arrays
47+
# cu_wrap(img::OffsetArray) = OffsetArray(CuArray(img.parent), img.offsets)
48+
# cu_wrap(img) = CuArray(img)
49+
50+
#rigid tests
51+
img = Float32.(testimage("cameraman"))
52+
SD = Matrix{Float64}(LinearAlgebra.I, 2, 2)
53+
tfm = Translation(@SVector([14, 17]))LinearMap(RotMatrix(0.3)) #no distortion for now
54+
# fixed, moving = cu_wrap.(fixedmov(centered(img), tfm));
55+
fixed, moving = fixedmov(centered(img), tfm)
56+
mxshift = (100,100) #make sure this isn't too small
57+
mxrot = (0.5,)
58+
minwidth_rot = fill(0.002, 3)
59+
60+
61+
tform, mm = qd_rigid(fixed, moving, mxshift, mxrot; SD=SD, maxevals=1000, rtol=0, fvalue=0.0002);
62+
63+
tfmtest(tfm, tform)
64+
@test mm<0.001
65+
66+
67+
img3D = Float32.(testimage("mri-stack"))
68+
SD3D = Matrix{Float64}(LinearAlgebra.I, 3,3 )
69+
tfm3D = Translation(@SVector([15, 10, 2]))LinearMap(RotXYZ(0.05, 0.02, 0)) #no distortion for now
70+
fixed, moving = fixedmov(centered(img3D), tfm3D)
71+
mxshift = (30, 30, 5) #make sure this isn't too small
72+
mxrot = (0.1, 0.1, 0.1)
73+
minwidth_rot = fill(0.002, 3)
74+
75+
tform2, mm2 = qd_rigid(fixed, moving, mxshift, mxrot; SD=SD3D, maxevals=1000, rtol=0, fvalue=0.0002);
76+
tfmtest(tfm3D, tform2)
77+
@test mm2<0.01
78+
79+
#Coppied over from qd_random
80+
@testset "QuadDIRECT tests with standard images" begin
81+
img = testimage("cameraman");
82+
83+
#Translation (subpixel)
84+
tfm = Translation(@SVector([14.3, 17.6]))
85+
fixed, moving = fixedmov(img, tfm)
86+
mxshift = (100,100) #make sure this isn't too small
87+
tform, mm = qd_translate(fixed, moving, mxshift; maxevals=1000, rtol=0, fvalue=0.0003)
88+
tfmtest(tfm, tform)
89+
90+
#Rigid transform
91+
SD = Matrix{Float64}(LinearAlgebra.I, 2, 2)
92+
tfm = Translation(@SVector([14, 17]))LinearMap(RotMatrix(0.3)) #no distortion for now
93+
fixed, moving = fixedmov(centered(img), tfm)
94+
mxshift = (100,100) #make sure this isn't too small
95+
mxrot = (0.5,)
96+
minwidth_rot = fill(0.002, 3)
97+
tform, mm = qd_rigid(fixed, moving, mxshift, mxrot; SD=SD, maxevals=1000, rtol=0, fvalue=0.0002)
98+
tfmtest(tfm, tform)
99+
#with anisotropic sampling
100+
SD = Matrix(Diagonal([0.5; 1.0]))
101+
tfm = Translation(@SVector([14.3, 17.8]))LinearMap(SD\RotMatrix(0.3)*SD)
102+
fixed, moving = fixedmov(centered(img), tfm)
103+
tform, mm = qd_rigid(fixed, moving, mxshift, mxrot; SD=SD, maxevals=1000, rtol=0, fvalue=0.0002)
104+
tfmtest(tfm, arrayscale(tform, SD))
105+
106+
#Affine transform
107+
tfm = Translation(@SVector([14, 17]))LinearMap(RotMatrix(0.01))
108+
#make it harder with nonuniform scaling
109+
scale = @SMatrix [1.005 0; 0 0.995]
110+
SD = Matrix{Float64}(LinearAlgebra.I, 2, 2)
111+
tfm = AffineMap(tfm.linear*scale, tfm.translation)
112+
mxshift = (100,100) #make sure this isn't too small
113+
fixed, moving = fixedmov(centered(img), tfm)
114+
tform, mm = qd_affine(fixed, moving, mxshift; SD = SD, maxevals=1000, rtol=0, fvalue=0.0002)
115+
tfmtest(tfm, tform)
116+
117+
#with anisotropic sampling
118+
SD = Matrix(Diagonal([0.5; 1.0]))
119+
tfm = Translation(@SVector([14.3, 17.8]))LinearMap(RotMatrix(0.1)) #Translation(@SVector([14.3, 17.8]))∘LinearMap(SD\RotMatrix(0.01)*SD)
120+
scale = @SMatrix [1.005 0; 0 0.995]
121+
tfm = AffineMap(tfm.linear*scale, tfm.translation)
122+
tfm = arrayscale(tfm, SD)
123+
fixed, moving = fixedmov(centered(img), tfm)
124+
tform, mm = qd_affine(fixed, moving, mxshift; SD = SD, maxevals=10000, rtol=0, fvalue=0.0002, ndmax = 0.25)
125+
tform2 = arrayscale(tform, SD)
126+
tfmtest(tfm, tform2)
127+
end #tests with standard images
128+
129+
@testset "Quadratic interpolation (issue #7)" begin
130+
samplefrom(n) = rand(Poisson(n))
131+
132+
img = restrict(restrict(testimage("cameraman")))[2:end-1,2:end-1]
133+
# Convert to "photons" so we can mimic shot noise
134+
np = 1000 # maximum number of photons per pixel
135+
img = round.(Int, np.*gray.(img))
136+
fixed = samplefrom.(img)
137+
moving = samplefrom.(img)
138+
ff = qsmooth(fixed)
139+
140+
tform, mm = qd_translate(fixed, moving, (5, 5); print_interval=typemax(Int))
141+
tformq, mmq = qd_translate(ff, moving, (5, 5); presmoothed=true, print_interval=typemax(Int))
142+
@test all(abs.(tformq.translation) .< abs.(tform.translation))
143+
144+
tform, mm = qd_rigid(fixed, moving, (5, 5), 0.1; print_interval=typemax(Int))
145+
tformq, mmq = qd_rigid(ff, moving, (5, 5), 0.1; presmoothed=true, print_interval=typemax(Int))
146+
@test norm(tformq.linear-I) < norm(tform.linear-I)
147+
@test norm(tformq.translation) < norm(tform.translation)
148+
149+
tform, mm = qd_affine(fixed, moving, (5, 5); print_interval=typemax(Int))
150+
tformq, mmq = qd_affine(ff, moving, (5, 5); presmoothed=true, print_interval=typemax(Int))
151+
@test mmq < mm
152+
153+
# Test that we exactly reconstruct `qsmooth` with `presmoothed=true`
154+
tformq, mmq = qd_translate(ff, fixed, (5, 5); presmoothed=true, print_interval=typemax(Int))
155+
@test all(iszero, tformq.translation)
156+
@test mmq < 1e-10
157+
tformq, mmq = qd_rigid(ff, fixed, (5, 5), 0.1; presmoothed=true, print_interval=typemax(Int))
158+
@test mmq < 1e-8
159+
tformq, mmq = qd_affine(ff, fixed, (5, 5); presmoothed=true, print_interval=typemax(Int))
160+
@test mmq < 1e-6 # on 32-bit systems this can't be 1e-8, not quite sure why
161+
end

test/qd_random.jl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ using RegisterQD.Interpolations
44
using RegisterQD.Images
55
using RegisterQD.CoordinateTransformations
66
using RegisterQD.Rotations
7-
using RegisterQD.RegisterMismatch
87
using RegisterQD: _abs2
98
using Random
109

test/qd_standard.jl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ using RegisterQD.Images
88
using RegisterQD.CoordinateTransformations
99
using RegisterQD.Rotations
1010
using RegisterQD.OffsetArrays
11-
using RegisterQD.RegisterMismatch
1211

1312
using Test, TestImages
1413
using Random

test/runtests.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
using ImageMagick
2-
using RegisterQD
2+
using RegisterQD, RegisterMismatch
33
using Test
44

55
include("util.jl")

0 commit comments

Comments
 (0)