Skip to content

Commit c211e7d

Browse files
Georg SchrammGeorg Schramm
authored andcommitted
add geometry plot in recon script
1 parent d6cc343 commit c211e7d

File tree

3 files changed

+131
-287
lines changed

3 files changed

+131
-287
lines changed

python/02_lm_osem_recon_simulated_data.py

Lines changed: 48 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,5 @@
1-
# Copyright (C) 2024 University College London
2-
#
3-
# SPDX-License-Identifier: Apache-2.0
4-
5-
# basic plotting of the scanner geometry
6-
# preliminary code!
7-
81
import array_api_compat.numpy as xp
9-
import numpy.typing as npt
2+
import matplotlib.pyplot as plt
103
import petsird
114
import parallelproj
125

@@ -15,73 +8,17 @@
158
get_detection_efficiency,
169
)
1710

11+
from utils import (
12+
parse_float_tuple,
13+
parse_int_tuple,
14+
mult_transforms,
15+
transform_BoxShape,
16+
draw_BoxShape,
17+
)
18+
1819
from pathlib import Path
1920
import argparse
2021

21-
22-
def transform_to_mat44(
23-
transform: petsird.RigidTransformation,
24-
) -> npt.NDArray[xp.float32]:
25-
return xp.vstack([transform.matrix, [0, 0, 0, 1]])
26-
27-
28-
def mat44_to_transform(mat: npt.NDArray[xp.float32]) -> petsird.RigidTransformation:
29-
return petsird.RigidTransformation(matrix=mat[0:3, :])
30-
31-
32-
def coordinate_to_homogeneous(coord: petsird.Coordinate) -> npt.NDArray[xp.float32]:
33-
return xp.hstack([coord.c, 1])
34-
35-
36-
def homogeneous_to_coordinate(
37-
hom_coord: npt.NDArray[xp.float32],
38-
) -> petsird.Coordinate:
39-
return petsird.Coordinate(c=hom_coord[0:3])
40-
41-
42-
def mult_transforms(
43-
transforms: list[petsird.RigidTransformation],
44-
) -> petsird.RigidTransformation:
45-
"""multiply rigid transformations"""
46-
mat = xp.array(
47-
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1)),
48-
dtype="float32",
49-
)
50-
51-
for t in reversed(transforms):
52-
mat = xp.matmul(transform_to_mat44(t), mat)
53-
return mat44_to_transform(mat)
54-
55-
56-
def mult_transforms_coord(
57-
transforms: list[petsird.RigidTransformation], coord: petsird.Coordinate
58-
) -> petsird.Coordinate:
59-
"""apply list of transformations to coordinate"""
60-
# TODO better to multiply with coordinates in sequence, as first multiplying the matrices
61-
hom = xp.matmul(
62-
transform_to_mat44(mult_transforms(transforms)),
63-
coordinate_to_homogeneous(coord),
64-
)
65-
return homogeneous_to_coordinate(hom)
66-
67-
68-
def transform_BoxShape(
69-
transform: petsird.RigidTransformation, box_shape: petsird.BoxShape
70-
) -> petsird.BoxShape:
71-
72-
return petsird.BoxShape(
73-
corners=[mult_transforms_coord([transform], c) for c in box_shape.corners]
74-
)
75-
76-
77-
def parse_int_tuple(arg):
78-
return tuple(map(int, arg.split(",")))
79-
80-
81-
def parse_float_tuple(arg):
82-
return tuple(map(float, arg.split(",")))
83-
84-
8522
# %%
8623

8724
parser = argparse.ArgumentParser()
@@ -137,6 +74,9 @@ def parse_float_tuple(arg):
13774
# read the LOR endpoint coordinates for each detecting element in each crystal
13875
# we assume that the LOR endpoint corresponds to the center of the BoxShape
13976

77+
fig_scanner = plt.figure(figsize=(8, 8), tight_layout=True)
78+
ax_scanner = fig_scanner.add_subplot(111, projection="3d")
79+
14080
for rep_module in header.scanner.scanner_geometry.replicated_modules:
14181
det_el = rep_module.object.detecting_elements
14282

@@ -165,8 +105,21 @@ def parse_float_tuple(arg):
165105
det_element_centers[i_el, ...] = transformed_boxshape_vertices.mean(
166106
axis=0
167107
)
108+
109+
# visualize the detecting elements
110+
draw_BoxShape(ax_scanner, transformed_boxshape)
111+
if i_el == 0 or i_el == len(rep_volume.transforms) - 1:
112+
ax_scanner.text(
113+
float(transformed_boxshape_vertices[0][0]),
114+
float(transformed_boxshape_vertices[0][1]),
115+
float(transformed_boxshape_vertices[0][2]),
116+
f"{i_el:02}/{i_mod:02}",
117+
fontsize=7,
118+
)
119+
168120
det_element_center_list.append(det_element_centers)
169121

122+
# %%
170123
# create a list of the element detection efficiencies per module
171124
# this is a simple re-ordering of the detection efficiencies array which
172125
# makes the access easier
@@ -281,6 +234,14 @@ def parse_float_tuple(arg):
281234
# get the signed event TOF bin (0 is the central bin)
282235
tof_bin.append(event.tof_idx - num_tof_bins // 2)
283236

237+
# visualize the first 5 events in the time block
238+
if i_time_block == 0 and i_event < 5:
239+
ax_scanner.plot(
240+
[event_start_coord[0], event_end_coord[0]],
241+
[event_start_coord[1], event_end_coord[1]],
242+
[event_start_coord[2], event_end_coord[2]],
243+
)
244+
284245
event_counter += 1
285246

286247
reader.close()
@@ -290,6 +251,21 @@ def parse_float_tuple(arg):
290251
effs = xp.asarray(effs, device=dev)
291252
tof_bin = xp.asarray(tof_bin, device=dev)
292253

254+
# %%
255+
# set the x, y, z limits of the scanner plot
256+
xmin = xp.asarray([x.min(axis=0) for x in det_element_center_list]).min(axis=0)
257+
xmax = xp.asarray([x.max(axis=0) for x in det_element_center_list]).max(axis=0)
258+
r = (xmax - xmin).max()
259+
260+
ax_scanner.set_xlabel("x0")
261+
ax_scanner.set_ylabel("x1")
262+
ax_scanner.set_zlabel("x2")
263+
ax_scanner.set_xlim(xmin.min() - 0.05 * r, xmax.max() + 0.05 * r)
264+
ax_scanner.set_ylim(xmin.min() - 0.05 * r, xmax.max() + 0.05 * r)
265+
ax_scanner.set_zlim(xmin.min() - 0.05 * r, xmax.max() + 0.05 * r)
266+
267+
fig_scanner.savefig(output_dir / "scanner_geometry.png")
268+
fig_scanner.show()
293269

294270
# %%
295271
# run a LM OSEM recon

python/petsird_plot_scanner_and_events.py

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

0 commit comments

Comments
 (0)