Skip to content

Commit f3e697d

Browse files
author
Markus Jehl
committed
Added an additional test that compares sinogram bins for LORs that do not change during scanner downsampling.
1 parent ee436f9 commit f3e697d

File tree

2 files changed

+155
-3
lines changed

2 files changed

+155
-3
lines changed

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

src/test/test_interpolate_projdata.cxx

Lines changed: 154 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
#include "stir/Shape/Box3D.h"
4242
#include "stir/recon_buildblock/ProjMatrixByBinUsingRayTracing.h"
4343
#include "stir/recon_buildblock/ForwardProjectorByBinUsingProjMatrixByBin.h"
44+
#include "stir/scatter/SingleScatterSimulation.h"
4445

4546
#include "stir/RunTests.h"
4647

@@ -57,6 +58,7 @@ class InterpolationTests : public RunTests
5758
void scatter_interpolation_test_blocks_asymmetric();
5859
void scatter_interpolation_test_cyl_asymmetric();
5960
void scatter_interpolation_test_blocks_downsampled();
61+
void transaxial_upsampling_interpolation_test_blocks();
6062

6163
void check_symmetry(const SegmentBySinogram<float>& segment);
6264
void compare_segment(const SegmentBySinogram<float>& segment1, const SegmentBySinogram<float>& segment2, float maxDiff);
@@ -754,7 +756,7 @@ InterpolationTests::scatter_interpolation_test_blocks_downsampled()
754756
auto proj_data = ProjDataInMemory(std::make_shared<ExamInfo>(exam_info), proj_data_info);
755757
auto downsampled_proj_data = ProjDataInMemory(std::make_shared<ExamInfo>(exam_info), downsampled_proj_data_info);
756758

757-
// define a cylinder precisely in the middle of the FOV, such that symmetry can be used for validation
759+
// define a cylinder and a box that are off-centre, such that the shapes in the sinogram can be compared
758760
auto emission_map = VoxelsOnCartesianGrid<float>(*proj_data_info, 1);
759761
auto cyl_map = VoxelsOnCartesianGrid<float>(*proj_data_info, 1);
760762
auto cylinder = EllipsoidalCylinder(40, 40, 20, CartesianCoordinate3D<float>(80, 100, 0));
@@ -763,7 +765,7 @@ InterpolationTests::scatter_interpolation_test_blocks_downsampled()
763765
box.construct_volume(emission_map, CartesianCoordinate3D<int>(1, 1, 1));
764766
emission_map += cyl_map;
765767

766-
// project the cylinder onto the full-scale scanner proj data
768+
// project the emission map onto the full-scale scanner proj data
767769
auto pm = ProjMatrixByBinUsingRayTracing();
768770
pm.set_use_actual_detector_boundaries(true);
769771
pm.enable_cache(false);
@@ -801,6 +803,155 @@ InterpolationTests::scatter_interpolation_test_blocks_downsampled()
801803
compare_segment_shape(full_size_model_sino.get_segment_by_sinogram(0), interpolated_proj_data.get_segment_by_sinogram(0), 3);
802804
}
803805

806+
void
807+
InterpolationTests::transaxial_upsampling_interpolation_test_blocks()
808+
{
809+
info("Performing transaxial downampled interpolation test for BlocksOnCylindrical scanner");
810+
auto time_frame_def = TimeFrameDefinitions();
811+
time_frame_def.set_num_time_frames(1);
812+
time_frame_def.set_time_frame(1, 0, 1e9);
813+
auto exam_info = ExamInfo();
814+
exam_info.set_high_energy_thres(650);
815+
exam_info.set_low_energy_thres(425);
816+
exam_info.set_time_frame_definitions(time_frame_def);
817+
818+
// define the original scanner and a downsampled one, as it would be used for scatter simulation
819+
auto scanner = Scanner(Scanner::User_defined_scanner,
820+
"Some_BlocksOnCylindrical_Scanner",
821+
96,
822+
3,
823+
60,
824+
60,
825+
127,
826+
6.5,
827+
3.313,
828+
1.65,
829+
-3.1091819,
830+
1,
831+
3,
832+
3,
833+
4,
834+
1,
835+
1,
836+
1,
837+
0.14,
838+
511,
839+
1,
840+
0,
841+
500,
842+
"BlocksOnCylindrical",
843+
3.313,
844+
7.0,
845+
20.0,
846+
29.0);
847+
auto proj_data_info = shared_ptr<ProjDataInfo>(
848+
std::move(ProjDataInfo::construct_proj_data_info(std::make_shared<Scanner>(scanner), 1, 0, 48, 60, false)));
849+
850+
// use the code in scatter simulation to downsample the scanner
851+
auto scatter_simulation = SingleScatterSimulation();
852+
scatter_simulation.set_template_proj_data_info(*proj_data_info);
853+
scatter_simulation.set_exam_info(exam_info);
854+
scatter_simulation.downsample_scanner(-1, 96 / 4); // number of detectors per ring reduced by factor of four
855+
auto downsampled_proj_data_info = scatter_simulation.get_template_proj_data_info_sptr();
856+
857+
auto proj_data = ProjDataInMemory(std::make_shared<ExamInfo>(exam_info), proj_data_info);
858+
auto downsampled_proj_data = ProjDataInMemory(std::make_shared<ExamInfo>(exam_info), downsampled_proj_data_info);
859+
860+
// define a cylinder precisely in the middle of the FOV
861+
auto emission_map = VoxelsOnCartesianGrid<float>(*downsampled_proj_data_info, 1);
862+
make_symmetric_object(emission_map);
863+
864+
// project the cylinder onto the full-scale scanner proj data
865+
auto pm = ProjMatrixByBinUsingRayTracing();
866+
pm.set_use_actual_detector_boundaries(true);
867+
pm.enable_cache(false);
868+
auto forw_proj = ForwardProjectorByBinUsingProjMatrixByBin(std::make_shared<ProjMatrixByBinUsingRayTracing>(pm));
869+
forw_proj.set_up(proj_data_info, std::make_shared<VoxelsOnCartesianGrid<float>>(emission_map));
870+
auto full_size_model_sino = ProjDataInMemory(proj_data);
871+
full_size_model_sino.fill(0);
872+
forw_proj.forward_project(full_size_model_sino, emission_map);
873+
874+
// also project onto the downsampled scanner
875+
emission_map = VoxelsOnCartesianGrid<float>(*downsampled_proj_data_info, 1);
876+
make_symmetric_object(emission_map);
877+
forw_proj.set_up(downsampled_proj_data_info, std::make_shared<VoxelsOnCartesianGrid<float>>(emission_map));
878+
auto downsampled_model_sino = ProjDataInMemory(downsampled_proj_data);
879+
downsampled_model_sino.fill(0);
880+
forw_proj.forward_project(downsampled_model_sino, emission_map);
881+
882+
// write the proj data to file
883+
downsampled_model_sino.write_to_file("transaxially_downsampled_sino_for_LOR.hs");
884+
885+
// interpolate the downsampled proj data to the original scanner size and fill in oblique sinograms
886+
auto interpolated_direct_proj_data = ProjDataInMemory(proj_data);
887+
interpolate_projdata(interpolated_direct_proj_data, downsampled_model_sino, BSpline::linear, false);
888+
auto interpolated_proj_data = ProjDataInMemory(proj_data);
889+
inverse_SSRB(interpolated_proj_data, interpolated_direct_proj_data);
890+
891+
// write the proj data to file
892+
interpolated_proj_data.write_to_file("transaxially_interpolated_sino_for_LOR.hs");
893+
894+
// Identify the bins which should be identical between the downsampled and the interpolated sinogram:
895+
// Each module has 96 / 8 = 12 crystal in the full size scanner, organised in 3 blocks of 4 crystals, while
896+
// the downsampled scanner has 3 crystals per module. The idea is that the centre of the outer two
897+
// is in exactly the same position than the centre of the first and last crystal in the full size scanner.
898+
SegmentBySinogram<float> sinogram_downsampled = downsampled_proj_data.get_empty_segment_by_sinogram(0, false, 0);
899+
SegmentBySinogram<float> sinogram_full_size = proj_data.get_empty_segment_by_sinogram(0, false, 0);
900+
const auto pdi_downsampled
901+
= dynamic_cast<const ProjDataInfoGenericNoArcCorr*>(&(*downsampled_proj_data.get_proj_data_info_sptr()));
902+
const auto pdi_full_size = dynamic_cast<const ProjDataInfoGenericNoArcCorr*>(&(*sinogram_full_size.get_proj_data_info_sptr()));
903+
904+
int tested_LORs = 0;
905+
for (int det1_downsampled = 0; det1_downsampled < 3 * 8; det1_downsampled++)
906+
{
907+
if (det1_downsampled % 3 == 1)
908+
continue; // skip the central crystal of each module
909+
for (int det2_downsampled = 0; det2_downsampled < 3 * 8; det2_downsampled++)
910+
{
911+
if (det2_downsampled % 3 == 1 || det1_downsampled == det2_downsampled)
912+
continue; // skip the central crystal of each module
913+
if (det1_downsampled / 3 == det2_downsampled / 3)
914+
continue; // skip the LORs that lie on the same module
915+
916+
int view_ds, tang_pos_ds;
917+
pdi_downsampled->get_view_tangential_pos_num_for_det_num_pair(view_ds, tang_pos_ds, det1_downsampled, det2_downsampled);
918+
BasicCoordinate<3, int> index_downsampled;
919+
index_downsampled[1] = 1; // looking at central slice
920+
index_downsampled[2] = view_ds;
921+
index_downsampled[3] = tang_pos_ds;
922+
923+
if (tang_pos_ds < pdi_downsampled->get_min_tangential_pos_num()
924+
|| tang_pos_ds > pdi_downsampled->get_max_tangential_pos_num())
925+
continue;
926+
927+
int view_fs, tang_pos_fs;
928+
pdi_full_size->get_view_tangential_pos_num_for_det_num_pair(
929+
view_fs,
930+
tang_pos_fs,
931+
(det1_downsampled / 3) * 12 + ((det1_downsampled % 3) / 2) * 11,
932+
(det2_downsampled / 3) * 12 + ((det2_downsampled % 3) / 2) * 11);
933+
934+
BasicCoordinate<3, int> index_full_size;
935+
index_full_size[1] = 1; // looking at central slice
936+
index_full_size[2] = view_fs;
937+
index_full_size[3] = tang_pos_fs;
938+
939+
if (tang_pos_fs < pdi_full_size->get_min_tangential_pos_num()
940+
|| tang_pos_fs > pdi_full_size->get_max_tangential_pos_num())
941+
continue;
942+
943+
// confirm that the difference is smaller than an empirically found value
944+
check_if_less(std::abs(sinogram_downsampled[index_downsampled] - sinogram_full_size[index_full_size]),
945+
0.01,
946+
"difference between sinogram bin is larger than expected");
947+
948+
tested_LORs++;
949+
}
950+
}
951+
952+
info(boost::format("A total of %1% LORs were compared between the downsampled and the interpolated sinogram.") % tested_LORs);
953+
}
954+
804955
void
805956
InterpolationTests::run_tests()
806957
{
@@ -809,6 +960,7 @@ InterpolationTests::run_tests()
809960
scatter_interpolation_test_blocks_asymmetric();
810961
scatter_interpolation_test_cyl_asymmetric();
811962
scatter_interpolation_test_blocks_downsampled();
963+
transaxial_upsampling_interpolation_test_blocks();
812964
}
813965

814966
END_NAMESPACE_STIR

0 commit comments

Comments
 (0)