Skip to content

Commit 9a89147

Browse files
committed
Add initial cylindrical cone geometry support
1 parent 6aa366e commit 9a89147

File tree

7 files changed

+440
-3
lines changed

7 files changed

+440
-3
lines changed

tomosipo/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from . import geometry
1515
from .geometry.cone import cone
1616
from .geometry.cone_vec import cone_vec
17+
from .geometry.cyl_cone_vec import cyl_cone_vec
1718
from .geometry.parallel_vec import parallel_vec
1819
from .geometry.parallel import parallel
1920
from .geometry.transform import (

tomosipo/geometry/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
from . import base_projection
22
from . import det_vec
3+
from . import cyl_det_vec
34
from . import cone_vec
5+
from . import cyl_cone_vec
46
from . import cone
57
from . import parallel_vec
68
from . import parallel
@@ -25,6 +27,7 @@
2527
from .base_projection import ProjectionGeometry
2628
from .cone import ConeGeometry
2729
from .cone_vec import ConeVectorGeometry
30+
from .cyl_cone_vec import CylConeVectorGeometry
2831
from .parallel import ParallelGeometry
2932
from .parallel_vec import ParallelVectorGeometry
3033
from .volume import VolumeGeometry

tomosipo/geometry/base_projection.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,9 @@ def is_vec(self):
101101
"""Is this a vector geometry?
102102
103103
A geometry can either be a vector geometry, like
104-
``ConeVectorGeometry``, or ``ParallelVectorGeometry``, or it
105-
can be a parametrized geometry like ``ConeGeometry`` or
106-
``ParallelGeometry``.
104+
``ConeVectorGeometry``, ``CylConeVectorGeometry`` or
105+
``ParallelVectorGeometry``, or it can be a parametrized geometry like
106+
``ConeGeometry`` or ``ParallelGeometry``.
107107
108108
:returns:
109109
:rtype:

tomosipo/geometry/concatenate.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
ProjectionGeometry,
77
ConeGeometry,
88
ConeVectorGeometry,
9+
CylConeVectorGeometry,
910
ParallelGeometry,
1011
ParallelVectorGeometry,
1112
VolumeGeometry,

tomosipo/geometry/conversion.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from .base_projection import ProjectionGeometry
22
from .cone import ConeGeometry
33
from .cone_vec import ConeVectorGeometry
4+
from .cyl_cone_vec import CylConeVectorGeometry
45
from .det_vec import DetectorVectorGeometry
56
from .parallel_vec import ParallelVectorGeometry
67
from .parallel import ParallelGeometry
@@ -18,6 +19,8 @@ def from_astra_projection_geometry(astra_pg):
1819
return ConeGeometry.from_astra(astra_pg)
1920
elif pg_type == "cone_vec":
2021
return ConeVectorGeometry.from_astra(astra_pg)
22+
elif pg_type == "cyl_cone_vec":
23+
return CylConeVectorGeometry.from_astra(astra_pg)
2124
elif pg_type == "det_vec":
2225
return DetectorVectorGeometry.from_astra(astra_pg)
2326
elif pg_type == "parallel3d_vec":

tomosipo/geometry/cyl_cone_vec.py

Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
import numpy as np
2+
import tomosipo as ts
3+
from tomosipo.types import ToShape2D, ToVec, ToScalars
4+
import tomosipo.vector_calc as vc
5+
from .base_projection import ProjectionGeometry
6+
from . import cyl_det_vec as cdv
7+
from .transform import Transform
8+
from .cone_vec import ConeVectorGeometry
9+
10+
11+
def cyl_cone_vec(
12+
*, shape: ToShape2D, src_pos: ToVec, det_pos: ToVec,
13+
det_v: ToVec, det_u: ToVec, curvature: ToScalars
14+
):
15+
"""Create an arbitrarily oriented cone-beam geometry with a cylindrical detector.
16+
17+
Parameters
18+
----------
19+
20+
shape:
21+
The detector shape in pixels. If tuple, the order is
22+
`(height, width)`. Else the pixel has the same number of
23+
pixels in the `u` and `v` direction.
24+
25+
src_pos:
26+
A numpy array of dimension `(num_positions, 3)` with the
27+
source positions in `(z, y, x)` order.
28+
29+
det_pos:
30+
A numpy array of dimension `(num_positions, 3)` with the
31+
detector center positions in `(z, y, x)` order.
32+
33+
det_v:
34+
A numpy array of dimension `(num_positions, 3)` with the vector pointing
35+
from the detector `(0, 0)` to `(1, 0)` pixel (up).
36+
37+
det_u:
38+
A numpy array of dimension `(num_positions, 3)` with the
39+
vector pointing from the detector `(0, 0)` to `(0, 1)` pixel
40+
(sideways).
41+
42+
curvature:
43+
A float scalar specifying detector curvature.
44+
45+
Returns
46+
-------
47+
CylConeVectorGeometry
48+
An arbitrarily oriented cone-beam geometry with a cylindrical detector.
49+
"""
50+
return CylConeVectorGeometry(
51+
shape=shape, src_pos=src_pos, det_pos=det_pos, det_v=det_v, det_u=det_u,
52+
curvature=curvature
53+
)
54+
55+
56+
class CylConeVectorGeometry(ConeVectorGeometry):
57+
"""Documentation for CylConeVectorGeometry
58+
59+
A class for representing cone vector geometries with cylindrical detectors.
60+
"""
61+
62+
def __init__(self, *, shape, src_pos, det_pos, det_v, det_u, curvature):
63+
"""Create an arbitrarily oriented cone-beam geometry with cylindrical detector.
64+
65+
Parameters
66+
----------
67+
68+
shape:
69+
The detector shape in pixels. If tuple, the order is
70+
`(height, width)`. Else the pixel has the same number of
71+
pixels in the `u` and `v` direction.
72+
73+
src_pos:
74+
A numpy array of dimension `(num_positions, 3)` with the
75+
source positions in `(z, y, x)` order.
76+
77+
det_pos:
78+
A numpy array of dimension `(num_positions, 3)` with the
79+
detector center positions in `(z, y, x)` order.
80+
81+
det_v:
82+
A numpy array of dimension `(num_positions, 3)` with the vector pointing
83+
from the detector `(0, 0)` to `(1, 0)` pixel (up).
84+
85+
det_u:
86+
A numpy array of dimension `(num_positions, 3)` with the
87+
vector pointing from the detector `(0, 0)` to `(0, 1)` pixel
88+
(sideways).
89+
90+
curvature:
91+
A float scalar specifying detector curvature.
92+
"""
93+
super().__init__(shape=shape, src_pos=src_pos, det_pos=det_pos, det_v=det_v, det_u=det_u)
94+
self._det_vec = cdv.cyl_det_vec(shape, det_pos, det_v, det_u, curvature)
95+
96+
def __repr__(self):
97+
with ts.utils.print_options():
98+
return (
99+
f"ts.cy_cone_vec(\n"
100+
f" shape={self.det_shape},\n"
101+
f" src_pos={repr(self._src_pos)},\n"
102+
f" det_pos={repr(self._det_vec.det_pos)},\n"
103+
f" det_v={repr(self._det_vec.det_v)},\n"
104+
f" det_u={repr(self._det_vec.det_u)},\n"
105+
f" curvature={self._det_vec.curvature}\n"
106+
f")"
107+
)
108+
109+
def __eq__(self, other):
110+
if not isinstance(other, CylConeVectorGeometry):
111+
return False
112+
return super().__eq__(self, other)
113+
114+
def __getitem__(self, key):
115+
"""Slice the geometry to create a sub-geometry
116+
117+
This geometry can be sliced by angle. The following obtains a
118+
sub-geometry containing every second projection:
119+
120+
>>> ts.cone(angles=10, cone_angle=1/2).to_vec()[0::2].num_angles
121+
5
122+
123+
This geometry can also be sliced in the detector plane:
124+
125+
>>> ts.cone(shape=10, cone_angle=1/2).to_vec()[:, ::2, ::2].det_shape
126+
(5, 5)
127+
128+
:param key:
129+
:returns:
130+
:rtype:
131+
132+
"""
133+
134+
det_vec = self._det_vec[key]
135+
if isinstance(key, tuple):
136+
key, *_ = key
137+
138+
new_src_pos = self._src_pos[key]
139+
return CylConeVectorGeometry(
140+
shape=det_vec.det_shape,
141+
src_pos=new_src_pos,
142+
det_pos=det_vec.det_pos,
143+
det_v=det_vec.det_v,
144+
det_u=det_vec.det_u,
145+
curvature=self.curvature
146+
)
147+
148+
def to_astra(self):
149+
row_count, col_count = self.det_shape
150+
vectors = np.concatenate(
151+
[
152+
self._src_pos[:, ::-1],
153+
self._det_vec.det_pos[:, ::-1],
154+
self._det_vec.det_u[:, ::-1],
155+
self._det_vec.det_v[:, ::-1],
156+
self._det_vec.curvature * np.ones([self.num_angles, 1])
157+
],
158+
axis=1,
159+
)
160+
161+
return {
162+
"type": "cyl_cone_vec",
163+
"DetectorRowCount": row_count,
164+
"DetectorColCount": col_count,
165+
"Vectors": vectors,
166+
}
167+
168+
def from_astra(astra_pg):
169+
if astra_pg["type"] != "cyl_cone_vec":
170+
raise ValueError(
171+
"ConeVectorGeometry.from_astra only supports 'cone_vec' type astra geometries."
172+
)
173+
174+
vecs = astra_pg["Vectors"]
175+
# ray direction (parallel) / source_position (cone)
176+
src_pos = vecs[:, :3]
177+
# detector pos:
178+
det_pos = vecs[:, 3:6]
179+
# Detector u and v direction
180+
det_u = vecs[:, 6:9]
181+
det_v = vecs[:, 9:12]
182+
curvature = vecs[0, 12]
183+
assert len(np.unique(vecs[:, 12])) == 1, "Non-unique curvature values per geometry are not supported"
184+
185+
shape = (astra_pg["DetectorRowCount"], astra_pg["DetectorColCount"])
186+
return CylConeVectorGeometry(
187+
shape=shape,
188+
src_pos=src_pos[:, ::-1],
189+
det_pos=det_pos[:, ::-1],
190+
det_v=det_v[:, ::-1],
191+
det_u=det_u[:, ::-1],
192+
curvature=curvature
193+
)
194+
195+
###########################################################################
196+
# Properties #
197+
###########################################################################
198+
199+
@property
200+
def curvature(self):
201+
return self._det_vec.curvature
202+
203+
###########################################################################
204+
# Methods #
205+
###########################################################################
206+
207+
def rescale_det(self, scale):
208+
det_vec = self._det_vec.rescale_det(scale)
209+
return CylConeVectorGeometry(
210+
shape=det_vec.det_shape,
211+
src_pos=self.src_pos,
212+
det_pos=det_vec.det_pos,
213+
det_v=det_vec.det_v,
214+
det_u=det_vec.det_u,
215+
)
216+
217+
def reshape(self, new_shape):
218+
det_vec = self._det_vec.reshape(new_shape)
219+
return CylConeVectorGeometry(
220+
shape=new_shape,
221+
src_pos=self.src_pos,
222+
det_pos=det_vec.det_pos,
223+
det_v=det_vec.det_v,
224+
det_u=det_vec.det_u,
225+
)
226+
227+
def __rmul__(self, other):
228+
if isinstance(other, Transform):
229+
src_pos = other.transform_point(self._src_pos)
230+
231+
det_vec = other * self._det_vec
232+
return ConeVectorGeometry(
233+
shape=self.det_shape,
234+
src_pos=src_pos,
235+
det_pos=det_vec.det_pos,
236+
det_v=det_vec.det_v,
237+
det_u=det_vec.det_u,
238+
)

0 commit comments

Comments
 (0)