Skip to content

Commit 1fc3118

Browse files
authored
Improve type inference (#44)
* infer radon * infer ellipse(Matrix) * cannot infer ellipse(Matrix) * infer phantom-over * infer disk phantom * infer shepp logan * ellipse vector tuple * fix usage * infer xray1 * unify 2d tests * unify 2d tests further * unified shape2 tests * purge old 2d tests * radon gateway for 3d infer * try to infer radon for ellipsoid * toward 3d test unify * radon_type * type annotation * infer tests * clean 3d tests * final tests * rm todo * rm todo * v0.3.0 * type infer shepp_logan at last! * fix M,N bug * doc * cover * type infer * infer! * test/
1 parent acd32b8 commit 1fc3118

31 files changed

+538
-1201
lines changed

Project.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "ImagePhantoms"
22
uuid = "71a99df6-f52c-4da1-bd2a-69d6f37f3252"
33
authors = ["Jeff Fessler <[email protected]> and contributors"]
4-
version = "0.2.0"
4+
version = "0.3.0"
55

66
[deps]
77
LazyGrids = "7031d0ef-c40d-4431-b2f8-61a8d2f650db"

README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ function disk_phantom(title::String)
5959
x = (-M÷2:M÷2-1) * dx
6060
y = (-N÷2:N÷2-1) * dy
6161
params = disk_phantom_params( ; rhead = () -> rand(100:105))
62-
objects = Ellipse(params) # vector of Ellipse objects
63-
img = phantom(x, y, objects)
62+
objects = ellipse(params) # vector of Object2d{Ellipse}
63+
img = phantom(x, y, objects) # sampled at all (x,y) pairs
6464
jim(x, y, img; title, clim=(0,1300))
6565
end
6666
anim = @animate for i in 1:8

docs/lit/examples/02-ellipse.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ dr = 0.2mm # radial sample spacing
127127
nr = 2^10 # radial sinogram bins
128128
r = (-nr÷2:nr÷2-1) * dr # radial samples
129129
fr = (-nr÷2:nr÷2-1) / nr / dr # corresponding spectral axis
130-
ϕ = deg2rad.(0:180) # * Unitful.rad # todo round unitful Unitful.°
130+
ϕ = deg2rad.(0:180)
131131
sino = radon(ob).(r, ϕ') # sample Radon transform of a single shape object
132132
smax = ob.value * 2 * maximum(radii)
133133
p5 = jim(r, rad2deg.(ϕ), sino; title="sinogram",

docs/lit/examples/07-shepp.jl

+3-3
Original file line numberDiff line numberDiff line change
@@ -159,9 +159,9 @@ Sometimes it can be more convenient
159159
to have the middle ellipses be non-overlapping:
160160
=#
161161

162-
ob = ellipse_parameters(SheppLoganBrainWeb(), disjoint=true)
163-
ob[:,end] = 1:10
164-
ob = ellipse(ob)
162+
params = ellipse_parameters(SheppLoganBrainWeb(), disjoint=true)
163+
params = [(p[1:5]..., i) for (i, p) in enumerate(params)]
164+
ob = ellipse(params)
165165
x = LinRange(-0.4, 0.4, 206)
166166
y = LinRange(-0.5, 0.5, 256)
167167
oversample = 3

docs/lit/examples/10-mri-sense.jl

+4-3
Original file line numberDiff line numberDiff line change
@@ -88,10 +88,11 @@ with random complex phases
8888
to make it a bit more realistic.
8989
=#
9090

91-
oa = ellipse_parameters(SheppLoganBrainWeb() ; disjoint=true, fovs)
91+
params = ellipse_parameters(SheppLoganBrainWeb() ; disjoint=true, fovs)
9292
seed!(0)
93-
oa[:,end] = [1; randn(ComplexF32, 9)] # random phases
94-
oa = ellipse(oa)
93+
phases = [1; rand(ComplexF32,9)] # random phases
94+
params = [(p[1:5]..., phases[i]) for (i, p) in enumerate(params)]
95+
oa = ellipse(params)
9596
oversample = 3
9697
image0 = phantom(x, y, oa, oversample)
9798
cfun = z -> cat(dims = ndims(z)+1, real(z), imag(z))

src/cuboid.jl

+6-6
Original file line numberDiff line numberDiff line change
@@ -84,12 +84,12 @@ end
8484
# `u,v` should be unitless
8585
function xray1(
8686
::Cuboid,
87-
u::Real,
88-
v::Real,
87+
u::Ru,
88+
v::Rv,
8989
ϕ::RealU, # azim
90-
θ::RealU, # polar
91-
)
92-
T = promote_type(eltype(u), eltype(v), Float32)
90+
θ::RealU; # polar
91+
) where {Ru <: Real, Rv <: Real}
92+
T = promote_type(Ru, Rv, Float32)
9393

9494
(sϕ, cϕ) = sincos(ϕ)
9595
(sθ, cθ) = sincos(θ)
@@ -119,7 +119,7 @@ function xray1(
119119
if abs(e3) < eps(T)
120120
*= (-1/2 v < 1/2)
121121
end
122-
return
122+
return T(ℓ)::T
123123
end
124124

125125

src/disk-phantom.jl

+12-4
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ using Random: seed!
1212
"""
1313
params = disk_phantom_params( ; ...)
1414
15-
Generate `ndisk × 6` ellipse phantom parameters
15+
Generate `ndisk` ellipse phantom parameter 6-tuples
1616
for a head-sized disk plus many disks within it,
1717
designed so that the disks have some minimum separation `minsep`
1818
to avoid overlap and to simplify patch-based model fitting.
@@ -31,7 +31,15 @@ to avoid overlap and to simplify patch-based model fitting.
3131
3232
The function options can be replaced with rand() for other Distributions.
3333
"""
34-
function disk_phantom_params( ;
34+
function disk_phantom_params( ; kwargs...)
35+
params = _disk_phantom_params( ; kwargs...)
36+
37+
out = ellipse_parameters_uscale(params, (1,1), 1, 1, 1)
38+
return out
39+
end
40+
41+
42+
function _disk_phantom_params( ;
3543
fov::RealU = 240,
3644
# rhead::RealU = 100, # background radius for "head" [mm]
3745
rhead::Function = () -> 100, # background radius for "head" [mm]
@@ -63,9 +71,9 @@ function disk_phantom_params( ;
6371

6472
(seed != 0) && seed!(seed)
6573

66-
for id=1:ndisk
74+
for id in 1:ndisk
6775
trial = 0
68-
for ii=1:maxtry
76+
for ii in 1:maxtry
6977
# rc = randu(0, rhead - rmin - minsep, f = x->x^0.5)
7078
rc = cdisk()
7179
phi = rand() * 2π

src/ellipse.jl

+2-2
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ phantom1(ob::Object2d{Ellipse}, xy::NTuple{2,Real}) = (sum(abs2, xy) ≤ 1)
5959

6060
# x-ray transform (line integral) of unit circle
6161
# `r` should be unitless
62-
function xray1(::Ellipse, r::Real, ϕ::RealU)
63-
T = promote_type(eltype(r), Float32)
62+
function xray1(::Ellipse, r::R, ϕ::RealU) where {R <: Real}
63+
T = promote_type(R, Float32)
6464
r2 = r^2
6565
return r2 < 1 ? 2 * sqrt(one(T) - r2) : zero(T)
6666
end

src/ellipsoid.jl

+5-5
Original file line numberDiff line numberDiff line change
@@ -61,14 +61,14 @@ phantom1(ob::Object3d{Ellipsoid}, xyz::NTuple{3,Real}) = (sum(abs2, xyz) ≤ 1)
6161
# `u,v` should be unitless
6262
function xray1(
6363
::Ellipsoid,
64-
u::Real,
65-
v::Real,
64+
u::Ru,
65+
v::Rv,
6666
ϕ::RealU, # irrelevant
6767
θ::RealU, # irrelevant
68-
)
69-
T = promote_type(eltype(u), eltype(v), Float32)
68+
) where {Ru <: Real, Rv <: Real}
69+
T = promote_type(Ru, Rv, Float32)
7070
r2 = u^2 + v^2
71-
return r2 < 1 ? 2 * sqrt(one(T) - r2) : zero(T)
71+
return (r2 < 1 ? 2 * sqrt(one(T) - r2) : zero(T))::T
7272
end
7373

7474

src/gauss2.jl

+3-2
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,10 @@ phantom1(ob::Object2d{Gauss2}, xy::NTuple{2,Real}) =
6868

6969
# x-ray transform (line integral) of unit gauss2
7070
# `r` should be unitless
71-
function xray1(::Gauss2, r::Real, ϕ::RealU)
71+
function xray1(::Gauss2, r::R, ϕ::RealU) where {R <: Real}
72+
T = promote_type(R, Float32)
7273
s = fwhm2spread(1)
73-
return s * exp(-π * abs2(r / s))
74+
return T(s * exp(-π * abs2(r / s)))
7475
end
7576

7677

src/mri-sense.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ function mri_smap_fit(
8484

8585
smaps_fit = [embed(B * coef, mask) for coef in coefs]
8686
ss(v) = sum(x -> sum(abs2, x), v)
87-
nrmse = sqrt( ss(smaps_fit - smaps) / ss(smaps) )
87+
nrmse = Float64( sqrt( ss(smaps_fit - smaps) / ss(smaps) ))::Float64
8888
return (; B, ν, coefs, nrmse, smaps = smaps_fit)
8989
end
9090

src/object.jl

+17-5
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ abstract type AbstractObject end
1616

1717

1818
"""
19-
Object{S, D, V, ...}(center, width, angle, value) <: AbstractObject
19+
Object{S, D, V, C, A, Da}(center, width, angle, value) <: AbstractObject
20+
where {S <: AbstractShape, V <: Number, C,A <: RealU}
2021
General container for 2D and 3D objects for defining image phantoms.
2122
2223
* `center::NTuple{D,C}` coordinates of "center" of this object
@@ -83,16 +84,16 @@ end
8384

8485

8586
"""
86-
Object2d = Object{S,2} where S <: AbstractShape
87+
Object2d{S,V,C} = Object{S,2,V,C} where {S <: AbstractShape, V,C <: Number}
8788
For 2D objects
8889
"""
89-
const Object2d = Object{S,2} where S
90+
const Object2d{S,V,C} = Object{S,2,V,C}
9091

9192
"""
92-
Object3d = Object{S,3} where S <: AbstractShape
93+
Object3d{S,V,C} = Object{S,3,V,C} where {S <: AbstractShape, V,C <: Number}
9394
For 3D objects
9495
"""
95-
const Object3d = Object{S,3} where S
96+
const Object3d{S,V,C} = Object{S,3,V,C}
9697

9798

9899
# constructors
@@ -229,3 +230,14 @@ translate(ob::Object{S,D}, shift::NTuple{D,RealU}) where {S, D} =
229230
Object(S(), ob.center .+ shift, ob.width, ob.angle, ob.value)
230231
translate(ob::Object{S,2}, x::RealU, y::RealU) where {S} = translate(ob, (x,y))
231232
translate(ob::Object{S,3}, x::RealU, y::RealU, z::RealU) where {S} = translate(ob, (x,y,z))
233+
234+
235+
"""
236+
radon_type(::Object)
237+
Determine the element type of the Radon transform of an object
238+
(including units if applicable).
239+
Ensures that its precision is at least `Float32`.
240+
"""
241+
function radon_type(::Object{S, D, V, C, A}) where {S, D, V <: Number, C <: RealU, A <: RealU}
242+
return eltype(oneunit(C) * oneunit(V) * one(A) * one(Float32))
243+
end

src/object2.jl

+2-2
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ function phantom(
8484
x::AbstractVector,
8585
y::AbstractVector,
8686
oa::Array{<:Object2d},
87-
oversample::Int;
87+
oversample::Int ;
8888
T::DataType = promote_type(eltype.(oa)..., Float32),
8989
)
9090

@@ -201,7 +201,7 @@ function radon(
201201
ϕ::AbstractVector,
202202
oa::Array{<:Object2d},
203203
)
204-
return sum(ob -> radon(ob).(r,ϕ'), oa)
204+
return radon(oa).(r,ϕ')
205205
end
206206

207207

src/object3.jl

+14-7
Original file line numberDiff line numberDiff line change
@@ -88,10 +88,10 @@ function phantom(
8888
x::AbstractVector,
8989
y::AbstractVector,
9090
z::AbstractVector,
91-
oa::Array{<:Object3d},
91+
oa::Array{<:Object3d{S,V}},
9292
oversample::Int;
93-
T::DataType = promote_type(eltype.(oa)..., Float32),
94-
)
93+
T::DataType = promote_type(V, Float32),
94+
) where {S, V <: Number}
9595

9696
oversample < 1 && throw(ArgumentError("oversample $oversample"))
9797
dx = x[2] - x[1]
@@ -104,7 +104,8 @@ function phantom(
104104
o3 = oversample^3
105105
ophantom = ob -> (x,y,z) ->
106106
T(sum(phantom(ob).(ndgrid(x .+ dx*tmp, y .+ dy*tmp, z .+ dz*tmp)...)) / o3)
107-
return sum(ob -> ophantom(ob).(ndgrid(x,y,z)...), oa)
107+
out = sum(ob -> ophantom(ob).(ndgrid(x,y,z)...), oa)
108+
return out::Array{T, 3}
108109
end
109110

110111

@@ -173,6 +174,13 @@ function _xray(
173174
end
174175

175176

177+
# this gateway seems to help type inference
178+
function _radon(ob::Object3d{S}, u::RealU, v::RealU, ϕ::RealU, θ::RealU) where S
179+
T = radon_type(ob)
180+
return T(ob.value * _xray(S(), ob.center, ob.width, ob.angle, u, v, ϕ, θ))::T
181+
end
182+
183+
176184
"""
177185
radon(ob::Object3d)::Function
178186
Return function of `(u,v,ϕ,θ)` that user can sample
@@ -185,8 +193,7 @@ for an object ``f(x,y,z)``.
185193
Then as `ϕ` increases, the line integrals rotate counter-clockwise.
186194
"""
187195
function radon(ob::Object3d{S}) where S
188-
return (u,v,ϕ,θ) -> ob.value *
189-
_xray(S(), ob.center, ob.width, ob.angle, u, v, ϕ, θ)
196+
return (u::RealU, v::RealU, ϕ::RealU, θ::RealU) -> _radon(ob, u, v, ϕ, θ)
190197
end
191198

192199

@@ -198,7 +205,7 @@ to make projection views
198205
for an array of 3D objects.
199206
"""
200207
function radon(oa::Array{<:Object3d})
201-
return (u,v,ϕ,θ) -> sum(ob -> radon(ob)(u,v,ϕ,θ), oa)
208+
return (u::RealU, v::RealU, ϕ::RealU, θ::RealU) -> sum(ob -> radon(ob)(u,v,ϕ,θ), oa)
202209
end
203210

204211

0 commit comments

Comments
 (0)