@@ -35,6 +35,7 @@ limitations under the License.
35
35
#include " stir/Array.h"
36
36
#include " stir/make_array.h"
37
37
#include " stir/numerics/MatrixFunction.h"
38
+ #include " stir/warning.h"
38
39
#include < map>
39
40
#include < iostream>
40
41
#include < fstream>
@@ -63,86 +64,137 @@ GeometryBlocksOnCylindrical::build_crystal_maps(const Scanner& scanner)
63
64
{
64
65
// local variables to describe scanner
65
66
int num_axial_crystals_per_block = scanner.get_num_axial_crystals_per_block ();
67
+ int num_axial_buckets = scanner.get_num_axial_buckets ();
68
+ int num_axial_blocks_per_bucket = scanner.get_num_axial_blocks_per_bucket ();
66
69
int num_transaxial_crystals_per_block = scanner.get_num_transaxial_crystals_per_block ();
67
70
int num_transaxial_blocks_per_bucket = scanner.get_num_transaxial_blocks_per_bucket ();
68
- int num_axial_blocks_per_bucket = scanner.get_num_axial_blocks_per_bucket ();
69
71
int num_transaxial_buckets = scanner.get_num_transaxial_buckets ();
70
- int num_axial_buckets = scanner.get_num_axial_buckets ();
71
- int num_detectors_per_ring = scanner.get_num_detectors_per_ring ();
72
- float axial_block_spacing = scanner.get_axial_block_spacing ();
73
- float transaxial_block_spacing = scanner.get_transaxial_block_spacing ();
74
- float axial_crystal_spacing = scanner.get_axial_crystal_spacing ();
75
- float transaxial_crystal_spacing = scanner.get_transaxial_crystal_spacing ();
76
72
77
73
det_pos_to_coord_type cartesian_coord_map_given_detection_position_keys;
78
- /* Building starts from a bucket perpendicular to y axis, from its first crystal.
79
- see start_x*/
80
-
81
- // calculate start_point to build the map.
82
-
83
- // estimate the angle covered by half bucket, csi
84
- float csi = _PI / num_transaxial_buckets;
85
- float trans_blocks_gap = transaxial_block_spacing - num_transaxial_crystals_per_block * transaxial_crystal_spacing;
86
- float ax_blocks_gap = axial_block_spacing - num_axial_crystals_per_block * axial_crystal_spacing;
87
- float csi_minus_csiGaps = csi - (csi / transaxial_block_spacing * 2 ) * (transaxial_crystal_spacing / 2 + trans_blocks_gap);
88
- // distance between the center of the scannner and the first crystal in the bucket, r=Reffective/cos(csi)
89
- float r = scanner.get_effective_ring_radius () / cos (csi_minus_csiGaps);
90
-
91
- float start_z = -(axial_block_spacing * (num_axial_blocks_per_bucket)*num_axial_buckets - axial_crystal_spacing
92
- - ax_blocks_gap * (num_axial_blocks_per_bucket * num_axial_buckets - 1 ))
93
- / 2 ;
94
- float start_y = -1 * scanner.get_effective_ring_radius ();
95
- float start_x
96
- = -1 * r
97
- * sin (csi_minus_csiGaps); // (
98
- // ((num_transaxial_blocks_per_bucket-1)/2.)*transaxial_block_spacing
99
- // + ((num_transaxial_crystals_per_block-1)/2.)*transaxial_crystal_spacing
100
- // ); //the first crystal in the bucket
101
-
102
- stir::CartesianCoordinate3D<float > start_point (start_z, start_y, start_x);
103
74
75
+ float csi_minus_csiGaps = get_csi_minus_csi_gaps (scanner);
76
+
77
+ // calculate first_crystal_offset to build the map
78
+ stir::CartesianCoordinate3D<float > first_crystal_offset (get_initial_axial_z_offset (scanner),
79
+ -scanner.get_effective_ring_radius (),
80
+ get_initial_axial_x_offset_for_each_bucket (scanner));
81
+ int radial_coord = 0 ;
82
+
83
+ // Lopp over axial geometry
104
84
for (int ax_bucket_num = 0 ; ax_bucket_num < num_axial_buckets; ++ax_bucket_num)
105
85
for (int ax_block_num = 0 ; ax_block_num < num_axial_blocks_per_bucket; ++ax_block_num)
106
86
for (int ax_crys_num = 0 ; ax_crys_num < num_axial_crystals_per_block; ++ax_crys_num)
107
- for (int trans_bucket_num = 0 ; trans_bucket_num < num_transaxial_buckets; ++trans_bucket_num)
108
- for (int trans_block_num = 0 ; trans_block_num < num_transaxial_blocks_per_bucket; ++trans_block_num)
109
- for (int trans_crys_num = 0 ; trans_crys_num < num_transaxial_crystals_per_block; ++trans_crys_num)
110
- {
111
- // calculate detection position for a given detector
112
- // note: in STIR convention, crystal(0,0,0) corresponds to card_coord(z=0,y=-r,x=0)
113
- int tangential_coord;
114
- tangential_coord = trans_bucket_num * num_transaxial_blocks_per_bucket * num_transaxial_crystals_per_block
115
- + trans_block_num * num_transaxial_crystals_per_block + trans_crys_num;
116
-
117
- if (tangential_coord < 0 )
118
- tangential_coord += num_detectors_per_ring;
119
-
120
- int axial_coord = ax_bucket_num * num_axial_blocks_per_bucket * num_axial_crystals_per_block
121
- + ax_block_num * num_axial_crystals_per_block + ax_crys_num;
122
- int radial_coord = 0 ;
123
- stir::DetectionPosition<> det_pos (tangential_coord, axial_coord, radial_coord);
124
-
125
- // calculate cartesian coordinate for a given detector
126
- stir::CartesianCoordinate3D<float > transformation_matrix (
127
- ax_block_num * axial_block_spacing + ax_crys_num * axial_crystal_spacing,
128
- 0 .,
129
- trans_block_num * transaxial_block_spacing + trans_crys_num * transaxial_crystal_spacing);
130
- float alpha = scanner.get_intrinsic_azimuthal_tilt () + trans_bucket_num * (2 * _PI) / num_transaxial_buckets
131
- + csi_minus_csiGaps;
132
-
133
- stir::Array<2 , float > rotation_matrix = get_rotation_matrix (alpha);
134
- // to match index range of CartesianCoordinate3D, which is 1 to 3
135
- rotation_matrix.set_min_index (1 );
136
- rotation_matrix[1 ].set_min_index (1 );
137
- rotation_matrix[2 ].set_min_index (1 );
138
- rotation_matrix[3 ].set_min_index (1 );
139
-
140
- stir::CartesianCoordinate3D<float > transformed_coord = start_point + transformation_matrix;
141
- stir::CartesianCoordinate3D<float > cart_coord = stir::matrix_multiply (rotation_matrix, transformed_coord);
142
-
143
- cartesian_coord_map_given_detection_position_keys[det_pos] = cart_coord;
144
- }
87
+ {
88
+ int axial_coord = get_axial_coord (scanner, ax_bucket_num, ax_block_num, ax_crys_num);
89
+ float axial_translation = get_axial_translation (scanner, ax_bucket_num, ax_block_num, ax_crys_num);
90
+
91
+ // Loop over transaxial geometry
92
+ for (int trans_bucket_num = 0 ; trans_bucket_num < num_transaxial_buckets; ++trans_bucket_num)
93
+ for (int trans_block_num = 0 ; trans_block_num < num_transaxial_blocks_per_bucket; ++trans_block_num)
94
+ for (int trans_crys_num = 0 ; trans_crys_num < num_transaxial_crystals_per_block; ++trans_crys_num)
95
+ {
96
+ // calculate detection position for a given detector
97
+ // note: in STIR convention, crystal(0,0,0) corresponds to card_coord(z=0,y=-r,x=0)
98
+ int transaxial_coord = get_transaxial_coord (scanner, trans_bucket_num, trans_block_num, trans_crys_num);
99
+ stir::DetectionPosition<> det_pos (transaxial_coord, axial_coord, radial_coord);
100
+
101
+ // The translation matrix from the first crystal in the block
102
+ stir::CartesianCoordinate3D<float > translation_matrix (
103
+ axial_translation,
104
+ 0 .,
105
+ get_crystal_in_bucket_transaxial_translation (scanner, trans_block_num, trans_crys_num));
106
+
107
+ stir::CartesianCoordinate3D<float > transformed_coord = first_crystal_offset + translation_matrix;
108
+
109
+ // Calculate the rotation of the crystal
110
+ float alpha = scanner.get_intrinsic_azimuthal_tilt () + trans_bucket_num * (2 * _PI) / num_transaxial_buckets
111
+ + csi_minus_csiGaps;
112
+ cartesian_coord_map_given_detection_position_keys[det_pos]
113
+ = calculate_crystal_rotation (transformed_coord, alpha);
114
+ }
115
+ }
145
116
set_detector_map (cartesian_coord_map_given_detection_position_keys);
146
117
}
147
118
119
+ CartesianCoordinate3D<float >
120
+ GeometryBlocksOnCylindrical::calculate_crystal_rotation (const CartesianCoordinate3D<float >& crystal_position,
121
+ const float alpha) const
122
+ {
123
+ stir::Array<2 , float > rotation_matrix = get_rotation_matrix (alpha);
124
+ // to match index range of CartesianCoordinate3D, which is 1 to 3
125
+ rotation_matrix.set_min_index (1 );
126
+ rotation_matrix[1 ].set_min_index (1 );
127
+ rotation_matrix[2 ].set_min_index (1 );
128
+ rotation_matrix[3 ].set_min_index (1 );
129
+ return stir::matrix_multiply (rotation_matrix, crystal_position);
130
+ }
131
+
132
+ int
133
+ GeometryBlocksOnCylindrical::get_transaxial_coord (const Scanner& scanner,
134
+ int transaxial_bucket_num,
135
+ int transaxial_block_num,
136
+ int transaxial_crystal_num)
137
+ {
138
+ return transaxial_bucket_num * scanner.get_num_transaxial_blocks_per_bucket () * scanner.get_num_transaxial_crystals_per_block ()
139
+ + transaxial_block_num * scanner.get_num_transaxial_crystals_per_block () + transaxial_crystal_num;
140
+ }
141
+
142
+ int
143
+ GeometryBlocksOnCylindrical::get_axial_coord (const Scanner& scanner,
144
+ int axial_bucket_num,
145
+ int axial_block_num,
146
+ int axial_crystal_num)
147
+ {
148
+ return axial_bucket_num * scanner.get_num_axial_blocks_per_bucket () * scanner.get_num_axial_crystals_per_block ()
149
+ + axial_block_num * scanner.get_num_axial_crystals_per_block () + axial_crystal_num;
150
+ }
151
+
152
+ float
153
+ GeometryBlocksOnCylindrical::get_crystal_in_bucket_transaxial_translation (const Scanner& scanner,
154
+ int transaxial_block_num,
155
+ int transaxial_crystal_num)
156
+ {
157
+ // Currently, only supports 1 transaxial bucket per angle
158
+ return transaxial_block_num * scanner.get_transaxial_block_spacing ()
159
+ + transaxial_crystal_num * scanner.get_transaxial_crystal_spacing ();
160
+ }
161
+
162
+ float
163
+ GeometryBlocksOnCylindrical::get_axial_translation (const Scanner& scanner,
164
+ int axial_bucket_num,
165
+ int axial_block_num,
166
+ int axial_crystal_num)
167
+ {
168
+ return // axial_bucket_num * scanner.get_axial_bucket_spacing() +
169
+ axial_block_num * scanner.get_axial_block_spacing () + axial_crystal_num * scanner.get_axial_crystal_spacing ();
170
+ }
171
+
172
+ float
173
+ GeometryBlocksOnCylindrical::get_initial_axial_z_offset (const Scanner& scanner)
174
+ {
175
+ // Crystals in a block are centered, blocks in a bucket are centered, and buckets are centered in the z axis.
176
+ // This centers the scanner in z
177
+ float crystals_in_block_offset = (scanner.get_num_axial_crystals_per_block () - 1 ) * scanner.get_axial_crystal_spacing ();
178
+ float blocks_in_bucket_offset = (scanner.get_num_axial_blocks_per_bucket () - 1 ) * scanner.get_axial_block_spacing ();
179
+ // float bucket_offset = (scanner.get_num_axial_buckets() - 1) * scanner.get_axial_bucket_spacing();
180
+ float bucket_offset = 0 ;
181
+
182
+ // Negative because the scanner is centered at z=0 and increases axial coordinates increase
183
+ // 1/2 because it is half the distance from the center to the edge of the scanner
184
+ return -(1.0 / 2 ) * (crystals_in_block_offset + blocks_in_bucket_offset + bucket_offset);
185
+ }
186
+
187
+ float
188
+ GeometryBlocksOnCylindrical::get_initial_axial_x_offset_for_each_bucket (const Scanner& scanner)
189
+ {
190
+ // This is the old method... This is probably wrong
191
+ // float csi_minus_csiGaps = get_csi_minus_csi_gaps(scanner);
192
+ // float r = scanner.get_effective_ring_radius() / cos(csi_minus_csiGaps);
193
+ // return -1 * r * sin(csi_minus_csiGaps);
194
+
195
+ auto first_crystal_coord = get_crystal_in_bucket_transaxial_translation (scanner, 0 , 0 );
196
+ auto last_crystal_coord = get_crystal_in_bucket_transaxial_translation (
197
+ scanner, scanner.get_num_transaxial_blocks_per_bucket () - 1 , scanner.get_num_transaxial_crystals_per_block () - 1 );
198
+ return -(1.0 / 2 ) * (first_crystal_coord + last_crystal_coord);
199
+ }
148
200
END_NAMESPACE_STIR
0 commit comments