diffct 2.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- diffct/__init__.py +83 -0
- diffct/analytical.py +569 -0
- diffct/constants.py +72 -0
- diffct/differentiable.py +74 -0
- diffct/geometry.py +991 -0
- diffct/kernels/__init__.py +43 -0
- diffct/kernels/cone_beam.py +617 -0
- diffct/kernels/fan_beam.py +451 -0
- diffct/kernels/geometry_vjp.py +562 -0
- diffct/kernels/parallel_beam.py +400 -0
- diffct/operators.py +546 -0
- diffct/projectors.py +910 -0
- diffct/utils.py +357 -0
- diffct-2.0.0.dist-info/METADATA +240 -0
- diffct-2.0.0.dist-info/RECORD +18 -0
- diffct-2.0.0.dist-info/WHEEL +4 -0
- diffct-2.0.0.dist-info/licenses/LICENSE +201 -0
- diffct-2.0.0.dist-info/licenses/NOTICE +18 -0
|
@@ -0,0 +1,562 @@
|
|
|
1
|
+
"""CUDA kernels for gradients of the Siddon projector with respect to geometry.
|
|
2
|
+
|
|
3
|
+
For a cell-constant image the Siddon projection of one ray is
|
|
4
|
+
|
|
5
|
+
p = s * |D| * sum_k f_k (tau_{k+1} - tau_k), D = B - A,
|
|
6
|
+
|
|
7
|
+
where A is the source (or, for parallel beams, the detector pixel), B the
|
|
8
|
+
detector pixel, ``s`` the voxel spacing and ``tau_k`` the fractions along
|
|
9
|
+
``D`` at which the ray enters and leaves cell ``k``. Collecting terms gives
|
|
10
|
+
``sum_j c_j tau_j`` with ``c_j = f_before - f_after`` at each crossed cell
|
|
11
|
+
face (``-f_first`` at an entry face, ``+f_last`` at an exit face). A face
|
|
12
|
+
``x_a = b`` gives ``tau = (b - A_a) / D_a``, so
|
|
13
|
+
|
|
14
|
+
d tau / d A_a = (tau - 1) / D_a, d tau / d B_a = -tau / D_a.
|
|
15
|
+
|
|
16
|
+
Segment ends at the source or the detector do not move a face crossing and
|
|
17
|
+
contribute nothing. The derivative is exact for the discrete model wherever
|
|
18
|
+
the ray does not pass through a cell edge or corner. On an edge, where two
|
|
19
|
+
crossings coincide, the kernels return the derivative of one adjacent side.
|
|
20
|
+
|
|
21
|
+
Each thread traces one ray with the same setup and traversal as the
|
|
22
|
+
projector kernels and adds ``grad_sino * dp/dgeometry`` to per-view
|
|
23
|
+
gradients with atomic additions.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
import math
|
|
27
|
+
import numpy as np
|
|
28
|
+
from numba import cuda
|
|
29
|
+
|
|
30
|
+
from ..constants import (
|
|
31
|
+
_FASTMATH_DECORATOR,
|
|
32
|
+
_BIG,
|
|
33
|
+
_TINY,
|
|
34
|
+
_ZERO,
|
|
35
|
+
_ONE,
|
|
36
|
+
_HALF,
|
|
37
|
+
_EPSILON,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@cuda.jit(device=True)
|
|
42
|
+
def _face_terms(c, t_org, t_a0, t_b0, length, d):
|
|
43
|
+
"""Return (d/dA_a, d/dB_a) of ``c * tau`` for a face crossed at ``t_org``.
|
|
44
|
+
|
|
45
|
+
``t_org`` is the ray parameter of the crossing; ``t_a0`` and ``t_b0`` are
|
|
46
|
+
the parameters of A and B on the same unit-speed ray, ``d`` is the unit
|
|
47
|
+
direction component normal to the face.
|
|
48
|
+
"""
|
|
49
|
+
tau_minus_one = (t_org - t_b0) / length
|
|
50
|
+
tau = (t_org - t_a0) / length
|
|
51
|
+
return c * tau_minus_one / d, -c * tau / d
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@_FASTMATH_DECORATOR
|
|
55
|
+
def _fan_2d_geometry_vjp_kernel(
|
|
56
|
+
d_image, Nx, Ny,
|
|
57
|
+
d_grad_sino, n_ang, n_det,
|
|
58
|
+
det_spacing, d_src_pos, d_det_center, d_det_u_vec,
|
|
59
|
+
cx, cy, voxel_spacing,
|
|
60
|
+
d_grad_src, d_grad_det_center, d_grad_det_u,
|
|
61
|
+
):
|
|
62
|
+
"""Accumulate fan-beam geometry gradients of ``<grad_sino, A x>`` per view."""
|
|
63
|
+
iang, idet = cuda.grid(2)
|
|
64
|
+
if iang >= n_ang or idet >= n_det:
|
|
65
|
+
return
|
|
66
|
+
g = d_grad_sino[iang, idet]
|
|
67
|
+
if g == _ZERO:
|
|
68
|
+
return
|
|
69
|
+
|
|
70
|
+
# Ray setup: identical to _fan_2d_forward_kernel, plus the face that sets
|
|
71
|
+
# the entry and the exit (-1 = segment end at the source or detector).
|
|
72
|
+
src_x = d_src_pos[iang, 0] / voxel_spacing
|
|
73
|
+
src_y = d_src_pos[iang, 1] / voxel_spacing
|
|
74
|
+
det_cx = d_det_center[iang, 0] / voxel_spacing
|
|
75
|
+
det_cy = d_det_center[iang, 1] / voxel_spacing
|
|
76
|
+
u_vec_x = d_det_u_vec[iang, 0]
|
|
77
|
+
u_vec_y = d_det_u_vec[iang, 1]
|
|
78
|
+
u_phys = (np.float32(idet) + _HALF - np.float32(n_det) * _HALF) * det_spacing
|
|
79
|
+
u_offset = u_phys / voxel_spacing
|
|
80
|
+
det_x = det_cx + u_offset * u_vec_x
|
|
81
|
+
det_y = det_cy + u_offset * u_vec_y
|
|
82
|
+
|
|
83
|
+
dir_x, dir_y = det_x - src_x, det_y - src_y
|
|
84
|
+
length = math.sqrt(dir_x * dir_x + dir_y * dir_y)
|
|
85
|
+
if length < _EPSILON:
|
|
86
|
+
return
|
|
87
|
+
inv_len = _ONE / length
|
|
88
|
+
dir_x, dir_y = dir_x * inv_len, dir_y * inv_len
|
|
89
|
+
|
|
90
|
+
if src_x * src_x + src_y * src_y <= det_x * det_x + det_y * det_y:
|
|
91
|
+
org_x = src_x
|
|
92
|
+
org_y = src_y
|
|
93
|
+
t_min, t_max = _ZERO, length
|
|
94
|
+
t_a0, t_b0 = _ZERO, length
|
|
95
|
+
else:
|
|
96
|
+
org_x = det_x
|
|
97
|
+
org_y = det_y
|
|
98
|
+
t_min, t_max = -length, _ZERO
|
|
99
|
+
t_a0, t_b0 = -length, _ZERO
|
|
100
|
+
ent_axis = -1
|
|
101
|
+
ex_axis = -1
|
|
102
|
+
if abs(dir_x) > _TINY:
|
|
103
|
+
tx1, tx2 = (-cx - org_x) / dir_x, (cx - org_x) / dir_x
|
|
104
|
+
lo, hi = min(tx1, tx2), max(tx1, tx2)
|
|
105
|
+
if lo > t_min:
|
|
106
|
+
t_min = lo
|
|
107
|
+
ent_axis = 0
|
|
108
|
+
if hi < t_max:
|
|
109
|
+
t_max = hi
|
|
110
|
+
ex_axis = 0
|
|
111
|
+
elif org_x < -cx or org_x > cx:
|
|
112
|
+
return
|
|
113
|
+
if abs(dir_y) > _TINY:
|
|
114
|
+
ty1, ty2 = (-cy - org_y) / dir_y, (cy - org_y) / dir_y
|
|
115
|
+
lo, hi = min(ty1, ty2), max(ty1, ty2)
|
|
116
|
+
if lo > t_min:
|
|
117
|
+
t_min = lo
|
|
118
|
+
ent_axis = 1
|
|
119
|
+
if hi < t_max:
|
|
120
|
+
t_max = hi
|
|
121
|
+
ex_axis = 1
|
|
122
|
+
elif org_y < -cy or org_y > cy:
|
|
123
|
+
return
|
|
124
|
+
if t_min >= t_max:
|
|
125
|
+
return
|
|
126
|
+
|
|
127
|
+
ent_x = org_x + t_min * dir_x
|
|
128
|
+
ent_y = org_y + t_min * dir_y
|
|
129
|
+
ray_x = dir_x
|
|
130
|
+
ray_y = dir_y
|
|
131
|
+
t_end = t_max - t_min
|
|
132
|
+
|
|
133
|
+
accum = _ZERO
|
|
134
|
+
t = _ZERO
|
|
135
|
+
ix = int(math.floor(ent_x + cx))
|
|
136
|
+
iy = int(math.floor(ent_y + cy))
|
|
137
|
+
step_x, step_y = (1 if ray_x >= 0 else -1), (1 if ray_y >= 0 else -1)
|
|
138
|
+
inv_dir_x = (_ONE / ray_x) if abs(ray_x) > _TINY else _ZERO
|
|
139
|
+
inv_dir_y = (_ONE / ray_y) if abs(ray_y) > _TINY else _ZERO
|
|
140
|
+
dt_x = abs(inv_dir_x) if abs(ray_x) > _TINY else _BIG
|
|
141
|
+
dt_y = abs(inv_dir_y) if abs(ray_y) > _TINY else _BIG
|
|
142
|
+
next_ix = ix + (1 if step_x > 0 else 0)
|
|
143
|
+
next_iy = iy + (1 if step_y > 0 else 0)
|
|
144
|
+
tx = (np.float32(next_ix) - cx - ent_x) * inv_dir_x if abs(ray_x) > _TINY else _BIG
|
|
145
|
+
ty = (np.float32(next_iy) - cy - ent_y) * inv_dir_y if abs(ray_y) > _TINY else _BIG
|
|
146
|
+
|
|
147
|
+
ga_x = _ZERO
|
|
148
|
+
ga_y = _ZERO
|
|
149
|
+
gb_x = _ZERO
|
|
150
|
+
gb_y = _ZERO
|
|
151
|
+
f_prev = _ZERO
|
|
152
|
+
pend_axis = ent_axis
|
|
153
|
+
pend_t = _ZERO
|
|
154
|
+
first = True
|
|
155
|
+
while t < t_end:
|
|
156
|
+
f = _ZERO
|
|
157
|
+
if 0 <= ix < Nx and 0 <= iy < Ny:
|
|
158
|
+
f = d_image[iy, ix]
|
|
159
|
+
t_next = min(tx, ty, t_end)
|
|
160
|
+
seg_len = t_next - t
|
|
161
|
+
# The face crossed just before this cell: entry face or interior face.
|
|
162
|
+
if pend_axis >= 0:
|
|
163
|
+
c = (-f) if first else (f_prev - f)
|
|
164
|
+
if pend_axis == 0:
|
|
165
|
+
da, db = _face_terms(c, pend_t + t_min, t_a0, t_b0, length, ray_x)
|
|
166
|
+
ga_x += da
|
|
167
|
+
gb_x += db
|
|
168
|
+
else:
|
|
169
|
+
da, db = _face_terms(c, pend_t + t_min, t_a0, t_b0, length, ray_y)
|
|
170
|
+
ga_y += da
|
|
171
|
+
gb_y += db
|
|
172
|
+
first = False
|
|
173
|
+
if seg_len > _ZERO:
|
|
174
|
+
accum += f * seg_len
|
|
175
|
+
f_prev = f
|
|
176
|
+
if tx <= ty:
|
|
177
|
+
t = tx
|
|
178
|
+
pend_t = tx
|
|
179
|
+
pend_axis = 0
|
|
180
|
+
ix += step_x
|
|
181
|
+
tx += dt_x
|
|
182
|
+
else:
|
|
183
|
+
t = ty
|
|
184
|
+
pend_t = ty
|
|
185
|
+
pend_axis = 1
|
|
186
|
+
iy += step_y
|
|
187
|
+
ty += dt_y
|
|
188
|
+
if ex_axis == 0:
|
|
189
|
+
da, db = _face_terms(f_prev, t_max, t_a0, t_b0, length, ray_x)
|
|
190
|
+
ga_x += da
|
|
191
|
+
gb_x += db
|
|
192
|
+
elif ex_axis == 1:
|
|
193
|
+
da, db = _face_terms(f_prev, t_max, t_a0, t_b0, length, ray_y)
|
|
194
|
+
ga_y += da
|
|
195
|
+
gb_y += db
|
|
196
|
+
|
|
197
|
+
# Length term: p = s * |D| * S with S = accum / |D| (voxel units).
|
|
198
|
+
S = accum / length
|
|
199
|
+
ga_x -= S * ray_x
|
|
200
|
+
ga_y -= S * ray_y
|
|
201
|
+
gb_x += S * ray_x
|
|
202
|
+
gb_y += S * ray_y
|
|
203
|
+
|
|
204
|
+
cuda.atomic.add(d_grad_src, (iang, 0), g * ga_x)
|
|
205
|
+
cuda.atomic.add(d_grad_src, (iang, 1), g * ga_y)
|
|
206
|
+
cuda.atomic.add(d_grad_det_center, (iang, 0), g * gb_x)
|
|
207
|
+
cuda.atomic.add(d_grad_det_center, (iang, 1), g * gb_y)
|
|
208
|
+
cuda.atomic.add(d_grad_det_u, (iang, 0), g * u_phys * gb_x)
|
|
209
|
+
cuda.atomic.add(d_grad_det_u, (iang, 1), g * u_phys * gb_y)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
@_FASTMATH_DECORATOR
|
|
213
|
+
def _cone_3d_geometry_vjp_kernel(
|
|
214
|
+
d_vol, Nx, Ny, Nz,
|
|
215
|
+
d_grad_sino, n_views, n_u, n_v,
|
|
216
|
+
du, dv, d_src_pos, d_det_center, d_det_u_vec, d_det_v_vec,
|
|
217
|
+
cx, cy, cz, voxel_spacing,
|
|
218
|
+
d_grad_src, d_grad_det_center, d_grad_det_u, d_grad_det_v,
|
|
219
|
+
):
|
|
220
|
+
"""Accumulate cone-beam geometry gradients of ``<grad_sino, A x>`` per view."""
|
|
221
|
+
iv, iu, iview = cuda.grid(3)
|
|
222
|
+
if iview >= n_views or iu >= n_u or iv >= n_v:
|
|
223
|
+
return
|
|
224
|
+
g = d_grad_sino[iview, iu, iv]
|
|
225
|
+
if g == _ZERO:
|
|
226
|
+
return
|
|
227
|
+
|
|
228
|
+
src_x = d_src_pos[iview, 0] / voxel_spacing
|
|
229
|
+
src_y = d_src_pos[iview, 1] / voxel_spacing
|
|
230
|
+
src_z = d_src_pos[iview, 2] / voxel_spacing
|
|
231
|
+
det_cx = d_det_center[iview, 0] / voxel_spacing
|
|
232
|
+
det_cy = d_det_center[iview, 1] / voxel_spacing
|
|
233
|
+
det_cz = d_det_center[iview, 2] / voxel_spacing
|
|
234
|
+
u_vec_x = d_det_u_vec[iview, 0]
|
|
235
|
+
u_vec_y = d_det_u_vec[iview, 1]
|
|
236
|
+
u_vec_z = d_det_u_vec[iview, 2]
|
|
237
|
+
v_vec_x = d_det_v_vec[iview, 0]
|
|
238
|
+
v_vec_y = d_det_v_vec[iview, 1]
|
|
239
|
+
v_vec_z = d_det_v_vec[iview, 2]
|
|
240
|
+
u_phys = (np.float32(iu) + _HALF - np.float32(n_u) * _HALF) * du
|
|
241
|
+
v_phys = (np.float32(iv) + _HALF - np.float32(n_v) * _HALF) * dv
|
|
242
|
+
u_offset = u_phys / voxel_spacing
|
|
243
|
+
v_offset = v_phys / voxel_spacing
|
|
244
|
+
det_x = det_cx + u_offset * u_vec_x + v_offset * v_vec_x
|
|
245
|
+
det_y = det_cy + u_offset * u_vec_y + v_offset * v_vec_y
|
|
246
|
+
det_z = det_cz + u_offset * u_vec_z + v_offset * v_vec_z
|
|
247
|
+
|
|
248
|
+
dir_x, dir_y, dir_z = det_x - src_x, det_y - src_y, det_z - src_z
|
|
249
|
+
length = math.sqrt(dir_x * dir_x + dir_y * dir_y + dir_z * dir_z)
|
|
250
|
+
if length < _EPSILON:
|
|
251
|
+
return
|
|
252
|
+
inv_len = _ONE / length
|
|
253
|
+
dir_x, dir_y, dir_z = dir_x * inv_len, dir_y * inv_len, dir_z * inv_len
|
|
254
|
+
|
|
255
|
+
if (src_x * src_x + src_y * src_y + src_z * src_z
|
|
256
|
+
<= det_x * det_x + det_y * det_y + det_z * det_z):
|
|
257
|
+
org_x = src_x
|
|
258
|
+
org_y = src_y
|
|
259
|
+
org_z = src_z
|
|
260
|
+
t_min, t_max = _ZERO, length
|
|
261
|
+
t_a0, t_b0 = _ZERO, length
|
|
262
|
+
else:
|
|
263
|
+
org_x = det_x
|
|
264
|
+
org_y = det_y
|
|
265
|
+
org_z = det_z
|
|
266
|
+
t_min, t_max = -length, _ZERO
|
|
267
|
+
t_a0, t_b0 = -length, _ZERO
|
|
268
|
+
ent_axis = -1
|
|
269
|
+
ex_axis = -1
|
|
270
|
+
if abs(dir_x) > _TINY:
|
|
271
|
+
tx1, tx2 = (-cx - org_x) / dir_x, (cx - org_x) / dir_x
|
|
272
|
+
lo, hi = min(tx1, tx2), max(tx1, tx2)
|
|
273
|
+
if lo > t_min:
|
|
274
|
+
t_min = lo
|
|
275
|
+
ent_axis = 0
|
|
276
|
+
if hi < t_max:
|
|
277
|
+
t_max = hi
|
|
278
|
+
ex_axis = 0
|
|
279
|
+
elif org_x < -cx or org_x > cx:
|
|
280
|
+
return
|
|
281
|
+
if abs(dir_y) > _TINY:
|
|
282
|
+
ty1, ty2 = (-cy - org_y) / dir_y, (cy - org_y) / dir_y
|
|
283
|
+
lo, hi = min(ty1, ty2), max(ty1, ty2)
|
|
284
|
+
if lo > t_min:
|
|
285
|
+
t_min = lo
|
|
286
|
+
ent_axis = 1
|
|
287
|
+
if hi < t_max:
|
|
288
|
+
t_max = hi
|
|
289
|
+
ex_axis = 1
|
|
290
|
+
elif org_y < -cy or org_y > cy:
|
|
291
|
+
return
|
|
292
|
+
if abs(dir_z) > _TINY:
|
|
293
|
+
tz1, tz2 = (-cz - org_z) / dir_z, (cz - org_z) / dir_z
|
|
294
|
+
lo, hi = min(tz1, tz2), max(tz1, tz2)
|
|
295
|
+
if lo > t_min:
|
|
296
|
+
t_min = lo
|
|
297
|
+
ent_axis = 2
|
|
298
|
+
if hi < t_max:
|
|
299
|
+
t_max = hi
|
|
300
|
+
ex_axis = 2
|
|
301
|
+
elif org_z < -cz or org_z > cz:
|
|
302
|
+
return
|
|
303
|
+
if t_min >= t_max:
|
|
304
|
+
return
|
|
305
|
+
|
|
306
|
+
ent_x = org_x + t_min * dir_x
|
|
307
|
+
ent_y = org_y + t_min * dir_y
|
|
308
|
+
ent_z = org_z + t_min * dir_z
|
|
309
|
+
ray_x = dir_x
|
|
310
|
+
ray_y = dir_y
|
|
311
|
+
ray_z = dir_z
|
|
312
|
+
t_end = t_max - t_min
|
|
313
|
+
|
|
314
|
+
accum = _ZERO
|
|
315
|
+
t = _ZERO
|
|
316
|
+
ix = int(math.floor(ent_x + cx))
|
|
317
|
+
iy = int(math.floor(ent_y + cy))
|
|
318
|
+
iz = int(math.floor(ent_z + cz))
|
|
319
|
+
step_x, step_y, step_z = (1 if ray_x >= 0 else -1), (1 if ray_y >= 0 else -1), (1 if ray_z >= 0 else -1)
|
|
320
|
+
inv_dir_x = (_ONE / ray_x) if abs(ray_x) > _TINY else _ZERO
|
|
321
|
+
inv_dir_y = (_ONE / ray_y) if abs(ray_y) > _TINY else _ZERO
|
|
322
|
+
inv_dir_z = (_ONE / ray_z) if abs(ray_z) > _TINY else _ZERO
|
|
323
|
+
dt_x = abs(inv_dir_x) if abs(ray_x) > _TINY else _BIG
|
|
324
|
+
dt_y = abs(inv_dir_y) if abs(ray_y) > _TINY else _BIG
|
|
325
|
+
dt_z = abs(inv_dir_z) if abs(ray_z) > _TINY else _BIG
|
|
326
|
+
next_ix = ix + (1 if step_x > 0 else 0)
|
|
327
|
+
next_iy = iy + (1 if step_y > 0 else 0)
|
|
328
|
+
next_iz = iz + (1 if step_z > 0 else 0)
|
|
329
|
+
tx = (np.float32(next_ix) - cx - ent_x) * inv_dir_x if abs(ray_x) > _TINY else _BIG
|
|
330
|
+
ty = (np.float32(next_iy) - cy - ent_y) * inv_dir_y if abs(ray_y) > _TINY else _BIG
|
|
331
|
+
tz = (np.float32(next_iz) - cz - ent_z) * inv_dir_z if abs(ray_z) > _TINY else _BIG
|
|
332
|
+
|
|
333
|
+
ga_x = _ZERO
|
|
334
|
+
ga_y = _ZERO
|
|
335
|
+
ga_z = _ZERO
|
|
336
|
+
gb_x = _ZERO
|
|
337
|
+
gb_y = _ZERO
|
|
338
|
+
gb_z = _ZERO
|
|
339
|
+
f_prev = _ZERO
|
|
340
|
+
pend_axis = ent_axis
|
|
341
|
+
pend_t = _ZERO
|
|
342
|
+
first = True
|
|
343
|
+
while t < t_end:
|
|
344
|
+
f = _ZERO
|
|
345
|
+
if 0 <= ix < Nx and 0 <= iy < Ny and 0 <= iz < Nz:
|
|
346
|
+
f = d_vol[ix, iy, iz]
|
|
347
|
+
t_next = min(tx, ty, tz, t_end)
|
|
348
|
+
seg_len = t_next - t
|
|
349
|
+
if pend_axis >= 0:
|
|
350
|
+
c = (-f) if first else (f_prev - f)
|
|
351
|
+
if pend_axis == 0:
|
|
352
|
+
da, db = _face_terms(c, pend_t + t_min, t_a0, t_b0, length, ray_x)
|
|
353
|
+
ga_x += da
|
|
354
|
+
gb_x += db
|
|
355
|
+
elif pend_axis == 1:
|
|
356
|
+
da, db = _face_terms(c, pend_t + t_min, t_a0, t_b0, length, ray_y)
|
|
357
|
+
ga_y += da
|
|
358
|
+
gb_y += db
|
|
359
|
+
else:
|
|
360
|
+
da, db = _face_terms(c, pend_t + t_min, t_a0, t_b0, length, ray_z)
|
|
361
|
+
ga_z += da
|
|
362
|
+
gb_z += db
|
|
363
|
+
first = False
|
|
364
|
+
if seg_len > _ZERO:
|
|
365
|
+
accum += f * seg_len
|
|
366
|
+
f_prev = f
|
|
367
|
+
if tx <= ty and tx <= tz:
|
|
368
|
+
t = tx
|
|
369
|
+
pend_t = tx
|
|
370
|
+
pend_axis = 0
|
|
371
|
+
ix += step_x
|
|
372
|
+
tx += dt_x
|
|
373
|
+
elif ty <= tx and ty <= tz:
|
|
374
|
+
t = ty
|
|
375
|
+
pend_t = ty
|
|
376
|
+
pend_axis = 1
|
|
377
|
+
iy += step_y
|
|
378
|
+
ty += dt_y
|
|
379
|
+
else:
|
|
380
|
+
t = tz
|
|
381
|
+
pend_t = tz
|
|
382
|
+
pend_axis = 2
|
|
383
|
+
iz += step_z
|
|
384
|
+
tz += dt_z
|
|
385
|
+
if ex_axis == 0:
|
|
386
|
+
da, db = _face_terms(f_prev, t_max, t_a0, t_b0, length, ray_x)
|
|
387
|
+
ga_x += da
|
|
388
|
+
gb_x += db
|
|
389
|
+
elif ex_axis == 1:
|
|
390
|
+
da, db = _face_terms(f_prev, t_max, t_a0, t_b0, length, ray_y)
|
|
391
|
+
ga_y += da
|
|
392
|
+
gb_y += db
|
|
393
|
+
elif ex_axis == 2:
|
|
394
|
+
da, db = _face_terms(f_prev, t_max, t_a0, t_b0, length, ray_z)
|
|
395
|
+
ga_z += da
|
|
396
|
+
gb_z += db
|
|
397
|
+
|
|
398
|
+
S = accum / length
|
|
399
|
+
ga_x -= S * ray_x
|
|
400
|
+
ga_y -= S * ray_y
|
|
401
|
+
ga_z -= S * ray_z
|
|
402
|
+
gb_x += S * ray_x
|
|
403
|
+
gb_y += S * ray_y
|
|
404
|
+
gb_z += S * ray_z
|
|
405
|
+
|
|
406
|
+
cuda.atomic.add(d_grad_src, (iview, 0), g * ga_x)
|
|
407
|
+
cuda.atomic.add(d_grad_src, (iview, 1), g * ga_y)
|
|
408
|
+
cuda.atomic.add(d_grad_src, (iview, 2), g * ga_z)
|
|
409
|
+
cuda.atomic.add(d_grad_det_center, (iview, 0), g * gb_x)
|
|
410
|
+
cuda.atomic.add(d_grad_det_center, (iview, 1), g * gb_y)
|
|
411
|
+
cuda.atomic.add(d_grad_det_center, (iview, 2), g * gb_z)
|
|
412
|
+
cuda.atomic.add(d_grad_det_u, (iview, 0), g * u_phys * gb_x)
|
|
413
|
+
cuda.atomic.add(d_grad_det_u, (iview, 1), g * u_phys * gb_y)
|
|
414
|
+
cuda.atomic.add(d_grad_det_u, (iview, 2), g * u_phys * gb_z)
|
|
415
|
+
cuda.atomic.add(d_grad_det_v, (iview, 0), g * v_phys * gb_x)
|
|
416
|
+
cuda.atomic.add(d_grad_det_v, (iview, 1), g * v_phys * gb_y)
|
|
417
|
+
cuda.atomic.add(d_grad_det_v, (iview, 2), g * v_phys * gb_z)
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
@_FASTMATH_DECORATOR
|
|
421
|
+
def _parallel_2d_geometry_vjp_kernel(
|
|
422
|
+
d_image, Nx, Ny,
|
|
423
|
+
d_grad_sino, n_ang, n_det,
|
|
424
|
+
det_spacing, d_ray_dir, d_det_origin, d_det_u_vec,
|
|
425
|
+
cx, cy, voxel_spacing,
|
|
426
|
+
d_grad_ray_dir, d_grad_det_origin, d_grad_det_u,
|
|
427
|
+
):
|
|
428
|
+
"""Accumulate parallel-beam geometry gradients of ``<grad_sino, A x>`` per view.
|
|
429
|
+
|
|
430
|
+
The ray is ``P + t r`` over the whole line, with P the detector pixel and
|
|
431
|
+
r the ray direction as given. A face crossing ``t = (b - P_a) / r_a`` gives
|
|
432
|
+
``dt/dP_a = -1 / r_a`` and ``dt/dr_a = -t / r_a``.
|
|
433
|
+
"""
|
|
434
|
+
iang, idet = cuda.grid(2)
|
|
435
|
+
if iang >= n_ang or idet >= n_det:
|
|
436
|
+
return
|
|
437
|
+
g = d_grad_sino[iang, idet]
|
|
438
|
+
if g == _ZERO:
|
|
439
|
+
return
|
|
440
|
+
|
|
441
|
+
# Ray setup: identical to _parallel_2d_forward_kernel (float64), plus the
|
|
442
|
+
# entry and exit faces.
|
|
443
|
+
dir_x = np.float64(d_ray_dir[iang, 0])
|
|
444
|
+
dir_y = np.float64(d_ray_dir[iang, 1])
|
|
445
|
+
det_ox = np.float64(d_det_origin[iang, 0]) / voxel_spacing
|
|
446
|
+
det_oy = np.float64(d_det_origin[iang, 1]) / voxel_spacing
|
|
447
|
+
u_vec_x = np.float64(d_det_u_vec[iang, 0])
|
|
448
|
+
u_vec_y = np.float64(d_det_u_vec[iang, 1])
|
|
449
|
+
u_phys = (np.float32(idet) + _HALF - np.float32(n_det) * _HALF) * det_spacing
|
|
450
|
+
u_offset = (np.float64(idet) + _HALF - np.float64(n_det) * _HALF) * det_spacing / voxel_spacing
|
|
451
|
+
pnt_x = det_ox + u_offset * u_vec_x
|
|
452
|
+
pnt_y = det_oy + u_offset * u_vec_y
|
|
453
|
+
|
|
454
|
+
t_min, t_max = -_BIG, _BIG
|
|
455
|
+
ent_axis = -1
|
|
456
|
+
ex_axis = -1
|
|
457
|
+
if dir_x != 0.0:
|
|
458
|
+
tx1, tx2 = (-cx - pnt_x) / dir_x, (cx - pnt_x) / dir_x
|
|
459
|
+
lo, hi = min(tx1, tx2), max(tx1, tx2)
|
|
460
|
+
if lo > t_min:
|
|
461
|
+
t_min = lo
|
|
462
|
+
ent_axis = 0
|
|
463
|
+
if hi < t_max:
|
|
464
|
+
t_max = hi
|
|
465
|
+
ex_axis = 0
|
|
466
|
+
elif pnt_x < -cx or pnt_x > cx:
|
|
467
|
+
return
|
|
468
|
+
if dir_y != 0.0:
|
|
469
|
+
ty1, ty2 = (-cy - pnt_y) / dir_y, (cy - pnt_y) / dir_y
|
|
470
|
+
lo, hi = min(ty1, ty2), max(ty1, ty2)
|
|
471
|
+
if lo > t_min:
|
|
472
|
+
t_min = lo
|
|
473
|
+
ent_axis = 1
|
|
474
|
+
if hi < t_max:
|
|
475
|
+
t_max = hi
|
|
476
|
+
ex_axis = 1
|
|
477
|
+
elif pnt_y < -cy or pnt_y > cy:
|
|
478
|
+
return
|
|
479
|
+
if t_min >= t_max:
|
|
480
|
+
return
|
|
481
|
+
|
|
482
|
+
ent_x = np.float32(pnt_x + t_min * dir_x)
|
|
483
|
+
ent_y = np.float32(pnt_y + t_min * dir_y)
|
|
484
|
+
ray_x = np.float32(dir_x)
|
|
485
|
+
ray_y = np.float32(dir_y)
|
|
486
|
+
t_end = np.float32(t_max - t_min)
|
|
487
|
+
|
|
488
|
+
t = _ZERO
|
|
489
|
+
ix = int(math.floor(ent_x + cx))
|
|
490
|
+
iy = int(math.floor(ent_y + cy))
|
|
491
|
+
step_x, step_y = (1 if ray_x >= 0 else -1), (1 if ray_y >= 0 else -1)
|
|
492
|
+
inv_dir_x = (_ONE / ray_x) if abs(ray_x) > _TINY else _ZERO
|
|
493
|
+
inv_dir_y = (_ONE / ray_y) if abs(ray_y) > _TINY else _ZERO
|
|
494
|
+
dt_x = abs(inv_dir_x) if abs(ray_x) > _TINY else _BIG
|
|
495
|
+
dt_y = abs(inv_dir_y) if abs(ray_y) > _TINY else _BIG
|
|
496
|
+
next_ix = ix + (1 if step_x > 0 else 0)
|
|
497
|
+
next_iy = iy + (1 if step_y > 0 else 0)
|
|
498
|
+
tx = (np.float32(next_ix) - cx - ent_x) * inv_dir_x if abs(ray_x) > _TINY else _BIG
|
|
499
|
+
ty = (np.float32(next_iy) - cy - ent_y) * inv_dir_y if abs(ray_y) > _TINY else _BIG
|
|
500
|
+
|
|
501
|
+
# Per axis: C = sum of face coefficients, T = sum of coefficient * t,
|
|
502
|
+
# with t measured from the entry point (t_min is added afterwards).
|
|
503
|
+
c_x = _ZERO
|
|
504
|
+
c_y = _ZERO
|
|
505
|
+
ct_x = _ZERO
|
|
506
|
+
ct_y = _ZERO
|
|
507
|
+
f_prev = _ZERO
|
|
508
|
+
pend_axis = ent_axis
|
|
509
|
+
pend_t = _ZERO
|
|
510
|
+
first = True
|
|
511
|
+
while t < t_end:
|
|
512
|
+
f = _ZERO
|
|
513
|
+
if 0 <= ix < Nx and 0 <= iy < Ny:
|
|
514
|
+
f = d_image[iy, ix]
|
|
515
|
+
if pend_axis >= 0:
|
|
516
|
+
c = (-f) if first else (f_prev - f)
|
|
517
|
+
if pend_axis == 0:
|
|
518
|
+
c_x += c
|
|
519
|
+
ct_x += c * pend_t
|
|
520
|
+
else:
|
|
521
|
+
c_y += c
|
|
522
|
+
ct_y += c * pend_t
|
|
523
|
+
first = False
|
|
524
|
+
f_prev = f
|
|
525
|
+
if tx <= ty:
|
|
526
|
+
t = tx
|
|
527
|
+
pend_t = tx
|
|
528
|
+
pend_axis = 0
|
|
529
|
+
ix += step_x
|
|
530
|
+
tx += dt_x
|
|
531
|
+
else:
|
|
532
|
+
t = ty
|
|
533
|
+
pend_t = ty
|
|
534
|
+
pend_axis = 1
|
|
535
|
+
iy += step_y
|
|
536
|
+
ty += dt_y
|
|
537
|
+
if ex_axis == 0:
|
|
538
|
+
c_x += f_prev
|
|
539
|
+
ct_x += f_prev * t_end
|
|
540
|
+
elif ex_axis == 1:
|
|
541
|
+
c_y += f_prev
|
|
542
|
+
ct_y += f_prev * t_end
|
|
543
|
+
|
|
544
|
+
# p = s * sum_j c_j t_j; dt/dP_a = -1/r_a (P in voxels) and dt/dr_a = -t/r_a.
|
|
545
|
+
# t_min stays float64: a distant detector origin makes it large.
|
|
546
|
+
gp_x = _ZERO
|
|
547
|
+
gp_y = _ZERO
|
|
548
|
+
gr_x = _ZERO
|
|
549
|
+
gr_y = _ZERO
|
|
550
|
+
if abs(ray_x) > _TINY:
|
|
551
|
+
gp_x = -c_x / ray_x
|
|
552
|
+
gr_x = np.float32(-voxel_spacing * (t_min * c_x + ct_x) / ray_x)
|
|
553
|
+
if abs(ray_y) > _TINY:
|
|
554
|
+
gp_y = -c_y / ray_y
|
|
555
|
+
gr_y = np.float32(-voxel_spacing * (t_min * c_y + ct_y) / ray_y)
|
|
556
|
+
|
|
557
|
+
cuda.atomic.add(d_grad_ray_dir, (iang, 0), g * gr_x)
|
|
558
|
+
cuda.atomic.add(d_grad_ray_dir, (iang, 1), g * gr_y)
|
|
559
|
+
cuda.atomic.add(d_grad_det_origin, (iang, 0), g * gp_x)
|
|
560
|
+
cuda.atomic.add(d_grad_det_origin, (iang, 1), g * gp_y)
|
|
561
|
+
cuda.atomic.add(d_grad_det_u, (iang, 0), g * u_phys * gp_x)
|
|
562
|
+
cuda.atomic.add(d_grad_det_u, (iang, 1), g * u_phys * gp_y)
|