Skip to content

Commit 33a0106

Browse files
Merge pull request #1291 from markus-jehl/issue/1290-speeding-up-scatter-simulation-for-blocks-geometry
Added downsampling of BlocksOnCylindrical scanners in scatter
2 parents c51067d + f3e697d commit 33a0106

11 files changed

Lines changed: 740 additions & 97 deletions

documentation/release_6.3.htm

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<!DOCTYPE HTML>
2+
<html lang="en">
3+
<head>
4+
<title>Summary of changes in STIR release 6.3</title>
5+
</head>
6+
7+
<body>
8+
<h1>Summary of changes in STIR release 6.3</h1>
9+
10+
11+
<h2>Overall summary</h2>
12+
13+
14+
<h2>Patch release info</h2>
15+
16+
17+
<h2> Summary for end users (also to be read by developers)</h2>
18+
19+
20+
<h3>New functionality</h3>
21+
<ul>
22+
<li>
23+
<code>ScatterSimulation</code> can now downsample the scanner transaxially (crystals per ring) for <code>BlocksOnCylindrical</code>,
24+
scanners, which speeds up <code>ScatterEstimation</code> considerably. By default, downsampling the detectors per reading
25+
is disabled for backwards compatibility.<br>
26+
<a href=https://github.com/UCL/STIR/pull/1291>PR #1291</a>
27+
</li>
28+
</ul>
29+
30+
31+
<h3>Changed functionality</h3>
32+
33+
34+
<h3>Bug fixes</h3>
35+
36+
37+
<h3>Build system</h3>
38+
39+
40+
<h3>Known problems</h3>
41+
<p>See <a href=https://github.com/UCL/STIR/labels/bug>our issue tracker</a>.</p>
42+
43+
44+
<H2>What is new for developers (aside from what should be obvious from the above):</H2>
45+
46+
47+
<h3>Changed functionality</h3>
48+
49+
50+
<h3>Bug fixes</h3>
51+
52+
53+
<h3>Other code changes</h3>
54+
55+
56+
<h3>Test changes</h3>
57+
58+
59+
<h4>C++ tests</h4>
60+
61+
62+
<h4>recon_test_pack</h4>
63+
64+
</body>
65+
66+
</html>

src/buildblock/extend_projdata.cxx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ extend_segment(const SegmentBySinogram<float>& segment,
9393
if (extend_without_wrapping)
9494
{
9595
out[axial_pos][min_dim[2] + view_edge] = out[axial_pos][min_dim[2] + view_extension];
96-
out[axial_pos][max_dim[2] - view_extension] = out[axial_pos][max_dim[2] - view_extension];
96+
out[axial_pos][max_dim[2] - view_edge] = out[axial_pos][max_dim[2] - view_extension];
9797
}
9898
else if (flip_views)
9999
{

src/buildblock/interpolate_projdata.cxx

Lines changed: 307 additions & 79 deletions
Large diffs are not rendered by default.

src/include/stir/interpolate_projdata.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ Succeeded interpolate_projdata(ProjData& proj_data_out,
5959
const ProjData& proj_data_in,
6060
const BasicCoordinate<3, BSpline::BSplineType>& these_types,
6161
const bool remove_interleaving);
62+
Succeeded
63+
interpolate_blocks_on_cylindrical_projdata(ProjData& proj_data_out, const ProjData& proj_data_in, bool remove_interleaving);
6264
//@}
6365

6466
END_NAMESPACE_STIR

src/include/stir/recon_buildblock/DataSymmetriesForBins_PET_CartesianGrid.inl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,7 @@ DataSymmetriesForBins_PET_CartesianGrid::find_sym_op_bin0(int segment_num, int v
216216
// No symmetry is implemented for generic scanner
217217
return new TrivialSymmetryOperation();
218218
}
219+
return new TrivialSymmetryOperation();
219220
}
220221

221222
// from symmetries
@@ -390,6 +391,8 @@ DataSymmetriesForBins_PET_CartesianGrid::find_sym_op_general_bin(int s, int segm
390391
// No symmetry is implemented for generic scanner
391392
return new TrivialSymmetryOperation();
392393
}
394+
395+
return new TrivialSymmetryOperation();
393396
}
394397

395398
bool

src/include/stir/scatter/ScatterEstimation.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,12 @@ class ScatterEstimation : public ParsingObject
174174

175175
inline void set_num_iterations(int);
176176

177+
inline unsigned int get_half_filter_width() const;
178+
inline void set_half_filter_width(unsigned int);
179+
180+
inline void
181+
set_downsample_scanner(bool downsample_scanner, int downsampled_number_of_rings = -1, int downsampled_detectors_per_ring = -1);
182+
177183
void set_output_scatter_estimate_prefix(const std::string&);
178184
void set_export_scatter_estimates_of_each_iteration(bool);
179185

@@ -382,6 +388,9 @@ class ScatterEstimation : public ParsingObject
382388
float min_scale_value;
383389

384390
bool downsample_scanner_bool;
391+
int downsampled_number_of_rings;
392+
int downsampled_detectors_per_ring;
393+
385394
//!
386395
unsigned int half_filter_width;
387396

src/include/stir/scatter/ScatterEstimation.inl

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,4 +66,26 @@ ScatterEstimation::set_num_iterations(int arg)
6666
this->num_scatter_iterations = arg;
6767
}
6868

69+
unsigned int
70+
ScatterEstimation::get_half_filter_width() const
71+
{
72+
return this->half_filter_width;
73+
}
74+
75+
void
76+
ScatterEstimation::set_half_filter_width(unsigned int arg)
77+
{
78+
this->half_filter_width = arg;
79+
}
80+
81+
void
82+
ScatterEstimation::set_downsample_scanner(bool downsample_scanner,
83+
int downsampled_number_of_rings,
84+
int downsampled_detectors_per_ring)
85+
{
86+
this->downsample_scanner_bool = downsample_scanner;
87+
this->downsampled_number_of_rings = downsampled_number_of_rings;
88+
this->downsampled_detectors_per_ring = downsampled_detectors_per_ring;
89+
}
90+
6991
END_NAMESPACE_STIR

src/scatter_buildblock/ScatterEstimation.cxx

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include "stir/recon_buildblock/ChainedBinNormalisation.h"
2424
#include "stir/ProjDataInterfile.h"
2525
#include "stir/ProjDataInMemory.h"
26+
#include "stir/inverse_SSRB.h"
2627
#include "stir/ExamInfo.h"
2728
#include "stir/ProjDataInfo.h"
2829
#include "stir/ProjDataInfoCylindricalNoArcCorr.h"
@@ -77,6 +78,8 @@ ScatterEstimation::set_defaults()
7778
this->override_scanner_template = true;
7879
this->override_density_image = true;
7980
this->downsample_scanner_bool = true;
81+
this->downsampled_number_of_rings = -1;
82+
this->downsampled_detectors_per_ring = -1;
8083
this->remove_interleaving = true;
8184
this->atten_image_filename = "";
8285
this->atten_coeff_filename = "";
@@ -124,6 +127,8 @@ ScatterEstimation::initialise_keymap()
124127
this->parser.add_parsing_key("Scatter Simulation type", &this->scatter_simulation_sptr);
125128
this->parser.add_key("scatter simulation parameter filename", &this->scatter_sim_par_filename);
126129
this->parser.add_key("use scanner downsampling in scatter simulation", &this->downsample_scanner_bool);
130+
this->parser.add_key("override number of downsampled rings", &this->downsampled_number_of_rings);
131+
this->parser.add_key("override number of downsampled detectors per ring", &this->downsampled_detectors_per_ring);
127132

128133
this->parser.add_key("override attenuation image", &this->override_density_image);
129134
this->parser.add_key("override scanner template", &this->override_scanner_template);
@@ -597,7 +602,7 @@ ScatterEstimation::set_up()
597602
}
598603

599604
if (this->downsample_scanner_bool)
600-
this->scatter_simulation_sptr->downsample_scanner();
605+
this->scatter_simulation_sptr->downsample_scanner(this->downsampled_number_of_rings, this->downsampled_detectors_per_ring);
601606

602607
// Check if Load a mask proj_data
603608

@@ -834,7 +839,7 @@ ScatterEstimation::process_data()
834839
float local_min_scale_value = 0.5f;
835840
float local_max_scale_value = 0.5f;
836841

837-
stir::BSpline::BSplineType spline_type = stir::BSpline::quadratic;
842+
stir::BSpline::BSplineType spline_type = stir::BSpline::linear;
838843

839844
// This has been set to 2D or 3D in the set_up()
840845
shared_ptr<ProjData> unscaled_est_projdata_sptr(
@@ -1026,16 +1031,13 @@ ScatterEstimation::process_data()
10261031
shared_ptr<BinNormalisation> normalisation_factors_3d_sptr
10271032
= this->get_normalisation_object_sptr(this->multiplicative_binnorm_sptr);
10281033

1029-
upsample_and_fit_scatter_estimate(*scatter_estimate_sptr,
1030-
*this->input_projdata_sptr,
1031-
*temp_projdata,
1032-
*normalisation_factors_3d_sptr,
1033-
*this->input_projdata_sptr,
1034-
1.0f,
1035-
1.0f,
1036-
1,
1037-
spline_type,
1038-
false);
1034+
ProjDataInMemory interpolated_scatter(this->input_projdata_sptr->get_exam_info_sptr(),
1035+
this->input_projdata_sptr->get_proj_data_info_sptr()->create_shared_clone());
1036+
inverse_SSRB(interpolated_scatter, *temp_projdata);
1037+
normalisation_factors_3d_sptr->set_up(this->input_projdata_sptr->get_exam_info_sptr(),
1038+
this->input_projdata_sptr->get_proj_data_info_sptr()->create_shared_clone());
1039+
normalisation_factors_3d_sptr->undo(interpolated_scatter);
1040+
scatter_estimate_sptr->fill(interpolated_scatter);
10391041
}
10401042
else
10411043
{

src/scatter_buildblock/ScatterSimulation.cxx

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -841,19 +841,51 @@ ScatterSimulation::downsample_scanner(int new_num_rings, int new_num_dets)
841841
float approx_num_non_arccorrected_bins;
842842
if (new_scanner_sptr->get_scanner_geometry() != "Cylindrical")
843843
{
844-
new_num_dets = this->proj_data_info_sptr->get_scanner_ptr()->get_num_detectors_per_ring();
845-
approx_num_non_arccorrected_bins = this->proj_data_info_sptr->get_num_tangential_poss();
844+
if (new_num_dets <= 0)
845+
{ // by default, do not downsample the detectors per ring for BlocksOnCylindrical
846+
new_num_dets = this->proj_data_info_sptr->get_scanner_ptr()->get_num_detectors_per_ring();
847+
}
846848

849+
// STIR does not like block spacings that are smaller than number of crystals times crystal spacing,
850+
// therefore add a 1% extension on top for the downsampled scanner, to avoid running into floating point issues
851+
const float block_spacing_factor = 1.01;
852+
853+
// extend the bins by a small amount to avoid edge-effects, at the expense of longer computation time
854+
approx_num_non_arccorrected_bins = ceil(this->proj_data_info_sptr->get_num_tangential_poss() * float(new_num_dets)
855+
/ old_scanner_ptr->get_num_detectors_per_ring())
856+
+ 1;
847857
// preserve the length of the scanner, but place crystals equidistantly
848858
float scanner_length = old_scanner_ptr->get_axial_length();
849859
float new_ring_spacing = scanner_length / (new_num_rings - 1);
850-
851860
new_scanner_sptr->set_num_axial_blocks_per_bucket(1);
861+
new_scanner_sptr->set_num_transaxial_blocks_per_bucket(1);
862+
852863
new_scanner_sptr->set_num_rings(new_num_rings);
864+
new_scanner_sptr->set_num_detectors_per_ring(new_num_dets);
865+
853866
new_scanner_sptr->set_axial_crystal_spacing(new_ring_spacing);
854867
new_scanner_sptr->set_ring_spacing(new_ring_spacing);
855868
new_scanner_sptr->set_num_axial_crystals_per_block(new_num_rings);
856-
new_scanner_sptr->set_axial_block_spacing(new_ring_spacing * new_scanner_sptr->get_num_axial_crystals_per_block());
869+
new_scanner_sptr->set_axial_block_spacing(new_ring_spacing * new_num_rings * block_spacing_factor);
870+
871+
float transaxial_bucket_width
872+
= old_scanner_ptr->get_transaxial_block_spacing() * (old_scanner_ptr->get_num_transaxial_blocks_per_bucket() - 1)
873+
+ old_scanner_ptr->get_transaxial_crystal_spacing() * (old_scanner_ptr->get_num_transaxial_crystals_per_block() - 1);
874+
875+
int num_trans_buckets = old_scanner_ptr->get_num_transaxial_buckets();
876+
// get a new number of detectors that is a multiple of the number of buckets to preserve scanner shape
877+
new_scanner_sptr->set_num_detectors_per_ring(new_num_dets);
878+
int new_transaxial_dets_per_bucket = new_num_dets / num_trans_buckets;
879+
float new_det_spacing = transaxial_bucket_width / (new_transaxial_dets_per_bucket - 1);
880+
881+
new_scanner_sptr->set_num_transaxial_blocks_per_bucket(1);
882+
new_scanner_sptr->set_num_transaxial_crystals_per_block(new_transaxial_dets_per_bucket);
883+
new_scanner_sptr->set_transaxial_crystal_spacing(new_det_spacing);
884+
new_scanner_sptr->set_transaxial_block_spacing(new_transaxial_dets_per_bucket * new_det_spacing * block_spacing_factor);
885+
// avoid problems with Scanner checks by setting singles_units to 1 crystal
886+
// (only used for dead-time processing in ECAY norm)
887+
new_scanner_sptr->set_num_axial_crystals_per_singles_unit(1);
888+
new_scanner_sptr->set_num_transaxial_crystals_per_singles_unit(1);
857889
}
858890
else
859891
{

src/test/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ set(buildblock_simple_tests
7474
include(stir_test_exe_targets)
7575

7676
foreach(source ${buildblock_simple_tests})
77-
create_stir_test(${source} "buildblock;IO;buildblock;numerics_buildblock;display;IO;recon_buildblock;Shape_buildblock" "")
77+
create_stir_test(${source} "buildblock;IO;buildblock;numerics_buildblock;display;IO;recon_buildblock;Shape_buildblock;scatter_buildblock" "")
7878
endforeach()
7979
#
8080

0 commit comments

Comments
 (0)