Skip to content

Commit 9c5daee

Browse files
authored
Add cone (#62)
* Add cone * v0.7
1 parent 419661e commit 9c5daee

File tree

5 files changed

+205
-12
lines changed

5 files changed

+205
-12
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.6.0"
4+
version = "0.7.0"
55

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

docs/lit/examples/36-cone.jl

+93
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
#---------------------------------------------------------
2+
# # [Cone](@id 36-cone)
3+
#---------------------------------------------------------
4+
5+
#=
6+
This page illustrates the `Cone` shape in the Julia package
7+
[`ImagePhantoms`](https://github.com/JuliaImageRecon/ImagePhantoms.jl).
8+
9+
This page was generated from a single Julia file:
10+
[36-cone.jl](@__REPO_ROOT_URL__/36-cone.jl).
11+
=#
12+
13+
#md # In any such Julia documentation,
14+
#md # you can access the source code
15+
#md # using the "Edit on GitHub" link in the top right.
16+
17+
#md # The corresponding notebook can be viewed in
18+
#md # [nbviewer](http://nbviewer.jupyter.org/) here:
19+
#md # [`36-cone.ipynb`](@__NBVIEWER_ROOT_URL__/36-cone.ipynb),
20+
#md # and opened in [binder](https://mybinder.org/) here:
21+
#md # [`36-cone.ipynb`](@__BINDER_ROOT_URL__/36-cone.ipynb).
22+
23+
24+
# ### Setup
25+
26+
# Packages needed here.
27+
28+
using ImagePhantoms: Object, phantom, radon, spectrum
29+
using ImagePhantoms: Cone, cone
30+
import ImagePhantoms as IP
31+
using ImageGeoms: ImageGeom, axesf
32+
using MIRTjim: jim, prompt, mid3
33+
using FFTW: fft, fftshift, ifftshift
34+
using LazyGrids: ndgrid
35+
using Unitful: mm, unit, °
36+
using Plots: plot, plot!, scatter!, default
37+
using Plots # gif @animate
38+
default(markerstrokecolor=:auto)
39+
40+
41+
# The following line is helpful when running this file as a script;
42+
# this way it will prompt user to hit a key after each figure is displayed.
43+
44+
isinteractive() ? jim(:prompt, true) : prompt(:draw);
45+
46+
47+
# ### Overview
48+
49+
#=
50+
A basic shape used in constructing 3D digital image phantoms
51+
is the cone,
52+
specified by its center, base radii, height, angle(s) and value.
53+
All of the methods in `ImagePhantoms` support physical units,
54+
so we use such units throughout this example.
55+
(Using units is recommended but not required.)
56+
57+
Here are 3 ways to define a `Object{Cone}`,
58+
using physical units.
59+
=#
60+
61+
center = (20mm, 10mm, -15mm)
62+
width = (25mm, 30mm, 35mm) # x radius, y radius, height
63+
ϕ0s = :(π/6) # symbol version for nice plot titles
64+
ϕ0 = eval(ϕ0s)
65+
angles = (ϕ0, 0, 0)
66+
Object(Cone(), center, width, angles, 1.0f0) # top-level constructor
67+
cone( 20mm, 10mm, 5mm, 25mm, 35mm, 15mm, π/6, 0, 0, 1.0f0) # 9 arguments
68+
ob = cone(center, width, angles, 1.0f0) # tuples (recommended use)
69+
70+
71+
#=
72+
### Phantom image using `phantom`
73+
74+
Make a 3D digital image of it using `phantom` and display it.
75+
We use `ImageGeoms` to simplify the indexing.
76+
=#
77+
78+
deltas = (1.0mm, 1.1mm, 0.9mm)
79+
dims = (2^8, 2^8+2, 49) # odd
80+
ig = ImageGeom( ; dims, deltas, offsets=:dsp)
81+
oversample = 3
82+
img = phantom(axes(ig)..., [ob], oversample)
83+
p1 = jim(axes(ig), img;
84+
title="Cone, rotation ϕ=$ϕ0s", xlabel="x", ylabel="y")
85+
86+
87+
# The image integral should match the object volume:
88+
volume = IP.volume(ob)
89+
(sum(img)*prod(ig.deltas), volume)
90+
91+
92+
# Show middle slices
93+
jim(mid3(img), "Middle 3 planes")

src/ImagePhantoms.jl

+3-2
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,12 @@ include("rect.jl")
1818
include("triangle.jl")
1919

2020
# 3d:
21+
include("cone.jl")
22+
include("cuboid.jl")
23+
include("cylinder.jl")
2124
include("dirac3.jl")
2225
include("ellipsoid.jl")
2326
include("gauss3.jl")
24-
include("cuboid.jl")
25-
include("cylinder.jl")
2627

2728
# phantoms:
2829
include("shepplogan.jl")

src/cone.jl

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
#=
2+
cone.jl
3+
Right cone
4+
Base object is a cone with unit radius and unit height,
5+
with base at origin pointing up along z.
6+
=#
7+
8+
9+
export Cone, cone
10+
11+
12+
"""
13+
Cone <: AbstractShape{3}
14+
"""
15+
struct Cone <: AbstractShape{3} end
16+
17+
18+
# constructor
19+
20+
21+
"""
22+
cone(cx, cy, cz, wx, wy, wz, Φ, Θ, value::Number)
23+
cone(center::NTuple{3,RealU}, width::NTuple{3,RealU}, angle::NTuple{3,RealU}, v)
24+
Construct `Object{Cone}` from parameters;
25+
here `width` is the base radii for `wx` and `wy` and `wz` is the height.
26+
"""
27+
cone(args... ; kwargs...) = Object(Cone(), args...; kwargs...)
28+
29+
30+
# methods
31+
32+
33+
volume1(::Cone) = π/3 # volume of unit cone
34+
35+
ℓmax1(::Cone) = 2 # max line integral through unit cone
36+
37+
function ℓmax(ob::Object3d{Cone})
38+
rmax = maximum(ob.width[1:2])
39+
return max(2 * rmax, sqrt(rmax^2 + ob.width[3]^2))
40+
end
41+
42+
43+
"""
44+
phantom1(ob::Object3d{Cone}, (x,y,z))
45+
Evaluate unit cone at `(x,y,z)`,
46+
for unitless coordinates.
47+
"""
48+
function phantom1(ob::Object3d{Cone}, xyz::NTuple{3,Real})
49+
z = xyz[3]
50+
r = sqrt(sum(abs2, xyz[1:2]))
51+
return (0 z 1) && r 1 - z
52+
end
53+
54+
55+
# radon
56+
57+
58+
# x-ray transform (line integral) of unit cone
59+
# `u,v` should be unitless
60+
#=
61+
function xray1(
62+
::Cone,
63+
u::Ru,
64+
v::Rv,
65+
ϕ::RealU, # azim
66+
θ::RealU; # polar
67+
) where {Ru <: Real, Rv <: Real}
68+
T = promote_type(Ru, Rv, Float32)
69+
70+
# (sϕ, cϕ) = sincos(ϕ)
71+
# (sθ, cθ) = sincos(θ)
72+
73+
throw("todo: cone radon not done")
74+
end
75+
=#
76+
77+
78+
# spectrum
79+
80+
#=
81+
"""
82+
spectrum1(::Object3d{Cone}, (kx,ky,kz))
83+
Spectrum of unit cone at `(kx,ky,kz)`,
84+
for unitless spatial frequency coordinates.
85+
"""
86+
function spectrum1(::Object3d{Cone}, kxyz::NTuple{3,Real})
87+
throw("todo: cone spectrum not done")
88+
end
89+
=#

test/shape3.jl

+19-9
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ using ImagePhantoms: Object3d, sphere, cube
44
using ImagePhantoms: Object, AbstractShape, phantom, radon, spectrum
55
using ImagePhantoms: Gauss3, gauss3
66
using ImagePhantoms: Ellipsoid, ellipsoid
7+
using ImagePhantoms: Cone, cone
78
using ImagePhantoms: Cuboid, cuboid
89
using ImagePhantoms: Cylinder, cylinder
910
using ImagePhantoms: Dirac3, dirac3
@@ -43,12 +44,14 @@ end
4344

4445
# parameters for testing each shape
4546
# (Shape, shape, lmax, lmax1, tol1, tolk, tolp)
47+
# for width = (4//1, 5, 6)
4648
list = [
4749
(Dirac3, dirac3, 6, 1, Inf, Inf, Inf)
4850
(Ellipsoid, ellipsoid, 12, 2, 1e-2, 4e-4, 1e-3)
4951
(Gauss3, gauss3, IP.fwhm2spread(6), IP.fwhm2spread(1), 1e-2, 5e-4, 1e-5)
5052
(Cuboid, cuboid, sqrt(4^2 + 5^2 + 6^2), 3, 1e-2, 2e-2, 2e-3)
5153
(Cylinder, cylinder, sqrt(10^2 + 6^2), 5, 2e-2, 2e-2, 1e-3)
54+
(Cone, cone, 10, 2, Inf, Inf, Inf)
5255
]
5356

5457
macro isob3(ex) # macro to streamline tests
@@ -109,12 +112,12 @@ for (Shape, shape, lmax, lmax1, tol1, tolk, tolp) in list
109112

110113
has_xray = hasmethod(IP.xray1, (Shape, Real, Real, Real, Real))
111114
has_phantom = hasmethod(IP.phantom1, (typeof(shape()), NTuple{3,Real}))
115+
has_spectrum = hasmethod(IP.spectrum1, (typeof(shape()), NTuple{3,Real}))
112116

113117
@testset "construct-$shape" begin
114118
test3_construct(Shape, shape)
115119
end
116120

117-
118121
@testset "operations" begin
119122
test3_op(Shape, shape)
120123
end
@@ -161,8 +164,10 @@ for (Shape, shape, lmax, lmax1, tol1, tolk, tolp) in list
161164

162165
volume = IP.volume(ob)
163166

164-
fun = @inferred spectrum([ob])
165-
@test (@inferred fun(0,0,0)) ob.value * volume
167+
if has_spectrum
168+
fun = @inferred spectrum([ob])
169+
@test (@inferred fun(0,0,0)) ob.value * volume
170+
end
166171

167172
if has_xray
168173
test3_radon(Shape, shape, ob)
@@ -190,10 +195,12 @@ for (Shape, shape, lmax, lmax1, tol1, tolk, tolp) in list
190195
volume = IP.volume(ob)
191196
zscale = 1 / (ob.value * volume) # normalize spectra
192197

193-
kspace = (@inferred spectrum(axesf(ig)..., [ob])) * zscale
194-
@test spectrum(ob)(0/m, 0/m, 0/m) * zscale 1
195-
@test maximum(abs, kspace) 1
196-
@test kspace[L÷2+1,M÷2+1,N÷2+1] 1
198+
if has_spectrum
199+
kspace = (@inferred spectrum(axesf(ig)..., [ob])) * zscale
200+
@test spectrum(ob)(0/m, 0/m, 0/m) * zscale 1
201+
@test maximum(abs, kspace) 1
202+
@test kspace[L÷2+1,M÷2+1,N÷2+1] 1
203+
end
197204

198205
if has_phantom
199206
oversample = 2
@@ -202,8 +209,11 @@ for (Shape, shape, lmax, lmax1, tol1, tolk, tolp) in list
202209
X = myfft(img) * (prod(ig.deltas) * zscale)
203210

204211
@test abs(maximum(abs, X) - 1) < tol1
205-
errk = maximum(abs, kspace - X) / maximum(abs, kspace)
206-
@test errk < tolk
212+
213+
if has_spectrum
214+
errk = maximum(abs, kspace - X) / maximum(abs, kspace)
215+
@test errk < tolk
216+
end
207217
end
208218
end
209219

0 commit comments

Comments
 (0)