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-
81import array_api_compat .numpy as xp
9- import numpy . typing as npt
2+ import matplotlib . pyplot as plt
103import petsird
114import parallelproj
125
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+
1819from pathlib import Path
1920import 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
8724parser = 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+
14080for 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
286247reader .close ()
@@ -290,6 +251,21 @@ def parse_float_tuple(arg):
290251effs = xp .asarray (effs , device = dev )
291252tof_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
0 commit comments