Skip to content

Commit 8029ee4

Browse files
Merge pull request #130 from KrisThielemans/geom_helpers
- moved transform functions from plot_scanner to helpers.geometry (Python) petsird_helpers::geometry (C++) - add equivalent C++ functions - add get_detecting_box helpers to find the BoxShape for an ExpandedDetectionBin - C++: add petsird_helpers/create.h to make it easier to create new ScannerInformation objects
2 parents df88a05 + bdf6cac commit 8029ee4

File tree

10 files changed

+320
-117
lines changed

10 files changed

+320
-117
lines changed

cpp/CMakeLists.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,14 @@ endif()
1717

1818
add_executable(petsird_generator petsird_generator.cpp)
1919
target_link_libraries(petsird_generator petsird_generated)
20+
target_include_directories(petsird_generator PRIVATE "${PROJECT_SOURCE_DIR}/helpers/include")
21+
# needed for generated PETSIRD files
22+
target_include_directories(petsird_generator PRIVATE "${PROJECT_SOURCE_DIR}/")
2023

2124
add_executable(petsird_analysis petsird_analysis.cpp)
2225
target_link_libraries(petsird_analysis petsird_generated)
26+
target_include_directories(petsird_analysis PRIVATE "${PROJECT_SOURCE_DIR}/helpers/include")
27+
# needed for generated PETSIRD files
28+
target_include_directories(petsird_analysis PRIVATE "${PROJECT_SOURCE_DIR}/")
2329

2430
add_subdirectory(generated)
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
Copyright (C) 2025 University College London
3+
4+
SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#include "generated/types.h"
8+
#include <array>
9+
10+
namespace petsird_helpers
11+
{
12+
namespace create
13+
{
14+
15+
using namespace petsird;
16+
17+
//! Helper function to create a std::vector<T>
18+
/*! This function is added to have a 1D analogue construct_2D_nested_vector() */
19+
template <typename T>
20+
std::vector<T>
21+
construct_vector(std::size_t size)
22+
{
23+
std::vector<T> v(size);
24+
return v;
25+
}
26+
27+
//! Helper function to create a std::vector<std::vector<T>> as a 2D array of size (size0, size1)
28+
template <typename T>
29+
std::vector<std::vector<T>>
30+
construct_2D_nested_vector(std::size_t size0, std::size_t size1)
31+
{
32+
std::vector<std::vector<T>> v(size0);
33+
for (auto& one_of_them : v)
34+
{
35+
one_of_them.resize(size1);
36+
}
37+
return v;
38+
}
39+
40+
//! Set various structures to have the correct size for the given num_types_of_modules
41+
/*!
42+
This will set scanner.tof_bin_edges, scanner.tof_resolution,
43+
scanner.event_energy_bin_edges, scanner.energy_resolution_at_511, and
44+
and (optionally) scanner.detection_efficiencies.detection_bin_efficiencies,
45+
scanner.detection_efficiencies.module_pair_sgidlut and
46+
scanner.detection_efficiencies.module_pair_efficiencies_vectors
47+
to (nested) vectors of the appropriate type and size.
48+
49+
Elements will be constructed via the default constructors, so you will still have
50+
to fill in the actual values.
51+
*/
52+
inline void
53+
initialize_scanner_information_dimensions(petsird::ScannerInformation& scanner, const std::size_t num_module_types,
54+
bool allocate_detection_bin_efficiencies, bool allocate_module_pair_efficiencies)
55+
{
56+
scanner.tof_bin_edges = construct_2D_nested_vector<petsird::BinEdges>(num_module_types, num_module_types);
57+
scanner.tof_resolution = construct_2D_nested_vector<float>(num_module_types, num_module_types);
58+
scanner.event_energy_bin_edges = construct_vector<petsird::BinEdges>(num_module_types);
59+
scanner.energy_resolution_at_511 = construct_vector<float>(num_module_types);
60+
61+
scanner.detection_efficiencies = petsird::DetectionEfficiencies();
62+
63+
if (allocate_detection_bin_efficiencies)
64+
{
65+
scanner.detection_efficiencies.detection_bin_efficiencies
66+
= construct_vector<petsird::DetectionBinEfficiencies>(num_module_types);
67+
}
68+
if (allocate_module_pair_efficiencies)
69+
{
70+
scanner.detection_efficiencies.module_pair_sgidlut
71+
= construct_2D_nested_vector<petsird::ModulePairSGIDLUT>(num_module_types, num_module_types);
72+
scanner.detection_efficiencies.module_pair_efficiencies_vectors
73+
= construct_2D_nested_vector<petsird::ModulePairEfficienciesVector>(num_module_types, num_module_types);
74+
}
75+
}
76+
77+
} // namespace create
78+
} // namespace petsird_helpers
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
#include <xtensor/xarray.hpp>
2+
#include <xtensor/xview.hpp>
3+
#include <xtensor/xio.hpp>
4+
#include <xtensor-blas/xlinalg.hpp>
5+
#include <vector>
6+
#include "generated/types.h"
7+
8+
namespace petsird_helpers
9+
{
10+
namespace geometry
11+
{
12+
13+
//! Convert RigidTransformation to 4x4 matrix
14+
inline xt::xarray<float>
15+
transform_to_mat44(const RigidTransformation& transform)
16+
{
17+
xt::xarray<float> bottom_row = { { 0.0f, 0.0f, 0.0f, 1.0f } };
18+
return xt::concatenate(xt::xtuple(transform.matrix, bottom_row), 0);
19+
}
20+
21+
//! Convert 4x4 matrix to RigidTransformation
22+
inline RigidTransformation
23+
mat44_to_transform(const xt::xarray<float>& mat)
24+
{
25+
RigidTransformation transform;
26+
transform.matrix = xt::view(mat, xt::range(0, 3), xt::all());
27+
return transform;
28+
}
29+
30+
//! Convert Coordinate to homogeneous vector
31+
inline xt::xarray<float>
32+
coordinate_to_homogeneous(const Coordinate& coord)
33+
{
34+
return xt::concatenate(xt::xtuple(coord.c, xt::xarray<float>{ 1.0f }), 0);
35+
}
36+
37+
//! Convert homogeneous vector to Coordinate
38+
inline Coordinate
39+
homogeneous_to_coordinate(const xt::xarray<float>& hom_coord)
40+
{
41+
Coordinate coord;
42+
coord.c = xt::view(hom_coord, xt::range(0, 3));
43+
return coord;
44+
}
45+
46+
//! Multiply a list of transformations
47+
inline RigidTransformation
48+
mult_transforms(const std::vector<RigidTransformation>& transforms)
49+
{
50+
xt::xarray<float> mat = xt::eye<float>(4);
51+
for (auto it = transforms.rbegin(); it != transforms.rend(); ++it)
52+
{
53+
mat = xt::linalg::dot(transform_to_mat44(*it), mat);
54+
}
55+
return mat44_to_transform(mat);
56+
}
57+
58+
//! Apply transformations to a coordinate
59+
inline Coordinate
60+
mult_transforms_coord(const std::vector<RigidTransformation>& transforms, const Coordinate& coord)
61+
{
62+
xt::xarray<float> hom = xt::linalg::dot(transform_to_mat44(mult_transforms(transforms)), coordinate_to_homogeneous(coord));
63+
return homogeneous_to_coordinate(hom);
64+
}
65+
66+
//! Transform a BoxShape
67+
inline BoxShape
68+
transform_BoxShape(const RigidTransformation& transform, const BoxShape& box_shape)
69+
{
70+
BoxShape new_box;
71+
for (size_t i = 0; i < box_shape.corners.size(); ++i)
72+
{
73+
new_box.corners[i] = mult_transforms_coord({ transform }, box_shape.corners[i]);
74+
}
75+
return new_box;
76+
}
77+
78+
//! find the BoxShape corresponding to a ExpandedDetectionBin
79+
inline BoxShape
80+
get_detecting_box(const ScannerInformation& scanner, const TypeOfModule& type_of_module,
81+
const ExpandedDetectionBin& expanded_detection_bin)
82+
{
83+
const auto& rep_module = scanner.scanner_geometry.replicated_modules[type_of_module];
84+
const auto& det_els = rep_module.object.detecting_elements;
85+
const auto& mod_transform = rep_module.transforms[expanded_detection_bin.module_index];
86+
const auto& transform = det_els.transforms[expanded_detection_bin.element_index];
87+
return transform_BoxShape(mult_transforms({ mod_transform, transform }), det_els.object.shape);
88+
}
89+
90+
//! find the BoxShape corresponding to a DetectionBin
91+
inline BoxShape
92+
get_detecting_box(const ScannerInformation& scanner, const TypeOfModule& type_of_module, const DetectionBin& detection_bin)
93+
{
94+
return get_detecting_box(scanner, type_of_module, expand_detection_bin(scanner, type_of_module, detection_bin));
95+
}
96+
97+
} // namespace geometry
98+
} // namespace petsird_helpers

cpp/petsird_analysis.cpp

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ using petsird::hdf5::PETSIRDReader;
1616
using petsird::binary::PETSIRDReader;
1717
#endif
1818
#include "petsird_helpers.h"
19+
#include "petsird_helpers/geometry.h"
1920
#include <xtensor/xview.hpp>
2021
#include <xtensor/xio.hpp>
2122
#include <iostream>
@@ -99,7 +100,16 @@ main(int argc, char const* argv[])
99100
<< std::endl;
100101
std::cout << "Total number of 'crystals' in modules of first type : "
101102
<< petsird_helpers::get_num_det_els(header.scanner, type_of_module) << std::endl;
102-
103+
// example printing of coordinates
104+
{
105+
const petsird::ExpandedDetectionBin expanded_detection_bin{ 0, 0, 0 };
106+
const auto box_shape = petsird_helpers::geometry::get_detecting_box(header.scanner, type_of_module, expanded_detection_bin);
107+
std::cout << "Coordinates of first detecting bin:\n";
108+
for (auto& corner : box_shape.corners)
109+
{
110+
std::cout << " [" << corner.c[0] << ", " << corner.c[1] << ", " << corner.c[2] << "]\n";
111+
}
112+
}
103113
const auto& tof_bin_edges = header.scanner.tof_bin_edges[type_of_module][type_of_module];
104114
const auto num_tof_bins = tof_bin_edges.NumberOfBins();
105115
std::cout << "Number of TOF bins: " << num_tof_bins << std::endl;

cpp/petsird_generator.cpp

Lines changed: 31 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ using petsird::binary::PETSIRDWriter;
2222
#endif
2323

2424
#include "petsird_helpers.h"
25+
#include "petsird_helpers/create.h"
2526

2627
// these are constants for now
2728
constexpr uint32_t NUMBER_OF_EVENT_ENERGY_BINS = 3;
@@ -108,33 +109,30 @@ get_scanner_geometry()
108109
return scanner_geometry;
109110
}
110111

111-
petsird::DetectionEfficiencies
112-
get_detection_efficiencies(const petsird::ScannerInformation& scanner)
112+
// set some example efficiencies in the ScannerInformation object.
113+
void
114+
set_detection_efficiencies(petsird::ScannerInformation& scanner)
113115
{
114116
const auto num_module_types = scanner.scanner_geometry.replicated_modules.size();
115117
// only 1 type of module in the current scanner
116118
assert(num_module_types == 1);
117119
const petsird::TypeOfModule type_of_module{ 0 };
118120
const auto num_detection_bins = petsird_helpers::get_num_detection_bins(scanner, type_of_module);
119-
petsird::DetectionEfficiencies detection_efficiencies;
120121

121122
const auto& event_energy_bin_edges = scanner.event_energy_bin_edges[type_of_module];
122123
const auto num_event_energy_bins = event_energy_bin_edges.NumberOfBins();
123124

124-
std::vector<petsird::DetectionBinEfficiencies> detection_bin_efficiencies(num_module_types);
125-
detection_efficiencies.detection_bin_efficiencies = detection_bin_efficiencies;
126-
detection_efficiencies.module_pair_sgidlut = std::vector<std::vector<petsird::ModulePairSGIDLUT>>(num_module_types);
127-
for (auto& elem : *detection_efficiencies.module_pair_sgidlut)
125+
// set all detection_bin_efficiencies to 1 in this example
126+
if (scanner.detection_efficiencies.detection_bin_efficiencies)
128127
{
129-
elem.resize(num_module_types);
128+
auto& bin_effs = (*scanner.detection_efficiencies.detection_bin_efficiencies)[type_of_module];
129+
yardl::resize(bin_effs, { num_detection_bins });
130+
std::fill(begin(bin_effs), end(bin_effs), 1.F);
130131
}
131-
detection_efficiencies.module_pair_efficiencies_vectors
132-
= std::vector<std::vector<petsird::ModulePairEfficienciesVector>>(num_module_types);
133-
for (auto& elem : *detection_efficiencies.module_pair_efficiencies_vectors)
134-
{
135-
elem.resize(num_module_types);
136-
}
137-
(*detection_efficiencies.detection_bin_efficiencies)[type_of_module] = xt::ones<float>({ num_detection_bins });
132+
133+
// check if the caller wants to have module-pair stuff. If not, return.
134+
if (!scanner.detection_efficiencies.module_pair_efficiencies_vectors)
135+
return;
138136

139137
const auto& rep_module = scanner.scanner_geometry.replicated_modules[type_of_module];
140138
const auto num_modules = rep_module.transforms.size();
@@ -149,9 +147,9 @@ get_detection_efficiencies(const petsird::ScannerInformation& scanner)
149147
constexpr auto num_SGIDs = NUM_MODULES_ALONG_AXIS * NUM_MODULES_ALONG_AXIS * (NUM_MODULES_ALONG_RING - 1);
150148
// SGID = z1 + NZ * (z2 + NZ * abs(a2 - a1) - 1)
151149
constexpr auto NZ = NUM_MODULES_ALONG_AXIS;
152-
(*detection_efficiencies.module_pair_sgidlut)[type_of_module][type_of_module]
153-
= yardl::NDArray<int, 2>({ num_modules, num_modules });
154-
auto& module_pair_SGID_LUT = (*detection_efficiencies.module_pair_sgidlut)[type_of_module][type_of_module];
150+
151+
auto& module_pair_SGID_LUT = (*scanner.detection_efficiencies.module_pair_sgidlut)[type_of_module][type_of_module];
152+
module_pair_SGID_LUT = yardl::NDArray<int, 2>({ num_modules, num_modules });
155153
for (unsigned int mod1 = 0; mod1 < num_modules; ++mod1)
156154
{
157155
for (unsigned int mod2 = 0; mod2 < num_modules; ++mod2)
@@ -174,7 +172,7 @@ get_detection_efficiencies(const petsird::ScannerInformation& scanner)
174172

175173
// initialise module_pair_efficiencies
176174
auto& module_pair_efficiencies_vector
177-
= (*detection_efficiencies.module_pair_efficiencies_vectors)[type_of_module][type_of_module];
175+
= (*scanner.detection_efficiencies.module_pair_efficiencies_vectors)[type_of_module][type_of_module];
178176
// assign an empty vector first, and reserve correct size
179177
module_pair_efficiencies_vector = petsird::ModulePairEfficienciesVector();
180178
module_pair_efficiencies_vector.reserve(num_SGIDs);
@@ -194,8 +192,6 @@ get_detection_efficiencies(const petsird::ScannerInformation& scanner)
194192
module_pair_efficiencies_vector.emplace_back(module_pair_efficiencies);
195193
assert(module_pair_efficiencies_vector.size() == unsigned(SGID + 1));
196194
}
197-
198-
return detection_efficiencies;
199195
}
200196

201197
petsird::ScannerInformation
@@ -206,47 +202,46 @@ get_scanner_info()
206202

207203
scanner_info.scanner_geometry = get_scanner_geometry();
208204
const auto num_types_of_modules = scanner_info.scanner_geometry.replicated_modules.size();
205+
// Pre-allocate various structures to have the correct size for num_types_of_modules
206+
// (We will still have to set descent values into each of these.)
207+
petsird_helpers::create::initialize_scanner_information_dimensions(scanner_info, num_types_of_modules,
208+
/* allocate_detection_bin_efficiencies = */ true,
209+
/* allocate_module_pair_efficiencies = */ true);
209210

210211
// TODO scanner_info.bulk_materials
211212

212213
// TOF and energy information
213214
{
214-
auto all_tof_bin_edges
215-
= petsird_helpers::construct_2D_nested_vector<petsird::BinEdges>(num_types_of_modules, num_types_of_modules);
216-
auto all_tof_resolutions = petsird_helpers::construct_2D_nested_vector<float>(num_types_of_modules, num_types_of_modules);
217-
auto all_event_energy_bin_edges = petsird_helpers::construct_vector<petsird::BinEdges>(num_types_of_modules);
218-
auto all_event_energy_resolutions = petsird_helpers::construct_vector<float>(num_types_of_modules);
215+
auto& all_tof_bin_edges = scanner_info.tof_bin_edges;
216+
auto& all_tof_resolutions = scanner_info.tof_resolution;
217+
auto& all_event_energy_bin_edges = scanner_info.event_energy_bin_edges;
218+
auto& all_event_energy_resolutions = scanner_info.energy_resolution_at_511;
219219

220220
// only 1 type of module in the current scanner
221221
assert(num_types_of_modules == 1);
222222
const petsird::TypeOfModule type_of_module{ 0 };
223223

224224
typedef yardl::NDArray<float, 1> FArray1D;
225225
// TOF info (in mm)
226-
FArray1D::shape_type tof_bin_edges_shape = { NUMBER_OF_TOF_BINS + 1 };
227-
FArray1D tof_bin_edges_arr(tof_bin_edges_shape);
226+
FArray1D tof_bin_edges_arr;
227+
yardl::resize(tof_bin_edges_arr, { NUMBER_OF_TOF_BINS + 1 });
228228
for (std::size_t i = 0; i < tof_bin_edges_arr.size(); ++i)
229229
tof_bin_edges_arr[i] = (i - NUMBER_OF_TOF_BINS / 2.F) / NUMBER_OF_TOF_BINS * 2 * RADIUS;
230230
const petsird::BinEdges tof_bin_edges{ tof_bin_edges_arr };
231231
all_tof_bin_edges[type_of_module][type_of_module] = tof_bin_edges;
232232

233233
all_tof_resolutions[type_of_module][type_of_module] = 9.4F; // in mm
234234

235-
FArray1D::shape_type event_energy_bin_edges_shape = { NUMBER_OF_EVENT_ENERGY_BINS + 1 };
236-
FArray1D event_energy_bin_edges_arr(event_energy_bin_edges_shape);
235+
FArray1D event_energy_bin_edges_arr;
236+
yardl::resize(event_energy_bin_edges_arr, { NUMBER_OF_EVENT_ENERGY_BINS + 1 });
237237
for (std::size_t i = 0; i < event_energy_bin_edges_arr.size(); ++i)
238238
event_energy_bin_edges_arr[i] = 430.F + i * (650.F - 430.F) / NUMBER_OF_EVENT_ENERGY_BINS;
239239
petsird::BinEdges event_energy_bin_edges{ event_energy_bin_edges_arr };
240240
all_event_energy_bin_edges[type_of_module] = event_energy_bin_edges;
241241
all_event_energy_resolutions[type_of_module] = .11F; // as fraction of 511
242-
243-
scanner_info.tof_bin_edges = all_tof_bin_edges;
244-
scanner_info.tof_resolution = all_tof_resolutions;
245-
scanner_info.event_energy_bin_edges = all_event_energy_bin_edges;
246-
scanner_info.energy_resolution_at_511 = all_event_energy_resolutions;
247242
}
248243

249-
scanner_info.detection_efficiencies = get_detection_efficiencies(scanner_info);
244+
set_detection_efficiencies(scanner_info);
250245

251246
scanner_info.coincidence_policy = petsird::CoincidencePolicy::kRejectMultiples;
252247
scanner_info.delayed_coincidences_are_stored = false;

cpp/petsird_helpers.h

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -12,29 +12,6 @@ namespace petsird_helpers
1212

1313
using namespace petsird;
1414

15-
//! Helper function to create a std::vector<T>
16-
/*! This function is added to have a 1D analogue construct_2D_nested_vector() */
17-
template <typename T>
18-
std::vector<T>
19-
construct_vector(std::size_t size)
20-
{
21-
std::vector<T> v(size);
22-
return v;
23-
}
24-
25-
//! Helper function to create a std::vector<std::vector<T>> as a 2D array of size (size0, size1)
26-
template <typename T>
27-
std::vector<std::vector<T>>
28-
construct_2D_nested_vector(std::size_t size0, std::size_t size1)
29-
{
30-
std::vector<std::vector<T>> v(size0);
31-
for (auto& one_of_them : v)
32-
{
33-
one_of_them.resize(size1);
34-
}
35-
return v;
36-
}
37-
3815
// TODO remove?
3916
inline std::size_t
4017
get_num_det_els(const ScannerInformation& scanner, const TypeOfModule& type_of_module)

0 commit comments

Comments
 (0)