41
41
#include " stir/Shape/Box3D.h"
42
42
#include " stir/recon_buildblock/ProjMatrixByBinUsingRayTracing.h"
43
43
#include " stir/recon_buildblock/ForwardProjectorByBinUsingProjMatrixByBin.h"
44
+ #include " stir/scatter/SingleScatterSimulation.h"
44
45
45
46
#include " stir/RunTests.h"
46
47
@@ -57,6 +58,7 @@ class InterpolationTests : public RunTests
57
58
void scatter_interpolation_test_blocks_asymmetric ();
58
59
void scatter_interpolation_test_cyl_asymmetric ();
59
60
void scatter_interpolation_test_blocks_downsampled ();
61
+ void transaxial_upsampling_interpolation_test_blocks ();
60
62
61
63
void check_symmetry (const SegmentBySinogram<float >& segment);
62
64
void compare_segment (const SegmentBySinogram<float >& segment1, const SegmentBySinogram<float >& segment2, float maxDiff);
@@ -754,7 +756,7 @@ InterpolationTests::scatter_interpolation_test_blocks_downsampled()
754
756
auto proj_data = ProjDataInMemory (std::make_shared<ExamInfo>(exam_info), proj_data_info);
755
757
auto downsampled_proj_data = ProjDataInMemory (std::make_shared<ExamInfo>(exam_info), downsampled_proj_data_info);
756
758
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
758
760
auto emission_map = VoxelsOnCartesianGrid<float >(*proj_data_info, 1 );
759
761
auto cyl_map = VoxelsOnCartesianGrid<float >(*proj_data_info, 1 );
760
762
auto cylinder = EllipsoidalCylinder (40 , 40 , 20 , CartesianCoordinate3D<float >(80 , 100 , 0 ));
@@ -763,7 +765,7 @@ InterpolationTests::scatter_interpolation_test_blocks_downsampled()
763
765
box.construct_volume (emission_map, CartesianCoordinate3D<int >(1 , 1 , 1 ));
764
766
emission_map += cyl_map;
765
767
766
- // project the cylinder onto the full-scale scanner proj data
768
+ // project the emission map onto the full-scale scanner proj data
767
769
auto pm = ProjMatrixByBinUsingRayTracing ();
768
770
pm.set_use_actual_detector_boundaries (true );
769
771
pm.enable_cache (false );
@@ -801,6 +803,155 @@ InterpolationTests::scatter_interpolation_test_blocks_downsampled()
801
803
compare_segment_shape (full_size_model_sino.get_segment_by_sinogram (0 ), interpolated_proj_data.get_segment_by_sinogram (0 ), 3 );
802
804
}
803
805
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
+
804
955
void
805
956
InterpolationTests::run_tests ()
806
957
{
@@ -809,6 +960,7 @@ InterpolationTests::run_tests()
809
960
scatter_interpolation_test_blocks_asymmetric ();
810
961
scatter_interpolation_test_cyl_asymmetric ();
811
962
scatter_interpolation_test_blocks_downsampled ();
963
+ transaxial_upsampling_interpolation_test_blocks ();
812
964
}
813
965
814
966
END_NAMESPACE_STIR
0 commit comments