1+ /* !
2+
3+ \file
4+ \ingroup recontest
5+
6+ \brief Test program to ensure the axial coordinates of blocks on cylindrical are monotonic with axial indices
7+
8+ \author Robert Twyman
9+
10+ Copyright (C) 2024, Prescient Imaging
11+ This file is part of STIR.
12+
13+ SPDX-License-Identifier: Apache-2.0
14+
15+ See STIR/LICENSE.txt for details
16+ */
17+
18+ #include " stir/recon_buildblock/ProjMatrixByBinUsingRayTracing.h"
19+ #include " stir/ExamInfo.h"
20+ #include " stir/Verbosity.h"
21+ #include " stir/LORCoordinates.h"
22+ #include " stir/ProjDataInfoGenericNoArcCorr.h"
23+ #include " stir/Succeeded.h"
24+ #include " stir/RunTests.h"
25+ #include " stir/Scanner.h"
26+ #include " stir/HighResWallClockTimer.h"
27+ #include " stir/GeometryBlocksOnCylindrical.h"
28+ #include " stir/IO/write_to_file.h"
29+ #include < cmath>
30+
31+ START_NAMESPACE_STIR
32+
33+ /* !
34+ \ingroup test
35+ \brief Test class for BlocksOnCylindrical geometry
36+ */
37+ class GeometryBlocksOnCylindricalTests : public RunTests
38+ {
39+ public:
40+ void run_tests () override ;
41+
42+ private:
43+ /* ! \brief Tests multiple axial blocks/bucket configurations to ensure the detector map's axial indices and coordinates
44+ * are monotonic
45+ */
46+ void run_monotonic_axial_coordinates_in_detector_map_test ();
47+ // ! Tests the axial indices and coordinates are monotonic in the detector map
48+ static Succeeded monotonic_axial_coordinates_in_detector_map_test (const shared_ptr<Scanner>& scanner_sptr);
49+ };
50+
51+ void
52+ GeometryBlocksOnCylindricalTests::run_monotonic_axial_coordinates_in_detector_map_test ()
53+ {
54+ auto scanner_sptr = std::make_shared<Scanner>(Scanner::SAFIRDualRingPrototype);
55+ scanner_sptr->set_scanner_geometry (" BlocksOnCylindrical" );
56+ scanner_sptr->set_transaxial_block_spacing (scanner_sptr->get_transaxial_crystal_spacing ()
57+ * scanner_sptr->get_num_transaxial_crystals_per_block ());
58+ int num_axial_buckets = 1 ; // TODO add for loop when support is added
59+
60+ for (int num_axial_crystals_per_blocks = 1 ; num_axial_crystals_per_blocks < 3 ; ++num_axial_crystals_per_blocks)
61+ for (int num_axial_blocks_per_bucket = 1 ; num_axial_blocks_per_bucket < 3 ; ++num_axial_blocks_per_bucket)
62+ {
63+ scanner_sptr->set_num_axial_crystals_per_block (num_axial_crystals_per_blocks);
64+ scanner_sptr->set_num_axial_blocks_per_bucket (num_axial_blocks_per_bucket);
65+ scanner_sptr->set_num_rings (scanner_sptr->get_num_axial_crystals_per_bucket () * num_axial_buckets);
66+ scanner_sptr->set_axial_block_spacing (scanner_sptr->get_axial_crystal_spacing ()
67+ * (scanner_sptr->get_num_axial_crystals_per_block () + 0.5 ));
68+
69+ if (monotonic_axial_coordinates_in_detector_map_test (scanner_sptr) == Succeeded::no)
70+ {
71+ warning (boost::format (" Monothonic axial coordinates test failed for:\n "
72+ " \t axial_crystal_per_block =\t %1%\n "
73+ " \t axial_blocks_per_bucket =\t %2%\n "
74+ " \t num_axial_buckets =\t\t\t %3%" )
75+ % num_axial_crystals_per_blocks % num_axial_blocks_per_bucket % num_axial_buckets);
76+ everything_ok = false ;
77+ return ;
78+ }
79+ }
80+ }
81+
82+ Succeeded
83+ GeometryBlocksOnCylindricalTests::monotonic_axial_coordinates_in_detector_map_test (const shared_ptr<Scanner>& scanner_sptr)
84+ {
85+ if (scanner_sptr->get_scanner_geometry () != " BlocksOnCylindrical" )
86+ {
87+ warning (" monotonic_axial_coordinates_in_detector_map_test is only for the BlocksOnCylindrical geometry" );
88+ return Succeeded::no;
89+ }
90+
91+ shared_ptr<DetectorCoordinateMap> detector_map_sptr;
92+ try
93+ {
94+ detector_map_sptr.reset (new GeometryBlocksOnCylindrical (*scanner_sptr));
95+ }
96+ catch (const std::runtime_error& e)
97+ {
98+ warning (boost::format (" Caught runtime_error while creating GeometryBlocksOnCylindrical: %1%\n "
99+ " Failing the test." )
100+ % e.what ());
101+ return Succeeded::no;
102+ }
103+
104+ unsigned min_axial_pos = 0 ;
105+ float prev_min_axial_coord = -std::numeric_limits<float >::max ();
106+
107+ for (unsigned axial_idx = 0 ; axial_idx < detector_map_sptr->get_num_axial_coords (); ++axial_idx)
108+ for (unsigned tangential_idx = 0 ; tangential_idx < detector_map_sptr->get_num_tangential_coords (); ++tangential_idx)
109+ for (unsigned radial_idx = 0 ; radial_idx < detector_map_sptr->get_num_radial_coords (); ++radial_idx)
110+ {
111+ const DetectionPosition<> det_pos = DetectionPosition<>(tangential_idx, axial_idx, radial_idx);
112+ CartesianCoordinate3D<float > coord = detector_map_sptr->get_coordinate_for_det_pos (det_pos);
113+ if (coord.z () > prev_min_axial_coord)
114+ {
115+ min_axial_pos = axial_idx;
116+ prev_min_axial_coord = coord.z ();
117+ }
118+ else if (coord.z () < prev_min_axial_coord)
119+ {
120+ float delta = coord.z () - prev_min_axial_coord;
121+ warning (boost::format (" Axial Coordinates are not monotonic.\n "
122+ " Next axial index =\t\t %1%, Next axial coord (mm) =\t\t %2% (%3%)\n "
123+ " Previous axial index =\t %4%, Previous axial coord (mm) =\t %5%" )
124+ % axial_idx % coord.z () % delta % min_axial_pos % prev_min_axial_coord);
125+ return Succeeded::no;
126+ }
127+ }
128+
129+ return Succeeded::yes;
130+ }
131+
132+ void
133+ GeometryBlocksOnCylindricalTests::run_tests ()
134+ {
135+ HighResWallClockTimer timer;
136+ timer.start ();
137+ run_monotonic_axial_coordinates_in_detector_map_test ();
138+ timer.stop ();
139+ }
140+ END_NAMESPACE_STIR
141+
142+ USING_NAMESPACE_STIR
143+
144+ int
145+ main ()
146+ {
147+ Verbosity::set (1 );
148+ GeometryBlocksOnCylindricalTests tests;
149+ tests.run_tests ();
150+ return tests.main_return_value ();
151+ }
0 commit comments