Skip to content

Commit 84b641c

Browse files
committed
Refactor build_crystal_maps and fix some issues with initial x position
1 parent 5c3c804 commit 84b641c

File tree

3 files changed

+443
-114
lines changed

3 files changed

+443
-114
lines changed

Diff for: src/buildblock/GeometryBlocksOnCylindrical.cxx

+122-70
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ limitations under the License.
3535
#include "stir/Array.h"
3636
#include "stir/make_array.h"
3737
#include "stir/numerics/MatrixFunction.h"
38+
#include "stir/warning.h"
3839
#include <map>
3940
#include <iostream>
4041
#include <fstream>
@@ -63,86 +64,137 @@ GeometryBlocksOnCylindrical::build_crystal_maps(const Scanner& scanner)
6364
{
6465
// local variables to describe scanner
6566
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();
6669
int num_transaxial_crystals_per_block = scanner.get_num_transaxial_crystals_per_block();
6770
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();
6971
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();
7672

7773
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);
10374

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
10484
for (int ax_bucket_num = 0; ax_bucket_num < num_axial_buckets; ++ax_bucket_num)
10585
for (int ax_block_num = 0; ax_block_num < num_axial_blocks_per_bucket; ++ax_block_num)
10686
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+
}
145116
set_detector_map(cartesian_coord_map_given_detection_position_keys);
146117
}
147118

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+
}
148200
END_NAMESPACE_STIR

Diff for: src/include/stir/GeometryBlocksOnCylindrical.h

+39
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,45 @@ class GeometryBlocksOnCylindrical : public DetectorCoordinateMap
5252
public:
5353
GeometryBlocksOnCylindrical(const Scanner& scanner);
5454

55+
//! Calculates the transaxial coordinate of a crystal given a scanner and the crystal's indices
56+
static int
57+
get_transaxial_coord(const Scanner& scanner, int transaxial_bucket_num, int transaxial_block_num, int transaxial_crystal_num);
58+
59+
//! Calculates the axial coordinate of a crystal given a scanner and the crystal's indices
60+
static int get_axial_coord(const Scanner& scanner, int axial_bucket_num, int axial_block_num, int axial_crystal_num);
61+
62+
//! Calculates the transaxial translation of a crystal given a scanner and the crystal's indices
63+
static float
64+
get_crystal_in_bucket_transaxial_translation(const Scanner& scanner, int transaxial_block_num, int transaxial_crystal_num);
65+
66+
//! Calculates the axial translation of a crystal given a scanner and the crystal's indices
67+
static float get_axial_translation(const Scanner& scanner, int axial_bucket_num, int axial_block_num, int axial_crystal_num);
68+
69+
//! Calculate the initial axial z offset to center the scanner on 0,0,0
70+
static float get_initial_axial_z_offset(const Scanner& scanner);
71+
72+
//! Calculate the initial transaxial x offset to center the scanner on 0,0,0
73+
static float get_initial_axial_x_offset_for_each_bucket(const Scanner& scanner);
74+
75+
static float get_csi_minus_csi_gaps(const Scanner& scanner)
76+
{
77+
//! Calculate the CSI, angle covered by half a bucket
78+
// 2 * PI / num_transaxial_buckets / 2 (simplified)
79+
float csi = _PI / scanner.get_num_transaxial_buckets(); // TODO, this assumes 1 transaxial bucket per angle
80+
81+
// The difference between the transaxial block spacing and the sum of all transaxial crystal spacing's in the block
82+
float trans_blocks_gap = scanner.get_transaxial_block_spacing()
83+
- scanner.get_num_transaxial_crystals_per_block() * scanner.get_transaxial_crystal_spacing();
84+
// Calculate the angle covered by the gaps between the blocks
85+
float csi_gaps
86+
= 2 * csi * (scanner.get_transaxial_crystal_spacing() / 2 + trans_blocks_gap) / scanner.get_transaxial_block_spacing();
87+
return csi - csi_gaps;
88+
};
89+
90+
//!
91+
CartesianCoordinate3D<float> calculate_crystal_rotation(const CartesianCoordinate3D<float>& crystal_position,
92+
const float alpha) const;
93+
5594
private:
5695
//! Get rotation matrix for a given angle around z axis
5796
stir::Array<2, float> get_rotation_matrix(float alpha) const;

0 commit comments

Comments
 (0)