From 9e246fda374784453df89e5293df99ee4f8805ee Mon Sep 17 00:00:00 2001 From: NikEfth Date: Tue, 26 Mar 2024 01:23:36 -0400 Subject: [PATCH 01/78] Adding support for singleTOF scanner. A single TOF scanner can be used in the calculation of the sensitivity image, while applying the boundaries of the coincidence window. --- src/buildblock/ProjDataInfo.cxx | 34 ++++++++++++++++++- src/include/stir/ProjDataInfo.h | 4 ++- src/include/stir/ProjDataInfo.inl | 10 ++++++ .../stir/recon_buildblock/ProjMatrixByBin.inl | 15 ++++++-- src/recon_buildblock/ProjMatrixByBin.cxx | 17 ++++++++-- 5 files changed, 72 insertions(+), 8 deletions(-) diff --git a/src/buildblock/ProjDataInfo.cxx b/src/buildblock/ProjDataInfo.cxx index 05adde73ca..7a4ae51014 100644 --- a/src/buildblock/ProjDataInfo.cxx +++ b/src/buildblock/ProjDataInfo.cxx @@ -177,9 +177,41 @@ ProjDataInfo::set_tof_mash_factor(const int new_num) { tof_mash_factor = new_num; if (tof_mash_factor > scanner_ptr->get_max_num_timing_poss()) + { error("ProjDataInfo::set_tof_mash_factor: TOF mashing factor (" + std::to_string(tof_mash_factor) + +") must be smaller than or equal to the scanner's number of max timing bins (" + std::to_string(scanner_ptr->get_max_num_timing_poss()) + ")."); + } + else if (tof_mash_factor == num_tof_bins) + // This is a special case that we just want boundaries for the coincidence window. + { + + Bin bin; + bin.timing_pos_num() = min_tof_pos_num; + //Get lowest low + float cur_low = get_k(bin) - get_sampling_in_k(bin) / 2.f; + //Get highest high + bin.timing_pos_num() = max_tof_pos_num; + float cur_high = get_k(bin) + get_sampling_in_k(bin) / 2.f; + + + min_tof_pos_num = 0; + max_tof_pos_num = 1; + num_tof_bins = 1; + // Upper and lower boundaries of the timing poss; + tof_bin_boundaries_mm.recycle(); + tof_bin_boundaries_mm.grow(min_tof_pos_num, max_tof_pos_num); + tof_bin_boundaries_ps.recycle(); + tof_bin_boundaries_ps.grow(min_tof_pos_num, max_tof_pos_num); + + tof_bin_boundaries_mm[0].low_lim = cur_low; + tof_bin_boundaries_mm[0].high_lim = cur_high; + + tof_bin_boundaries_ps[0].low_lim = static_cast(mm_to_tof_delta_time(tof_bin_boundaries_mm[0].low_lim)); + tof_bin_boundaries_ps[0].high_lim = static_cast(mm_to_tof_delta_time(tof_bin_boundaries_mm[0].high_lim)); + + return; + } #if 0 // KT: code disabled as buggy but currently not needed @@ -288,7 +320,7 @@ ProjDataInfo::ProjDataInfo(const shared_ptr& scanner_ptr_v, max_tof_pos_num = 0; tof_increament_in_mm = 0.f; tof_mash_factor = 0; - num_tof_bins = 1; + num_tof_bins = 0; } // TOF version. diff --git a/src/include/stir/ProjDataInfo.h b/src/include/stir/ProjDataInfo.h index f6ef1a032a..f93178380a 100644 --- a/src/include/stir/ProjDataInfo.h +++ b/src/include/stir/ProjDataInfo.h @@ -150,7 +150,9 @@ class ProjDataInfo //! Similar to create_shared_clone() but returns a non-tof version of ProjDataInfo setting tof mashing factor = 0 inline shared_ptr create_non_tof_clone() const; - + //! Similar to create_non_tof_clone() but returns a single TOF version of the ProjDataInfo setting the TOF mashing factor sush that, + //! a single TOF bin remains. This is usefull to apply the coincidence window boundaries without affecting the probabilities. + inline shared_ptr create_single_tof_clone() const; //! Destructor virtual ~ProjDataInfo() {} diff --git a/src/include/stir/ProjDataInfo.inl b/src/include/stir/ProjDataInfo.inl index 8677a7083e..f54b412946 100644 --- a/src/include/stir/ProjDataInfo.inl +++ b/src/include/stir/ProjDataInfo.inl @@ -44,6 +44,16 @@ ProjDataInfo::create_non_tof_clone() const return sptr; } +shared_ptr +ProjDataInfo::create_single_tof_clone() const +{ + shared_ptr sptr(this->clone()); + int a = sptr->get_num_tof_poss(); + sptr->set_tof_mash_factor(a); + int b = sptr->get_num_tof_poss(); + return sptr; +} + int ProjDataInfo::get_num_segments() const { diff --git a/src/include/stir/recon_buildblock/ProjMatrixByBin.inl b/src/include/stir/recon_buildblock/ProjMatrixByBin.inl index 7ff4ac1530..70e4ac98b4 100644 --- a/src/include/stir/recon_buildblock/ProjMatrixByBin.inl +++ b/src/include/stir/recon_buildblock/ProjMatrixByBin.inl @@ -67,7 +67,7 @@ ProjMatrixByBin::get_proj_matrix_elems_for_one_bin(ProjMatrixElemsForOneBin& pro #ifndef NDEBUG probabilities.check_state(); #endif - if (proj_data_info_sptr->is_tof_data() && this->tof_enabled) + if (proj_data_info_sptr->is_tof_data()) { // Apply TOF kernel to basic bin apply_tof_kernel(probabilities); } @@ -97,7 +97,7 @@ ProjMatrixByBin::get_proj_matrix_elems_for_one_bin(ProjMatrixElemsForOneBin& pro #ifndef NDEBUG probabilities.check_state(); #endif - if (proj_data_info_sptr->is_tof_data() && this->tof_enabled) + if (proj_data_info_sptr->is_tof_data()) { // Apply TOF kernel to basic bin apply_tof_kernel(probabilities); } @@ -149,7 +149,16 @@ ProjMatrixByBin::get_tof_value(const float d1, const float d2) const if ((d1_n >= 4.f && d2_n >= 4.f) || (d1_n <= -4.f && d2_n <= -4.f)) return 0.F; else - return 0.5f * (erf_interpolation(d2_n) - erf_interpolation(d1_n)); + { + if (this->tof_enabled) + { + return 0.5f * (erf_interpolation(d2_n) - erf_interpolation(d1_n)); + } + else + { + return 1.0f; + } + } } END_NAMESPACE_STIR diff --git a/src/recon_buildblock/ProjMatrixByBin.cxx b/src/recon_buildblock/ProjMatrixByBin.cxx index 16a0987db1..120fb555c8 100644 --- a/src/recon_buildblock/ProjMatrixByBin.cxx +++ b/src/recon_buildblock/ProjMatrixByBin.cxx @@ -68,9 +68,20 @@ ProjMatrixByBin::enable_tof(const shared_ptr& _proj_data_inf { if (v) { - tof_enabled = true; - gauss_sigma_in_mm = tof_delta_time_to_mm(proj_data_info_sptr->get_scanner_ptr()->get_timing_resolution()) / 2.355f; - r_sqrt2_gauss_sigma = 1.0f / (gauss_sigma_in_mm * static_cast(sqrt(2.0))); + if (proj_data_info_sptr->get_num_tof_poss() == 1) // This is a special case that we do a nonTOF backprojection with coincidence window. + { + tof_enabled = false; + } + else + { + tof_enabled = true; + gauss_sigma_in_mm = tof_delta_time_to_mm(proj_data_info_sptr->get_scanner_ptr()->get_timing_resolution()) / 2.355f; + r_sqrt2_gauss_sigma = 1.0f / (gauss_sigma_in_mm * static_cast(sqrt(2.0))); + } + } + else + { + tof_enabled = false; } } From 4634d3021d9373e9b727da7aa6de5e849ad11f27 Mon Sep 17 00:00:00 2001 From: NikEfth Date: Wed, 15 May 2024 14:59:58 -0400 Subject: [PATCH 02/78] Fast reconstruction for LAFOV scanners * Building upon the cached lm reconstruction, we further parallelize the calculation of the sensitity image to muliple passes when the scanner is long. * Now, a scanner is TOF ready even if it does not have set timing resolution. If the timing resolution is 0 ps or if the TOF mashing leads to a scanner with 1 TOF bin then we apply boundaries on the LOR's length, equal to the coinc window. The probabilities do not change as we set them to 1. * cxx17 is nessesary for newer ROOT. --- CMakeLists.txt | 2 +- src/IO/InterfileHeader.cxx | 7 +- src/buildblock/Scanner.cxx | 58 +++++++++++++ src/include/stir/Scanner.h | 8 ++ src/include/stir/Scanner.inl | 2 +- ...ataSymmetriesForBins_PET_CartesianGrid.cxx | 2 +- ...MeanAndListModeDataWithProjMatrixByBin.cxx | 83 +++++++++++-------- src/recon_buildblock/ProjMatrixByBin.cxx | 2 +- 8 files changed, 124 insertions(+), 40 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 14ba3da322..600fbc50d6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,7 +33,7 @@ include(src/cmake/SetC++Version.cmake) # minimum C++ version required (you can still ask for a more recent one # by setting CMAKE_CXX_STANDARD) -UseCXX(14) +UseCXX(17) # set default build-type to Release if(NOT CMAKE_BUILD_TYPE) diff --git a/src/IO/InterfileHeader.cxx b/src/IO/InterfileHeader.cxx index ec911f68ab..1e5c0fd397 100644 --- a/src/IO/InterfileHeader.cxx +++ b/src/IO/InterfileHeader.cxx @@ -612,19 +612,19 @@ InterfilePDFSHeader::InterfilePDFSHeader() reference_energy = -1.f; add_key("Reference energy (in keV)", &reference_energy); - max_num_timing_poss = -1; + max_num_timing_poss = 1; add_key("Maximum number of (unmashed) TOF time bins", &max_num_timing_poss); #if STIR_VERSION < 070000 add_alias_key("Maximum number of (unmashed) TOF time bins", "Number of TOF time bins"); #endif timing_poss_sequence.clear(); add_key("TOF bin order", &timing_poss_sequence); - size_of_timing_pos = -1.f; + size_of_timing_pos = 0.0f; add_key("Size of unmashed TOF time bins (ps)", &size_of_timing_pos); #if STIR_VERSION < 070000 add_alias_key("Size of unmashed TOF time bins (ps)", "Size of timing bin (ps)"); #endif - timing_resolution = -1.f; + timing_resolution = 0.0f; add_key("TOF timing resolution (ps)", &timing_resolution); #if STIR_VERSION < 070000 add_alias_key("TOF timing resolution (ps)", "timing resolution (ps)"); @@ -1430,6 +1430,7 @@ InterfilePDFSHeader::post_processing() data_info_sptr.reset(new ProjDataInfoGenericNoArcCorr( scanner_sptr_from_file, sorted_num_rings_per_segment, sorted_min_ring_diff, sorted_max_ring_diff, num_views, num_bins)); } + std::cout << data_info_sptr->get_num_tof_poss() << " " << num_timing_poss << std::endl; if (data_info_sptr->get_num_tof_poss() != num_timing_poss) error(boost::format("Interfile header parsing with TOF: inconsistency between number of TOF bins in data (%d), " "TOF mashing factor (%d) and max number of TOF bins in scanner info (%d)") diff --git a/src/buildblock/Scanner.cxx b/src/buildblock/Scanner.cxx index b66dee692e..8151ce6de5 100644 --- a/src/buildblock/Scanner.cxx +++ b/src/buildblock/Scanner.cxx @@ -1231,6 +1231,64 @@ Scanner::Scanner(Type scanner_type) ); break; + + case PSMR_3rings_scanner: + set_params(PSMR_3rings_scanner, + string_list("PSMR_3rings_scanner"), + 3 * 14 * 6,// + 8, + 344, + 344, + 24 * 7 * 5, + 405.0 - 18/2, + 7.0F, + 2.85F, + 2.08626F, + static_cast(-0.11), + 14,// int num_axial_blocks_per_bucket_v, + 5, // int num_transaxial_blocks_per_bucket_v, + 6, // int num_axial_crystals_per_block_v, + 7,// int num_transaxial_crystals_per_block_v, + 252, // int num_axial_crystals_per_singles_unit_v, + 35, // int num_transaxial_crystals_per_singles_unit_v, + 1, + 0.0F, 511.F, +#ifdef STIR_TOF + 1, + 2011.5F*2.F, // Size of coincidence window + 0.F +#endif + ); + break; + + case PSMR_3rings_scanner_TOF: + set_params(PSMR_3rings_scanner_TOF, + string_list("PSMR_3rings_scanner_TOF"), + 3 * 14 * 6,// + 8, + 344, 344, + 24 * 7 * 5, + 405.0 - 18/2, + 7.0F, + 2.85F, + 2.08626F, + static_cast(-0.11), + 14,// int num_axial_blocks_per_bucket_v, + 5, // int num_transaxial_blocks_per_bucket_v, + 6, // int num_axial_crystals_per_block_v, + 7,// int num_transaxial_crystals_per_block_v, + 252, // int num_axial_crystals_per_singles_unit_v, + 35, // int num_transaxial_crystals_per_singles_unit_v, + 1, + 0.0F, 511.F, +#ifdef STIR_TOF + // (short int)(2*3.51*1000 -1), + // (float)(1.f), + (short int)(13), // 13 TOF bins + (float)(2011.5F*2.F/13), // + (float)(500.F) +#endif + ); + break; + case User_defined_scanner: // zlong, 08-04-2004, Userdefined support set_params(User_defined_scanner, diff --git a/src/include/stir/Scanner.h b/src/include/stir/Scanner.h index 79c8cbde82..40650f8224 100644 --- a/src/include/stir/Scanner.h +++ b/src/include/stir/Scanner.h @@ -83,6 +83,12 @@ class Succeeded; while others (such as CTI scanners) give only singles for a collection of blocks. A \c singles_unit is then a set of crystals for which we can get singles rates. + \li \c If the user set number for TOF positions to 1 and a bin size equal to the + coincidence window then the reconstruction will essential be nonTOF but the + projector will restrict the size of the LOR to the size of the coincidence window. + \li \c The scanner will be classified as TOF enabled when the numer of TOF bins and +TOF bin size are >1 and >0, respectively. If the energy resolution is not set that will be fine +as long as the final TOF possitions is 1. Then we just restict the size of the LOR. A further complication is that some scanners (including many Siemens scanners) insert virtual crystals in the sinogram data (corresponding to gaps between @@ -168,6 +174,8 @@ class Scanner HZLR, RATPET, PANDA, + PSMR_3rings_scanner, + PSMR_3rings_scanner_TOF, HYPERimage, nanoPET, HRRT, diff --git a/src/include/stir/Scanner.inl b/src/include/stir/Scanner.inl index 874f97e45c..b7db176437 100644 --- a/src/include/stir/Scanner.inl +++ b/src/include/stir/Scanner.inl @@ -262,7 +262,7 @@ Scanner::get_timing_resolution() const bool Scanner::is_tof_ready() const { - return (max_num_of_timing_poss > 0 && size_timing_pos > 0.0f && timing_resolution > 0.0f); + return (max_num_of_timing_poss > 0 && size_timing_pos > 0.0f ); //&& timing_resolution > 0.0f); } std::string diff --git a/src/recon_buildblock/DataSymmetriesForBins_PET_CartesianGrid.cxx b/src/recon_buildblock/DataSymmetriesForBins_PET_CartesianGrid.cxx index 4a919a9897..ca1df6899b 100644 --- a/src/recon_buildblock/DataSymmetriesForBins_PET_CartesianGrid.cxx +++ b/src/recon_buildblock/DataSymmetriesForBins_PET_CartesianGrid.cxx @@ -340,7 +340,7 @@ DataSymmetriesForBins_PET_CartesianGrid::DataSymmetriesForBins_PET_CartesianGrid } // RT Disabling some symmetries due to tof data - if (proj_data_info_ptr->is_tof_data()) + if (proj_data_info_ptr->is_tof_data() && proj_data_info_ptr->get_num_tof_poss() > 1) { if (this->do_symmetry_90degrees_min_phi || this->do_symmetry_180degrees_min_phi) { diff --git a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx index 00a4a3aed6..3c2c29ba61 100644 --- a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx +++ b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx @@ -280,14 +280,14 @@ PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin 1)) // TODO this check needs to cover the case if we reconstruct only TOF bin 0 { // sets non-tof backprojector for sensitivity calculation (clone of the back_projector + set projdatainfo to non-tof) - this->sens_proj_data_info_sptr = this->proj_data_info_sptr->create_non_tof_clone(); + this->sens_proj_data_info_sptr = this->proj_data_info_sptr->create_single_tof_clone(); // TODO disable caching of the matrix this->sens_backprojector_sptr.reset(projector_pair_sptr->get_back_projector_sptr()->clone()); this->sens_backprojector_sptr->set_up(this->sens_proj_data_info_sptr, target_sptr); } else { - // just use the normal backprojector + // just use a signle TOF backprojector this->sens_proj_data_info_sptr = this->proj_data_info_sptr; this->sens_backprojector_sptr = projector_pair_sptr->get_back_projector_sptr(); } @@ -622,19 +622,31 @@ PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBinproj_data_info_sptr->get_min_segment_num(); - const int max_segment_num = this->proj_data_info_sptr->get_max_segment_num(); - info(boost::format("Calculating sensitivity for subset %1%") % subset_num); int min_timing_pos_num = use_tofsens ? this->proj_data_info_sptr->get_min_tof_pos_num() : 0; int max_timing_pos_num = use_tofsens ? this->proj_data_info_sptr->get_max_tof_pos_num() : 0; - if (min_timing_pos_num < 0 || max_timing_pos_num > 0) + if (min_timing_pos_num < 0 || max_timing_pos_num > 1) error("TOF code for sensitivity needs work"); this->sens_backprojector_sptr->start_accumulating_in_new_target(); - // warning: has to be same as subset scheme used as in distributable_computation + int runs = 1; + if((this->proj_data_info_sptr->get_max_segment_num() - + this->proj_data_info_sptr->get_min_segment_num()) > 100) + { + runs = ceil(this->proj_data_info_sptr->get_max_segment_num() - + this->proj_data_info_sptr->get_min_segment_num())/ 100; + } + + info(boost::format("The number of runs needed for the sensitivity image is %1%: ") % runs); + + for (int run = 0; run < runs; ++run) + { + const int min_segment_num = this->proj_data_info_sptr->get_min_segment_num(); + const int max_segment_num = this->proj_data_info_sptr->get_max_segment_num(); + info(boost::format("Current run %1% of %2%: ") % run % runs); + // warning: has to be same as subset scheme used as in distributable_computation #ifdef STIR_OPENMP # if _OPENMP < 201107 # pragma omp parallel for schedule(dynamic) @@ -642,40 +654,45 @@ PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBinsens_proj_data_info_sptr->get_min_view_num() + subset_num; - view <= this->sens_proj_data_info_sptr->get_max_view_num(); - view += this->num_subsets) + for (int segment_num = min_segment_num; segment_num <= max_segment_num; ++segment_num) { - const ViewSegmentNumbers view_segment_num(view, segment_num); + for (int view = this->sens_proj_data_info_sptr->get_min_view_num() + subset_num; + view <= this->sens_proj_data_info_sptr->get_max_view_num(); + view += this->num_subsets) + { + const ViewSegmentNumbers view_segment_num(view, segment_num); - if (!this->sens_backprojector_sptr->get_symmetries_used()->is_basic(view_segment_num)) - continue; - // for (int timing_pos_num = min_timing_pos_num; timing_pos_num <= max_timing_pos_num; ++timing_pos_num) - { - shared_ptr symmetries_used( - this->sens_backprojector_sptr->get_symmetries_used()->clone()); + if (!this->sens_backprojector_sptr->get_symmetries_used()->is_basic(view_segment_num)) + continue; - RelatedViewgrams viewgrams = this->sens_proj_data_info_sptr->get_empty_related_viewgrams( - view_segment_num, symmetries_used, false); //, timing_pos_num); + info(boost::format("Current seg %1%, view: %1% ") % segment_num, view); + // for (int timing_pos_num = min_timing_pos_num; timing_pos_num <= max_timing_pos_num; ++timing_pos_num) + { + shared_ptr symmetries_used( + this->sens_backprojector_sptr->get_symmetries_used()->clone()); - viewgrams.fill(1.F); - // find efficiencies - { - this->normalisation_sptr->undo(viewgrams); - } - // backproject - { - const int min_ax_pos_num = viewgrams.get_min_axial_pos_num(); - const int max_ax_pos_num = viewgrams.get_max_axial_pos_num(); + RelatedViewgrams viewgrams = this->sens_proj_data_info_sptr->get_empty_related_viewgrams( + view_segment_num, symmetries_used, false); //, timing_pos_num); + + viewgrams.fill(1.F); + // find efficiencies + { + this->normalisation_sptr->undo(viewgrams); + } + // backproject + { + const int min_ax_pos_num = viewgrams.get_min_axial_pos_num(); + const int max_ax_pos_num = viewgrams.get_max_axial_pos_num(); - this->sens_backprojector_sptr->back_project(viewgrams, min_ax_pos_num, max_ax_pos_num); + this->sens_backprojector_sptr->back_project(viewgrams, min_ax_pos_num, max_ax_pos_num); + } + } } - } } + this->sens_backprojector_sptr->get_output(sensitivity); + // Next run } - this->sens_backprojector_sptr->get_output(sensitivity); + } template diff --git a/src/recon_buildblock/ProjMatrixByBin.cxx b/src/recon_buildblock/ProjMatrixByBin.cxx index 120fb555c8..beb2491c48 100644 --- a/src/recon_buildblock/ProjMatrixByBin.cxx +++ b/src/recon_buildblock/ProjMatrixByBin.cxx @@ -68,7 +68,7 @@ ProjMatrixByBin::enable_tof(const shared_ptr& _proj_data_inf { if (v) { - if (proj_data_info_sptr->get_num_tof_poss() == 1) // This is a special case that we do a nonTOF backprojection with coincidence window. + if (proj_data_info_sptr->get_num_tof_poss() == 1 || proj_data_info_sptr->get_scanner_ptr()->get_timing_resolution() == 0 ) // This is a special case that we do a nonTOF backprojection with coincidence window. { tof_enabled = false; } From 4ba55e63d6b1d6e84a0ee1c10cf0cc0be28e3af2 Mon Sep 17 00:00:00 2001 From: Nikos Efthimiou Date: Wed, 15 May 2024 21:21:47 -0400 Subject: [PATCH 03/78] Update src/buildblock/ProjDataInfo.cxx Co-authored-by: Kris Thielemans --- src/buildblock/ProjDataInfo.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/buildblock/ProjDataInfo.cxx b/src/buildblock/ProjDataInfo.cxx index 7a4ae51014..021b01408c 100644 --- a/src/buildblock/ProjDataInfo.cxx +++ b/src/buildblock/ProjDataInfo.cxx @@ -196,7 +196,7 @@ ProjDataInfo::set_tof_mash_factor(const int new_num) min_tof_pos_num = 0; - max_tof_pos_num = 1; + max_tof_pos_num = 0; num_tof_bins = 1; // Upper and lower boundaries of the timing poss; tof_bin_boundaries_mm.recycle(); From f5d663d27f2d407ae08cb33881780eb55d0712c5 Mon Sep 17 00:00:00 2001 From: Nikos Efthimiou Date: Wed, 15 May 2024 21:21:59 -0400 Subject: [PATCH 04/78] Update src/include/stir/ProjDataInfo.inl Co-authored-by: Kris Thielemans --- src/include/stir/ProjDataInfo.inl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/include/stir/ProjDataInfo.inl b/src/include/stir/ProjDataInfo.inl index f54b412946..824c0f581f 100644 --- a/src/include/stir/ProjDataInfo.inl +++ b/src/include/stir/ProjDataInfo.inl @@ -50,7 +50,6 @@ ProjDataInfo::create_single_tof_clone() const shared_ptr sptr(this->clone()); int a = sptr->get_num_tof_poss(); sptr->set_tof_mash_factor(a); - int b = sptr->get_num_tof_poss(); return sptr; } From 438be2fd6a70201e49d0d3a9979bec4db81d8f93 Mon Sep 17 00:00:00 2001 From: Nikos Efthimiou Date: Wed, 15 May 2024 21:24:55 -0400 Subject: [PATCH 05/78] Update src/recon_buildblock/ProjMatrixByBin.cxx Co-authored-by: Kris Thielemans --- src/recon_buildblock/ProjMatrixByBin.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/recon_buildblock/ProjMatrixByBin.cxx b/src/recon_buildblock/ProjMatrixByBin.cxx index beb2491c48..4ad7acb40e 100644 --- a/src/recon_buildblock/ProjMatrixByBin.cxx +++ b/src/recon_buildblock/ProjMatrixByBin.cxx @@ -68,7 +68,7 @@ ProjMatrixByBin::enable_tof(const shared_ptr& _proj_data_inf { if (v) { - if (proj_data_info_sptr->get_num_tof_poss() == 1 || proj_data_info_sptr->get_scanner_ptr()->get_timing_resolution() == 0 ) // This is a special case that we do a nonTOF backprojection with coincidence window. + if (proj_data_info_sptr->get_num_tof_poss() == 1 || proj_data_info_sptr->get_scanner_ptr()->get_timing_resolution() == 0 ) // This is a special case that we do a nonTOF backprojection without coincidence window. { tof_enabled = false; } From 0fb593dac1d067627ca871837a4dc39e1eb9ab91 Mon Sep 17 00:00:00 2001 From: NikEfth Date: Wed, 15 May 2024 21:51:41 -0400 Subject: [PATCH 06/78] Minor fixes and comments. --- src/include/stir/ProjDataInfo.inl | 6 +++++- ...elForMeanAndListModeDataWithProjMatrixByBin.cxx | 14 ++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/include/stir/ProjDataInfo.inl b/src/include/stir/ProjDataInfo.inl index 824c0f581f..dad09e3d8e 100644 --- a/src/include/stir/ProjDataInfo.inl +++ b/src/include/stir/ProjDataInfo.inl @@ -49,7 +49,11 @@ ProjDataInfo::create_single_tof_clone() const { shared_ptr sptr(this->clone()); int a = sptr->get_num_tof_poss(); - sptr->set_tof_mash_factor(a); + if (a > 0) + sptr->set_tof_mash_factor(a); + // - TODO: remove this later + // int b = sptr->get_num_tof_poss(); + // std::cout <<"Old number of TOF poss " << a << " new number of TOF poss " << b << std::endl; return sptr; } diff --git a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx index 3c2c29ba61..740a99df28 100644 --- a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx +++ b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx @@ -632,12 +632,14 @@ PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBinsens_backprojector_sptr->start_accumulating_in_new_target(); int runs = 1; - if((this->proj_data_info_sptr->get_max_segment_num() - - this->proj_data_info_sptr->get_min_segment_num()) > 100) - { - runs = ceil(this->proj_data_info_sptr->get_max_segment_num() - - this->proj_data_info_sptr->get_min_segment_num())/ 100; - } + + //TODO: For long scanners we should run better split this. - Let's discuss more. + // if((this->proj_data_info_sptr->get_max_segment_num() - + // this->proj_data_info_sptr->get_min_segment_num()) > 100) + // { + // runs = ceil(this->proj_data_info_sptr->get_max_segment_num() - + // this->proj_data_info_sptr->get_min_segment_num())/ 100; + // } info(boost::format("The number of runs needed for the sensitivity image is %1%: ") % runs); From 7bf7e524b8032ff22e156176a5e45f3793048524 Mon Sep 17 00:00:00 2001 From: NikEfth Date: Thu, 16 May 2024 15:06:59 -0400 Subject: [PATCH 07/78] Minor fixes and executable to cache listmode files without reconstruction --- src/include/stir/ProjDataInfo.inl | 2 +- src/include/stir/Scanner.h | 2 +- src/utilities/cache_lm.cxx | 293 ++++++++++++++++++++++++++++++ 3 files changed, 295 insertions(+), 2 deletions(-) create mode 100644 src/utilities/cache_lm.cxx diff --git a/src/include/stir/ProjDataInfo.inl b/src/include/stir/ProjDataInfo.inl index dad09e3d8e..69c6f82d78 100644 --- a/src/include/stir/ProjDataInfo.inl +++ b/src/include/stir/ProjDataInfo.inl @@ -198,7 +198,7 @@ ProjDataInfo::is_tof_data() const { if (num_tof_bins != 1) { - error("Non-TOF data with inconsistent Time-of-Flight bin number - Aborted operation."); + warning("Non-TOF data with inconsistent Time-of-Flight bin number - Note. As we are updating the scanner template style please make sure to have one TOF position (whole coincidence window for nonTOF scanners)."); } return false; } diff --git a/src/include/stir/Scanner.h b/src/include/stir/Scanner.h index 40650f8224..d8a751498e 100644 --- a/src/include/stir/Scanner.h +++ b/src/include/stir/Scanner.h @@ -87,7 +87,7 @@ class Succeeded; coincidence window then the reconstruction will essential be nonTOF but the projector will restrict the size of the LOR to the size of the coincidence window. \li \c The scanner will be classified as TOF enabled when the numer of TOF bins and -TOF bin size are >1 and >0, respectively. If the energy resolution is not set that will be fine +TOF bin size are >1 and >0, respectively. If the timing resolution is not set that will be fine as long as the final TOF possitions is 1. Then we just restict the size of the LOR. A further complication is that some scanners (including many Siemens scanners) diff --git a/src/utilities/cache_lm.cxx b/src/utilities/cache_lm.cxx new file mode 100644 index 0000000000..ccc55eb8a6 --- /dev/null +++ b/src/utilities/cache_lm.cxx @@ -0,0 +1,293 @@ +// +// +/*! + \file + \ingroup listmode_utilities + +\brief Program to bin listmode data to 3d sinograms + +\see cache_lm for info on parameter file format + +\author Nikos Efthimiou + + */ + +#include "stir/ProjDataInfoCylindricalNoArcCorr.h" +#include "stir/ProjData.h" +#include "stir/utilities.h" +#include "stir/ParsingObject.h" +#include "stir/listmode/ListModeData.h" +#include "stir/listmode/ListRecord.h" +#include "stir/listmode/CListEventCylindricalScannerWithDiscreteDetectors.h" +#include "stir/IO/read_from_file.h" +#include "stir/error.h" +#include "stir/FilePath.h" + +using std::cerr; +using std::min; + +USING_NAMESPACE_STIR + + class LmCache : public ParsingObject +{ +public: + LmCache(const char* const par_filename); + + int max_segment_num_to_process; + + shared_ptr lm_data_sptr; + + void cache_listmode_file(); + + std::string get_cache_filename(unsigned int file_id) const; + + std::string get_cache_path() const; + + Succeeded write_listmode_cache_file(unsigned int file_id) const; + +private: + void set_defaults() override; + void initialise_keymap() override; + bool post_processing() override; + + unsigned long int cache_size; + std::string cache_path; + std::string input_filename; + std::string output_filename_prefix; + bool store_prompts; + int num_cache_files; +long int num_events_to_use; + shared_ptr proj_data_info_sptr; + std::vector record_cache; +}; + + +void LmCache::set_defaults() +{ + max_segment_num_to_process = -1; + store_prompts = true; + num_cache_files = 0; + cache_size = 150000000; + num_events_to_use = -1; +} + +void +LmCache::initialise_keymap() +{ + parser.add_start_key("lm_cache Parameters"); + parser.add_key("input file", &input_filename); + parser.add_key("num_events_to_use", &num_events_to_use); + // parser.add_key("frame_definition file", &frame_definition_filename); + parser.add_key("output filename prefix", &output_filename_prefix); + parser.add_key("maximum absolute segment number to process", &max_segment_num_to_process); + // TODO can't do this yet + // if (CListEvent::has_delayeds()) + { + parser.add_key("Store 'prompts'", &store_prompts); + } + parser.add_stop_key("END"); +} + +bool +LmCache::post_processing() +{ + lm_data_sptr = read_from_file(input_filename); + + const int num_rings = lm_data_sptr->get_scanner().get_num_rings(); + if (max_segment_num_to_process == -1) + max_segment_num_to_process = num_rings - 1; + else + max_segment_num_to_process = min(max_segment_num_to_process, num_rings - 1); + + proj_data_info_sptr = lm_data_sptr->get_proj_data_info_sptr()->create_shared_clone()->create_single_tof_clone(); + proj_data_info_sptr->reduce_segment_range(-max_segment_num_to_process, max_segment_num_to_process); + + return false; +} + +std::string +LmCache::get_cache_path() const +{ + if (this->cache_path.size() > 0) + return this->cache_path; + else + return FilePath::get_current_working_directory(); +} + + +LmCache::LmCache(const char* const par_filename) +{ + set_defaults(); + if (par_filename != 0) + parse(par_filename); + else + ask_parameters(); +} + +std::string +LmCache::get_cache_filename(unsigned int file_id) const +{ + std::string cache_filename = "my_CACHE" + std::to_string(file_id) + ".bin"; + FilePath icache(cache_filename, false); + icache.prepend_directory_name(this->get_cache_path()); + return icache.get_as_string(); +} + +Succeeded +LmCache::write_listmode_cache_file( + unsigned int file_id) const +{ + const auto cache_filename = this->get_cache_filename(file_id); + const bool with_add = false; + + { + info("Storing Listmode cache to file \"" + cache_filename + "\"."); + // open the file, overwriting whatever was there before + std::ofstream fout(cache_filename, std::ios::out | std::ios::binary | std::ios::trunc); + if (!fout) + error("Error opening cache file \"" + cache_filename + "\" for writing."); + + for (unsigned long int ie = 0; ie < record_cache.size(); ++ie) + { + Bin tmp = record_cache[ie].my_bin; + if (with_add) + tmp.set_bin_value(record_cache[ie].my_corr); + fout.write((char*)&tmp, sizeof(Bin)); + } + if (!fout) + error("Error writing to cache file \"" + cache_filename + "\"."); + + fout.close(); + } + + return Succeeded::yes; +} + +void +LmCache::cache_listmode_file() +{ + + // warning("Looking for existing cache files such as \"" + this->get_cache_filename(0) + "\".\n" + // + "We will be ignoring any time frame definitions as well as num_events_to_use!"); + // // find how many cache files there are + // num_cache_files = 0; + // while (true) + // { + // if (!FilePath::exists(this->get_cache_filename(this->num_cache_files))) + // break; + // ++this->num_cache_files; + // } + // if (!this->num_cache_files) + // error("No cache files found."); + // return ; // Stop here!!! + + num_cache_files = 0; + + info("Listmode reconstruction: Creating cache...", 2); + + record_cache.clear(); + try + { + record_cache.reserve(this->cache_size); + } + catch (...) + { + error("Listmode: cannot allocate cache for " + std::to_string(this->cache_size) + " records. Reduce cache size."); + } + + this->lm_data_sptr->reset(); + const shared_ptr record_sptr = this->lm_data_sptr->get_empty_record_sptr(); + + double current_time = 0.; + unsigned long int cached_events = 0; + + bool stop_caching = false; + record_cache.reserve(this->cache_size); + + while (true) // keep caching across multiple files. + { + record_cache.clear(); + + while (true) // Start for the current cache + { + if (this->lm_data_sptr->get_next_record(*record_sptr) == Succeeded::no) + { + stop_caching = true; + break; + } + + + if (record_sptr->is_event() && record_sptr->event().is_prompt()) + { + BinAndCorr tmp; + tmp.my_bin.set_bin_value(1.0); + record_sptr->event().get_bin(tmp.my_bin, *this->proj_data_info_sptr); + + if (tmp.my_bin.get_bin_value() != 1.0f + || tmp.my_bin.segment_num() < this->proj_data_info_sptr->get_min_segment_num() + || tmp.my_bin.segment_num() > this->proj_data_info_sptr->get_max_segment_num() + || tmp.my_bin.tangential_pos_num() < this->proj_data_info_sptr->get_min_tangential_pos_num() + || tmp.my_bin.tangential_pos_num() > this->proj_data_info_sptr->get_max_tangential_pos_num() + || tmp.my_bin.axial_pos_num() < this->proj_data_info_sptr->get_min_axial_pos_num(tmp.my_bin.segment_num()) + || tmp.my_bin.axial_pos_num() > this->proj_data_info_sptr->get_max_axial_pos_num(tmp.my_bin.segment_num()) + || tmp.my_bin.timing_pos_num() < this->proj_data_info_sptr->get_min_tof_pos_num() + || tmp.my_bin.timing_pos_num() > this->proj_data_info_sptr->get_max_tof_pos_num()) + { + continue; + } + try + { + record_cache.push_back(tmp); + ++cached_events; + } + catch (...) + { + // should never get here due to `reserve` statement above, but best to check... + error("Listmode: running out of memory for cache. Current size: " + + std::to_string(this->record_cache.size()) + " records"); + } + + if (record_cache.size() > 1 && record_cache.size() % 500000L == 0) + info(boost::format("Cached Prompt Events (this cache): %1% ") % record_cache.size()); + + if (this->num_events_to_use > 0) + if (cached_events >= static_cast(this->num_events_to_use)) + { + stop_caching = true; + break; + } + + if (record_cache.size() == this->cache_size) + break; // cache is full. go to next cache. + } + } + + if (write_listmode_cache_file(this->num_cache_files) == Succeeded::no) + { + error("Error writing cache file!"); + } + ++this->num_cache_files; + + if (stop_caching) + break; + } + info(boost::format("Cached Events: %1% ") % cached_events); + return; + +} + + +int +main(int argc, char* argv[]) +{ + + if (argc != 1 && argc != 2) + { + cerr << "Usage: " << argv[0] << " [par_file]\n"; + exit(EXIT_FAILURE); + } + LmCache lm_cache(argc == 2 ? argv[1] : 0); + lm_cache.cache_listmode_file(); + + return EXIT_SUCCESS; +} From 6aa04b0eb84ce3e63c50ceebbbe51e783e9fab72 Mon Sep 17 00:00:00 2001 From: Nikos Efthimiou Date: Thu, 16 May 2024 17:05:10 -0400 Subject: [PATCH 08/78] Update CMakeLists.txt Co-authored-by: Kris Thielemans --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 600fbc50d6..14ba3da322 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,7 +33,7 @@ include(src/cmake/SetC++Version.cmake) # minimum C++ version required (you can still ask for a more recent one # by setting CMAKE_CXX_STANDARD) -UseCXX(17) +UseCXX(14) # set default build-type to Release if(NOT CMAKE_BUILD_TYPE) From 5fa949cd98b1821255c3761ff6f6b56c14ba5dfc Mon Sep 17 00:00:00 2001 From: Nikos Efthimiou Date: Thu, 16 May 2024 18:02:30 -0400 Subject: [PATCH 09/78] Update src/IO/InterfileHeader.cxx Co-authored-by: Kris Thielemans --- src/IO/InterfileHeader.cxx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/IO/InterfileHeader.cxx b/src/IO/InterfileHeader.cxx index 1e5c0fd397..d4b6639f56 100644 --- a/src/IO/InterfileHeader.cxx +++ b/src/IO/InterfileHeader.cxx @@ -1430,7 +1430,6 @@ InterfilePDFSHeader::post_processing() data_info_sptr.reset(new ProjDataInfoGenericNoArcCorr( scanner_sptr_from_file, sorted_num_rings_per_segment, sorted_min_ring_diff, sorted_max_ring_diff, num_views, num_bins)); } - std::cout << data_info_sptr->get_num_tof_poss() << " " << num_timing_poss << std::endl; if (data_info_sptr->get_num_tof_poss() != num_timing_poss) error(boost::format("Interfile header parsing with TOF: inconsistency between number of TOF bins in data (%d), " "TOF mashing factor (%d) and max number of TOF bins in scanner info (%d)") From e67faecaffbe2af4b314c166e1f06ade20760b43 Mon Sep 17 00:00:00 2001 From: NikEfth Date: Thu, 16 May 2024 18:07:05 -0400 Subject: [PATCH 10/78] Minor fixes --- src/buildblock/Scanner.cxx | 8 ++++---- ...nearModelForMeanAndListModeDataWithProjMatrixByBin.cxx | 7 ++++--- src/utilities/CMakeLists.txt | 1 + 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/buildblock/Scanner.cxx b/src/buildblock/Scanner.cxx index 8151ce6de5..dd6ef256ad 100644 --- a/src/buildblock/Scanner.cxx +++ b/src/buildblock/Scanner.cxx @@ -1236,14 +1236,14 @@ Scanner::Scanner(Type scanner_type) set_params(PSMR_3rings_scanner, string_list("PSMR_3rings_scanner"), 3 * 14 * 6,// + 8, - 344, - 344, + 411, + 401, 24 * 7 * 5, 405.0 - 18/2, 7.0F, 2.85F, 2.08626F, - static_cast(-0.11), + static_cast(-0.137), //-0.18 14,// int num_axial_blocks_per_bucket_v, 5, // int num_transaxial_blocks_per_bucket_v, 6, // int num_axial_crystals_per_block_v, @@ -1270,7 +1270,7 @@ Scanner::Scanner(Type scanner_type) 7.0F, 2.85F, 2.08626F, - static_cast(-0.11), + static_cast(-0.137), 14,// int num_axial_blocks_per_bucket_v, 5, // int num_transaxial_blocks_per_bucket_v, 6, // int num_axial_crystals_per_block_v, diff --git a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx index 740a99df28..25ab386151 100644 --- a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx +++ b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx @@ -624,9 +624,10 @@ PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBinproj_data_info_sptr->get_min_tof_pos_num() : 0; - int max_timing_pos_num = use_tofsens ? this->proj_data_info_sptr->get_max_tof_pos_num() : 0; - if (min_timing_pos_num < 0 || max_timing_pos_num > 1) + // int min_timing_pos_num = use_tofsens ? this->proj_data_info_sptr->get_min_tof_pos_num() : 0; + // int max_timing_pos_num = use_tofsens ? this->proj_data_info_sptr->get_max_tof_pos_num() : 0; + // if (min_timing_pos_num < 0 || max_timing_pos_num > 1) + if ( this->proj_data_info_sptr->get_num_tof_poss() > 1) error("TOF code for sensitivity needs work"); this->sens_backprojector_sptr->start_accumulating_in_new_target(); diff --git a/src/utilities/CMakeLists.txt b/src/utilities/CMakeLists.txt index d279c024f7..98259cd137 100644 --- a/src/utilities/CMakeLists.txt +++ b/src/utilities/CMakeLists.txt @@ -42,6 +42,7 @@ set(${dir_EXE_SOURCES} get_time_frame_info.cxx generate_image.cxx list_ROI_values.cxx + cache_lm.cxx zoom_image.cxx find_fwhm_in_image.cxx abs_image.cxx From 9179482e508680b480443eb6c6fca830203b5475 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Fri, 26 Apr 2024 16:58:25 +0100 Subject: [PATCH 11/78] Split GeneralisedPriorTests into several classes This will allow implementation specific tests for a particular prior. --- src/recon_test/test_priors.cxx | 105 ++++++++++++++++++++++++++++++--- 1 file changed, 98 insertions(+), 7 deletions(-) diff --git a/src/recon_test/test_priors.cxx b/src/recon_test/test_priors.cxx index fbdcd903b0..5c2f4eec5b 100644 --- a/src/recon_test/test_priors.cxx +++ b/src/recon_test/test_priors.cxx @@ -1,6 +1,6 @@ /* Copyright (C) 2011, Hammersmith Imanet Ltd - Copyright (C) 2020-2023 University College London + Copyright (C) 2020-2024 University College London This file is part of STIR. SPDX-License-Identifier: Apache-2.0 @@ -70,8 +70,6 @@ class GeneralisedPriorTests : public RunTests typedef DiscretisedDensity<3, float> target_type; void construct_input_data(shared_ptr& density_sptr); - void run_tests() override; - //! Set methods that control which tests are run. void configure_prior_tests(bool gradient, bool Hessian_convexity, bool Hessian_numerical); @@ -442,8 +440,20 @@ GeneralisedPriorTests::construct_input_data(shared_ptr& density_spt } } +/*! + \brief tests for QuadraticPrior + \ingroup recontest + \ingroup priors +*/ +class QuadraticPriorTests : public GeneralisedPriorTests +{ +public: + using GeneralisedPriorTests::GeneralisedPriorTests; + void run_tests() override; +}; + void -GeneralisedPriorTests::run_tests() +QuadraticPriorTests::run_tests() { shared_ptr density_sptr; construct_input_data(density_sptr); @@ -454,6 +464,26 @@ GeneralisedPriorTests::run_tests() this->configure_prior_tests(true, true, true); this->run_tests_for_objective_function("Quadratic_no_kappa", objective_function, density_sptr); } +} + +/*! + \brief tests for RelativeDifferencePrior + \ingroup recontest + \ingroup priors +*/ +class RelativeDifferencePriorTests : public GeneralisedPriorTests +{ +public: + using GeneralisedPriorTests::GeneralisedPriorTests; + void run_tests() override; +}; + +void +RelativeDifferencePriorTests::run_tests() +{ + shared_ptr density_sptr; + construct_input_data(density_sptr); + std::cerr << "\n\nTests for Relative Difference Prior with epsilon = 0\n"; { // gamma is default and epsilon is 0.0 @@ -468,6 +498,25 @@ GeneralisedPriorTests::run_tests() this->configure_prior_tests(true, true, true); // With a large enough epsilon the RDP Hessian numerical test will pass this->run_tests_for_objective_function("RDP_no_kappa_with_eps", objective_function, density_sptr); } +} + +/*! + \brief tests for PLSPrior + \ingroup recontest + \ingroup priors +*/ +class PLSPriorTests : public GeneralisedPriorTests +{ +public: + using GeneralisedPriorTests::GeneralisedPriorTests; + void run_tests() override; +}; + +void +PLSPriorTests::run_tests() +{ + shared_ptr density_sptr; + construct_input_data(density_sptr); std::cerr << "\n\nTests for PLSPrior\n"; { @@ -479,6 +528,26 @@ GeneralisedPriorTests::run_tests() this->configure_prior_tests(false, false, false); this->run_tests_for_objective_function("PLS_no_kappa_flat_anatomical", objective_function, density_sptr); } +} + +/*! + \brief tests for LogCoshPrior + \ingroup recontest + \ingroup priors +*/ +class LogCoshPriorTests : public GeneralisedPriorTests +{ +public: + using GeneralisedPriorTests::GeneralisedPriorTests; + void run_tests() override; +}; + +void +LogCoshPriorTests::run_tests() +{ + shared_ptr density_sptr; + construct_input_data(density_sptr); + std::cerr << "\n\nTests for Logcosh Prior\n"; { // scalar is off @@ -497,7 +566,29 @@ main(int argc, char** argv) { set_default_num_threads(); - GeneralisedPriorTests tests(argc > 1 ? argv[1] : nullptr); - tests.run_tests(); - return tests.main_return_value(); + bool everything_ok = true; + + { + QuadraticPriorTests tests(argc > 1 ? argv[1] : nullptr); + tests.run_tests(); + everything_ok = everything_ok && tests.is_everything_ok(); + } + { + RelativeDifferencePriorTests tests(argc > 1 ? argv[1] : nullptr); + tests.run_tests(); + everything_ok = everything_ok && tests.is_everything_ok(); + } + { + PLSPriorTests tests(argc > 1 ? argv[1] : nullptr); + tests.run_tests(); + everything_ok = everything_ok && tests.is_everything_ok(); + } + { + PLSPriorTests tests(argc > 1 ? argv[1] : nullptr); + tests.run_tests(); + everything_ok = everything_ok && tests.is_everything_ok(); + } + + + return everything_ok ? EXIT_SUCCESS : EXIT_FAILURE; } From f7c15e68ac82667e39081172002b7edd8a203ea6 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sat, 27 Apr 2024 00:31:01 +0100 Subject: [PATCH 12/78] fix abs() bug for RDP and slightly cleaner code - we were using abs() which (at least on some systems) converted to integers, giving the wrong result. now using std::abs() - introduce value/derivative_10 function for more concise code --- .../RelativeDifferencePrior.h | 16 +++++--- .../RelativeDifferencePrior.cxx | 40 ++++++++++--------- 2 files changed, 31 insertions(+), 25 deletions(-) diff --git a/src/include/stir/recon_buildblock/RelativeDifferencePrior.h b/src/include/stir/recon_buildblock/RelativeDifferencePrior.h index 26f9508707..2c27a9c890 100644 --- a/src/include/stir/recon_buildblock/RelativeDifferencePrior.h +++ b/src/include/stir/recon_buildblock/RelativeDifferencePrior.h @@ -185,21 +185,25 @@ class RelativeDifferencePrior : public RegisteredParsingObject> kappa_ptr; - //! The second partial derivatives of the Relative Difference Prior + //! The value and partial derivatives of the Relative Difference Prior /*! - derivative_20 refers to the second derivative w.r.t. x_j only (i.e. diagonal elements of the Hessian) - derivative_11 refers to the second derivative w.r.t. x_j and x_k (i.e. off-diagonal elements of the Hessian) - See J. Nuyts, et al., 2002, Equation 7. + * derivative_10 refers to the derivative w.r.t. x_j only + * derivative_20 refers to the second derivative w.r.t. x_j only (i.e. diagonal elements of the Hessian) + * derivative_11 refers to the second derivative w.r.t. x_j and x_k (i.e. off-diagonal elements of the Hessian) + + See J. Nuyts, et al., 2002, Equation 7 etc, but here an epsilon is added. In the instance x_j, x_k and epsilon equal 0.0, these functions return 0.0 to prevent returning an undefined value - due to 0/0 computation. This is a "reasonable" solution to this issue. + due to 0/0 computation. This is a "reasonable" solution to this issue, although not necessarily continuous. * @param x_j is the target voxel. * @param x_k is the voxel in the neighbourhood. * @return the second order partial derivatives of the Relative Difference Prior */ //@{ + double value(const elemT x_j, const elemT x_k) const; + elemT derivative_10(const elemT x, const elemT y) const; elemT derivative_20(const elemT x_j, const elemT x_k) const; elemT derivative_11(const elemT x_j, const elemT x_k) const; //@} diff --git a/src/recon_buildblock/RelativeDifferencePrior.cxx b/src/recon_buildblock/RelativeDifferencePrior.cxx index d23b9528a1..897275e587 100644 --- a/src/recon_buildblock/RelativeDifferencePrior.cxx +++ b/src/recon_buildblock/RelativeDifferencePrior.cxx @@ -2,7 +2,7 @@ // /* Copyright (C) 2000- 2019, Hammersmith Imanet Ltd - Copyright (C) 2019- 2020, UCL + Copyright (C) 2019- 2024, UCL This file is part of STIR. SPDX-License-Identifier: Apache-2.0 @@ -31,9 +31,9 @@ #include "stir/warning.h" #include "stir/error.h" #include +#include using std::min; using std::max; - /* Pretty horrible code because we don't have an iterator of neigbhourhoods yet */ @@ -265,6 +265,22 @@ compute_weights(Array<3, float>& weights, const CartesianCoordinate3D& gr } } +template +double +RelativeDifferencePrior::value(const elemT x, const elemT y) const +{ + return 0.5 * (square(static_cast(x - y)) / (x + y + this->gamma * std::abs(x - y) + this->epsilon)); +} + +template +elemT +RelativeDifferencePrior::derivative_10(const elemT x, const elemT y) const +{ + const double num = (static_cast(x - y) * (this->gamma * std::abs(x - y) + x + 3 * y + 2 * this->epsilon)); + const double denom_sqrt = static_cast(x + y) + this->gamma * std::abs(x - y) + this->epsilon; + return static_cast(num / (denom_sqrt * denom_sqrt)); +} + template double RelativeDifferencePrior::compute_value(const DiscretisedDensity<3, elemT>& current_image_estimate) @@ -326,13 +342,8 @@ RelativeDifferencePrior::compute_value(const DiscretisedDensity<3, elemT> } else { - current = weights[dz][dy][dx] * 0.5 - * (pow(current_image_estimate[z][y][x] - current_image_estimate[z + dz][y + dy][x + dx], 2) - / (current_image_estimate[z][y][x] + current_image_estimate[z + dz][y + dy][x + dx] - + this->gamma - * abs(current_image_estimate[z][y][x] - - current_image_estimate[z + dz][y + dy][x + dx]) - + this->epsilon)); + current = weights[dz][dy][dx] + * value(current_image_estimate[z][y][x], current_image_estimate[z + dz][y + dy][x + dx]); } if (do_kappa) current *= (*kappa_ptr)[z][y][x] * (*kappa_ptr)[z + dz][y + dy][x + dx]; @@ -411,16 +422,7 @@ RelativeDifferencePrior::compute_gradient(DiscretisedDensity<3, elemT>& p { current = weights[dz][dy][dx] - * (((current_image_estimate[z][y][x] - current_image_estimate[z + dz][y + dy][x + dx]) - * (this->gamma - * abs(current_image_estimate[z][y][x] - current_image_estimate[z + dz][y + dy][x + dx]) - + current_image_estimate[z][y][x] + 3 * current_image_estimate[z + dz][y + dy][x + dx] - + 2 * this->epsilon)) - / (square((current_image_estimate[z][y][x] + current_image_estimate[z + dz][y + dy][x + dx]) - + this->gamma - * abs(current_image_estimate[z][y][x] - - current_image_estimate[z + dz][y + dy][x + dx]) - + this->epsilon))); + * derivative_10(current_image_estimate[z][y][x], current_image_estimate[z + dz][y + dy][x + dx]); } if (do_kappa) current *= (*kappa_ptr)[z][y][x] * (*kappa_ptr)[z + dz][y + dy][x + dx]; From a9c3ddbda7a24d760e32f2a3646d53fb13e7de76 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sat, 27 Apr 2024 02:12:01 +0100 Subject: [PATCH 13/78] change RDP Hessian behaviour at 0,0 when epsilon==0 now use INFINITY for second order derivatives, obtained by continuity --- .../RelativeDifferencePrior.h | 12 +++++-- .../RelativeDifferencePrior.cxx | 32 ++++++++----------- 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/src/include/stir/recon_buildblock/RelativeDifferencePrior.h b/src/include/stir/recon_buildblock/RelativeDifferencePrior.h index 2c27a9c890..7c67e7acbf 100644 --- a/src/include/stir/recon_buildblock/RelativeDifferencePrior.h +++ b/src/include/stir/recon_buildblock/RelativeDifferencePrior.h @@ -51,6 +51,9 @@ START_NAMESPACE_STIR "A Concave Prior Penalizing Relative Differences for Maximum-a-Posteriori Reconstruction in Emission Tomography," vol. 49, no. 1, pp. 56-60, 2002. + Note that if \f$ \epsilon=0 \f$, we resolve 0/0 by using the limit, ensuring continuity. Note that + the Hessian explodes to infinity when both voxel values approach 0, and we currently return INFINITY. + The \f$\kappa\f$ image can be used to have spatially-varying penalties such as in Jeff Fessler's papers. It should have identical dimensions to the image for which the penalty is computed. If \f$\kappa\f$ is not set, this class will effectively @@ -190,13 +193,18 @@ class RelativeDifferencePrior : public RegisteredParsingObject elemT RelativeDifferencePrior::derivative_10(const elemT x, const elemT y) const { + if (this->epsilon == 0.0 && x == 0 && y == 0) + { + // handle 0/0 by taking the limit with x=y->0 + // note that the limit y=0,x->0 is 1/(1+gamma) + return elemT(0); + } + const double num = (static_cast(x - y) * (this->gamma * std::abs(x - y) + x + 3 * y + 2 * this->epsilon)); const double denom_sqrt = static_cast(x + y) + this->gamma * std::abs(x - y) + this->epsilon; return static_cast(num / (denom_sqrt * denom_sqrt)); @@ -410,20 +417,9 @@ RelativeDifferencePrior::compute_gradient(DiscretisedDensity<3, elemT>& p for (int dy = min_dy; dy <= max_dy; ++dy) for (int dx = min_dx; dx <= max_dx; ++dx) { - - elemT current; - if (this->epsilon == 0.0 && current_image_estimate[z][y][x] == 0.0 - && current_image_estimate[z + dz][y + dy][x + dx] == 0.0) - { - // handle the undefined nature of the gradient - current = 0.0; - } - else - { - current - = weights[dz][dy][dx] - * derivative_10(current_image_estimate[z][y][x], current_image_estimate[z + dz][y + dy][x + dx]); - } + elemT current + = weights[dz][dy][dx] + * derivative_10(current_image_estimate[z][y][x], current_image_estimate[z + dz][y + dy][x + dx]); if (do_kappa) current *= (*kappa_ptr)[z][y][x] * (*kappa_ptr)[z + dz][y + dy][x + dx]; @@ -628,9 +624,9 @@ elemT RelativeDifferencePrior::derivative_20(const elemT x_j, const elemT x_k) const { if (x_j > 0.0 || x_k > 0.0 || this->epsilon > 0.0) - return 2 * pow(2 * x_k + this->epsilon, 2) / pow(x_j + x_k + this->gamma * abs(x_j - x_k) + this->epsilon, 3); + return 2 * pow(2 * x_k + this->epsilon, 2) / pow(x_j + x_k + this->gamma * std::abs(x_j - x_k) + this->epsilon, 3); else - return 0.0; + return INFINITY; } template @@ -639,9 +635,9 @@ RelativeDifferencePrior::derivative_11(const elemT x_j, const elemT x_k) { if (x_j > 0.0 || x_k > 0.0 || this->epsilon > 0.0) return -2 * (2 * x_j + this->epsilon) * (2 * x_k + this->epsilon) - / pow(x_j + x_k + this->gamma * abs(x_j - x_k) + this->epsilon, 3); + / pow(x_j + x_k + this->gamma * std::abs(x_j - x_k) + this->epsilon, 3); else - return 0.0; + return INFINITY; } #ifdef _MSC_VER From 005acb23e410cccc109e6473cb9f4a91ec6c02ac Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sat, 27 Apr 2024 00:30:06 +0100 Subject: [PATCH 14/78] extra tests for the Relative Difference Prior split the tests for the different priors in different classes, such that we can have specific tests for each. Currently, only added some for the RDP, including a Lipschitz test and limit for large values. --- src/recon_test/test_priors.cxx | 53 ++++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 3 deletions(-) diff --git a/src/recon_test/test_priors.cxx b/src/recon_test/test_priors.cxx index 5c2f4eec5b..6eafff044e 100644 --- a/src/recon_test/test_priors.cxx +++ b/src/recon_test/test_priors.cxx @@ -37,6 +37,7 @@ #include "stir/Verbosity.h" #include "stir/Succeeded.h" #include "stir/num_threads.h" +#include "stir/numerics/norm.h" #include #include #include @@ -475,9 +476,53 @@ class RelativeDifferencePriorTests : public GeneralisedPriorTests { public: using GeneralisedPriorTests::GeneralisedPriorTests; + void run_specific_tests(const std::string& test_name, + RelativeDifferencePrior& rdp, + const shared_ptr& target_sptr); void run_tests() override; }; +void +RelativeDifferencePriorTests::run_specific_tests(const std::string& test_name, + RelativeDifferencePrior& rdp, + const shared_ptr>& target_sptr) +{ + std::cerr << "----- test " << test_name << " --> RDP gradient limit tests\n"; + shared_ptr grad_sptr(target_sptr->get_empty_copy()); + const Array<3, float> weights = rdp.get_weights() * rdp.get_penalisation_factor(); + const bool do_kappa = rdp.get_kappa_sptr() != 0; + // strictly speaking, we should be checking product of the kappas in a neighbourhood, but they usually very smoothly. In any + // case, this will give an upper-bound + const double kappa2_max = do_kappa ? square(rdp.get_kappa_sptr()->find_max()) : 1.; + const auto weights_sum = weights.sum() * kappa2_max; + + if (rdp.get_epsilon() > 0) + { + const double grad_Lipschitz = 4 * weights_sum * kappa2_max / rdp.get_epsilon(); + + rdp.compute_gradient(*grad_sptr, *target_sptr); + check_if_less(norm(grad_sptr->begin_all(), grad_sptr->end_all()), + grad_Lipschitz * norm(target_sptr->begin_all(), target_sptr->end_all()) * 1.001F, + "gradient Lipschitz with x = input_image, y = 0"); + } + + shared_ptr delta_sptr(target_sptr->get_empty_copy()); + delta_sptr->fill(0.F); + + auto idx = make_coordinate(1, 1, 1); + (*delta_sptr)[idx] = 1E10F * rdp.get_epsilon(); + rdp.compute_gradient(*grad_sptr, *delta_sptr); + check_if_less((*grad_sptr)[idx], weights_sum / (1 + rdp.get_gamma()), "RDP gradient large limit"); + (*delta_sptr)[idx] = 1E20F * rdp.get_epsilon(); + rdp.compute_gradient(*grad_sptr, *delta_sptr); + check_if_less((*grad_sptr)[idx], weights_sum / (1 + rdp.get_gamma()), "RDP gradient very large limit"); + // check at boundary + idx = make_coordinate(0, 0, 0); + (*delta_sptr)[idx] = 1E10F * rdp.get_epsilon(); + rdp.compute_gradient(*grad_sptr, *delta_sptr); + check_if_less((*grad_sptr)[idx], weights_sum / (1 + rdp.get_gamma()), "RDP gradient large limit at boundary"); +} + void RelativeDifferencePriorTests::run_tests() { @@ -488,8 +533,10 @@ RelativeDifferencePriorTests::run_tests() { // gamma is default and epsilon is 0.0 RelativeDifferencePrior objective_function(false, 1.F, 2.F, 0.F); - this->configure_prior_tests(true, true, false); // RDP, with epsilon = 0.0, will fail the numerical Hessian test + this->configure_prior_tests( + true, true, false); // RDP, with epsilon = 0.0, will fail the numerical Hessian test (it can become infinity) this->run_tests_for_objective_function("RDP_no_kappa_no_eps", objective_function, density_sptr); + this->run_specific_tests("RDP_specific_no_kappa_no_eps", objective_function, density_sptr); } std::cerr << "\n\nTests for Relative Difference Prior with epsilon = 0.1\n"; { @@ -497,6 +544,7 @@ RelativeDifferencePriorTests::run_tests() RelativeDifferencePrior objective_function(false, 1.F, 2.F, 0.1F); this->configure_prior_tests(true, true, true); // With a large enough epsilon the RDP Hessian numerical test will pass this->run_tests_for_objective_function("RDP_no_kappa_with_eps", objective_function, density_sptr); + this->run_specific_tests("RDP_specific_no_kappa_with_eps", objective_function, density_sptr); } } @@ -584,11 +632,10 @@ main(int argc, char** argv) everything_ok = everything_ok && tests.is_everything_ok(); } { - PLSPriorTests tests(argc > 1 ? argv[1] : nullptr); + LogCoshPriorTests tests(argc > 1 ? argv[1] : nullptr); tests.run_tests(); everything_ok = everything_ok && tests.is_everything_ok(); } - return everything_ok ? EXIT_SUCCESS : EXIT_FAILURE; } From b7f727d7f521f71b35b2065467cf971118447a0f Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sat, 27 Apr 2024 12:25:42 +0100 Subject: [PATCH 15/78] make epsilon in numerical test for prior gradient a bit smaller The RDP test was failing with eps=1.e-3, so now using 1.e-4 --- src/recon_test/test_priors.cxx | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/recon_test/test_priors.cxx b/src/recon_test/test_priors.cxx index 6eafff044e..0e37bf9564 100644 --- a/src/recon_test/test_priors.cxx +++ b/src/recon_test/test_priors.cxx @@ -173,6 +173,7 @@ GeneralisedPriorTests::test_gradient(const std::string& test_name, GeneralisedPrior& objective_function, const shared_ptr& target_sptr) { + using value_type = target_type::full_value_type; // setup images target_type& target(*target_sptr); shared_ptr gradient_sptr(target.get_empty_copy()); @@ -192,7 +193,8 @@ GeneralisedPriorTests::test_gradient(const std::string& test_name, target_type::full_iterator gradient_2_iter = gradient_2_sptr->begin_all(); // setup perturbation response - const float eps = 1e-3F; + const auto target_max = target_sptr->find_max(); + const auto eps = static_cast(1e-4F * target_max); bool testOK = true; info("Computing gradient of objective function by numerical differences (this will take a while)", 3); while (target_iter != target.end_all()) // && testOK) @@ -201,9 +203,14 @@ GeneralisedPriorTests::test_gradient(const std::string& test_name, *target_iter += eps; // perturb current voxel const double value_at_inc = objective_function.compute_value(target); *target_iter = org_image_value; // restore - const auto ngradient_at_iter = static_cast((value_at_inc - value_at_target) / eps); + const auto ngradient_at_iter = static_cast((value_at_inc - value_at_target) / eps); *gradient_2_iter = ngradient_at_iter; - testOK = testOK && this->check_if_equal(ngradient_at_iter, *gradient_iter, "gradient"); + const bool this_testOK = this->check_if_equal(ngradient_at_iter, *gradient_iter, "gradient"); + testOK = testOK && this_testOK; + if (!this_testOK) + { + // std::cerr << "failure x=" << org_image_value << ", eps_Value = " << eps << '\n'; + } // for (int i=0; i<5 && target_iter!=target.end_all(); ++i) { ++gradient_2_iter; From d8b3826cfb4e2669f656c5f4fae932c157d913c7 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sat, 27 Apr 2024 12:37:55 +0100 Subject: [PATCH 16/78] fix and document RDP large limit test for numerical error --- src/recon_test/test_priors.cxx | 38 +++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/src/recon_test/test_priors.cxx b/src/recon_test/test_priors.cxx index 0e37bf9564..681e0f64e0 100644 --- a/src/recon_test/test_priors.cxx +++ b/src/recon_test/test_priors.cxx @@ -505,6 +505,7 @@ RelativeDifferencePriorTests::run_specific_tests(const std::string& test_name, if (rdp.get_epsilon() > 0) { + // test Lipschitz condition on current image const double grad_Lipschitz = 4 * weights_sum * kappa2_max / rdp.get_epsilon(); rdp.compute_gradient(*grad_sptr, *target_sptr); @@ -513,21 +514,34 @@ RelativeDifferencePriorTests::run_specific_tests(const std::string& test_name, "gradient Lipschitz with x = input_image, y = 0"); } + // do some checks on a "delta" image shared_ptr delta_sptr(target_sptr->get_empty_copy()); delta_sptr->fill(0.F); - auto idx = make_coordinate(1, 1, 1); - (*delta_sptr)[idx] = 1E10F * rdp.get_epsilon(); - rdp.compute_gradient(*grad_sptr, *delta_sptr); - check_if_less((*grad_sptr)[idx], weights_sum / (1 + rdp.get_gamma()), "RDP gradient large limit"); - (*delta_sptr)[idx] = 1E20F * rdp.get_epsilon(); - rdp.compute_gradient(*grad_sptr, *delta_sptr); - check_if_less((*grad_sptr)[idx], weights_sum / (1 + rdp.get_gamma()), "RDP gradient very large limit"); - // check at boundary - idx = make_coordinate(0, 0, 0); - (*delta_sptr)[idx] = 1E10F * rdp.get_epsilon(); - rdp.compute_gradient(*grad_sptr, *delta_sptr); - check_if_less((*grad_sptr)[idx], weights_sum / (1 + rdp.get_gamma()), "RDP gradient large limit at boundary"); + { + // The derivative of the RDP_potential(x,0) limits to 1/(1+gamma). Therefore, the + // gradient of the prior will limit to + const auto grad_limit_no_kappa = weights_sum / (1 + rdp.get_gamma()); + + const auto scale = rdp.get_epsilon() ? rdp.get_epsilon() : 1; + auto idx = make_coordinate(1, 1, 1); + double kappa_at_idx_2 = do_kappa ? square((*rdp.get_kappa_sptr())[idx]) : 1.; + auto grad_limit = grad_limit_no_kappa * kappa_at_idx_2; + (*delta_sptr)[idx] = 1E5F * scale; + rdp.compute_gradient(*grad_sptr, *delta_sptr); + check_if_less(std::abs((*grad_sptr)[idx] / grad_limit - 1), 1e-4, "RDP gradient large limit"); + (*delta_sptr)[idx] = 1E20F * scale; + rdp.compute_gradient(*grad_sptr, *delta_sptr); + check_if_less(std::abs((*grad_sptr)[idx] / grad_limit - 1), 1e-4, "RDP gradient very large limit"); + + // check at boundary (fewer neighbours) + idx = make_coordinate(0, 0, 0); + (*delta_sptr)[idx] = 1E5F * scale; + kappa_at_idx_2 = do_kappa ? square((*rdp.get_kappa_sptr())[idx]) : 1.; + grad_limit = grad_limit_no_kappa * kappa_at_idx_2; + rdp.compute_gradient(*grad_sptr, *delta_sptr); + check_if_less((*grad_sptr)[idx] / grad_limit, 1., "RDP gradient large limit at boundary"); + } } void From f17a7b0eb34401456023cbd01cfc2cf5a2ffeac1 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sat, 27 Apr 2024 13:17:35 +0100 Subject: [PATCH 17/78] update RDP doc [ci skip] --- documentation/release_6.1.htm | 5 +++-- .../recon_buildblock/RelativeDifferencePrior.h | 14 +++++++++++--- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/documentation/release_6.1.htm b/documentation/release_6.1.htm index 899265a79c..5461d74058 100644 --- a/documentation/release_6.1.htm +++ b/documentation/release_6.1.htm @@ -66,8 +66,9 @@

Changed functionality

Bug fixes

    -
  • -
  • +
  • The Relative Difference Prior gave incorrect results, probably since switching to C++-14 in version 6.0, although we are not sure. + See PR #1410 and associated issue #1409. +

Known problems

diff --git a/src/include/stir/recon_buildblock/RelativeDifferencePrior.h b/src/include/stir/recon_buildblock/RelativeDifferencePrior.h index 7c67e7acbf..3eec07e046 100644 --- a/src/include/stir/recon_buildblock/RelativeDifferencePrior.h +++ b/src/include/stir/recon_buildblock/RelativeDifferencePrior.h @@ -51,8 +51,12 @@ START_NAMESPACE_STIR "A Concave Prior Penalizing Relative Differences for Maximum-a-Posteriori Reconstruction in Emission Tomography," vol. 49, no. 1, pp. 56-60, 2002. - Note that if \f$ \epsilon=0 \f$, we resolve 0/0 by using the limit, ensuring continuity. Note that - the Hessian explodes to infinity when both voxel values approach 0, and we currently return INFINITY. + If \f$ \epsilon=0 \f$, we attempt to resolve 0/0 at \f$ \lambda_r = \lambda_{r+dr}=0 \f$ by using the limit. + Note that the Hessian explodes to infinity when both voxel values approach 0, and we currently return \c INFINITY. + Also, as the RDP is not differentiable at this point, we have chosen to return 0 for the gradient + (such that a zero background is not modified). + + \warning the default value for \f$ \epsilon \f$ is zero, which can be problematic for gradient-based algorithms. The \f$\kappa\f$ image can be used to have spatially-varying penalties such as in Jeff Fessler's papers. It should have identical dimensions to the image for which the @@ -62,6 +66,10 @@ START_NAMESPACE_STIR By default, a 3x3 or 3x3x3 neighbourhood is used where the weights are set to x-voxel_size divided by the Euclidean distance between the points. + The prior computation excludes voxel-pairs where one voxel is outside the volume. This is + effectively the same as extending the volume by replicating the edges (which is different + from zero boundary conditions). + \par Parsing These are the keywords that can be used in addition to the ones in GeneralPrior. \verbatim @@ -203,7 +211,7 @@ class RelativeDifferencePrior : public RegisteredParsingObject Date: Sat, 27 Apr 2024 12:39:43 +0100 Subject: [PATCH 18/78] increase numerical precision of priors use double in neighbourhood calculations --- documentation/release_6.1.htm | 5 +- src/recon_buildblock/LogcoshPrior.cxx | 14 ++--- src/recon_buildblock/PLSPrior.cxx | 4 +- src/recon_buildblock/QuadraticPrior.cxx | 61 +++---------------- .../RelativeDifferencePrior.cxx | 10 +-- 5 files changed, 25 insertions(+), 69 deletions(-) diff --git a/documentation/release_6.1.htm b/documentation/release_6.1.htm index 5461d74058..562fa25c91 100644 --- a/documentation/release_6.1.htm +++ b/documentation/release_6.1.htm @@ -59,8 +59,9 @@

New functionality

Changed functionality

    -
  • -
  • +
  • + Accumulation in computation of priors now uses doubles, which could result in slightly better precision. Part of PR #1410. +
diff --git a/src/recon_buildblock/LogcoshPrior.cxx b/src/recon_buildblock/LogcoshPrior.cxx index abccd71023..0876407ea7 100644 --- a/src/recon_buildblock/LogcoshPrior.cxx +++ b/src/recon_buildblock/LogcoshPrior.cxx @@ -289,12 +289,12 @@ LogcoshPrior::compute_value(const DiscretisedDensity<3, elemT>& current_i for (int dx = min_dx; dx <= max_dx; ++dx) { // 1/scalar^2 * log(cosh(x * scalar)) - elemT voxel_diff = current_image_estimate[z][y][x] - current_image_estimate[z + dz][y + dy][x + dx]; - elemT current + double voxel_diff = current_image_estimate[z][y][x] - current_image_estimate[z + dz][y + dy][x + dx]; + double current = weights[dz][dy][dx] * 1 / (this->scalar * this->scalar) * logcosh(this->scalar * voxel_diff); if (do_kappa) current *= (*kappa_ptr)[z][y][x] * (*kappa_ptr)[z + dz][y + dy][x + dx]; - result += static_cast(current); + result += current; } } } @@ -349,21 +349,21 @@ LogcoshPrior::compute_gradient(DiscretisedDensity<3, elemT>& prior_gradie const int min_dx = max(weights[0][0].get_min_index(), min_x - x); const int max_dx = min(weights[0][0].get_max_index(), max_x - x); - elemT gradient = 0; + double gradient = 0; for (int dz = min_dz; dz <= max_dz; ++dz) for (int dy = min_dy; dy <= max_dy; ++dy) for (int dx = min_dx; dx <= max_dx; ++dx) { // 1/scalar * tanh(x * scalar) - elemT voxel_diff = current_image_estimate[z][y][x] - current_image_estimate[z + dz][y + dy][x + dx]; - elemT current = weights[dz][dy][dx] * (1 / this->scalar) * tanh(this->scalar * voxel_diff); + double voxel_diff = current_image_estimate[z][y][x] - current_image_estimate[z + dz][y + dy][x + dx]; + double current = weights[dz][dy][dx] * (1 / this->scalar) * tanh(this->scalar * voxel_diff); if (do_kappa) current *= (*kappa_ptr)[z][y][x] * (*kappa_ptr)[z + dz][y + dy][x + dx]; gradient += current; } - prior_gradient[z][y][x] = gradient * this->penalisation_factor; + prior_gradient[z][y][x] = static_cast(gradient * this->penalisation_factor); } } } diff --git a/src/recon_buildblock/PLSPrior.cxx b/src/recon_buildblock/PLSPrior.cxx index 74ae5b6ea0..6ff213b1a4 100644 --- a/src/recon_buildblock/PLSPrior.cxx +++ b/src/recon_buildblock/PLSPrior.cxx @@ -498,12 +498,12 @@ PLSPrior::compute_value(const DiscretisedDensity<3, elemT>& current_image (penalty[z][y][x]) * (*kappa_ptr)[z][y][x]; */ - elemT current = (*penalty_sptr)[z][y][x]; + double current = (*penalty_sptr)[z][y][x]; if (do_kappa) current *= (*kappa_ptr)[z][y][x]; - result += static_cast(current); + result += current; } } } diff --git a/src/recon_buildblock/QuadraticPrior.cxx b/src/recon_buildblock/QuadraticPrior.cxx index 93c4db3fb0..f9c8561986 100644 --- a/src/recon_buildblock/QuadraticPrior.cxx +++ b/src/recon_buildblock/QuadraticPrior.cxx @@ -278,14 +278,14 @@ QuadraticPrior::compute_value(const DiscretisedDensity<3, elemT>& current for (int dy = min_dy; dy <= max_dy; ++dy) for (int dx = min_dx; dx <= max_dx; ++dx) { - elemT current = weights[dz][dy][dx] - * square(current_image_estimate[z][y][x] - current_image_estimate[z + dz][y + dy][x + dx]) - / 4; + double current = weights[dz][dy][dx] + * square(current_image_estimate[z][y][x] - current_image_estimate[z + dz][y + dy][x + dx]) + / 4; if (do_kappa) current *= (*kappa_ptr)[z][y][x] * (*kappa_ptr)[z + dz][y + dy][x + dx]; - result += static_cast(current); + result += current; } } } @@ -348,65 +348,20 @@ QuadraticPrior::compute_gradient(DiscretisedDensity<3, elemT>& prior_grad (current_image_estimate[z][y][x] - current_image_estimate[z+dz][y+dy][x+dx]) * (*kappa_ptr)[z][y][x] * (*kappa_ptr)[z+dz][y+dy][x+dx]; */ -#if 1 - elemT gradient = 0; + double gradient = 0.; for (int dz = min_dz; dz <= max_dz; ++dz) for (int dy = min_dy; dy <= max_dy; ++dy) for (int dx = min_dx; dx <= max_dx; ++dx) { - elemT current = weights[dz][dy][dx] - * (current_image_estimate[z][y][x] - current_image_estimate[z + dz][y + dy][x + dx]); + double current = weights[dz][dy][dx] + * (current_image_estimate[z][y][x] - current_image_estimate[z + dz][y + dy][x + dx]); if (do_kappa) current *= (*kappa_ptr)[z][y][x] * (*kappa_ptr)[z + dz][y + dy][x + dx]; gradient += current; } -#else - // attempt to speed up by precomputing the sum of weights. - // The current code gives identical results but is actually slower - // than the above, at least when kappas are present. - - // precompute sum of weights - // TODO without kappas, this is just weights.sum() most of the time, - // but not near edges - float sum_of_weights = 0; - { - if (do_kappa) - { - for (int dz = min_dz; dz <= max_dz; ++dz) - for (int dy = min_dy; dy <= max_dy; ++dy) - for (int dx = min_dx; dx <= max_dx; ++dx) - sum_of_weights += weights[dz][dy][dx] * (*kappa_ptr)[z + dz][y + dy][x + dx]; - } - else - { - for (int dz = min_dz; dz <= max_dz; ++dz) - for (int dy = min_dy; dy <= max_dy; ++dy) - for (int dx = min_dx; dx <= max_dx; ++dx) - sum_of_weights += weights[dz][dy][dx]; - } - } - // now compute contribution of central term - elemT gradient = sum_of_weights * current_image_estimate[z][y][x]; - - // subtract the rest - for (int dz = min_dz; dz <= max_dz; ++dz) - for (int dy = min_dy; dy <= max_dy; ++dy) - for (int dx = min_dx; dx <= max_dx; ++dx) - { - elemT current = weights[dz][dy][dx] * current_image_estimate[z + dz][y + dy][x + dx]; - - if (do_kappa) - current *= (*kappa_ptr)[z + dz][y + dy][x + dx]; - - gradient -= current; - } - // multiply with central kappa - if (do_kappa) - gradient *= (*kappa_ptr)[z][y][x]; -#endif - prior_gradient[z][y][x] = gradient * this->penalisation_factor; + prior_gradient[z][y][x] = static_cast(gradient * this->penalisation_factor); } } } diff --git a/src/recon_buildblock/RelativeDifferencePrior.cxx b/src/recon_buildblock/RelativeDifferencePrior.cxx index e3718d92bf..c61fe1be04 100644 --- a/src/recon_buildblock/RelativeDifferencePrior.cxx +++ b/src/recon_buildblock/RelativeDifferencePrior.cxx @@ -340,7 +340,7 @@ RelativeDifferencePrior::compute_value(const DiscretisedDensity<3, elemT> for (int dy = min_dy; dy <= max_dy; ++dy) for (int dx = min_dx; dx <= max_dx; ++dx) { - elemT current; + double current; if (this->epsilon == 0.0 && current_image_estimate[z][y][x] == 0.0 && current_image_estimate[z + dz][y + dy][x + dx] == 0.0) { @@ -355,7 +355,7 @@ RelativeDifferencePrior::compute_value(const DiscretisedDensity<3, elemT> if (do_kappa) current *= (*kappa_ptr)[z][y][x] * (*kappa_ptr)[z + dz][y + dy][x + dx]; - result += static_cast(current); + result += current; } } } @@ -412,12 +412,12 @@ RelativeDifferencePrior::compute_gradient(DiscretisedDensity<3, elemT>& p const int min_dx = max(weights[0][0].get_min_index(), min_x - x); const int max_dx = min(weights[0][0].get_max_index(), max_x - x); - elemT gradient = 0; + double gradient = 0; for (int dz = min_dz; dz <= max_dz; ++dz) for (int dy = min_dy; dy <= max_dy; ++dy) for (int dx = min_dx; dx <= max_dx; ++dx) { - elemT current + double current = weights[dz][dy][dx] * derivative_10(current_image_estimate[z][y][x], current_image_estimate[z + dz][y + dy][x + dx]); if (do_kappa) @@ -426,7 +426,7 @@ RelativeDifferencePrior::compute_gradient(DiscretisedDensity<3, elemT>& p gradient += current; } - prior_gradient[z][y][x] = gradient * this->penalisation_factor; + prior_gradient[z][y][x] = static_cast(gradient * this->penalisation_factor); } } } From 32dc591cc5e26d4fa6300ef74ac3201a7d8be7e8 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sun, 28 Apr 2024 20:17:46 +0100 Subject: [PATCH 19/78] [GHA] remove explicit setting of xcode version Fixes #1411 --- .github/workflows/build-test.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 7d1e65357c..8c0cb93cba 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -162,9 +162,6 @@ jobs: CXX=${CXX}-${{ matrix.compiler_version }} fi if test 'XX${{ matrix.os }}' = 'XXmacOS-latest'; then - # need to force XCode version for the moment due to a linker bug - # see https://github.com/UCL/STIR/issues/1103 - echo DEVELOPER_DIR="/Applications/Xcode_14.1.app/Contents/Developer" >> $GITHUB_ENV if test 'XX${{ matrix.compiler }}' = 'XXclang'; then brew install llvm@${{ matrix.compiler_version }} if test XX${HOMEBREW_PREFIX} = XX; then From fa21161bbc5d1e861e8513726ad37b787ae00c0e Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Tue, 30 Apr 2024 22:24:40 +0100 Subject: [PATCH 20/78] update Byte Order compile-time checks previous version failed on macos-14-arm64. We have a very fragile list of checks. Presumably, CMake does a lot better, so use CMAKE_CXX_BYTE_ORDER in ByteOrderDefine (via config.h) --- documentation/release_6.1.htm | 5 +++++ src/CMakeLists.txt | 12 ++++++++++++ src/cmake/STIRConfig.h.in | 4 ++++ src/include/stir/ByteOrderDefine.h | 12 ++++++++---- 4 files changed, 29 insertions(+), 4 deletions(-) diff --git a/documentation/release_6.1.htm b/documentation/release_6.1.htm index 562fa25c91..1bb9ab02d6 100644 --- a/documentation/release_6.1.htm +++ b/documentation/release_6.1.htm @@ -70,6 +70,11 @@

Bug fixes

  • The Relative Difference Prior gave incorrect results, probably since switching to C++-14 in version 6.0, although we are not sure. See PR #1410 and associated issue #1409.
  • +
  • + Our checks for determining system byte-order were out-of-date and in particular did not work on MacOS 14 on ARM. + We now use CMake's CMAKE_CXX_BYTE_ORDER (available since CMake 3.20). + See PR #1412. +
  • Known problems

    diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4101bf85d4..7a731c2e63 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -192,6 +192,18 @@ else() set(STIR_USE_BOOST_SHARED_PTR ON) endif() +# Byte order +if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.20) + # Use CMake to find byte order of target system. + # We create some variables that are then used in config.h.in + if (CMAKE_CXX_BYTE_ORDER STREQUAL "BIG_ENDIAN") + set(BIG_ENDIAN_BYTE_ORDER_FROM_CMAKE TRUE) + message(STATUS "CMake detected a big_endian system") + elseif (CMAKE_CXX_BYTE_ORDER STREQUAL "LITTLE_ENDIAN") + set(LITTLE_ENDIAN_BYTE_ORDER_FROM_CMAKE TRUE) + message(STATUS "CMake detected a little_endian system") + endif() +endif() #### Create stir/config.h diff --git a/src/cmake/STIRConfig.h.in b/src/cmake/STIRConfig.h.in index 515f021e5b..ecb62fa4ac 100644 --- a/src/cmake/STIRConfig.h.in +++ b/src/cmake/STIRConfig.h.in @@ -52,6 +52,10 @@ namespace stir { #include "boost/config.hpp" +// 2 variables set via CMake +#cmakedefine BIG_ENDIAN_BYTE_ORDER_FROM_CMAKE +#cmakedefine LITTLE_ENDIAN_BYTE_ORDER_FROM_CMAKE + #cmakedefine HAVE_ECAT #ifdef HAVE_ECAT #define HAVE_LLN_MATRIX diff --git a/src/include/stir/ByteOrderDefine.h b/src/include/stir/ByteOrderDefine.h index de909de3b9..456502afae 100644 --- a/src/include/stir/ByteOrderDefine.h +++ b/src/include/stir/ByteOrderDefine.h @@ -23,10 +23,14 @@ START_NAMESPACE_STIR // currently checked by asserts() -#if !defined(__alpha) && (!defined(_WIN32) || defined(_M_PPC) || defined(_M_MPPC)) && !defined(__i386__) && !defined(__i486__) \ - && !defined(__i586__) && !defined(__i686__) && !defined(__i786__) && !defined(__i886__) && !defined(__k6__) \ - && !defined(__athlon__) && !defined(__x86_64__) && !defined(__k6__) \ - || (defined(__MSL__) && !defined(__LITTLE_ENDIAN)) +// partially from https://stackoverflow.com/a/27054190 +#if defined(BIG_ENDIAN_BYTE_ORDER_FROM_CMAKE) /* variable set via CMake in stir/config.h */ \ + || (!defined(LITTLE_ENDIAN_BYTE_ORDER_FROM_CMAKE) /* variable set via CMake in stir/config.h */ \ + && ((!defined(__alpha) && (!defined(_WIN32) || defined(_M_PPC) || defined(_M_MPPC)) && !defined(__i386__) \ + && !defined(__i486__) && !defined(__i586__) && !defined(__i686__) && !defined(__i786__) && !defined(__i886__) \ + && !defined(__k6__) && !defined(__athlon__) && !defined(__x86_64__) && !defined(__k6__)) \ + || (defined(__MSL__) && !defined(__LITTLE_ENDIAN)) || (defined(__BYTE_ORDER) && __BYTE_ORDER == __BIG_ENDIAN) \ + || (defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) /* gcc */) || defined(__BIG_ENDIAN__))) # define STIRIsNativeByteOrderBigEndian 1 # define STIRIsNativeByteOrderLittleEndian 0 #else From ead279e4e3c5a02df809ac18cce1ed484f304cec Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Thu, 9 May 2024 23:39:17 +0100 Subject: [PATCH 21/78] fix white-space run clang-format on Ubuntu on ByteOrderDefine.h. Apparently, there was a version mismatch. --- src/include/stir/ByteOrderDefine.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/include/stir/ByteOrderDefine.h b/src/include/stir/ByteOrderDefine.h index 456502afae..e846a99ac7 100644 --- a/src/include/stir/ByteOrderDefine.h +++ b/src/include/stir/ByteOrderDefine.h @@ -27,10 +27,10 @@ START_NAMESPACE_STIR #if defined(BIG_ENDIAN_BYTE_ORDER_FROM_CMAKE) /* variable set via CMake in stir/config.h */ \ || (!defined(LITTLE_ENDIAN_BYTE_ORDER_FROM_CMAKE) /* variable set via CMake in stir/config.h */ \ && ((!defined(__alpha) && (!defined(_WIN32) || defined(_M_PPC) || defined(_M_MPPC)) && !defined(__i386__) \ - && !defined(__i486__) && !defined(__i586__) && !defined(__i686__) && !defined(__i786__) && !defined(__i886__) \ - && !defined(__k6__) && !defined(__athlon__) && !defined(__x86_64__) && !defined(__k6__)) \ - || (defined(__MSL__) && !defined(__LITTLE_ENDIAN)) || (defined(__BYTE_ORDER) && __BYTE_ORDER == __BIG_ENDIAN) \ - || (defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) /* gcc */) || defined(__BIG_ENDIAN__))) + && !defined(__i486__) && !defined(__i586__) && !defined(__i686__) && !defined(__i786__) && !defined(__i886__) \ + && !defined(__k6__) && !defined(__athlon__) && !defined(__x86_64__) && !defined(__k6__)) \ + || (defined(__MSL__) && !defined(__LITTLE_ENDIAN)) || (defined(__BYTE_ORDER) && __BYTE_ORDER == __BIG_ENDIAN) \ + || (defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) /* gcc */) || defined(__BIG_ENDIAN__))) # define STIRIsNativeByteOrderBigEndian 1 # define STIRIsNativeByteOrderLittleEndian 0 #else From 205218d7dbe96d6a486c339168aaf502c27cb41e Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Thu, 9 May 2024 22:22:25 +0100 Subject: [PATCH 22/78] List-mode objective function: always uses OpenMP for gradient - split caching in separate functions for reading a batch from list-mode file or cache - always use LM_distributable_computation for gradient Resulting code avoids duplication and is faster when caching is not used. --- ...orMeanAndListModeDataWithProjMatrixByBin.h | 32 +- ...MeanAndListModeDataWithProjMatrixByBin.cxx | 467 +++++++----------- 2 files changed, 219 insertions(+), 280 deletions(-) diff --git a/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.h b/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.h index a2ac8f7909..aaa581f240 100644 --- a/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.h +++ b/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.h @@ -3,7 +3,7 @@ /* Copyright (C) 2003- 2011, Hammersmith Imanet Ltd Copyright (C) 2015, Univ. of Leeds - Copyright (C) 2016, 2022 UCL + Copyright (C) 2016, 2022, 2024 UCL Copyright (C) 2021, University of Pennsylvania SPDX-License-Identifier: Apache-2.0 @@ -148,20 +148,44 @@ class PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByB bool skip_balanced_subsets; private: - //! Cache of the listmode file + //! Cache of the current "batch" in the listmode file /*! \todo Move this higher-up in the hierarchy as it doesn't depend on ProjMatrixByBin */ std::vector record_cache; - //! This function caches the listmode file, or reads it. It is run during set_up() + //! This function loads the next "batch" of data from the listmode file. + /*! + This function will either use read_listmode_batch or load_listmode_cache_file. + + \param[in] ibatch the batch number to be read. + \return \c true if there are no more events to read after this call, \c false otherwise + \todo Move this function higher-up in the hierarchy as it doesn't depend on ProjMatrixByBin + */ + bool load_listmode_batch(unsigned int ibatch); + + //! This function reads the next "batch" of data from the listmode file. + /*! + This function keeps on reading from the current position in the list-mode data and stores + prompts events and additive terms in \c record_cache. It also updates \c end_time_per_batch + such that we know when each batch starts/ends. + + \param[in] ibatch the batch number to be read. + \return \c true if there are no more events to read after this call, \c false otherwise + \todo Move this function higher-up in the hierarchy as it doesn't depend on ProjMatrixByBin + \warning This function has to be called in sequence. + */ + bool read_listmode_batch(unsigned int ibatch); + //! This function caches the list-mode batches to file. It is run during set_up() /*! \todo Move this function higher-up in the hierarchy as it doesn't depend on ProjMatrixByBin */ Succeeded cache_listmode_file(); - Succeeded load_listmode_cache_file(unsigned int file_id); + //! Reads the "batch" of data from the cache + bool load_listmode_cache_file(unsigned int file_id); Succeeded write_listmode_cache_file(unsigned int file_id) const; unsigned int num_cache_files; + std::vector end_time_per_batch; }; END_NAMESPACE_STIR diff --git a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx index 25ab386151..a60dbd3e26 100644 --- a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx +++ b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx @@ -1,6 +1,6 @@ /* Copyright (C) 2003- 2011, Hammersmith Imanet Ltd - Copyright (C) 2014, 2016, 2018, 2022 University College London + Copyright (C) 2014, 2016, 2018, 2022, 2024 University College London Copyright (C) 2016, University of Hull Copyright (C) 2021, University of Pennsylvania This file is part of STIR. @@ -44,6 +44,7 @@ #include "stir/listmode/ListModeData_dummy.h" #include +#include #include #include "stir/recon_buildblock/ForwardProjectorByBinUsingProjMatrixByBin.h" @@ -324,6 +325,11 @@ PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBincache_lm_file = true; return cache_listmode_file(); } + else + { + this->cache_lm_file = false; + this->cache_size = 1000000; + } return Succeeded::yes; } @@ -353,7 +359,7 @@ PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin -Succeeded +bool PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin::load_listmode_cache_file( unsigned int file_id) { @@ -399,11 +405,11 @@ PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBinnum_cache_files; } template @@ -438,132 +444,120 @@ PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin -Succeeded -PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin::cache_listmode_file() +bool +PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin::read_listmode_batch(unsigned int ibatch) { - if (!this->recompute_cache && this->cache_lm_file) + double current_time = 0.; + if (ibatch == 0) + this->list_mode_data_sptr->reset(); + else + current_time = this->end_time_per_batch[ibatch - 1]; + + record_cache.clear(); + try { - warning("Looking for existing cache files such as \"" + this->get_cache_filename(0) + "\".\n" - + "We will be ignoring any time frame definitions as well as num_events_to_use!"); - // find how many cache files there are - this->num_cache_files = 0; - while (true) - { - if (!FilePath::exists(this->get_cache_filename(this->num_cache_files))) - break; - ++this->num_cache_files; - } - if (!this->num_cache_files) - error("No cache files found."); - return Succeeded::yes; // Stop here!!! + record_cache.reserve(this->cache_size); } - - this->num_cache_files = 0; - if (this->cache_lm_file) + catch (...) { - info("Listmode reconstruction: Creating cache...", 2); + error("Listmode: cannot allocate cache for " + std::to_string(this->cache_size) + " records. Reduce cache size."); + } - record_cache.clear(); - try + const shared_ptr record_sptr = this->list_mode_data_sptr->get_empty_record_sptr(); + + const double start_time = this->frame_defs.get_start_time(this->current_frame_num); + const double end_time = this->frame_defs.get_end_time(this->current_frame_num); + unsigned long int cached_events = 0; + + bool stop_caching = false; + + while (true) // Start for the current cache + { + if (this->list_mode_data_sptr->get_next_record(*record_sptr) == Succeeded::no) { - record_cache.reserve(this->cache_size); + stop_caching = true; + break; } - catch (...) + if (record_sptr->is_time()) { - error("Listmode: cannot allocate cache for " + std::to_string(this->cache_size) + " records. Reduce cache size."); + current_time = record_sptr->time().get_time_in_secs(); + if (this->do_time_frame && current_time >= end_time) + { + stop_caching = true; + break; // get out of while loop + } } - - this->list_mode_data_sptr->reset(); - const shared_ptr record_sptr = this->list_mode_data_sptr->get_empty_record_sptr(); - - const double start_time = this->frame_defs.get_start_time(this->current_frame_num); - const double end_time = this->frame_defs.get_end_time(this->current_frame_num); - double current_time = 0.; - unsigned long int cached_events = 0; - - bool stop_caching = false; - record_cache.reserve(this->cache_size); - - while (true) // keep caching across multiple files. + if (current_time < start_time) + continue; // skip + if (record_sptr->is_event() && record_sptr->event().is_prompt()) { - record_cache.clear(); - - while (true) // Start for the current cache + BinAndCorr tmp; + tmp.my_bin.set_bin_value(1.0); + record_sptr->event().get_bin(tmp.my_bin, *this->proj_data_info_sptr); + + if (tmp.my_bin.get_bin_value() != 1.0f || tmp.my_bin.segment_num() < this->proj_data_info_sptr->get_min_segment_num() + || tmp.my_bin.segment_num() > this->proj_data_info_sptr->get_max_segment_num() + || tmp.my_bin.tangential_pos_num() < this->proj_data_info_sptr->get_min_tangential_pos_num() + || tmp.my_bin.tangential_pos_num() > this->proj_data_info_sptr->get_max_tangential_pos_num() + || tmp.my_bin.axial_pos_num() < this->proj_data_info_sptr->get_min_axial_pos_num(tmp.my_bin.segment_num()) + || tmp.my_bin.axial_pos_num() > this->proj_data_info_sptr->get_max_axial_pos_num(tmp.my_bin.segment_num()) + || tmp.my_bin.timing_pos_num() < this->proj_data_info_sptr->get_min_tof_pos_num() + || tmp.my_bin.timing_pos_num() > this->proj_data_info_sptr->get_max_tof_pos_num()) { - if (this->list_mode_data_sptr->get_next_record(*record_sptr) == Succeeded::no) - { - stop_caching = true; - break; - } - if (record_sptr->is_time()) - { - current_time = record_sptr->time().get_time_in_secs(); - if (this->do_time_frame && current_time >= end_time) - { - stop_caching = true; - break; // get out of while loop - } - } - if (current_time < start_time) - continue; - if (record_sptr->is_event() && record_sptr->event().is_prompt()) - { - BinAndCorr tmp; - tmp.my_bin.set_bin_value(1.0); - record_sptr->event().get_bin(tmp.my_bin, *this->proj_data_info_sptr); - - if (tmp.my_bin.get_bin_value() != 1.0f - || tmp.my_bin.segment_num() < this->proj_data_info_sptr->get_min_segment_num() - || tmp.my_bin.segment_num() > this->proj_data_info_sptr->get_max_segment_num() - || tmp.my_bin.tangential_pos_num() < this->proj_data_info_sptr->get_min_tangential_pos_num() - || tmp.my_bin.tangential_pos_num() > this->proj_data_info_sptr->get_max_tangential_pos_num() - || tmp.my_bin.axial_pos_num() < this->proj_data_info_sptr->get_min_axial_pos_num(tmp.my_bin.segment_num()) - || tmp.my_bin.axial_pos_num() > this->proj_data_info_sptr->get_max_axial_pos_num(tmp.my_bin.segment_num()) - || tmp.my_bin.timing_pos_num() < this->proj_data_info_sptr->get_min_tof_pos_num() - || tmp.my_bin.timing_pos_num() > this->proj_data_info_sptr->get_max_tof_pos_num()) - { - continue; - } - try - { - record_cache.push_back(tmp); - ++cached_events; - } - catch (...) - { - // should never get here due to `reserve` statement above, but best to check... - error("Listmode: running out of memory for cache. Current size: " - + std::to_string(this->record_cache.size()) + " records"); - } + continue; + } + try + { + record_cache.push_back(tmp); + ++cached_events; + } + catch (...) + { + // should never get here due to `reserve` statement above, but best to check... + error("Listmode: running out of memory for cache. Current size: " + std::to_string(this->record_cache.size()) + + " records"); + } - if (record_cache.size() > 1 && record_cache.size() % 500000L == 0) - info(boost::format("Cached Prompt Events (this cache): %1% ") % record_cache.size()); + if (record_cache.size() > 1 && record_cache.size() % 500000L == 0) + info(boost::format("Read Prompt Events (this batch): %1% ") % record_cache.size(), 3); - if (this->num_events_to_use > 0) - if (cached_events >= static_cast(this->num_events_to_use)) - { - stop_caching = true; - break; - } + if (this->num_events_to_use > 0) + if (cached_events >= static_cast(this->num_events_to_use)) + { + stop_caching = true; + break; + } - if (record_cache.size() == this->cache_size) - break; // cache is full. go to next cache. - } - } + if (record_cache.size() == this->cache_size) + break; // cache is full. + } + } + if (this->end_time_per_batch.size() < (ibatch + 1)) + { + assert(this->end_time_per_batch.size() == ibatch); + this->end_time_per_batch.push_back(end_time); + } + else + { + if (std::abs(this->end_time_per_batch[ibatch] - end_time) > 0.0005) + error("Internal error in reading listmode files: end times do not match. Please raise a bug report"); + } - // add additive term to current cache - if (this->has_add) - { - info(boost::format("Caching Additive corrections for : %1% events.") % record_cache.size()); + info(boost::format("Loaded %1% prompts from list-mode file") % cached_events, 2); + + // add additive term to current cache + if (this->has_add) + { + info(boost::format("Caching Additive corrections for : %1% events.") % record_cache.size(), 2); #ifdef STIR_OPENMP # pragma omp parallel - { + { # pragma omp single - { - info("Caching add background with " + std::to_string(omp_get_num_threads()) + " threads", 2); - } - } + { + info("Caching add background with " + std::to_string(omp_get_num_threads()) + " threads", 2); + } + } #endif #ifdef STIR_OPENMP @@ -573,19 +567,19 @@ PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBinadditive_proj_data_sptr->get_min_segment_num(); - seg <= this->additive_proj_data_sptr->get_max_segment_num(); - ++seg) - for (int timing_pos_num = this->additive_proj_data_sptr->get_min_tof_pos_num(); - timing_pos_num <= this->additive_proj_data_sptr->get_max_tof_pos_num(); - ++timing_pos_num) + for (int seg = this->additive_proj_data_sptr->get_min_segment_num(); + seg <= this->additive_proj_data_sptr->get_max_segment_num(); + ++seg) + for (int timing_pos_num = this->additive_proj_data_sptr->get_min_tof_pos_num(); + timing_pos_num <= this->additive_proj_data_sptr->get_max_tof_pos_num(); + ++timing_pos_num) + { + const auto segment(this->additive_proj_data_sptr->get_segment_by_view(seg, timing_pos_num)); + + for (BinAndCorr& cur_bin : record_cache) + { + if (cur_bin.my_bin.segment_num() == seg) { - const auto segment(this->additive_proj_data_sptr->get_segment_by_view(seg, timing_pos_num)); - - for (BinAndCorr& cur_bin : record_cache) - { - if (cur_bin.my_bin.segment_num() == seg) - { #ifdef STIR_OPENMP # if _OPENMP >= 201012 # pragma omp atomic write @@ -593,26 +587,70 @@ PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBinnum_cache_files) == Succeeded::no) - { - error("Error writing cache file!"); - } - ++this->num_cache_files; +template +bool +PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin::load_listmode_batch(unsigned int ibatch) +{ + if (this->cache_lm_file) + { + return this->load_listmode_cache_file(ibatch); + } + else + { + return this->read_listmode_batch(ibatch); + } +} - if (stop_caching) +template +Succeeded +PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin::cache_listmode_file() +{ + if (!this->recompute_cache && this->cache_lm_file) + { + warning("Looking for existing cache files such as \"" + this->get_cache_filename(0) + "\".\n" + + "We will be ignoring any time frame definitions as well as num_events_to_use!"); + // find how many cache files there are + this->num_cache_files = 0; + while (true) + { + if (!FilePath::exists(this->get_cache_filename(this->num_cache_files))) break; + ++this->num_cache_files; } - info(boost::format("Cached Events: %1% ") % cached_events); - return Succeeded::yes; + if (!this->num_cache_files) + error("No cache files found."); + return Succeeded::yes; // Stop here!!! } - return Succeeded::no; + + assert(this->cache_lm_file); + + this->num_cache_files = 0; + + while (true) + { + info("Listmode reconstruction: Creating cache...", 2); + + bool stop_caching = this->read_listmode_batch(this->num_cache_files); + + if (write_listmode_cache_file(this->num_cache_files) == Succeeded::no) + { + error("Error writing cache file!"); + } + ++this->num_cache_files; + + if (stop_caching) + break; + } + return Succeeded::yes; } template @@ -738,145 +776,22 @@ PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin< "actual_compute_subset_gradient_without_penalty(): cannot subtract subset sensitivity because " "use_subset_sensitivities is false. This will result in an error in the gradient computation."); - if (this->cache_lm_file) - { - for (unsigned int icache = 0; icache < this->num_cache_files; ++icache) - { - load_listmode_cache_file(icache); - LM_distributable_computation(this->PM_sptr, - this->proj_data_info_sptr, - &gradient, - ¤t_estimate, - record_cache, - subset_num, - this->num_subsets, - this->has_add, - /* accumulate = */ icache != 0); - } - } - else + unsigned int icache = 0; + while (true) { - // list_mode_data_sptr->set_get_position(start_time); - // TODO implement function that will do this for a random time - - this->list_mode_data_sptr->reset(); - - const double start_time = this->frame_defs.get_start_time(this->current_frame_num); - const double end_time = this->frame_defs.get_end_time(this->current_frame_num); - - long num_used_events = 0; - const float max_quotient = 10000.F; - - double current_time = 0.; - - // need get_bin_value(), so currently need to cast to either of the 2 below (ugly. TODO) - shared_ptr add_from_stream_sptr; - shared_ptr add_in_mem_sptr; - - if (!is_null_ptr(this->additive_proj_data_sptr)) - { - add_in_mem_sptr = std::dynamic_pointer_cast(this->additive_proj_data_sptr); - if (is_null_ptr(add_in_mem_sptr)) - { - add_from_stream_sptr = std::dynamic_pointer_cast(this->additive_proj_data_sptr); - // TODO could create a ProjDataInMemory instead, but for now we give up. - if (is_null_ptr(add_from_stream_sptr)) - error("Additive projection data is in unsupported file format. You need to create an Interfile copy. sorry."); - } - } - - ProjMatrixElemsForOneBin proj_matrix_row; - gradient.fill(0); - shared_ptr record_sptr = this->list_mode_data_sptr->get_empty_record_sptr(); - ListRecord& record = *record_sptr; - - VectorWithOffset frame_start_positions(1, static_cast(this->frame_defs.get_num_frames())); - - while (true) - { - - if (this->list_mode_data_sptr->get_next_record(record) == Succeeded::no) - { - info("End of listmode file!", 2); - break; // get out of while loop - } - - if (record.is_time()) - { - current_time = record.time().get_time_in_secs(); - if (this->do_time_frame && current_time >= end_time) - { - break; // get out of while loop - } - } - - if (current_time < start_time) - continue; - - if (record.is_event() && record.event().is_prompt()) - { - Bin measured_bin; - measured_bin.set_bin_value(1.0f); - record.event().get_bin(measured_bin, *this->proj_data_info_sptr); - - if (measured_bin.get_bin_value() != 1.0f - || measured_bin.segment_num() < this->proj_data_info_sptr->get_min_segment_num() - || measured_bin.segment_num() > this->proj_data_info_sptr->get_max_segment_num() - || measured_bin.tangential_pos_num() < this->proj_data_info_sptr->get_min_tangential_pos_num() - || measured_bin.tangential_pos_num() > this->proj_data_info_sptr->get_max_tangential_pos_num() - || measured_bin.axial_pos_num() < this->proj_data_info_sptr->get_min_axial_pos_num(measured_bin.segment_num()) - || measured_bin.axial_pos_num() > this->proj_data_info_sptr->get_max_axial_pos_num(measured_bin.segment_num()) - || measured_bin.timing_pos_num() < this->proj_data_info_sptr->get_min_tof_pos_num() - || measured_bin.timing_pos_num() > this->proj_data_info_sptr->get_max_tof_pos_num()) - { - continue; - } - - measured_bin.set_bin_value(1.0f); - // If more than 1 subsets, check if the current bin belongs to the current. - bool in_subset = true; - if (this->num_subsets > 1) - { - Bin basic_bin = measured_bin; - this->PM_sptr->get_symmetries_ptr()->find_basic_bin(basic_bin); - in_subset = (subset_num == static_cast(basic_bin.view_num() % this->num_subsets)); - } - if (in_subset) - { - this->PM_sptr->get_proj_matrix_elems_for_one_bin(proj_matrix_row, measured_bin); - Bin fwd_bin; - fwd_bin.set_bin_value(0.0f); - proj_matrix_row.forward_project(fwd_bin, current_estimate); - // additive sinogram - if (!is_null_ptr(this->additive_proj_data_sptr)) - { - // TODO simplify once we don't need the casting for get_bin_value() anymore - const float add_value = add_in_mem_sptr ? add_in_mem_sptr->get_bin_value(measured_bin) - : add_from_stream_sptr->get_bin_value(measured_bin); - const float value = fwd_bin.get_bin_value() + add_value; - fwd_bin.set_bin_value(value); - } - - if (measured_bin.get_bin_value() <= max_quotient * fwd_bin.get_bin_value()) - { - const float measured_div_fwd = 1.0f / fwd_bin.get_bin_value(); - measured_bin.set_bin_value(measured_div_fwd); - proj_matrix_row.back_project(gradient, measured_bin); - } - } - - ++num_used_events; - - if (num_used_events % 200000L == 0) - info(boost::format("Used Events: %1% ") % num_used_events); - - // if we use event-count-based processing, see if we need to stop - if (this->num_events_to_use > 0) - if (num_used_events >= this->num_events_to_use) - break; - } - } - info(boost::format("Number of used events (for all subsets): %1%") % num_used_events); + bool stop = this->load_listmode_batch(icache); + LM_distributable_computation(this->PM_sptr, + this->proj_data_info_sptr, + &gradient, + ¤t_estimate, + record_cache, + subset_num, + this->num_subsets, + this->has_add, + /* accumulate = */ icache != 0); + ++icache; + if (stop) + break; } if (!add_sensitivity) From eb31e8d36be5740a75426bf9eccbf4d321f8897f Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Fri, 10 May 2024 23:37:08 +0100 Subject: [PATCH 23/78] use more local variables in LM_distributable_computation The code pre-allocated bins for every thread, but local variables are allocated on the stack, which should take no time. Removing th epre-allocations simplifies the code, and prepares us for re-use for Hessian calculation etc --- src/recon_buildblock/distributable.cxx | 66 ++++++++++---------------- 1 file changed, 24 insertions(+), 42 deletions(-) diff --git a/src/recon_buildblock/distributable.cxx b/src/recon_buildblock/distributable.cxx index d422a07239..800d9c1e12 100644 --- a/src/recon_buildblock/distributable.cxx +++ b/src/recon_buildblock/distributable.cxx @@ -622,17 +622,9 @@ LM_distributable_computation(const shared_ptr PM_sptr, std::vector>> local_output_image_sptrs; std::vector local_log_likelihoods; std::vector local_counts, local_count2s; - std::vector local_measured_bin, local_basic_bin, local_fwd_bin; std::vector local_row; - std::vector measured_div_fwd; #ifdef STIR_OPENMP -# pragma omp parallel shared(local_output_image_sptrs, \ - local_row, \ - local_log_likelihoods, \ - local_counts, \ - local_count2s, \ - local_measured_bin, \ - local_fwd_bin) +# pragma omp parallel shared(local_output_image_sptrs, local_row, local_log_likelihoods, local_counts, local_count2s) #endif // start of threaded section if openmp { @@ -644,10 +636,6 @@ LM_distributable_computation(const shared_ptr PM_sptr, // local_log_likelihoods.resize(omp_get_max_threads(), 0.); local_counts.resize(omp_get_max_threads(), 0); local_count2s.resize(omp_get_max_threads(), 0); - measured_div_fwd.resize(omp_get_max_threads(), 0.f); - local_measured_bin.resize(omp_get_max_threads(), Bin()); - local_basic_bin.resize(omp_get_max_threads(), Bin()); - local_fwd_bin.resize(omp_get_max_threads(), Bin()); local_row.resize(omp_get_max_threads(), ProjMatrixElemsForOneBin()); } @@ -659,65 +647,59 @@ LM_distributable_computation(const shared_ptr PM_sptr, // local_log_likelihoods.resize(omp_get_max_threads(), 0.); local_counts.resize(1, 0); local_count2s.resize(1, 0); - measured_div_fwd.resize(1, 0.f); - local_measured_bin.resize(1, Bin()); - local_basic_bin.resize(1, Bin()); - local_fwd_bin.resize(1, Bin()); local_row.resize(1, ProjMatrixElemsForOneBin()); } #endif - // Putting the Bins here I avoid rellocation. for (long int ievent = 0; ievent < record_ptr.size(); ++ievent) { + if (record_ptr.at(ievent).my_bin.get_bin_value() == 0.0f) // shouldn't happen really, but a check probably doesn't hurt + continue; + #ifdef STIR_OPENMP const int thread_num = omp_get_thread_num(); #else const int thread_num = 0; #endif - local_measured_bin[thread_num] = record_ptr.at(ievent).my_bin; + if (output_image_ptr != NULL) + { + if (is_null_ptr(local_output_image_sptrs[thread_num])) + local_output_image_sptrs[thread_num].reset(output_image_ptr->get_empty_copy()); + } - if (local_measured_bin[thread_num].get_bin_value() == 0.0f) - continue; + Bin measured_bin = record_ptr.at(ievent).my_bin; if (num_subsets > 1) { - local_basic_bin[thread_num] = local_measured_bin[thread_num]; - if (!PM_sptr->get_symmetries_ptr()->is_basic(local_measured_bin[thread_num])) - PM_sptr->get_symmetries_ptr()->find_basic_bin(local_basic_bin[thread_num]); + Bin basic_bin = measured_bin; + if (!PM_sptr->get_symmetries_ptr()->is_basic(measured_bin)) + PM_sptr->get_symmetries_ptr()->find_basic_bin(basic_bin); - if (subset_num != static_cast(local_basic_bin[thread_num].view_num() % num_subsets)) + if (subset_num != static_cast(basic_bin.view_num() % num_subsets)) { continue; } } - PM_sptr->get_proj_matrix_elems_for_one_bin(local_row[thread_num], local_measured_bin[thread_num]); + PM_sptr->get_proj_matrix_elems_for_one_bin(local_row[thread_num], measured_bin); - local_fwd_bin[thread_num].set_bin_value(0.0f); - local_row[thread_num].forward_project(local_fwd_bin[thread_num], *input_image_ptr); + Bin fwd_bin = measured_bin; + fwd_bin.set_bin_value(0.0f); + local_row[thread_num].forward_project(fwd_bin, *input_image_ptr); if (has_add) { - local_fwd_bin[thread_num].set_bin_value(local_fwd_bin[thread_num].get_bin_value() + record_ptr.at(ievent).my_corr); - } - - measured_div_fwd[thread_num] = 0.0; - - if (output_image_ptr != NULL) - { - if (is_null_ptr(local_output_image_sptrs[thread_num])) - local_output_image_sptrs[thread_num].reset(output_image_ptr->get_empty_copy()); + fwd_bin.set_bin_value(fwd_bin.get_bin_value() + record_ptr.at(ievent).my_corr); } - if (local_measured_bin[thread_num].get_bin_value() <= max_quotient * local_fwd_bin[thread_num].get_bin_value()) - measured_div_fwd[thread_num] - = local_measured_bin[thread_num].get_bin_value() / local_fwd_bin[thread_num].get_bin_value(); + float measured_div_fwd = 0.F; + if (measured_bin.get_bin_value() <= max_quotient * fwd_bin.get_bin_value()) + measured_div_fwd = measured_bin.get_bin_value() / fwd_bin.get_bin_value(); else continue; - local_measured_bin[thread_num].set_bin_value(measured_div_fwd[thread_num]); - local_row[thread_num].back_project(*local_output_image_sptrs[thread_num], local_measured_bin[thread_num]); + measured_bin.set_bin_value(measured_div_fwd); + local_row[thread_num].back_project(*local_output_image_sptrs[thread_num], measured_bin); } } #ifdef STIR_OPENMP From 45a102c1b34f168f6e1ea965c1ca3e605cee54bb Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sun, 12 May 2024 09:40:50 +0100 Subject: [PATCH 24/78] prepare LM_distributable function for Hessian - make list-mode loop into templated function - introduce separate functions for gradient and Hessian --- src/recon_buildblock/distributable.cxx | 134 +++++++++++++++++++------ 1 file changed, 102 insertions(+), 32 deletions(-) diff --git a/src/recon_buildblock/distributable.cxx b/src/recon_buildblock/distributable.cxx index 800d9c1e12..6eb092c6b5 100644 --- a/src/recon_buildblock/distributable.cxx +++ b/src/recon_buildblock/distributable.cxx @@ -595,16 +595,18 @@ distributable_computation(const shared_ptr& forward_proje % wall_clock_timer.value()); } +template void -LM_distributable_computation(const shared_ptr PM_sptr, - const shared_ptr& proj_data_info_sptr, - DiscretisedDensity<3, float>* output_image_ptr, - const DiscretisedDensity<3, float>* input_image_ptr, - const std::vector& record_ptr, - const int subset_num, - const int num_subsets, - const bool has_add, - const bool accumulate) +LM_distributable_computation_template(const shared_ptr PM_sptr, + const shared_ptr& proj_data_info_sptr, + DiscretisedDensity<3, float>* output_image_ptr, + const DiscretisedDensity<3, float>* input_image_ptr, + const std::vector& record_ptr, + const int subset_num, + const int num_subsets, + const bool has_add, + const bool accumulate, + CallBackT&& call_back) { CPUTimer CPU_timer; @@ -614,8 +616,6 @@ LM_distributable_computation(const shared_ptr PM_sptr, assert(!record_ptr.empty()); - const float max_quotient = 10000.F; - if (output_image_ptr != NULL && !accumulate) output_image_ptr->fill(0.F); @@ -650,9 +650,11 @@ LM_distributable_computation(const shared_ptr PM_sptr, local_row.resize(1, ProjMatrixElemsForOneBin()); } #endif - for (long int ievent = 0; ievent < record_ptr.size(); ++ievent) + // note: VC uses OpenMP 2.0, so need signed integer for loop + for (long int ievent = 0; ievent < static_cast(record_ptr.size()); ++ievent) { - if (record_ptr.at(ievent).my_bin.get_bin_value() == 0.0f) // shouldn't happen really, but a check probably doesn't hurt + auto& record = record_ptr.at(ievent); + if (record.my_bin.get_bin_value() == 0.0f) // shouldn't happen really, but a check probably doesn't hurt continue; #ifdef STIR_OPENMP @@ -667,7 +669,7 @@ LM_distributable_computation(const shared_ptr PM_sptr, local_output_image_sptrs[thread_num].reset(output_image_ptr->get_empty_copy()); } - Bin measured_bin = record_ptr.at(ievent).my_bin; + const Bin& measured_bin = record.my_bin; if (num_subsets > 1) { @@ -682,24 +684,11 @@ LM_distributable_computation(const shared_ptr PM_sptr, } PM_sptr->get_proj_matrix_elems_for_one_bin(local_row[thread_num], measured_bin); - - Bin fwd_bin = measured_bin; - fwd_bin.set_bin_value(0.0f); - local_row[thread_num].forward_project(fwd_bin, *input_image_ptr); - - if (has_add) - { - fwd_bin.set_bin_value(fwd_bin.get_bin_value() + record_ptr.at(ievent).my_corr); - } - - float measured_div_fwd = 0.F; - if (measured_bin.get_bin_value() <= max_quotient * fwd_bin.get_bin_value()) - measured_div_fwd = measured_bin.get_bin_value() / fwd_bin.get_bin_value(); - else - continue; - - measured_bin.set_bin_value(measured_div_fwd); - local_row[thread_num].back_project(*local_output_image_sptrs[thread_num], measured_bin); + call_back(*local_output_image_sptrs[thread_num], + measured_bin, + *input_image_ptr, + local_row[thread_num], + has_add ? record.my_corr : 0); } } #ifdef STIR_OPENMP @@ -719,4 +708,85 @@ LM_distributable_computation(const shared_ptr PM_sptr, % wall_clock_timer.value()); } +constexpr float max_quotient = 10000.F; + +inline void +LM_gradient(DiscretisedDensity<3, float>& output_image, + const Bin& measured_bin, + const DiscretisedDensity<3, float>& input_image, + const ProjMatrixElemsForOneBin& row, + const float add_term) +{ + Bin fwd_bin = measured_bin; + fwd_bin.set_bin_value(0.0f); + row.forward_project(fwd_bin, input_image); + + if (add_term) + fwd_bin.set_bin_value(fwd_bin.get_bin_value() + add_term); + + float measured_div_fwd = 0.F; + if (measured_bin.get_bin_value() <= max_quotient * fwd_bin.get_bin_value()) + measured_div_fwd = measured_bin.get_bin_value() / fwd_bin.get_bin_value(); + else + return; + + fwd_bin.set_bin_value(measured_div_fwd); + row.back_project(output_image, fwd_bin); +} + +/* Hessian + +\sum_e A_e^t (y_e/(A_e lambda+ c)^2 A_e x) +*/ +inline void +LM_Hessian(DiscretisedDensity<3, float>& output_image, + const Bin& measured_bin, + const DiscretisedDensity<3, float>& input_image, + const DiscretisedDensity<3, float>& rhs, + const ProjMatrixElemsForOneBin& row, + const float add_term) +{ + Bin fwd_bin = measured_bin; + fwd_bin.set_bin_value(0.0f); + row.forward_project(fwd_bin, input_image); + + if (add_term) + fwd_bin.set_bin_value(fwd_bin.get_bin_value() + add_term); + + if (measured_bin.get_bin_value() > max_quotient * fwd_bin.get_bin_value()) + return; + float measured_div_fwd2 = measured_bin.get_bin_value() / square(fwd_bin.get_bin_value()); + + fwd_bin.set_bin_value(0.0f); + row.forward_project(fwd_bin, rhs); + + if (fwd_bin.get_bin_value() == 0) + return; + + fwd_bin.set_bin_value(measured_div_fwd2 * fwd_bin.get_bin_value()); + row.back_project(output_image, fwd_bin); +} + +void +LM_distributable_computation(const shared_ptr PM_sptr, + const shared_ptr& proj_data_info_sptr, + DiscretisedDensity<3, float>* output_image_ptr, + const DiscretisedDensity<3, float>* input_image_ptr, + const std::vector& record_ptr, + const int subset_num, + const int num_subsets, + const bool has_add, + const bool accumulate) +{ + LM_distributable_computation_template(PM_sptr, + proj_data_info_sptr, + output_image_ptr, + input_image_ptr, + record_ptr, + subset_num, + num_subsets, + has_add, + accumulate, + LM_gradient); +} END_NAMESPACE_STIR From 7286488d2a2bc7987d85f89c49216770933f7d37 Mon Sep 17 00:00:00 2001 From: NikEfth Date: Thu, 16 May 2024 18:26:04 -0400 Subject: [PATCH 25/78] minor clean-up and clarifications --- .gitignore | 1 + .../stir/recon_buildblock/ProjMatrixByBin.inl | 2 +- .../ProjMatrixElemsForOneBin.h | 8 ++-- src/recon_buildblock/distributable.cxx | 48 +++++++++---------- 4 files changed, 29 insertions(+), 30 deletions(-) diff --git a/.gitignore b/.gitignore index d3689c4f86..075f775d2a 100644 --- a/.gitignore +++ b/.gitignore @@ -57,6 +57,7 @@ install_manifest.txt ~$* *~ *.bak +\#*# # Ignore Visual Studio User-specific files *.suo diff --git a/src/include/stir/recon_buildblock/ProjMatrixByBin.inl b/src/include/stir/recon_buildblock/ProjMatrixByBin.inl index 70e4ac98b4..da964e4eb1 100644 --- a/src/include/stir/recon_buildblock/ProjMatrixByBin.inl +++ b/src/include/stir/recon_buildblock/ProjMatrixByBin.inl @@ -152,7 +152,7 @@ ProjMatrixByBin::get_tof_value(const float d1, const float d2) const { if (this->tof_enabled) { - return 0.5f * (erf_interpolation(d2_n) - erf_interpolation(d1_n)); + return static_cast(0.5 * (erf_interpolation(d2_n) - erf_interpolation(d1_n))); } else { diff --git a/src/include/stir/recon_buildblock/ProjMatrixElemsForOneBin.h b/src/include/stir/recon_buildblock/ProjMatrixElemsForOneBin.h index a006b02f4e..a619096ee4 100644 --- a/src/include/stir/recon_buildblock/ProjMatrixElemsForOneBin.h +++ b/src/include/stir/recon_buildblock/ProjMatrixElemsForOneBin.h @@ -178,14 +178,14 @@ class ProjMatrixElemsForOneBin //******************** projection operations ********************// - //! back project a single bin + //! back project a single bin (accumulates) void back_project(DiscretisedDensity<3, float>&, const Bin&) const; - //! forward project into a single bin + //! forward project into a single bin (accumulates) void forward_project(Bin&, const DiscretisedDensity<3, float>&) const; - //! back project related bins + //! back project related bins (accumulates) void back_project(DiscretisedDensity<3, float>&, const RelatedBins&) const; - //! forward project related bins + //! forward project related bins (accumulates) void forward_project(RelatedBins&, const DiscretisedDensity<3, float>&) const; private: diff --git a/src/recon_buildblock/distributable.cxx b/src/recon_buildblock/distributable.cxx index 6eb092c6b5..c2f776a531 100644 --- a/src/recon_buildblock/distributable.cxx +++ b/src/recon_buildblock/distributable.cxx @@ -685,10 +685,10 @@ LM_distributable_computation_template(const shared_ptr PM_sptr, PM_sptr->get_proj_matrix_elems_for_one_bin(local_row[thread_num], measured_bin); call_back(*local_output_image_sptrs[thread_num], - measured_bin, - *input_image_ptr, local_row[thread_num], - has_add ? record.my_corr : 0); + has_add ? record.my_corr : 0.F, + measured_bin, + *input_image_ptr); } } #ifdef STIR_OPENMP @@ -710,25 +710,25 @@ LM_distributable_computation_template(const shared_ptr PM_sptr, constexpr float max_quotient = 10000.F; +/* gradient without the sensitivity term + +\sum_e A_e^t (y_e/(A_e lambda+ c)) +*/ inline void LM_gradient(DiscretisedDensity<3, float>& output_image, - const Bin& measured_bin, - const DiscretisedDensity<3, float>& input_image, const ProjMatrixElemsForOneBin& row, - const float add_term) + const float add_term, + const Bin& measured_bin, + const DiscretisedDensity<3, float>& input_image) { Bin fwd_bin = measured_bin; fwd_bin.set_bin_value(0.0f); row.forward_project(fwd_bin, input_image); + const auto fwd = fwd_bin.get_bin_value() + add_term; - if (add_term) - fwd_bin.set_bin_value(fwd_bin.get_bin_value() + add_term); - - float measured_div_fwd = 0.F; - if (measured_bin.get_bin_value() <= max_quotient * fwd_bin.get_bin_value()) - measured_div_fwd = measured_bin.get_bin_value() / fwd_bin.get_bin_value(); - else - return; + if (measured_bin.get_bin_value() > max_quotient * fwd) + return; // cancel singularity + const auto measured_div_fwd = measured_bin.get_bin_value() / fwd; fwd_bin.set_bin_value(measured_div_fwd); row.back_project(output_image, fwd_bin); @@ -736,30 +736,28 @@ LM_gradient(DiscretisedDensity<3, float>& output_image, /* Hessian -\sum_e A_e^t (y_e/(A_e lambda+ c)^2 A_e x) +\sum_e A_e^t (y_e/(A_e lambda+ c)^2 A_e rhs) */ inline void LM_Hessian(DiscretisedDensity<3, float>& output_image, + const ProjMatrixElemsForOneBin& row, + const float add_term, const Bin& measured_bin, const DiscretisedDensity<3, float>& input_image, - const DiscretisedDensity<3, float>& rhs, - const ProjMatrixElemsForOneBin& row, - const float add_term) + const DiscretisedDensity<3, float>& rhs) { Bin fwd_bin = measured_bin; fwd_bin.set_bin_value(0.0f); row.forward_project(fwd_bin, input_image); + const auto fwd = fwd_bin.get_bin_value() + add_term; - if (add_term) - fwd_bin.set_bin_value(fwd_bin.get_bin_value() + add_term); - - if (measured_bin.get_bin_value() > max_quotient * fwd_bin.get_bin_value()) - return; - float measured_div_fwd2 = measured_bin.get_bin_value() / square(fwd_bin.get_bin_value()); + if (measured_bin.get_bin_value() > max_quotient * fwd) + return; // cancel singularity + const auto measured_div_fwd2 = measured_bin.get_bin_value() / square(fwd); + // forward project rhs fwd_bin.set_bin_value(0.0f); row.forward_project(fwd_bin, rhs); - if (fwd_bin.get_bin_value() == 0) return; From e1438a89c9437ac54c665292f9c7ce1c6f3bd037 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sun, 12 May 2024 12:23:03 +0100 Subject: [PATCH 26/78] LM_listmode_function: implement Hessian and move functions - move LM_distributable_function implementation to .txx as it now uses a template for the callback - implement actual_accumulate_Hessian... for LM - try to implement accumulation of log-likelihood WARNING: LM_distributable_computation is now generic and incompatible with the previous one (which was only for the gradient) Hessian function and log-likelihood value are currently untested/ --- ...orMeanAndListModeDataWithProjMatrixByBin.h | 16 +- .../stir/recon_buildblock/distributable.h | 7 +- .../stir/recon_buildblock/distributable.txx | 167 +++++++++++++++ ...MeanAndListModeDataWithProjMatrixByBin.cxx | 193 +++++++++++++++-- src/recon_buildblock/distributable.cxx | 196 +----------------- 5 files changed, 366 insertions(+), 213 deletions(-) create mode 100644 src/include/stir/recon_buildblock/distributable.txx diff --git a/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.h b/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.h index aaa581f240..1bd095f150 100644 --- a/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.h +++ b/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.h @@ -79,12 +79,16 @@ class PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByB \warning If add_sensitivity = false and use_subset_sensitivities = false will return an error because the gradient will not be correct. Try use_subset_sensitivities = true. */ - void actual_compute_subset_gradient_without_penalty(TargetT& gradient, const TargetT& current_estimate, const int subset_num, const bool add_sensitivity) override; + Succeeded actual_accumulate_sub_Hessian_times_input_without_penalty(TargetT& output, + const TargetT& current_image_estimate, + const TargetT& input, + const int subset_num) const override; + TargetT* construct_target_ptr() const override; int set_num_subsets(const int new_num_subsets) override; @@ -151,7 +155,7 @@ class PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByB //! Cache of the current "batch" in the listmode file /*! \todo Move this higher-up in the hierarchy as it doesn't depend on ProjMatrixByBin */ - std::vector record_cache; + mutable std::vector record_cache; //! This function loads the next "batch" of data from the listmode file. /*! @@ -161,7 +165,7 @@ class PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByB \return \c true if there are no more events to read after this call, \c false otherwise \todo Move this function higher-up in the hierarchy as it doesn't depend on ProjMatrixByBin */ - bool load_listmode_batch(unsigned int ibatch); + bool load_listmode_batch(unsigned int ibatch) const; //! This function reads the next "batch" of data from the listmode file. /*! @@ -174,18 +178,18 @@ class PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByB \todo Move this function higher-up in the hierarchy as it doesn't depend on ProjMatrixByBin \warning This function has to be called in sequence. */ - bool read_listmode_batch(unsigned int ibatch); + bool read_listmode_batch(unsigned int ibatch) const; //! This function caches the list-mode batches to file. It is run during set_up() /*! \todo Move this function higher-up in the hierarchy as it doesn't depend on ProjMatrixByBin */ Succeeded cache_listmode_file(); //! Reads the "batch" of data from the cache - bool load_listmode_cache_file(unsigned int file_id); + bool load_listmode_cache_file(unsigned int file_id) const; Succeeded write_listmode_cache_file(unsigned int file_id) const; unsigned int num_cache_files; - std::vector end_time_per_batch; + mutable std::vector end_time_per_batch; }; END_NAMESPACE_STIR diff --git a/src/include/stir/recon_buildblock/distributable.h b/src/include/stir/recon_buildblock/distributable.h index 7377f74425..84da1f21cb 100644 --- a/src/include/stir/recon_buildblock/distributable.h +++ b/src/include/stir/recon_buildblock/distributable.h @@ -188,7 +188,10 @@ void distributable_computation(const shared_ptr& forward_ \param has_add if \c true, the additive term in \c record_cache is taken into account \param accumulate if \c true, add to \c output_image_ptr, otherwise fill it with zeroes before doing anything. + \param double_out_ptr accumulated value (for every event) computed by the call-back, unless the pointer is zero + \param call_back !*/ +template void LM_distributable_computation(const shared_ptr PM_sptr, const shared_ptr& proj_data_info_sptr, DiscretisedDensity<3, float>* output_image_ptr, @@ -197,7 +200,9 @@ void LM_distributable_computation(const shared_ptr PM_sptr, const int subset_num, const int num_subsets, const bool has_add, - const bool accumulate); + const bool accumulate, + double* double_out_ptr, + CallBackT&& call_back); /*! \name Tag-names currently used by stir::distributable_computation and related functions \ingroup distributable diff --git a/src/include/stir/recon_buildblock/distributable.txx b/src/include/stir/recon_buildblock/distributable.txx new file mode 100644 index 0000000000..8dbbaa8406 --- /dev/null +++ b/src/include/stir/recon_buildblock/distributable.txx @@ -0,0 +1,167 @@ +/* + Copyright (C) 2024 University College London + Copyright (C) 2020, 2022, Univeristy of Pennsylvania + This file is part of STIR. + + SPDX-License-Identifier: Apache-2.0 AND License-ref-PARAPET-license + + See STIR/LICENSE.txt for details +*/ +/*! + + \file + \ingroup distributable + + \brief Implementation of stir::LM_distributable_computation() and related functions + + \author Nikos Efthimiou + \author Kris Thielemans +*/ +#include "stir/shared_ptr.h" +#include "stir/recon_buildblock/distributable.h" +#include "stir/DiscretisedDensity.h" +#include "stir/CPUTimer.h" +#include "stir/HighResWallClockTimer.h" +#include "stir/is_null_ptr.h" +#include "stir/info.h" +#include + +#include "stir/recon_buildblock/ProjMatrixByBin.h" +#include "stir/recon_buildblock/ProjMatrixElemsForOneBin.h" +#include "stir/Bin.h" + +#include "stir/num_threads.h" + +START_NAMESPACE_STIR + +template +void +LM_distributable_computation(const shared_ptr PM_sptr, + const shared_ptr& proj_data_info_sptr, + DiscretisedDensity<3, float>* output_image_ptr, + const DiscretisedDensity<3, float>* input_image_ptr, + const std::vector& record_ptr, + const int subset_num, + const int num_subsets, + const bool has_add, + const bool accumulate, + double* double_out_ptr, + CallBackT&& call_back) +{ + + CPUTimer CPU_timer; + CPU_timer.start(); + HighResWallClockTimer wall_clock_timer; + wall_clock_timer.start(); + + assert(!record_ptr.empty()); + + if (output_image_ptr != NULL && !accumulate) + output_image_ptr->fill(0.F); + + std::vector>> local_output_image_sptrs; + std::vector local_double_outs; + std::vector local_double_out_ptrs; + std::vector local_counts, local_count2s; + std::vector local_row; +#ifdef STIR_OPENMP +# pragma omp parallel shared(local_output_image_sptrs, local_row, local_double_outs, local_counts, local_count2s) +#endif + // start of threaded section if openmp + { +#ifdef STIR_OPENMP +# pragma omp single + { + info("Listmode gradient calculation: starting loop with " + std::to_string(omp_get_num_threads()) + " threads", 2); + local_output_image_sptrs.resize(omp_get_max_threads(), shared_ptr>()); + local_double_out_ptrs.resize(omp_get_max_threads(), 0); + if (double_out_ptr) + { + local_double_outs.resize(omp_get_max_threads(), 0.); + for (unsigned t = 0; t < omp_get_max_threads(); ++t) + local_double_out_ptrs[t] = &local_double_outs[t]; + } + local_counts.resize(omp_get_max_threads(), 0); + local_count2s.resize(omp_get_max_threads(), 0); + local_row.resize(omp_get_max_threads(), ProjMatrixElemsForOneBin()); + } + +# pragma omp for schedule(dynamic) +#else + { + info("Listmode gradient calculation: starting loop with 1 thread", 2); + local_output_image_sptrs.resize(1, shared_ptr>()); + local_double_outs.resize(1, double_out_ptr); + local_counts.resize(1, 0); + local_count2s.resize(1, 0); + local_row.resize(1, ProjMatrixElemsForOneBin()); + } +#endif + // note: VC uses OpenMP 2.0, so need signed integer for loop + for (long int ievent = 0; ievent < static_cast(record_ptr.size()); ++ievent) + { + auto& record = record_ptr.at(ievent); + if (record.my_bin.get_bin_value() == 0.0f) // shouldn't happen really, but a check probably doesn't hurt + continue; + +#ifdef STIR_OPENMP + const int thread_num = omp_get_thread_num(); +#else + const int thread_num = 0; +#endif + + if (output_image_ptr != NULL) + { + if (is_null_ptr(local_output_image_sptrs[thread_num])) + local_output_image_sptrs[thread_num].reset(output_image_ptr->get_empty_copy()); + } + + const Bin& measured_bin = record.my_bin; + + if (num_subsets > 1) + { + Bin basic_bin = measured_bin; + if (!PM_sptr->get_symmetries_ptr()->is_basic(measured_bin)) + PM_sptr->get_symmetries_ptr()->find_basic_bin(basic_bin); + + if (subset_num != static_cast(basic_bin.view_num() % num_subsets)) + { + continue; + } + } + + PM_sptr->get_proj_matrix_elems_for_one_bin(local_row[thread_num], measured_bin); + call_back(*local_output_image_sptrs[thread_num], + local_row[thread_num], + has_add ? record.my_corr : 0.F, + measured_bin, + *input_image_ptr, + local_double_out_ptrs[thread_num]); + } + } +#ifdef STIR_OPENMP + // flatten data constructed by threads + { + if (double_out_ptr != NULL) + { + for (int i = 0; i < static_cast(local_double_outs.size()); ++i) + *double_out_ptr += local_double_outs[i]; // accumulate all (as they were initialised to zero) + } + // count += std::accumulate(local_counts.begin(), local_counts.end(), 0); + // count2 += std::accumulate(local_count2s.begin(), local_count2s.end(), 0); + + if (output_image_ptr != NULL) + { + for (int i = 0; i < static_cast(local_output_image_sptrs.size()); ++i) + if (!is_null_ptr(local_output_image_sptrs[i])) // only accumulate if a thread filled something in + *output_image_ptr += *(local_output_image_sptrs[i]); + } + } +#endif + CPU_timer.stop(); + wall_clock_timer.stop(); + info(boost::format("Computation times for distributable_computation, CPU %1%s, wall-clock %2%s") % CPU_timer.value() + % wall_clock_timer.value()); +} + +END_NAMESPACE_STIR diff --git a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx index a60dbd3e26..6d2379d538 100644 --- a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx +++ b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx @@ -39,6 +39,7 @@ #include "stir/FilePath.h" #include #include +#include #include #include "stir/stream.h" #include "stir/listmode/ListModeData_dummy.h" @@ -55,6 +56,7 @@ #include "stir/recon_buildblock/PresmoothingForwardProjectorByBin.h" #include "stir/recon_buildblock/PostsmoothingBackProjectorByBin.h" +#include "stir/recon_buildblock/distributable.txx" #ifdef STIR_MPI # include "stir/recon_buildblock/distributed_functions.h" #endif @@ -361,7 +363,7 @@ PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin bool PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin::load_listmode_cache_file( - unsigned int file_id) + unsigned int file_id) const { FilePath icache(this->get_cache_filename(file_id), false); @@ -445,7 +447,8 @@ PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin bool -PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin::read_listmode_batch(unsigned int ibatch) +PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin::read_listmode_batch( + unsigned int ibatch) const { double current_time = 0.; if (ibatch == 0) @@ -598,7 +601,8 @@ PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin bool -PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin::load_listmode_batch(unsigned int ibatch) +PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin::load_listmode_batch( + unsigned int ibatch) const { if (this->cache_lm_file) { @@ -761,6 +765,126 @@ PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBintarget_parameter_parser.create(this->get_input_data()); } +constexpr float max_quotient = 10000.F; + +/* gradient without the sensitivity term + +\sum_e A_e^t (y_e/(A_e lambda+ c)) +*/ +inline void +LM_gradient(DiscretisedDensity<3, float>& output_image, + const ProjMatrixElemsForOneBin& row, + const float add_term, + const Bin& measured_bin, + const DiscretisedDensity<3, float>& input_image, + double* value_ptr) +{ + Bin fwd_bin = measured_bin; + fwd_bin.set_bin_value(0.0f); + row.forward_project(fwd_bin, input_image); + const auto fwd = fwd_bin.get_bin_value() + add_term; + + if (measured_bin.get_bin_value() > max_quotient * fwd) + { + // cancel singularity + if (value_ptr) + { + const auto num = measured_bin.get_bin_value(); + *value_ptr -= num * log(double(num / max_quotient)); + return; + } + } + const auto measured_div_fwd = measured_bin.get_bin_value() / fwd; + + fwd_bin.set_bin_value(measured_div_fwd); + row.back_project(output_image, fwd_bin); + if (value_ptr) + *value_ptr -= measured_bin.get_bin_value() * log(double(fwd)); +} + +/* Hessian + +\sum_e A_e^t (y_e/(A_e lambda+ c)^2 A_e rhs) +*/ +inline void +LM_Hessian(DiscretisedDensity<3, float>& output_image, + const ProjMatrixElemsForOneBin& row, + const float add_term, + const Bin& measured_bin, + const DiscretisedDensity<3, float>& input_image, + const DiscretisedDensity<3, float>& rhs) +{ + Bin fwd_bin = measured_bin; + fwd_bin.set_bin_value(0.0f); + row.forward_project(fwd_bin, input_image); + const auto fwd = fwd_bin.get_bin_value() + add_term; + + if (measured_bin.get_bin_value() > max_quotient * fwd) + return; // cancel singularity + const auto measured_div_fwd2 = measured_bin.get_bin_value() / square(fwd); + + // forward project rhs + fwd_bin.set_bin_value(0.0f); + row.forward_project(fwd_bin, rhs); + if (fwd_bin.get_bin_value() == 0) + return; + + fwd_bin.set_bin_value(measured_div_fwd2 * fwd_bin.get_bin_value()); + row.back_project(output_image, fwd_bin); +} + +void +LM_gradient_distributable_computation(const shared_ptr PM_sptr, + const shared_ptr& proj_data_info_sptr, + DiscretisedDensity<3, float>* output_image_ptr, + const DiscretisedDensity<3, float>* input_image_ptr, + const std::vector& record_ptr, + const int subset_num, + const int num_subsets, + const bool has_add, + const bool accumulate, + double* value_ptr) +{ + LM_distributable_computation(PM_sptr, + proj_data_info_sptr, + output_image_ptr, + input_image_ptr, + record_ptr, + subset_num, + num_subsets, + has_add, + accumulate, + value_ptr, + LM_gradient); +} + +void +LM_Hessian_distributable_computation(const shared_ptr PM_sptr, + const shared_ptr& proj_data_info_sptr, + DiscretisedDensity<3, float>* output_image_ptr, + const DiscretisedDensity<3, float>* input_image_ptr, + const DiscretisedDensity<3, float>* rhs_ptr, + const std::vector& record_ptr, + const int subset_num, + const int num_subsets, + const bool has_add, + const bool accumulate) +{ + using namespace std::placeholders; + auto H_func = std::bind(LM_Hessian, _1, _2, _3, _4, _5, std::cref(*rhs_ptr)); + LM_distributable_computation(PM_sptr, + proj_data_info_sptr, + output_image_ptr, + input_image_ptr, + record_ptr, + subset_num, + num_subsets, + has_add, + /* accumulate = */ true, + nullptr, + H_func); +} + template void PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin< @@ -780,15 +904,16 @@ PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin< while (true) { bool stop = this->load_listmode_batch(icache); - LM_distributable_computation(this->PM_sptr, - this->proj_data_info_sptr, - &gradient, - ¤t_estimate, - record_cache, - subset_num, - this->num_subsets, - this->has_add, - /* accumulate = */ icache != 0); + LM_gradient_distributable_computation(this->PM_sptr, + this->proj_data_info_sptr, + &gradient, + ¤t_estimate, + record_cache, + subset_num, + this->num_subsets, + this->has_add, + /* accumulate = */ icache != 0, + nullptr); ++icache; if (stop) break; @@ -810,6 +935,50 @@ PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin< } } +template +Succeeded +PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin< + TargetT>::actual_accumulate_sub_Hessian_times_input_without_penalty(TargetT& output, + const TargetT& current_estimate, + const TargetT& rhs, + const int subset_num) const +{ + { // check characteristics + + std::string explanation; + if (!output.has_same_characteristics(this->get_sensitivity(), explanation)) + { + error("PoissonLogLikelihoodWithLinearModelForMeanAndListModeData:\n" + "sensitivity and output for add_multiplication_with_approximate_Hessian_without_penalty\n" + "should have the same characteristics.\n%s", + explanation.c_str()); + return Succeeded::no; + } + } + assert(subset_num >= 0); + assert(subset_num < this->num_subsets); + + unsigned int icache = 0; + while (true) + { + bool stop = this->load_listmode_batch(icache); + LM_Hessian_distributable_computation(this->PM_sptr, + this->proj_data_info_sptr, + &output, + ¤t_estimate, + &rhs, + record_cache, + subset_num, + this->num_subsets, + this->has_add, + /* accumulate = */ icache != 0); + ++icache; + if (stop) + break; + } + return Succeeded::yes; +} + #ifdef _MSC_VER // prevent warning message on instantiation of abstract class # pragma warning(disable : 4661) diff --git a/src/recon_buildblock/distributable.cxx b/src/recon_buildblock/distributable.cxx index c2f776a531..38cbae2f4e 100644 --- a/src/recon_buildblock/distributable.cxx +++ b/src/recon_buildblock/distributable.cxx @@ -1,8 +1,8 @@ /* Copyright (C) 2000 PARAPET partners Copyright (C) 2000 - 2011, Hammersmith Imanet Ltd - Copyright (C) 2013-2014, University College London - Copyright (C) 2022, Univeristy of Pennsylvania + Copyright (C) 2013-2014, 2017-2022, 2024 University College London + Copyright (C) 2020, 2022, Univeristy of Pennsylvania This file is part of STIR. SPDX-License-Identifier: Apache-2.0 AND License-ref-PARAPET-license @@ -595,196 +595,4 @@ distributable_computation(const shared_ptr& forward_proje % wall_clock_timer.value()); } -template -void -LM_distributable_computation_template(const shared_ptr PM_sptr, - const shared_ptr& proj_data_info_sptr, - DiscretisedDensity<3, float>* output_image_ptr, - const DiscretisedDensity<3, float>* input_image_ptr, - const std::vector& record_ptr, - const int subset_num, - const int num_subsets, - const bool has_add, - const bool accumulate, - CallBackT&& call_back) -{ - - CPUTimer CPU_timer; - CPU_timer.start(); - HighResWallClockTimer wall_clock_timer; - wall_clock_timer.start(); - - assert(!record_ptr.empty()); - - if (output_image_ptr != NULL && !accumulate) - output_image_ptr->fill(0.F); - - std::vector>> local_output_image_sptrs; - std::vector local_log_likelihoods; - std::vector local_counts, local_count2s; - std::vector local_row; -#ifdef STIR_OPENMP -# pragma omp parallel shared(local_output_image_sptrs, local_row, local_log_likelihoods, local_counts, local_count2s) -#endif - // start of threaded section if openmp - { -#ifdef STIR_OPENMP -# pragma omp single - { - info("Listmode gradient calculation: starting loop with " + std::to_string(omp_get_num_threads()) + " threads", 2); - local_output_image_sptrs.resize(omp_get_max_threads(), shared_ptr>()); - // local_log_likelihoods.resize(omp_get_max_threads(), 0.); - local_counts.resize(omp_get_max_threads(), 0); - local_count2s.resize(omp_get_max_threads(), 0); - local_row.resize(omp_get_max_threads(), ProjMatrixElemsForOneBin()); - } - -# pragma omp for schedule(dynamic) -#else - { - info("Listmode gradient calculation: starting loop with 1 thread", 2); - local_output_image_sptrs.resize(1, shared_ptr>()); - // local_log_likelihoods.resize(omp_get_max_threads(), 0.); - local_counts.resize(1, 0); - local_count2s.resize(1, 0); - local_row.resize(1, ProjMatrixElemsForOneBin()); - } -#endif - // note: VC uses OpenMP 2.0, so need signed integer for loop - for (long int ievent = 0; ievent < static_cast(record_ptr.size()); ++ievent) - { - auto& record = record_ptr.at(ievent); - if (record.my_bin.get_bin_value() == 0.0f) // shouldn't happen really, but a check probably doesn't hurt - continue; - -#ifdef STIR_OPENMP - const int thread_num = omp_get_thread_num(); -#else - const int thread_num = 0; -#endif - - if (output_image_ptr != NULL) - { - if (is_null_ptr(local_output_image_sptrs[thread_num])) - local_output_image_sptrs[thread_num].reset(output_image_ptr->get_empty_copy()); - } - - const Bin& measured_bin = record.my_bin; - - if (num_subsets > 1) - { - Bin basic_bin = measured_bin; - if (!PM_sptr->get_symmetries_ptr()->is_basic(measured_bin)) - PM_sptr->get_symmetries_ptr()->find_basic_bin(basic_bin); - - if (subset_num != static_cast(basic_bin.view_num() % num_subsets)) - { - continue; - } - } - - PM_sptr->get_proj_matrix_elems_for_one_bin(local_row[thread_num], measured_bin); - call_back(*local_output_image_sptrs[thread_num], - local_row[thread_num], - has_add ? record.my_corr : 0.F, - measured_bin, - *input_image_ptr); - } - } -#ifdef STIR_OPENMP - // flatten data constructed by threads - { - if (output_image_ptr != NULL) - { - for (int i = 0; i < static_cast(local_output_image_sptrs.size()); ++i) - if (!is_null_ptr(local_output_image_sptrs[i])) // only accumulate if a thread filled something in - *output_image_ptr += *(local_output_image_sptrs[i]); - } - } -#endif - CPU_timer.stop(); - wall_clock_timer.stop(); - info(boost::format("Computation times for distributable_computation, CPU %1%s, wall-clock %2%s") % CPU_timer.value() - % wall_clock_timer.value()); -} - -constexpr float max_quotient = 10000.F; - -/* gradient without the sensitivity term - -\sum_e A_e^t (y_e/(A_e lambda+ c)) -*/ -inline void -LM_gradient(DiscretisedDensity<3, float>& output_image, - const ProjMatrixElemsForOneBin& row, - const float add_term, - const Bin& measured_bin, - const DiscretisedDensity<3, float>& input_image) -{ - Bin fwd_bin = measured_bin; - fwd_bin.set_bin_value(0.0f); - row.forward_project(fwd_bin, input_image); - const auto fwd = fwd_bin.get_bin_value() + add_term; - - if (measured_bin.get_bin_value() > max_quotient * fwd) - return; // cancel singularity - const auto measured_div_fwd = measured_bin.get_bin_value() / fwd; - - fwd_bin.set_bin_value(measured_div_fwd); - row.back_project(output_image, fwd_bin); -} - -/* Hessian - -\sum_e A_e^t (y_e/(A_e lambda+ c)^2 A_e rhs) -*/ -inline void -LM_Hessian(DiscretisedDensity<3, float>& output_image, - const ProjMatrixElemsForOneBin& row, - const float add_term, - const Bin& measured_bin, - const DiscretisedDensity<3, float>& input_image, - const DiscretisedDensity<3, float>& rhs) -{ - Bin fwd_bin = measured_bin; - fwd_bin.set_bin_value(0.0f); - row.forward_project(fwd_bin, input_image); - const auto fwd = fwd_bin.get_bin_value() + add_term; - - if (measured_bin.get_bin_value() > max_quotient * fwd) - return; // cancel singularity - const auto measured_div_fwd2 = measured_bin.get_bin_value() / square(fwd); - - // forward project rhs - fwd_bin.set_bin_value(0.0f); - row.forward_project(fwd_bin, rhs); - if (fwd_bin.get_bin_value() == 0) - return; - - fwd_bin.set_bin_value(measured_div_fwd2 * fwd_bin.get_bin_value()); - row.back_project(output_image, fwd_bin); -} - -void -LM_distributable_computation(const shared_ptr PM_sptr, - const shared_ptr& proj_data_info_sptr, - DiscretisedDensity<3, float>* output_image_ptr, - const DiscretisedDensity<3, float>* input_image_ptr, - const std::vector& record_ptr, - const int subset_num, - const int num_subsets, - const bool has_add, - const bool accumulate) -{ - LM_distributable_computation_template(PM_sptr, - proj_data_info_sptr, - output_image_ptr, - input_image_ptr, - record_ptr, - subset_num, - num_subsets, - has_add, - accumulate, - LM_gradient); -} END_NAMESPACE_STIR From 60445db23ffd410542e60c676802d8832394311c Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sun, 12 May 2024 14:17:42 +0100 Subject: [PATCH 27/78] fix error when OpenMP is not present --- src/include/stir/recon_buildblock/distributable.txx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/include/stir/recon_buildblock/distributable.txx b/src/include/stir/recon_buildblock/distributable.txx index 8dbbaa8406..0e6e469f0c 100644 --- a/src/include/stir/recon_buildblock/distributable.txx +++ b/src/include/stir/recon_buildblock/distributable.txx @@ -91,7 +91,7 @@ LM_distributable_computation(const shared_ptr PM_sptr, { info("Listmode gradient calculation: starting loop with 1 thread", 2); local_output_image_sptrs.resize(1, shared_ptr>()); - local_double_outs.resize(1, double_out_ptr); + local_double_out_ptrs.resize(1, double_out_ptr); local_counts.resize(1, 0); local_count2s.resize(1, 0); local_row.resize(1, ProjMatrixElemsForOneBin()); From 74870395da34b5c7058947b3ee97dc32ec0297d6 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Mon, 13 May 2024 07:42:16 +0100 Subject: [PATCH 28/78] Add compute_gradient* (as opposed to only subset) Also expand test to use all subsets, make it slower of course. --- documentation/release_6.1.htm | 9 ++++ .../GeneralisedObjectiveFunction.h | 27 ++++++++-- .../GeneralisedObjectiveFunction.cxx | 51 +++++++++++++++++++ ...ihoodWithLinearModelForMeanAndProjData.cxx | 25 ++------- 4 files changed, 87 insertions(+), 25 deletions(-) diff --git a/documentation/release_6.1.htm b/documentation/release_6.1.htm index 1bb9ab02d6..905568554d 100644 --- a/documentation/release_6.1.htm +++ b/documentation/release_6.1.htm @@ -52,8 +52,17 @@

    New functionality

    • Add TOF capability of the parallelproj projector (see PR #1356) +
    • +
    • Read TOF bin order from interfile header (see PR #1389)
    • +
    • + GeneralisedObjectiveFunction has 2 new papers to add compute full gradient + (compute_gradient and compute_gradient_without_penalty). Previously, + only subset gradient were available. +
      + PR #1418 +
    diff --git a/src/include/stir/recon_buildblock/GeneralisedObjectiveFunction.h b/src/include/stir/recon_buildblock/GeneralisedObjectiveFunction.h index 0a4d732225..db00e16b98 100644 --- a/src/include/stir/recon_buildblock/GeneralisedObjectiveFunction.h +++ b/src/include/stir/recon_buildblock/GeneralisedObjectiveFunction.h @@ -2,7 +2,7 @@ // /* Copyright (C) 2003- 2009, Hammersmith Imanet Ltd - Copyright (C) 2018, University College London + Copyright (C) 2018, 2020, 2024 University College London Copyright (C) 2016, University of Hull This file is part of STIR. @@ -17,6 +17,7 @@ \author Nikos Efthimiou \author Kris Thielemans + \author Robert Twyman Skelly \author Sanida Mustafovic */ @@ -98,8 +99,8 @@ class GeneralisedObjectiveFunction : public RegisteredObject const& target_sptr); - //! This should compute the sub-gradient of the objective function at the \a current_estimate - /*! The subgradient is the gradient of the objective function restricted to the + //! Compute the subset-gradient of the objective function at \a current_estimate + /*! The subset-gradient is the gradient of the objective function restricted to the subset specified. What this means depends on how this function is implemented later on in the hierarchy. @@ -112,12 +113,30 @@ class GeneralisedObjectiveFunction : public RegisteredObjectdifference of + compute_gradient_without_penalty + and + get_prior_ptr()->compute_gradient(). + + \warning Any data in \a gradient will be overwritten. + */ + virtual void compute_gradient(TargetT& gradient, const TargetT& current_estimate); + + //! Compute the gradient of the unregularised objective function at the \a current_estimate + /*! + Computed by summing subset-gradients. + + \warning Any data in \a gradient will be overwritten. + */ + virtual void compute_gradient_without_penalty(TargetT& gradient, const TargetT& current_estimate); + //! Compute the value of the unregularised sub-objective function at the \a current_estimate /*! Implemented in terms of actual_compute_objective_function_without_penalty. */ virtual double compute_objective_function_without_penalty(const TargetT& current_estimate, const int subset_num); diff --git a/src/recon_buildblock/GeneralisedObjectiveFunction.cxx b/src/recon_buildblock/GeneralisedObjectiveFunction.cxx index 0e74d76170..f289216e45 100644 --- a/src/recon_buildblock/GeneralisedObjectiveFunction.cxx +++ b/src/recon_buildblock/GeneralisedObjectiveFunction.cxx @@ -3,6 +3,7 @@ /* Copyright (C) 2003- 2011, Hammersmith Imanet Ltd Copyright (C) 2016, University of Hull + Copyright (C) 2020, 2024 University College London This file is part of STIR. SPDX-License-Identifier: Apache-2.0 @@ -16,6 +17,7 @@ \author Nikos Efthimiou \author Kris Thielemans + \author Robert Twyman Skelly \author Sanida Mustafovic */ @@ -154,6 +156,55 @@ GeneralisedObjectiveFunction::compute_sub_gradient(TargetT& gradient, } } +template +void +GeneralisedObjectiveFunction::compute_gradient_without_penalty(TargetT& gradient, const TargetT& current_estimate) +{ + // do first subset + compute_sub_gradient_without_penalty(gradient, current_estimate, 0); + if (this->get_num_subsets() == 1) + return; + + shared_ptr subset_gradient_sptr(gradient.get_empty_copy()); + for (int subset_num = 1; subset_num < this->get_num_subsets(); ++subset_num) + { + this->compute_sub_gradient_without_penalty(*subset_gradient_sptr, current_estimate, subset_num); + + auto subset_gradient_iter = subset_gradient_sptr->begin_all_const(); + const auto end_subset_gradient_iter = subset_gradient_sptr->end_all_const(); + auto gradient_iter = gradient.begin_all(); + while (subset_gradient_iter != end_subset_gradient_iter) + { + *gradient_iter += (*subset_gradient_iter); + ++gradient_iter; + ++subset_gradient_iter; + } + } +} + +template +void +GeneralisedObjectiveFunction::compute_gradient(TargetT& gradient, const TargetT& current_estimate) +{ + this->compute_gradient_without_penalty(gradient, current_estimate); + if (!this->prior_is_zero()) + { + shared_ptr prior_gradient_sptr(gradient.get_empty_copy()); + this->prior_sptr->compute_gradient(*prior_gradient_sptr, current_estimate); + + // gradient -= *prior_gradient_sptr; + auto prior_gradient_iter = prior_gradient_sptr->begin_all_const(); + const auto end_prior_gradient_iter = prior_gradient_sptr->end_all_const(); + auto gradient_iter = gradient.begin_all(); + while (prior_gradient_iter != end_prior_gradient_iter) + { + *gradient_iter -= (*prior_gradient_iter); + ++gradient_iter; + ++prior_gradient_iter; + } + } +} + template int GeneralisedObjectiveFunction::get_num_subsets() const diff --git a/src/recon_test/test_PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx b/src/recon_test/test_PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx index d940b07527..51e124ef14 100644 --- a/src/recon_test/test_PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx +++ b/src/recon_test/test_PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx @@ -147,12 +147,11 @@ PoissonLogLikelihoodWithLinearModelForMeanAndProjDataTests::test_objective_funct { shared_ptr gradient_sptr(target.get_empty_copy()); shared_ptr gradient_2_sptr(target.get_empty_copy()); - const int subset_num = 0; info("Computing gradient"); - objective_function.compute_sub_gradient(*gradient_sptr, target, subset_num); + objective_function.compute_gradient(*gradient_sptr, target); this->set_tolerance(std::max(fabs(double(gradient_sptr->find_min())), double(gradient_sptr->find_max())) / 1000); info("Computing objective function at target"); - const double value_at_target = objective_function.compute_objective_function(target, subset_num); + const double value_at_target = objective_function.compute_objective_function(target); target_type::full_iterator target_iter = target.begin_all(); target_type::full_iterator gradient_iter = gradient_sptr->begin_all(); target_type::full_iterator gradient_2_iter = gradient_2_sptr->begin_all(); @@ -162,7 +161,7 @@ PoissonLogLikelihoodWithLinearModelForMeanAndProjDataTests::test_objective_funct while (target_iter != target.end_all()) { *target_iter += eps; - const double value_at_inc = objective_function.compute_objective_function(target, subset_num); + const double value_at_inc = objective_function.compute_objective_function(target); *target_iter -= eps; const float gradient_at_iter = static_cast((value_at_inc - value_at_target) / eps); *gradient_2_iter++ = gradient_at_iter; @@ -176,22 +175,6 @@ PoissonLogLikelihoodWithLinearModelForMeanAndProjDataTests::test_objective_funct write_to_file("target.hv", target); write_to_file("gradient.hv", *gradient_sptr); write_to_file("numerical_gradient.hv", *gradient_2_sptr); -#if 1 - info("Writing diagnostic files subsens.hv, gradient-without-sens.hv, " - "proj_data.hs, mult_proj_data.hs and add_proj_data.hs"); - - write_to_file( - "subsens.hv", - reinterpret_cast&>(objective_function) - .get_subset_sensitivity(subset_num)); - gradient_sptr->fill(0.F); - reinterpret_cast&>(objective_function) - .compute_sub_gradient_without_penalty_plus_sensitivity(*gradient_sptr, target, subset_num); - write_to_file("gradient-without-sens.hv", *gradient_sptr); - proj_data_sptr->write_to_file("proj_data.hs"); - mult_proj_data_sptr->write_to_file("mult_proj_data.hs"); - add_proj_data_sptr->write_to_file("add_proj_data.hs"); -#endif } } @@ -263,7 +246,7 @@ PoissonLogLikelihoodWithLinearModelForMeanAndProjDataTests::construct_input_data /*max_delta=*/4, /*num_views=*/16, /*num_tang_poss=*/16)); - shared_ptr exam_info_sptr(new ExamInfo); + shared_ptr exam_info_sptr(new ExamInfo(ImagingModality::PT)); proj_data_sptr.reset(new ProjDataInMemory(exam_info_sptr, proj_data_info_sptr)); for (int seg_num = proj_data_sptr->get_min_segment_num(); seg_num <= proj_data_sptr->get_max_segment_num(); ++seg_num) { From 055b17e43d065e148a482c255bcab74755796640 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Mon, 13 May 2024 09:05:40 +0100 Subject: [PATCH 29/78] Move common test to ObjectiveFunctionTests test_PoissonLogLikelihoodWithLinearModelForMeanAndProjData and test_priors contained nearly similar code, so moved it to a new class. This needed minor modifications to the tests, but also addition of GeneralisedObjectiveFunction::compute_value() --- .../GeneralisedObjectiveFunction.h | 3 + .../stir/recon_buildblock/distributable.txx | 2 +- .../test/ObjectiveFunctionTests.h | 146 ++++++++++++++++++ ...ihoodWithLinearModelForMeanAndProjData.cxx | 109 +++---------- src/recon_test/test_priors.cxx | 74 +-------- 5 files changed, 174 insertions(+), 160 deletions(-) create mode 100644 src/include/stir/recon_buildblock/test/ObjectiveFunctionTests.h diff --git a/src/include/stir/recon_buildblock/GeneralisedObjectiveFunction.h b/src/include/stir/recon_buildblock/GeneralisedObjectiveFunction.h index db00e16b98..515a945cc3 100644 --- a/src/include/stir/recon_buildblock/GeneralisedObjectiveFunction.h +++ b/src/include/stir/recon_buildblock/GeneralisedObjectiveFunction.h @@ -176,6 +176,9 @@ class GeneralisedObjectiveFunction : public RegisteredObject PM_sptr, if (double_out_ptr) { local_double_outs.resize(omp_get_max_threads(), 0.); - for (unsigned t = 0; t < omp_get_max_threads(); ++t) + for (int t = 0; t < omp_get_max_threads(); ++t) local_double_out_ptrs[t] = &local_double_outs[t]; } local_counts.resize(omp_get_max_threads(), 0); diff --git a/src/include/stir/recon_buildblock/test/ObjectiveFunctionTests.h b/src/include/stir/recon_buildblock/test/ObjectiveFunctionTests.h new file mode 100644 index 0000000000..857fd31547 --- /dev/null +++ b/src/include/stir/recon_buildblock/test/ObjectiveFunctionTests.h @@ -0,0 +1,146 @@ +/* + Copyright (C) 2011, Hammersmith Imanet Ltd + Copyright (C) 2013, 2020, 2022-2024 University College London + This file is part of STIR. + + SPDX-License-Identifier: Apache-2.0 + + See STIR/LICENSE.txt for details +*/ +/*! + + \file + \ingroup recon_test + + \brief Test skeleton for stir::GeneralisedObjectiveFunction and stir::GeneralisedPrior + + \author Kris Thielemans + \author Reobert Twyman Skelly +*/ + +#include "stir/RunTests.h" +#include "stir/IO/read_from_file.h" +#include "stir/IO/write_to_file.h" +#include "stir/info.h" +#include "stir/Succeeded.h" +#include +#include +#include +#include +#include +#include + +#include "stir/IO/OutputFileFormat.h" +START_NAMESPACE_STIR + +/*! + \ingroup recon_test + \brief Test class for GeneralisedObjectiveFunction and GeneralisedPrior + + This is a somewhat preliminary implementation of a test that compares the result + of ObjectiveFunction::compute_gradient + with a numerical gradient computed by using the + ObjectiveFunction::compute_objective_function() function. + + The trouble with this is that compute the gradient voxel by voxel is obviously + terribly slow. A solution (for the test) would be to compute it only in + a subset of voxels or so. We'll leave this for later. + + Note that the test only works if the objective function is well-defined. For example, + if certain projections are non-zero, while the model estimates them to be zero, the + Poisson objective function is in theory infinite. + ObjectiveFunction uses some thresholds to try to + avoid overflow, but if there are too many of these bins, the total objective + function will become infinite. The numerical gradient then becomes ill-defined + (even in voxels that do not contribute to these bins). + +*/ +template +class ObjectiveFunctionTests : public RunTests +{ +public: + typedef ObjectiveFunctionT objective_function_type; + typedef TargetT target_type; + + //! Test the gradient of the objective function by comparing to the numerical gradient via perturbation + virtual void + test_gradient(const std::string& test_name, ObjectiveFunctionT& objective_function, TargetT& target, const float eps); + + //! Test the Hessian of the objective function by testing the (\a mult_factor * x^T Hx > 0) condition + virtual void test_Hessian_concavity(const std::string& test_name, + ObjectiveFunctionT& objective_function, + TargetT& target, + const float mult_factor = 1.F); +}; + +template +void +ObjectiveFunctionTests::test_gradient(const std::string& test_name, + ObjectiveFunctionT& objective_function, + TargetT& target, + const float eps) +{ + shared_ptr gradient_sptr(target.get_empty_copy()); + shared_ptr gradient_2_sptr(target.get_empty_copy()); + info("Computing gradient"); + objective_function.compute_gradient(*gradient_sptr, target); + this->set_tolerance(std::max(fabs(double(gradient_sptr->find_min())), double(gradient_sptr->find_max())) / 1000); + info("Computing objective function at target"); + const double value_at_target = objective_function.compute_value(target); + auto target_iter = target.begin_all(); + auto gradient_iter = gradient_sptr->begin_all(); + auto gradient_2_iter = gradient_2_sptr->begin_all(); + bool testOK = true; + info("Computing gradient of objective function by numerical differences (this will take a while)"); + while (target_iter != target.end_all()) + { + *target_iter += eps; + const double value_at_inc = objective_function.compute_value(target); + *target_iter -= eps; + const float gradient_at_iter = static_cast((value_at_inc - value_at_target) / eps); + *gradient_2_iter++ = gradient_at_iter; + testOK = testOK && this->check_if_equal(gradient_at_iter, *gradient_iter, "gradient"); + ++target_iter; + ++gradient_iter; + } + if (!testOK) + { + std::cerr << "Numerical gradient test failed with for " + test_name + "\n"; + info("Writing diagnostic files target.hv, gradient.hv, numerical_gradient.hv"); + write_to_file("target.hv", target); + write_to_file("gradient.hv", *gradient_sptr); + write_to_file("numerical_gradient.hv", *gradient_2_sptr); + } +} + +template +void +ObjectiveFunctionTests::test_Hessian_concavity(const std::string& test_name, + ObjectiveFunctionT& objective_function, + TargetT& target, + const float mult_factor) +{ + /// setup images + shared_ptr output(target.get_empty_copy()); + + /// Compute H x + objective_function.accumulate_Hessian_times_input(*output, target, target); + + /// Compute dot(x,(H x)) + const float my_sum = std::inner_product(target.begin_all(), target.end_all(), output->begin_all(), 0.F) * mult_factor; + + // test for a CONCAVE function + if (this->check_if_less(my_sum, 0)) + { + // info("PASS: Computation of x^T H x = " + std::to_string(my_sum) + " < 0" (Hessian) and is therefore concave); + } + else + { + // print to console the FAILED configuration + info("FAIL: " + test_name + ": Computation of x^T H x = " + std::to_string(my_sum) + + " > 0 (Hessian) and is therefore NOT concave" + "\n >target image max=" + std::to_string(target.find_max()) + + "\n >target image min=" + std::to_string(target.find_min())); + } +} + +END_NAMESPACE_STIR diff --git a/src/recon_test/test_PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx b/src/recon_test/test_PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx index 51e124ef14..e8cb62eeff 100644 --- a/src/recon_test/test_PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx +++ b/src/recon_test/test_PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx @@ -1,6 +1,6 @@ /* Copyright (C) 2011, Hammersmith Imanet Ltd - Copyright (C) 2013, University College London + Copyright (C) 2013, 2021, 2024 University College London This file is part of STIR. SPDX-License-Identifier: Apache-2.0 @@ -22,8 +22,10 @@ where the 2 arguments are optional. See the class documentation for more info. \author Kris Thielemans + \author Robert Twyman Skelly */ +#include "stir/recon_buildblock/test/ObjectiveFunctionTests.h" #include "stir/VoxelsOnCartesianGrid.h" #include "stir/ProjData.h" #include "stir/ExamInfo.h" @@ -39,7 +41,6 @@ #include "stir/recon_buildblock/TrivialBinNormalisation.h" //#include "stir/OSMAPOSL/OSMAPOSLReconstruction.h" #include "stir/recon_buildblock/distributable_main.h" -#include "stir/RunTests.h" #include "stir/IO/read_from_file.h" #include "stir/IO/write_to_file.h" #include "stir/info.h" @@ -47,10 +48,6 @@ #include "stir/num_threads.h" #include #include -#include -#include -#include -#include #include "stir/IO/OutputFileFormat.h" #include "stir/recon_buildblock/distributable_main.h" @@ -78,7 +75,9 @@ START_NAMESPACE_STIR (even in voxels that do not contribute to these bins). */ -class PoissonLogLikelihoodWithLinearModelForMeanAndProjDataTests : public RunTests +class PoissonLogLikelihoodWithLinearModelForMeanAndProjDataTests + : public ObjectiveFunctionTests>, + DiscretisedDensity<3, float>> { public: //! Constructor that can take some input data to run the test with @@ -91,7 +90,6 @@ class PoissonLogLikelihoodWithLinearModelForMeanAndProjDataTests : public RunTes */ PoissonLogLikelihoodWithLinearModelForMeanAndProjDataTests(char const* const proj_data_filename = 0, char const* const density_filename = 0); - typedef DiscretisedDensity<3, float> target_type; void construct_input_data(shared_ptr& density_sptr); void run_tests() override; @@ -102,22 +100,13 @@ class PoissonLogLikelihoodWithLinearModelForMeanAndProjDataTests : public RunTes shared_ptr proj_data_sptr; shared_ptr mult_proj_data_sptr; shared_ptr add_proj_data_sptr; - shared_ptr> objective_function_sptr; + shared_ptr> objective_function_sptr; //! run the test - /*! Note that this function is not specific to PoissonLogLikelihoodWithLinearModelForMeanAndProjData */ - void run_tests_for_objective_function(GeneralisedObjectiveFunction& objective_function, target_type& target); - - //! Test the gradient of the objective function by comparing to the numerical gradient via perturbation - void test_objective_function_gradient(GeneralisedObjectiveFunction& objective_function, target_type& target); - - //! Test the Hessian of the objective function by testing the (x^T Hx > 0) condition - void test_objective_function_Hessian_concavity(GeneralisedObjectiveFunction& objective_function, - target_type& target); + void run_tests_for_objective_function(objective_function_type& objective_function, target_type& target); //! Test the approximate Hessian of the objective function by testing the (x^T Hx > 0) condition - void test_objective_function_approximate_Hessian_concavity(GeneralisedObjectiveFunction& objective_function, - target_type& target); + void test_approximate_Hessian_concavity(objective_function_type& objective_function, target_type& target); }; PoissonLogLikelihoodWithLinearModelForMeanAndProjDataTests::PoissonLogLikelihoodWithLinearModelForMeanAndProjDataTests( @@ -128,86 +117,21 @@ PoissonLogLikelihoodWithLinearModelForMeanAndProjDataTests::PoissonLogLikelihood void PoissonLogLikelihoodWithLinearModelForMeanAndProjDataTests::run_tests_for_objective_function( - GeneralisedObjectiveFunction& objective_function, - PoissonLogLikelihoodWithLinearModelForMeanAndProjDataTests::target_type& target) + objective_function_type& objective_function, target_type& target) { std::cerr << "----- testing Gradient\n"; - test_objective_function_gradient(objective_function, target); + test_gradient("PoissonLLProjData", objective_function, target, 0.01F); std::cerr << "----- testing Hessian-vector product (accumulate_Hessian_times_input)\n"; - test_objective_function_Hessian_concavity(objective_function, target); + test_Hessian_concavity("PoissonLLProjData", objective_function, target); std::cerr << "----- testing approximate-Hessian-vector product (accumulate_Hessian_times_input)\n"; - test_objective_function_approximate_Hessian_concavity(objective_function, target); -} - -void -PoissonLogLikelihoodWithLinearModelForMeanAndProjDataTests::test_objective_function_gradient( - GeneralisedObjectiveFunction& objective_function, target_type& target) -{ - shared_ptr gradient_sptr(target.get_empty_copy()); - shared_ptr gradient_2_sptr(target.get_empty_copy()); - info("Computing gradient"); - objective_function.compute_gradient(*gradient_sptr, target); - this->set_tolerance(std::max(fabs(double(gradient_sptr->find_min())), double(gradient_sptr->find_max())) / 1000); - info("Computing objective function at target"); - const double value_at_target = objective_function.compute_objective_function(target); - target_type::full_iterator target_iter = target.begin_all(); - target_type::full_iterator gradient_iter = gradient_sptr->begin_all(); - target_type::full_iterator gradient_2_iter = gradient_2_sptr->begin_all(); - const float eps = 1e-2F; - bool testOK = true; - info("Computing gradient of objective function by numerical differences (this will take a while)"); - while (target_iter != target.end_all()) - { - *target_iter += eps; - const double value_at_inc = objective_function.compute_objective_function(target); - *target_iter -= eps; - const float gradient_at_iter = static_cast((value_at_inc - value_at_target) / eps); - *gradient_2_iter++ = gradient_at_iter; - testOK = testOK && this->check_if_equal(gradient_at_iter, *gradient_iter, "gradient"); - ++target_iter; - ++gradient_iter; - } - if (!testOK) - { - info("Writing diagnostic files target.hv, gradient.hv, numerical_gradient.hv"); - write_to_file("target.hv", target); - write_to_file("gradient.hv", *gradient_sptr); - write_to_file("numerical_gradient.hv", *gradient_2_sptr); - } -} - -void -PoissonLogLikelihoodWithLinearModelForMeanAndProjDataTests::test_objective_function_Hessian_concavity( - GeneralisedObjectiveFunction& objective_function, target_type& target) -{ - /// setup images - shared_ptr output(target.get_empty_copy()); - - /// Compute H x - objective_function.accumulate_Hessian_times_input(*output, target, target); - - /// Compute dot(x,(H x)) - const float my_sum = std::inner_product(target.begin_all(), target.end_all(), output->begin_all(), 0.F); - - // test for a CONCAVE function - if (this->check_if_less(my_sum, 0)) - { - // info("PASS: Computation of x^T H x = " + std::to_string(my_sum) + " < 0" (Hessian) and is therefore concave); - } - else - { - // print to console the FAILED configuration - info("FAIL: Computation of x^T H x = " + std::to_string(my_sum) + " > 0 (Hessian) and is therefore NOT concave" - + "\n >target image max=" + std::to_string(target.find_max()) - + "\n >target image min=" + std::to_string(target.find_min())); - } + test_approximate_Hessian_concavity(objective_function, target); } void -PoissonLogLikelihoodWithLinearModelForMeanAndProjDataTests::test_objective_function_approximate_Hessian_concavity( - GeneralisedObjectiveFunction& objective_function, target_type& target) +PoissonLogLikelihoodWithLinearModelForMeanAndProjDataTests::test_approximate_Hessian_concavity( + objective_function_type& objective_function, target_type& target) { /// setup images shared_ptr output(target.get_empty_copy()); @@ -376,6 +300,8 @@ void PoissonLogLikelihoodWithLinearModelForMeanAndProjDataTests::run_tests() { std::cerr << "Tests for PoissonLogLikelihoodWithLinearModelForMeanAndProjData\n"; + const int verbosity_default = Verbosity::get(); + Verbosity::set(0); #if 1 shared_ptr density_sptr; @@ -390,6 +316,7 @@ PoissonLogLikelihoodWithLinearModelForMeanAndProjDataTests::run_tests() return; this->run_tests_for_objective_function(*objective_function_sptr, *recon.get_initial_data_ptr()); #endif + Verbosity::set(verbosity_default); } END_NAMESPACE_STIR diff --git a/src/recon_test/test_priors.cxx b/src/recon_test/test_priors.cxx index 681e0f64e0..eb5597075b 100644 --- a/src/recon_test/test_priors.cxx +++ b/src/recon_test/test_priors.cxx @@ -1,5 +1,4 @@ /* - Copyright (C) 2011, Hammersmith Imanet Ltd Copyright (C) 2020-2024 University College London This file is part of STIR. @@ -30,7 +29,7 @@ #include "stir/recon_buildblock/RelativeDifferencePrior.h" #include "stir/recon_buildblock/LogcoshPrior.h" #include "stir/recon_buildblock/PLSPrior.h" -#include "stir/RunTests.h" +#include "stir/recon_buildblock/test/ObjectiveFunctionTests.h" #include "stir/IO/read_from_file.h" #include "stir/IO/write_to_file.h" #include "stir/info.h" @@ -57,7 +56,8 @@ START_NAMESPACE_STIR by evaluating the x^T Hx > 0 constraint. */ -class GeneralisedPriorTests : public RunTests +class GeneralisedPriorTests + : public ObjectiveFunctionTests>, DiscretisedDensity<3, float>> { public: //! Constructor that can take some input data to run the test with @@ -84,11 +84,6 @@ class GeneralisedPriorTests : public RunTests GeneralisedPrior& objective_function, const shared_ptr& target_sptr); - //! Tests the prior's gradient by comparing to the numerical gradient computed using perturbation response. - void test_gradient(const std::string& test_name, - GeneralisedPrior& objective_function, - const shared_ptr& target_sptr); - //! Test various configurations of the Hessian of the prior via accumulate_Hessian_times_input() for convexity /*! Tests the convexity condition: @@ -152,7 +147,9 @@ GeneralisedPriorTests::run_tests_for_objective_function(const std::string& test_ if (do_test_gradient) { std::cerr << "----- test " << test_name << " --> Gradient\n"; - test_gradient(test_name, objective_function, target_sptr); + using value_type = target_type::full_value_type; + const auto eps = static_cast(1e-4F * target_sptr->find_max()); + test_gradient(test_name, objective_function, *target_sptr, eps); } if (do_test_Hessian_convexity) @@ -168,65 +165,6 @@ GeneralisedPriorTests::run_tests_for_objective_function(const std::string& test_ } } -void -GeneralisedPriorTests::test_gradient(const std::string& test_name, - GeneralisedPrior& objective_function, - const shared_ptr& target_sptr) -{ - using value_type = target_type::full_value_type; - // setup images - target_type& target(*target_sptr); - shared_ptr gradient_sptr(target.get_empty_copy()); - shared_ptr gradient_2_sptr(target.get_empty_copy()); - - info("Computing gradient", 3); - const int verbosity_default = Verbosity::get(); - Verbosity::set(0); - objective_function.compute_gradient(*gradient_sptr, target); - Verbosity::set(verbosity_default); - this->set_tolerance(std::max(fabs(double(gradient_sptr->find_min())), fabs(double(gradient_sptr->find_max()))) / 1000); - - info("Computing objective function at target", 3); - const double value_at_target = objective_function.compute_value(target); - target_type::full_iterator target_iter = target.begin_all(); - target_type::full_iterator gradient_iter = gradient_sptr->begin_all(); - target_type::full_iterator gradient_2_iter = gradient_2_sptr->begin_all(); - - // setup perturbation response - const auto target_max = target_sptr->find_max(); - const auto eps = static_cast(1e-4F * target_max); - bool testOK = true; - info("Computing gradient of objective function by numerical differences (this will take a while)", 3); - while (target_iter != target.end_all()) // && testOK) - { - const float org_image_value = *target_iter; - *target_iter += eps; // perturb current voxel - const double value_at_inc = objective_function.compute_value(target); - *target_iter = org_image_value; // restore - const auto ngradient_at_iter = static_cast((value_at_inc - value_at_target) / eps); - *gradient_2_iter = ngradient_at_iter; - const bool this_testOK = this->check_if_equal(ngradient_at_iter, *gradient_iter, "gradient"); - testOK = testOK && this_testOK; - if (!this_testOK) - { - // std::cerr << "failure x=" << org_image_value << ", eps_Value = " << eps << '\n'; - } - // for (int i=0; i<5 && target_iter!=target.end_all(); ++i) - { - ++gradient_2_iter; - ++target_iter; - ++gradient_iter; - } - } - if (!testOK) - { - std::cerr << "Numerical gradient test failed with for " + test_name + " prior\n"; - info("Writing diagnostic files gradient" + test_name + ".hv, numerical_gradient" + test_name + ".hv"); - write_to_file("gradient" + test_name + ".hv", *gradient_sptr); - write_to_file("numerical_gradient" + test_name + ".hv", *gradient_2_sptr); - } -} - void GeneralisedPriorTests::test_Hessian_convexity(const std::string& test_name, GeneralisedPrior& objective_function, From e11fbf966067b62f26d3ab3006dadf093d69248c Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Mon, 13 May 2024 11:43:21 +0100 Subject: [PATCH 30/78] add numerical test for accumulate_Hessian_times_input --- .../test/ObjectiveFunctionTests.h | 101 ++++++++++++++---- ...ihoodWithLinearModelForMeanAndProjData.cxx | 23 +++- src/recon_test/test_priors.cxx | 2 + 3 files changed, 103 insertions(+), 23 deletions(-) diff --git a/src/include/stir/recon_buildblock/test/ObjectiveFunctionTests.h b/src/include/stir/recon_buildblock/test/ObjectiveFunctionTests.h index 857fd31547..4e97dd4693 100644 --- a/src/include/stir/recon_buildblock/test/ObjectiveFunctionTests.h +++ b/src/include/stir/recon_buildblock/test/ObjectiveFunctionTests.h @@ -19,30 +19,20 @@ */ #include "stir/RunTests.h" -#include "stir/IO/read_from_file.h" #include "stir/IO/write_to_file.h" #include "stir/info.h" #include "stir/Succeeded.h" #include -#include -#include -#include -#include -#include -#include "stir/IO/OutputFileFormat.h" START_NAMESPACE_STIR /*! \ingroup recon_test \brief Test class for GeneralisedObjectiveFunction and GeneralisedPrior - This is a somewhat preliminary implementation of a test that compares the result - of ObjectiveFunction::compute_gradient - with a numerical gradient computed by using the - ObjectiveFunction::compute_objective_function() function. + This contains some numerical tests to check gradient and Hessian calculations. - The trouble with this is that compute the gradient voxel by voxel is obviously + Note that the gradient is computed numerically voxel by voxel which is obviously terribly slow. A solution (for the test) would be to compute it only in a subset of voxels or so. We'll leave this for later. @@ -63,18 +53,28 @@ class ObjectiveFunctionTests : public RunTests typedef TargetT target_type; //! Test the gradient of the objective function by comparing to the numerical gradient via perturbation - virtual void + /*! Note: \a target is non-\c const, as the code will add/subtract eps, but the actual values + are not modified after the test exits. + */ + virtual Succeeded test_gradient(const std::string& test_name, ObjectiveFunctionT& objective_function, TargetT& target, const float eps); + //! Test the accumulate_Hessian_times_input of the objective function by comparing to the numerical result via perturbation + /*! + This test checks that \f$ H dx \approx G(x+dx) - G(x) \f$. + \f$dx\f$ is currently computed by as eps*(target / target.find_max() + 0.5). + */ + virtual Succeeded + test_Hessian(const std::string& test_name, ObjectiveFunctionT& objective_function, const TargetT& target, const float eps); //! Test the Hessian of the objective function by testing the (\a mult_factor * x^T Hx > 0) condition - virtual void test_Hessian_concavity(const std::string& test_name, - ObjectiveFunctionT& objective_function, - TargetT& target, - const float mult_factor = 1.F); + virtual Succeeded test_Hessian_concavity(const std::string& test_name, + ObjectiveFunctionT& objective_function, + const TargetT& target, + const float mult_factor = 1.F); }; template -void +Succeeded ObjectiveFunctionTests::test_gradient(const std::string& test_name, ObjectiveFunctionT& objective_function, TargetT& target, @@ -106,18 +106,75 @@ ObjectiveFunctionTests::test_gradient(const std::st if (!testOK) { std::cerr << "Numerical gradient test failed with for " + test_name + "\n"; - info("Writing diagnostic files target.hv, gradient.hv, numerical_gradient.hv"); + std::cerr << "Writing diagnostic files target.hv, gradient.hv, numerical_gradient.hv\n"; write_to_file("target.hv", target); write_to_file("gradient.hv", *gradient_sptr); write_to_file("numerical_gradient.hv", *gradient_2_sptr); + return Succeeded::no; + } + else + { + return Succeeded::yes; + } +} + +template +Succeeded +ObjectiveFunctionTests::test_Hessian(const std::string& test_name, + ObjectiveFunctionT& objective_function, + const TargetT& target, + const float eps) +{ + /* test G(x+dx) = G(x) + H dx + small stuff */ + shared_ptr gradient_sptr(target.get_empty_copy()); + shared_ptr gradient_2_sptr(target.get_empty_copy()); + shared_ptr output(target.get_empty_copy()); + shared_ptr increment_sptr(target.clone()); + *increment_sptr *= eps / increment_sptr->find_max(); + *increment_sptr += eps / 2; + shared_ptr target_plus_inc_sptr(target.clone()); + *target_plus_inc_sptr += *increment_sptr; + + info("Computing gradient"); + objective_function.compute_gradient(*gradient_sptr, target); + objective_function.compute_gradient(*gradient_2_sptr, *target_plus_inc_sptr); + this->set_tolerance(std::max(fabs(double(gradient_sptr->find_min())), double(gradient_sptr->find_max())) / 1E5); + info("Computing Hessian * increment at target"); + objective_function.accumulate_Hessian_times_input(*output, target, *increment_sptr); + auto output_iter = output->begin_all_const(); + auto gradient_iter = gradient_sptr->begin_all_const(); + auto gradient_2_iter = gradient_2_sptr->begin_all_const(); + bool testOK = true; + info("Computing gradient of objective function by numerical differences (this will take a while)"); + while (output_iter != output->end_all()) + { + testOK = testOK && this->check_if_equal(*gradient_2_iter - *gradient_iter, *output_iter, "Hessian*increment"); + ++output_iter; + ++gradient_iter; + ++gradient_2_iter; + } + if (!testOK) + { + std::cerr << "Numerical Hessian test failed with for " + test_name + "\n"; + std::cerr << "Writing diagnostic files target.hv, gradient.hv, increment, numerical_gradient.hv, Hessian_times_increment\n"; + write_to_file("target.hv", target); + write_to_file("gradient.hv", *gradient_sptr); + write_to_file("increment.hv", *increment_sptr); + write_to_file("gradient_at_increment.hv", *gradient_2_sptr); + write_to_file("Hessian_times_increment.hv", *output); + return Succeeded::no; + } + else + { + return Succeeded::yes; } } template -void +Succeeded ObjectiveFunctionTests::test_Hessian_concavity(const std::string& test_name, ObjectiveFunctionT& objective_function, - TargetT& target, + const TargetT& target, const float mult_factor) { /// setup images @@ -133,6 +190,7 @@ ObjectiveFunctionTests::test_Hessian_concavity(cons if (this->check_if_less(my_sum, 0)) { // info("PASS: Computation of x^T H x = " + std::to_string(my_sum) + " < 0" (Hessian) and is therefore concave); + return Succeeded::yes; } else { @@ -140,6 +198,7 @@ ObjectiveFunctionTests::test_Hessian_concavity(cons info("FAIL: " + test_name + ": Computation of x^T H x = " + std::to_string(my_sum) + " > 0 (Hessian) and is therefore NOT concave" + "\n >target image max=" + std::to_string(target.find_max()) + "\n >target image min=" + std::to_string(target.find_min())); + return Succeeded::no; } } diff --git a/src/recon_test/test_PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx b/src/recon_test/test_PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx index e8cb62eeff..73d47fece6 100644 --- a/src/recon_test/test_PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx +++ b/src/recon_test/test_PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx @@ -46,6 +46,10 @@ #include "stir/info.h" #include "stir/Succeeded.h" #include "stir/num_threads.h" +#include +#include +#include +#include #include #include @@ -122,11 +126,23 @@ PoissonLogLikelihoodWithLinearModelForMeanAndProjDataTests::run_tests_for_object std::cerr << "----- testing Gradient\n"; test_gradient("PoissonLLProjData", objective_function, target, 0.01F); - std::cerr << "----- testing Hessian-vector product (accumulate_Hessian_times_input)\n"; + std::cerr << "----- testing concavity via Hessian-vector product (accumulate_Hessian_times_input)\n"; test_Hessian_concavity("PoissonLLProjData", objective_function, target); std::cerr << "----- testing approximate-Hessian-vector product (accumulate_Hessian_times_input)\n"; test_approximate_Hessian_concavity(objective_function, target); + + std::cerr << "----- testing Hessian-vector product (accumulate_Hessian_times_input)\n"; + test_Hessian("PoissonLLProjData", objective_function, target, 0.5F); + + if (!this->is_everything_ok()) + { + std::cerr << "Writing diagnostic files proj_data.hs, mult_proj_data.hs and add_proj_data.hs"; + + proj_data_sptr->write_to_file("proj_data.hs"); + mult_proj_data_sptr->write_to_file("mult_proj_data.hs"); + add_proj_data_sptr->write_to_file("add_proj_data.hs"); + } } void @@ -202,7 +218,8 @@ PoissonLogLikelihoodWithLinearModelForMeanAndProjDataTests::construct_input_data CartesianCoordinate3D origin(0, 0, 0); const float zoom = 1.F; - density_sptr.reset(new VoxelsOnCartesianGrid(*proj_data_sptr->get_proj_data_info_sptr(), zoom, origin)); + density_sptr.reset(new VoxelsOnCartesianGrid( + proj_data_sptr->get_exam_info_sptr(), *proj_data_sptr->get_proj_data_info_sptr(), zoom, origin)); // fill with random numbers between 0 and 1 typedef boost::mt19937 base_generator_type; // initialize by reproducible seed @@ -249,6 +266,7 @@ PoissonLogLikelihoodWithLinearModelForMeanAndProjDataTests::construct_input_data value = float(fabs(seg_num * value - .2)); // needs to be positive for Poisson *iter = value; } + segment /= 0.5F * segment.find_max(); // normalise to 2 (to avoid large numbers) mult_proj_data_sptr->set_segment(segment); } } @@ -272,6 +290,7 @@ PoissonLogLikelihoodWithLinearModelForMeanAndProjDataTests::construct_input_data value = float(fabs(seg_num * value - .3)); // needs to be positive for Poisson *iter = value; } + segment /= 0.333F * segment.find_max(); // normalise to 3 (to avoid large numbers) add_proj_data_sptr->set_segment(segment); } } diff --git a/src/recon_test/test_priors.cxx b/src/recon_test/test_priors.cxx index eb5597075b..6c4ce8692b 100644 --- a/src/recon_test/test_priors.cxx +++ b/src/recon_test/test_priors.cxx @@ -162,6 +162,8 @@ GeneralisedPriorTests::run_tests_for_objective_function(const std::string& test_ { std::cerr << "----- test " << test_name << " --> Hessian against numerical\n"; test_Hessian_against_numerical(test_name, objective_function, target_sptr); + std::cerr << "----- testing Hessian-vector product (accumulate_Hessian_times_input)\n"; + test_Hessian(test_name, objective_function, *target_sptr, 0.00001F); } } From 2315597594d1e1ac15c0f4717cca0e33e2f53715 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Mon, 13 May 2024 11:46:25 +0100 Subject: [PATCH 31/78] fix sign of LM Hessian --- ...thLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx index 6d2379d538..f69d1fdec4 100644 --- a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx +++ b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx @@ -804,7 +804,7 @@ LM_gradient(DiscretisedDensity<3, float>& output_image, /* Hessian -\sum_e A_e^t (y_e/(A_e lambda+ c)^2 A_e rhs) +\sum_e -A_e^t (y_e/(A_e lambda+ c)^2 A_e rhs) */ inline void LM_Hessian(DiscretisedDensity<3, float>& output_image, @@ -821,7 +821,7 @@ LM_Hessian(DiscretisedDensity<3, float>& output_image, if (measured_bin.get_bin_value() > max_quotient * fwd) return; // cancel singularity - const auto measured_div_fwd2 = measured_bin.get_bin_value() / square(fwd); + const auto measured_div_fwd2 = -measured_bin.get_bin_value() / square(fwd); // forward project rhs fwd_bin.set_bin_value(0.0f); From 9022195bf02fb34e9274ab3d1577a0686744a400 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Mon, 13 May 2024 14:10:24 +0100 Subject: [PATCH 32/78] LM obj-fun: add value() and tests - compute_objective_function() is now implemented as well - Tests are (almost) a duplicate of the ProjData version, but gradient-test is disabled as for this sample data it is too slow --- documentation/release_6.1.htm | 23 +- ...orMeanAndListModeDataWithProjMatrixByBin.h | 14 +- ...MeanAndListModeDataWithProjMatrixByBin.cxx | 68 ++++- src/recon_test/CMakeLists.txt | 4 + ...lForMeanAndListModeWithProjMatrixByBin.cxx | 283 ++++++++++++++++++ 5 files changed, 371 insertions(+), 21 deletions(-) create mode 100644 src/recon_test/test_PoissonLogLikelihoodWithLinearModelForMeanAndListModeWithProjMatrixByBin.cxx diff --git a/documentation/release_6.1.htm b/documentation/release_6.1.htm index 905568554d..f0512e672d 100644 --- a/documentation/release_6.1.htm +++ b/documentation/release_6.1.htm @@ -57,9 +57,15 @@

    New functionality

    Read TOF bin order from interfile header (see PR #1389)
  • - GeneralisedObjectiveFunction has 2 new papers to add compute full gradient + PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin can now compute the value + as well as accumulate_Hessian_times_input. +
    + PR #1418 +
  • +
  • + GeneralisedObjectiveFunction has 2 new members to compute the full gradient (compute_gradient and compute_gradient_without_penalty). Previously, - only subset gradient were available. + only subset gradients were available.
    PR #1418
  • @@ -68,6 +74,12 @@

    New functionality

    Changed functionality

      +
    • + PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin now computes the gradient + multi-threaded (if OpenMP is enabled), even if caching to file of the list-mode file is not enabled. +
      + PR #1418 +
    • Accumulation in computation of priors now uses doubles, which could result in slightly better precision. Part of PR #1410.
    • @@ -136,6 +148,13 @@

      recon_test_pack changes

    C++ tests

    +
      +
    • + objective function and priors now have a numerical test for accumulate_Hessian_times_input +
      + PR #1418 +
    • +
    diff --git a/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.h b/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.h index 1bd095f150..be1950f229 100644 --- a/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.h +++ b/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.h @@ -74,6 +74,13 @@ class PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByB PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin(); + //! Computes the value of the objective function at the \a current_estimate. + /*! + \warning If add_sensitivity = false and use_subset_sensitivities = false will return an error + because the value will not be correct. Try use_subset_sensitivities = true. + */ + double actual_compute_objective_function_without_penalty(const TargetT& current_estimate, const int subset_num) override; + //! Computes the gradient of the objective function at the \a current_estimate overwriting \a gradient. /*! \warning If add_sensitivity = false and use_subset_sensitivities = false will return an error @@ -107,13 +114,6 @@ class PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByB #endif protected: - /*! \todo this function is not implemented yet and currently calls error() */ - double actual_compute_objective_function_without_penalty(const TargetT& current_estimate, const int subset_num) override - { // TODO - error("compute_objective_function_without_penalty Not implemented yet"); - return 0; - } - Succeeded set_up_before_sensitivity(shared_ptr const& target_sptr) override; void add_subset_sensitivity(TargetT& sensitivity, const int subset_num) const override; diff --git a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx index f69d1fdec4..a71ac1a929 100644 --- a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx +++ b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx @@ -771,13 +771,14 @@ constexpr float max_quotient = 10000.F; \sum_e A_e^t (y_e/(A_e lambda+ c)) */ +template inline void -LM_gradient(DiscretisedDensity<3, float>& output_image, - const ProjMatrixElemsForOneBin& row, - const float add_term, - const Bin& measured_bin, - const DiscretisedDensity<3, float>& input_image, - double* value_ptr) +LM_gradient_and_value(DiscretisedDensity<3, float>& output_image, + const ProjMatrixElemsForOneBin& row, + const float add_term, + const Bin& measured_bin, + const DiscretisedDensity<3, float>& input_image, + double* value_ptr) { Bin fwd_bin = measured_bin; fwd_bin.set_bin_value(0.0f); @@ -787,18 +788,22 @@ LM_gradient(DiscretisedDensity<3, float>& output_image, if (measured_bin.get_bin_value() > max_quotient * fwd) { // cancel singularity - if (value_ptr) + if (do_value) { + assert(value_ptr); const auto num = measured_bin.get_bin_value(); *value_ptr -= num * log(double(num / max_quotient)); return; } } - const auto measured_div_fwd = measured_bin.get_bin_value() / fwd; + if (do_gradient) + { + const auto measured_div_fwd = measured_bin.get_bin_value() / fwd; - fwd_bin.set_bin_value(measured_div_fwd); - row.back_project(output_image, fwd_bin); - if (value_ptr) + fwd_bin.set_bin_value(measured_div_fwd); + row.back_project(output_image, fwd_bin); + } + if (do_value) *value_ptr -= measured_bin.get_bin_value() * log(double(fwd)); } @@ -855,7 +860,7 @@ LM_gradient_distributable_computation(const shared_ptr PM_sptr, has_add, accumulate, value_ptr, - LM_gradient); + LM_gradient_and_value); } void @@ -885,6 +890,45 @@ LM_Hessian_distributable_computation(const shared_ptr PM_sptr, H_func); } +template +double +PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin< + TargetT>::actual_compute_objective_function_without_penalty(const TargetT& current_estimate, const int subset_num) +{ + assert(subset_num >= 0); + assert(subset_num < this->num_subsets); + if (!this->get_use_subset_sensitivities() && this->num_subsets > 1) + error("PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin::" + "actual_compute_subset_gradient_without_penalty(): cannot subtract subset sensitivity because " + "use_subset_sensitivities is false. This will result in an error in the gradient computation."); + + double accum = 0.; + unsigned int icache = 0; + while (true) + { + bool stop = this->load_listmode_batch(icache); + LM_distributable_computation(this->PM_sptr, + this->proj_data_info_sptr, + nullptr, + ¤t_estimate, + record_cache, + subset_num, + this->num_subsets, + this->has_add, + /* accumulate */ true, + &accum, + LM_gradient_and_value); + ++icache; + if (stop) + break; + } + std::inner_product(current_estimate.begin_all_const(), + current_estimate.end_all_const(), + this->get_subset_sensitivity(subset_num).begin_all_const(), + accum); + return accum; +} + template void PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin< diff --git a/src/recon_test/CMakeLists.txt b/src/recon_test/CMakeLists.txt index f7fe116313..6e9e329f13 100644 --- a/src/recon_test/CMakeLists.txt +++ b/src/recon_test/CMakeLists.txt @@ -36,6 +36,7 @@ set(${dir_INVOLVED_TEST_EXE_SOURCES} recontest.cxx test_data_processor_projectors.cxx test_OSMAPOSL.cxx + test_PoissonLogLikelihoodWithLinearModelForMeanAndListModeWithProjMatrixByBin.cxx ) if (DOWNLOAD_ZENODO_TEST_DATA) @@ -128,6 +129,9 @@ include(stir_test_exe_targets) # a test that uses MPI create_stir_mpi_test(test_PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx "${STIR_LIBRARIES}" $) +ADD_TEST(test_PoissonLogLikelihoodWithLinearModelForMeanAndListModeWithProjMatrixByBin + test_PoissonLogLikelihoodWithLinearModelForMeanAndListModeWithProjMatrixByBin "${CMAKE_SOURCE_DIR}/recon_test_pack/PET_ACQ_small.l.hdr.STIR") + # fwdtest and bcktest could be useful on their own, so we'll add them to the installation targets if (BUILD_TESTING) install(TARGETS fwdtest bcktest DESTINATION bin) diff --git a/src/recon_test/test_PoissonLogLikelihoodWithLinearModelForMeanAndListModeWithProjMatrixByBin.cxx b/src/recon_test/test_PoissonLogLikelihoodWithLinearModelForMeanAndListModeWithProjMatrixByBin.cxx new file mode 100644 index 0000000000..a8c6ba669c --- /dev/null +++ b/src/recon_test/test_PoissonLogLikelihoodWithLinearModelForMeanAndListModeWithProjMatrixByBin.cxx @@ -0,0 +1,283 @@ +/* + Copyright (C) 2011, Hammersmith Imanet Ltd + Copyright (C) 2013, 2021, 2024 University College London + This file is part of STIR. + + SPDX-License-Identifier: Apache-2.0 + + See STIR/LICENSE.txt for details +*/ +/*! + + \file + \ingroup recon_test + + \brief Test program for stir::PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin + + \par Usage + +
    +  test_PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin lm_data_filename [ density_filename ]
    +  
    + where the last arguments are optional. See the class documentation for more info. + + \author Kris Thielemans + \author Robert Twyman Skelly +*/ + +#include "stir/recon_buildblock/test/ObjectiveFunctionTests.h" +#include "stir/VoxelsOnCartesianGrid.h" +#include "stir/listmode/CListModeData.h" +#include "stir/ExamInfo.h" +#include "stir/ProjDataInfo.h" +#include "stir/ProjDataInMemory.h" +#include "stir/SegmentByView.h" +#include "stir/Scanner.h" +#include "stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.h" +#include "stir/recon_buildblock/ProjMatrixByBinUsingRayTracing.h" +#include "stir/recon_buildblock/ProjectorByBinPairUsingProjMatrixByBin.h" +#include "stir/recon_buildblock/BinNormalisationFromProjData.h" +#include "stir/recon_buildblock/TrivialBinNormalisation.h" +//#include "stir/OSMAPOSL/OSMAPOSLReconstruction.h" +#include "stir/recon_buildblock/distributable_main.h" +#include "stir/IO/read_from_file.h" +#include "stir/IO/write_to_file.h" +#include "stir/info.h" +#include "stir/Succeeded.h" +#include "stir/num_threads.h" +#include +#include +#include +#include +#include +#include + +START_NAMESPACE_STIR + +/*! + \ingroup test + \brief Test class for PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin + + This is a somewhat preliminary implementation of a test that compares the result + of GeneralisedObjectiveFunction::compute_gradient + with a numerical gradient computed by using the + GeneralisedObjectiveFunction::compute_objective_function() function. + + The trouble with this is that compute the gradient voxel by voxel is obviously + terribly slow. A solution (for the test) would be to compute it only in + a subset of voxels or so. We'll leave this for later. + + Note that the test only works if the objective function is well-defined. For example, + if certain projections are non-zero, while the model estimates them to be zero, the + Poisson objective function is in theory infinite. + PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin uses some thresholds to try to + avoid overflow, but if there are too many of these bins, the total objective + function will become infinite. The numerical gradient then becomes ill-defined + (even in voxels that do not contribute to these bins). + +*/ +class PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBinTests + : public ObjectiveFunctionTests< + PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin>, + DiscretisedDensity<3, float>> +{ +public: + //! Constructor that can take some input data to run the test with + /*! This makes it possible to run the test with your own data. However, beware that + it is very easy to set up a very long computation. See also the note about + non-zero measured bins. + + \todo it would be better to parse an objective function. That would allow us to set + all parameters from the command line. + */ + PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBinTests(char const* const lm_data_filename, + char const* const density_filename = 0); + void construct_input_data(shared_ptr& density_sptr); + + void run_tests() override; + +protected: + char const* lm_data_filename; + char const* density_filename; + shared_ptr lm_data_sptr; + shared_ptr mult_proj_data_sptr; + shared_ptr add_proj_data_sptr; + shared_ptr> objective_function_sptr; + + //! run the test + void run_tests_for_objective_function(objective_function_type& objective_function, target_type& target); +}; + +PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBinTests:: + PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBinTests(char const* lm_data_filename, + char const* const density_filename) + : lm_data_filename(lm_data_filename), + density_filename(density_filename) +{} + +void +PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBinTests::run_tests_for_objective_function( + objective_function_type& objective_function, target_type& target) +{ + // TODO enable this, but far too slow ATM + // std::cerr << "----- testing Gradient\n"; + // test_gradient("PoissonLLProjData", objective_function, target, 0.01F); + + std::cerr << "----- testing concavity via Hessian-vector product (accumulate_Hessian_times_input)\n"; + test_Hessian_concavity("PoissonLLProjData", objective_function, target); + + std::cerr << "----- testing Hessian-vector product (accumulate_Hessian_times_input)\n"; + test_Hessian("PoissonLLProjData", objective_function, target, 0.5F); +} + +void +PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBinTests::construct_input_data( + shared_ptr& density_sptr) +{ + lm_data_sptr = read_from_file(this->lm_data_filename); + + if (this->density_filename == 0) + { + // construct a small image + + CartesianCoordinate3D origin(0, 0, 0); + const float zoom = 0.03F; + + density_sptr.reset(new VoxelsOnCartesianGrid( + lm_data_sptr->get_exam_info_sptr(), *lm_data_sptr->get_proj_data_info_sptr(), zoom, origin)); + write_to_file("target.hv", *density_sptr); + // fill with random numbers between 0 and 1 + typedef boost::mt19937 base_generator_type; + // initialize by reproducible seed + static base_generator_type generator(boost::uint32_t(42)); + static boost::uniform_01 random01(generator); + for (target_type::full_iterator iter = density_sptr->begin_all(); iter != density_sptr->end_all(); ++iter) + *iter = static_cast(random01()); + } + else + { + shared_ptr aptr(read_from_file(this->density_filename)); + density_sptr = aptr; + } + + // make odd to avoid difficulties with outer-bin that isn't filled-in when using symmetries + { + BasicCoordinate<3, int> min_ind, max_ind; + if (density_sptr->get_regular_range(min_ind, max_ind)) + { + for (int d = 2; d <= 3; ++d) + { + min_ind[d] = std::min(min_ind[d], -max_ind[d]); + max_ind[d] = std::max(-min_ind[d], max_ind[d]); + } + density_sptr->grow(IndexRange<3>(min_ind, max_ind)); + } + } + + auto proj_data_info_sptr = lm_data_sptr->get_proj_data_info_sptr()->create_shared_clone(); + // multiplicative term + shared_ptr bin_norm_sptr(new TrivialBinNormalisation()); + { + + mult_proj_data_sptr.reset(new ProjDataInMemory(lm_data_sptr->get_exam_info_sptr(), proj_data_info_sptr)); + for (int seg_num = proj_data_info_sptr->get_min_segment_num(); seg_num <= proj_data_info_sptr->get_max_segment_num(); + ++seg_num) + { + for (int timing_pos_num = proj_data_info_sptr->get_min_tof_pos_num(); + timing_pos_num <= proj_data_info_sptr->get_max_tof_pos_num(); + ++timing_pos_num) + { + SegmentByView segment = proj_data_info_sptr->get_empty_segment_by_view(seg_num, false, timing_pos_num); + // fill in some crazy values + float value = 0; + for (SegmentByView::full_iterator iter = segment.begin_all(); iter != segment.end_all(); ++iter) + { + value = float(fabs(seg_num * value - .2)); // needs to be positive for Poisson + *iter = value; + } + segment /= 0.5F * segment.find_max(); // normalise to 2 (to avoid large numbers) + mult_proj_data_sptr->set_segment(segment); + } + } + bin_norm_sptr.reset(new BinNormalisationFromProjData(mult_proj_data_sptr)); + } + + // additive term + add_proj_data_sptr.reset(new ProjDataInMemory(lm_data_sptr->get_exam_info_sptr(), proj_data_info_sptr)); + { + for (int seg_num = proj_data_info_sptr->get_min_segment_num(); seg_num <= proj_data_info_sptr->get_max_segment_num(); + ++seg_num) + { + for (int timing_pos_num = proj_data_info_sptr->get_min_tof_pos_num(); + timing_pos_num <= proj_data_info_sptr->get_max_tof_pos_num(); + ++timing_pos_num) + { + SegmentByView segment = proj_data_info_sptr->get_empty_segment_by_view(seg_num, false, timing_pos_num); + // fill in some crazy values + float value = 0; + for (SegmentByView::full_iterator iter = segment.begin_all(); iter != segment.end_all(); ++iter) + { + value = float(fabs(seg_num * value - .3)); // needs to be positive for Poisson + *iter = value; + } + segment /= 0.333F * segment.find_max(); // normalise to 3 (to avoid large numbers) + add_proj_data_sptr->set_segment(segment); + } + } + } + + objective_function_sptr.reset(new PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin); + PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin& objective_function + = reinterpret_cast&>( + *objective_function_sptr); + objective_function.set_input_data(lm_data_sptr); + objective_function.set_use_subset_sensitivities(true); + objective_function.set_max_segment_num_to_process(1); + shared_ptr proj_matrix_sptr(new ProjMatrixByBinUsingRayTracing()); + objective_function.set_proj_matrix(proj_matrix_sptr); + objective_function.set_normalisation_sptr(bin_norm_sptr); + objective_function.set_additive_proj_data_sptr(add_proj_data_sptr); + objective_function.set_num_subsets(2); + if (!check(objective_function.set_up(density_sptr) == Succeeded::yes, "set-up of objective function")) + return; +} + +void +PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBinTests::run_tests() +{ + std::cerr << "Tests for PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin\n"; + const int verbosity_default = Verbosity::get(); + Verbosity::set(2); + +#if 1 + shared_ptr density_sptr; + construct_input_data(density_sptr); + this->run_tests_for_objective_function(*this->objective_function_sptr, *density_sptr); +#else + // alternative that gets the objective function from an OSMAPOSL .par file + // currently disabled + OSMAPOSLReconstruction recon(proj_data_filename); // actually .par + shared_ptr> objective_function_sptr = recon.get_objective_function_sptr(); + if (!check(objective_function_sptr->set_up(recon.get_initial_data_ptr()) == Succeeded::yes, "set-up of objective function")) + return; + this->run_tests_for_objective_function(*objective_function_sptr, *recon.get_initial_data_ptr()); +#endif + Verbosity::set(verbosity_default); +} + +END_NAMESPACE_STIR + +USING_NAMESPACE_STIR + +int +main(int argc, char** argv) +{ + if (argc <= 1) + error("Need to specify a list-mode filename"); + + set_default_num_threads(); + + PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBinTests tests(argv[1], argc > 2 ? argv[2] : 0); + tests.run_tests(); + return tests.main_return_value(); +} From f2f29bd7e44840211663094d4bcf7903f05b63e1 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Mon, 13 May 2024 18:44:37 +0100 Subject: [PATCH 33/78] LM objfun tests slightly better diagnostics --- .../test/ObjectiveFunctionTests.h | 31 +++++++++++-------- ...ihoodWithLinearModelForMeanAndProjData.cxx | 6 ++-- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/src/include/stir/recon_buildblock/test/ObjectiveFunctionTests.h b/src/include/stir/recon_buildblock/test/ObjectiveFunctionTests.h index 4e97dd4693..58a53731c3 100644 --- a/src/include/stir/recon_buildblock/test/ObjectiveFunctionTests.h +++ b/src/include/stir/recon_buildblock/test/ObjectiveFunctionTests.h @@ -106,10 +106,10 @@ ObjectiveFunctionTests::test_gradient(const std::st if (!testOK) { std::cerr << "Numerical gradient test failed with for " + test_name + "\n"; - std::cerr << "Writing diagnostic files target.hv, gradient.hv, numerical_gradient.hv\n"; - write_to_file("target.hv", target); - write_to_file("gradient.hv", *gradient_sptr); - write_to_file("numerical_gradient.hv", *gradient_2_sptr); + std::cerr << "Writing diagnostic files " << test_name << "_target.hv, *gradient.hv, *numerical_gradient.hv\n"; + write_to_file(test_name + "_target.hv", target); + write_to_file(test_name + "_gradient.hv", *gradient_sptr); + write_to_file(test_name + "_numerical_gradient.hv", *gradient_2_sptr); return Succeeded::no; } else @@ -156,12 +156,13 @@ ObjectiveFunctionTests::test_Hessian(const std::str if (!testOK) { std::cerr << "Numerical Hessian test failed with for " + test_name + "\n"; - std::cerr << "Writing diagnostic files target.hv, gradient.hv, increment, numerical_gradient.hv, Hessian_times_increment\n"; - write_to_file("target.hv", target); - write_to_file("gradient.hv", *gradient_sptr); - write_to_file("increment.hv", *increment_sptr); - write_to_file("gradient_at_increment.hv", *gradient_2_sptr); - write_to_file("Hessian_times_increment.hv", *output); + std::cerr << "Writing diagnostic files " << test_name + << "_target.hv, *gradient.hv, *increment, *numerical_gradient.hv, *Hessian_times_increment\n"; + write_to_file(test_name + "_target.hv", target); + write_to_file(test_name + "_gradient.hv", *gradient_sptr); + write_to_file(test_name + "_increment.hv", *increment_sptr); + write_to_file(test_name + "_gradient_at_increment.hv", *gradient_2_sptr); + write_to_file(test_name + "_Hessian_times_increment.hv", *output); return Succeeded::no; } else @@ -195,9 +196,13 @@ ObjectiveFunctionTests::test_Hessian_concavity(cons else { // print to console the FAILED configuration - info("FAIL: " + test_name + ": Computation of x^T H x = " + std::to_string(my_sum) - + " > 0 (Hessian) and is therefore NOT concave" + "\n >target image max=" + std::to_string(target.find_max()) - + "\n >target image min=" + std::to_string(target.find_min())); + std::cerr << "FAIL: " + test_name + ": Computation of x^T H x = " + std::to_string(my_sum) + << " > 0 (Hessian) and is therefore NOT concave" + << "\n >target image max=" << target.find_max() << "\n >target image min=" << target.find_min() + << "\n >output image max=" << output->find_max() << "\n >output image min=" << output->find_min(); + std::cerr << "Writing diagnostic files to " << test_name + "_concavity_out.hv etc\n."; + write_to_file(test_name + "_concavity_out.hv", *output); + write_to_file(test_name + "_target.hv", target); return Succeeded::no; } } diff --git a/src/recon_test/test_PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx b/src/recon_test/test_PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx index 73d47fece6..8fb8ee4a79 100644 --- a/src/recon_test/test_PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx +++ b/src/recon_test/test_PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx @@ -124,16 +124,16 @@ PoissonLogLikelihoodWithLinearModelForMeanAndProjDataTests::run_tests_for_object objective_function_type& objective_function, target_type& target) { std::cerr << "----- testing Gradient\n"; - test_gradient("PoissonLLProjData", objective_function, target, 0.01F); + test_gradient("PoissonLLListModeData", objective_function, target, 0.01F); std::cerr << "----- testing concavity via Hessian-vector product (accumulate_Hessian_times_input)\n"; - test_Hessian_concavity("PoissonLLProjData", objective_function, target); + test_Hessian_concavity("PoissonLLListModeData", objective_function, target); std::cerr << "----- testing approximate-Hessian-vector product (accumulate_Hessian_times_input)\n"; test_approximate_Hessian_concavity(objective_function, target); std::cerr << "----- testing Hessian-vector product (accumulate_Hessian_times_input)\n"; - test_Hessian("PoissonLLProjData", objective_function, target, 0.5F); + test_Hessian("PoissonLLListModeData", objective_function, target, 0.5F); if (!this->is_everything_ok()) { From 735e590a6ddf2bc6935ba4d60ab92c8f6fc5a002 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Mon, 13 May 2024 18:45:13 +0100 Subject: [PATCH 34/78] [GHA] upload ctest artefact if failure Also update to upload-artefacts@v4 --- .github/workflows/build-test.yml | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 8c0cb93cba..93bfde5be0 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -360,6 +360,19 @@ jobs: # execute tests ctest --output-on-failure -C ${BUILD_TYPE} ${EXCLUDE} + - name: Upload ctest log files for debugging + uses: actions/upload-artifact@v4 + if: failure() + with: + name: ctest_log_files-${{ matrix.os }}-${{ matrix.compiler }}${{ matrix.compiler_version }}-cuda${{ matrix.cuda_version }}-${{ matrix.BUILD_TYPE }}-pp=${{ matrix.parallelproj }}-ROOT=${{ matrix.ROOT }} + path: | + ${{ github.workspace }}/build/**/*.log + ${{ github.workspace }}/build/**/*.hv + ${{ github.workspace }}/build/**/*.v + ${{ github.workspace }}/build/**/*.hs + ${{ github.workspace }}/build/**/*.s + retention-days: 7 + - name: C++ examples with STIR_LOCAL shell: bash run: | @@ -432,11 +445,12 @@ jobs: fi - name: Upload recon_test_pack log files for debugging - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: failure() with: name: recon_test_pack_log_files-${{ matrix.os }}-${{ matrix.compiler }}${{ matrix.compiler_version }}-cuda${{ matrix.cuda_version }}-${{ matrix.BUILD_TYPE }}-pp=${{ matrix.parallelproj }}-ROOT=${{ matrix.ROOT }} - path: ${{ github.workspace }}/recon_test_pack/**/*.log + path: | + ${{ github.workspace }}/recon_test_pack/**/*.log ${{ github.workspace }}/recon_test_pack/**/my_*v ${{ github.workspace }}/recon_test_pack/**/my_*s retention-days: 7 From f3daa55b631d08109d73f72f54d4f1f0dfe672c9 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Tue, 14 May 2024 08:36:09 +0100 Subject: [PATCH 35/78] add "increment" test for gradient (enabled for LM data) also minor clean-up --- .../test/ObjectiveFunctionTests.h | 105 ++++++++++++------ src/recon_test/CMakeLists.txt | 1 + ...lForMeanAndListModeWithProjMatrixByBin.cxx | 9 +- ...ihoodWithLinearModelForMeanAndProjData.cxx | 6 +- 4 files changed, 79 insertions(+), 42 deletions(-) diff --git a/src/include/stir/recon_buildblock/test/ObjectiveFunctionTests.h b/src/include/stir/recon_buildblock/test/ObjectiveFunctionTests.h index 58a53731c3..fce90ef360 100644 --- a/src/include/stir/recon_buildblock/test/ObjectiveFunctionTests.h +++ b/src/include/stir/recon_buildblock/test/ObjectiveFunctionTests.h @@ -15,7 +15,7 @@ \brief Test skeleton for stir::GeneralisedObjectiveFunction and stir::GeneralisedPrior \author Kris Thielemans - \author Reobert Twyman Skelly + \author Robert Twyman Skelly */ #include "stir/RunTests.h" @@ -32,10 +32,6 @@ START_NAMESPACE_STIR This contains some numerical tests to check gradient and Hessian calculations. - Note that the gradient is computed numerically voxel by voxel which is obviously - terribly slow. A solution (for the test) would be to compute it only in - a subset of voxels or so. We'll leave this for later. - Note that the test only works if the objective function is well-defined. For example, if certain projections are non-zero, while the model estimates them to be zero, the Poisson objective function is in theory infinite. @@ -53,16 +49,22 @@ class ObjectiveFunctionTests : public RunTests typedef TargetT target_type; //! Test the gradient of the objective function by comparing to the numerical gradient via perturbation - /*! Note: \a target is non-\c const, as the code will add/subtract eps, but the actual values + /*! + If \a full_gradient=true, all elements in the gradient are tested (using single-element increments). This is slow. + Otherwise, the test checks that \f$ G dx \approx F(x+dx) - F(x) \f$. dx is computed via construct_increment(). + + Note: \a target is non-\c const, as the code will add/subtract eps, but the actual values are not modified after the test exits. */ - virtual Succeeded - test_gradient(const std::string& test_name, ObjectiveFunctionT& objective_function, TargetT& target, const float eps); + virtual Succeeded test_gradient(const std::string& test_name, + ObjectiveFunctionT& objective_function, + TargetT& target, + const float eps, + const bool full_gradient = true); //! Test the accumulate_Hessian_times_input of the objective function by comparing to the numerical result via perturbation /*! - This test checks that \f$ H dx \approx G(x+dx) - G(x) \f$. - \f$dx\f$ is currently computed by as eps*(target / target.find_max() + 0.5). + This test checks that \f$ H dx \approx G(x+dx) - G(x) \f$. dx is computed via construct_increment(). */ virtual Succeeded test_Hessian(const std::string& test_name, ObjectiveFunctionT& objective_function, const TargetT& target, const float eps); @@ -71,6 +73,14 @@ class ObjectiveFunctionTests : public RunTests ObjectiveFunctionT& objective_function, const TargetT& target, const float mult_factor = 1.F); + +protected: + //! Construct small increment for target + /*! + Result is eps*(target / target.find_max() + 0.5), i.e. it is always + positive (if target is non-negative). + */ + virtual shared_ptr construct_increment(const TargetT& target, const float eps) const; }; template @@ -78,7 +88,8 @@ Succeeded ObjectiveFunctionTests::test_gradient(const std::string& test_name, ObjectiveFunctionT& objective_function, TargetT& target, - const float eps) + const float eps, + const bool full_gradient) { shared_ptr gradient_sptr(target.get_empty_copy()); shared_ptr gradient_2_sptr(target.get_empty_copy()); @@ -87,29 +98,47 @@ ObjectiveFunctionTests::test_gradient(const std::st this->set_tolerance(std::max(fabs(double(gradient_sptr->find_min())), double(gradient_sptr->find_max())) / 1000); info("Computing objective function at target"); const double value_at_target = objective_function.compute_value(target); - auto target_iter = target.begin_all(); - auto gradient_iter = gradient_sptr->begin_all(); - auto gradient_2_iter = gradient_2_sptr->begin_all(); bool testOK = true; - info("Computing gradient of objective function by numerical differences (this will take a while)"); - while (target_iter != target.end_all()) + if (full_gradient) { - *target_iter += eps; - const double value_at_inc = objective_function.compute_value(target); - *target_iter -= eps; - const float gradient_at_iter = static_cast((value_at_inc - value_at_target) / eps); - *gradient_2_iter++ = gradient_at_iter; - testOK = testOK && this->check_if_equal(gradient_at_iter, *gradient_iter, "gradient"); - ++target_iter; - ++gradient_iter; + info("Computing gradient of objective function by numerical differences (this will take a while)"); + auto target_iter = target.begin_all(); + auto gradient_iter = gradient_sptr->begin_all(); + auto gradient_2_iter = gradient_2_sptr->begin_all(); + while (target_iter != target.end_all()) + { + *target_iter += eps; + const double value_at_inc = objective_function.compute_value(target); + *target_iter -= eps; + const float gradient_at_iter = static_cast((value_at_inc - value_at_target) / eps); + *gradient_2_iter++ = gradient_at_iter; + testOK = testOK && this->check_if_equal(gradient_at_iter, *gradient_iter, "gradient"); + ++target_iter; + ++gradient_iter; + } + } + else + { + /* test f(x+dx) - f(x) ~ dx^t G(x) */ + shared_ptr increment_sptr = this->construct_increment(target, eps); + shared_ptr target_plus_inc_sptr(target.clone()); + *target_plus_inc_sptr += *increment_sptr; + const double value_at_inc = objective_function.compute_value(*target_plus_inc_sptr); + const double my_sum = std::inner_product( + gradient_sptr->begin_all_const(), gradient_sptr->end_all_const(), increment_sptr->begin_all_const(), 0.); + + testOK = testOK && this->check_if_equal(value_at_inc - value_at_target, my_sum, "gradient"); } + if (!testOK) { std::cerr << "Numerical gradient test failed with for " + test_name + "\n"; - std::cerr << "Writing diagnostic files " << test_name << "_target.hv, *gradient.hv, *numerical_gradient.hv\n"; + std::cerr << "Writing diagnostic files " << test_name + << "_target.hv, *gradient.hv (and *numerical_gradient.hv if full gradient test is used)\n"; write_to_file(test_name + "_target.hv", target); write_to_file(test_name + "_gradient.hv", *gradient_sptr); - write_to_file(test_name + "_numerical_gradient.hv", *gradient_2_sptr); + if (full_gradient) + write_to_file(test_name + "_numerical_gradient.hv", *gradient_2_sptr); return Succeeded::no; } else @@ -118,6 +147,16 @@ ObjectiveFunctionTests::test_gradient(const std::st } } +template +shared_ptr +ObjectiveFunctionTests::construct_increment(const TargetT& target, const float eps) const +{ + shared_ptr increment_sptr(target.clone()); + *increment_sptr *= eps / increment_sptr->find_max(); + *increment_sptr += eps / 2; + return increment_sptr; +} + template Succeeded ObjectiveFunctionTests::test_Hessian(const std::string& test_name, @@ -125,13 +164,13 @@ ObjectiveFunctionTests::test_Hessian(const std::str const TargetT& target, const float eps) { + info("Comparing Hessian*dx with difference of gradients"); + /* test G(x+dx) = G(x) + H dx + small stuff */ shared_ptr gradient_sptr(target.get_empty_copy()); shared_ptr gradient_2_sptr(target.get_empty_copy()); shared_ptr output(target.get_empty_copy()); - shared_ptr increment_sptr(target.clone()); - *increment_sptr *= eps / increment_sptr->find_max(); - *increment_sptr += eps / 2; + shared_ptr increment_sptr = this->construct_increment(target, eps); shared_ptr target_plus_inc_sptr(target.clone()); *target_plus_inc_sptr += *increment_sptr; @@ -145,7 +184,6 @@ ObjectiveFunctionTests::test_Hessian(const std::str auto gradient_iter = gradient_sptr->begin_all_const(); auto gradient_2_iter = gradient_2_sptr->begin_all_const(); bool testOK = true; - info("Computing gradient of objective function by numerical differences (this will take a while)"); while (output_iter != output->end_all()) { testOK = testOK && this->check_if_equal(*gradient_2_iter - *gradient_iter, *output_iter, "Hessian*increment"); @@ -187,10 +225,9 @@ ObjectiveFunctionTests::test_Hessian_concavity(cons /// Compute dot(x,(H x)) const float my_sum = std::inner_product(target.begin_all(), target.end_all(), output->begin_all(), 0.F) * mult_factor; - // test for a CONCAVE function + // test for a CONCAVE function (after multiplying with mult_factor of course) if (this->check_if_less(my_sum, 0)) { - // info("PASS: Computation of x^T H x = " + std::to_string(my_sum) + " < 0" (Hessian) and is therefore concave); return Succeeded::yes; } else @@ -199,8 +236,8 @@ ObjectiveFunctionTests::test_Hessian_concavity(cons std::cerr << "FAIL: " + test_name + ": Computation of x^T H x = " + std::to_string(my_sum) << " > 0 (Hessian) and is therefore NOT concave" << "\n >target image max=" << target.find_max() << "\n >target image min=" << target.find_min() - << "\n >output image max=" << output->find_max() << "\n >output image min=" << output->find_min(); - std::cerr << "Writing diagnostic files to " << test_name + "_concavity_out.hv etc\n."; + << "\n >output image max=" << output->find_max() << "\n >output image min=" << output->find_min() << '\n'; + std::cerr << "Writing diagnostic files to " << test_name + "_concavity_out.hv, *target.hv\n"; write_to_file(test_name + "_concavity_out.hv", *output); write_to_file(test_name + "_target.hv", target); return Succeeded::no; diff --git a/src/recon_test/CMakeLists.txt b/src/recon_test/CMakeLists.txt index 6e9e329f13..8a5eaf1694 100644 --- a/src/recon_test/CMakeLists.txt +++ b/src/recon_test/CMakeLists.txt @@ -129,6 +129,7 @@ include(stir_test_exe_targets) # a test that uses MPI create_stir_mpi_test(test_PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx "${STIR_LIBRARIES}" $) +# pass list-mode file as argument ADD_TEST(test_PoissonLogLikelihoodWithLinearModelForMeanAndListModeWithProjMatrixByBin test_PoissonLogLikelihoodWithLinearModelForMeanAndListModeWithProjMatrixByBin "${CMAKE_SOURCE_DIR}/recon_test_pack/PET_ACQ_small.l.hdr.STIR") diff --git a/src/recon_test/test_PoissonLogLikelihoodWithLinearModelForMeanAndListModeWithProjMatrixByBin.cxx b/src/recon_test/test_PoissonLogLikelihoodWithLinearModelForMeanAndListModeWithProjMatrixByBin.cxx index a8c6ba669c..46b2bce257 100644 --- a/src/recon_test/test_PoissonLogLikelihoodWithLinearModelForMeanAndListModeWithProjMatrixByBin.cxx +++ b/src/recon_test/test_PoissonLogLikelihoodWithLinearModelForMeanAndListModeWithProjMatrixByBin.cxx @@ -119,15 +119,14 @@ void PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBinTests::run_tests_for_objective_function( objective_function_type& objective_function, target_type& target) { - // TODO enable this, but far too slow ATM - // std::cerr << "----- testing Gradient\n"; - // test_gradient("PoissonLLProjData", objective_function, target, 0.01F); + std::cerr << "----- testing Gradient\n"; + test_gradient("PoissonLLListModeData", objective_function, target, 0.01F, /* full_gradient = */ false); std::cerr << "----- testing concavity via Hessian-vector product (accumulate_Hessian_times_input)\n"; - test_Hessian_concavity("PoissonLLProjData", objective_function, target); + test_Hessian_concavity("PoissonLLListModeData", objective_function, target); std::cerr << "----- testing Hessian-vector product (accumulate_Hessian_times_input)\n"; - test_Hessian("PoissonLLProjData", objective_function, target, 0.5F); + test_Hessian("PoissonLLListModeData", objective_function, target, 0.5F); } void diff --git a/src/recon_test/test_PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx b/src/recon_test/test_PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx index 8fb8ee4a79..73d47fece6 100644 --- a/src/recon_test/test_PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx +++ b/src/recon_test/test_PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx @@ -124,16 +124,16 @@ PoissonLogLikelihoodWithLinearModelForMeanAndProjDataTests::run_tests_for_object objective_function_type& objective_function, target_type& target) { std::cerr << "----- testing Gradient\n"; - test_gradient("PoissonLLListModeData", objective_function, target, 0.01F); + test_gradient("PoissonLLProjData", objective_function, target, 0.01F); std::cerr << "----- testing concavity via Hessian-vector product (accumulate_Hessian_times_input)\n"; - test_Hessian_concavity("PoissonLLListModeData", objective_function, target); + test_Hessian_concavity("PoissonLLProjData", objective_function, target); std::cerr << "----- testing approximate-Hessian-vector product (accumulate_Hessian_times_input)\n"; test_approximate_Hessian_concavity(objective_function, target); std::cerr << "----- testing Hessian-vector product (accumulate_Hessian_times_input)\n"; - test_Hessian("PoissonLLListModeData", objective_function, target, 0.5F); + test_Hessian("PoissonLLProjData", objective_function, target, 0.5F); if (!this->is_everything_ok()) { From 0be24031b9ddf025c7c735394406cfcf6735c55f Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Tue, 14 May 2024 10:31:28 +0100 Subject: [PATCH 36/78] [GHA] disabled LM objfunc test on MacOS Cannot figure out what the problem is, but the test works on Ubuntu and Windows --- .github/workflows/build-test.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 93bfde5be0..c7d1576e0b 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -350,7 +350,10 @@ jobs: fi # don't run all of them in Debug mode as it takes too long if test ${BUILD_TYPE} = Debug; then - EXCLUDE_Debug="test_data_processor_projectors|test_export_array|test_ArcCorrection" + # Also excluding test_PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin + # as it is a bit slow. Moreover it fails on MacOS (Debug) + # https://github.com/UCL/STIR/pull/1418#issuecomment-2109518132 + EXCLUDE_Debug="test_data_processor_projectors|test_export_array|test_ArcCorrection|test_PoissonLogLikelihoodWithLinearModelForMeanAndListModeWithProjMatrixByBin" EXCLUDE="${EXCLUDE_Debug}${EXCLUDE:+"|"}${EXCLUDE}" fi # prepend -E From db1d7b7eeea4f8ba819aa992ac9043993cab57a2 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Tue, 14 May 2024 14:37:12 +0100 Subject: [PATCH 37/78] fix return value FastErf::get_maximum_sample_value was int, now double Fixes #1421 --- src/include/stir/numerics/FastErf.h | 2 +- src/include/stir/numerics/FastErf.inl | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/include/stir/numerics/FastErf.h b/src/include/stir/numerics/FastErf.h index abc6d13ce5..b5cea6eb86 100644 --- a/src/include/stir/numerics/FastErf.h +++ b/src/include/stir/numerics/FastErf.h @@ -65,7 +65,7 @@ class FastErf inline void set_num_samples(int num_samples); //! Returns the maximum sample value - inline int get_maximum_sample_value() const; + inline double get_maximum_sample_value() const; //! Sets the maximum sample value inline void set_maximum_sample_value(double maximum_sample_value); diff --git a/src/include/stir/numerics/FastErf.inl b/src/include/stir/numerics/FastErf.inl index 7fb19d7678..e2f941e59b 100644 --- a/src/include/stir/numerics/FastErf.inl +++ b/src/include/stir/numerics/FastErf.inl @@ -18,7 +18,7 @@ START_NAMESPACE_STIR -inline void +void FastErf::set_up() { this->_sampling_period = (2 * this->_maximum_sample_value) / this->get_num_samples(); @@ -36,7 +36,7 @@ FastErf::set_up() // this->_is_setup = true; } -inline double +double FastErf::get_erf_BSplines_interpolation(double xp) const { #if 0 @@ -47,7 +47,7 @@ FastErf::get_erf_BSplines_interpolation(double xp) const return this->_spline.BSplines((xp + this->_maximum_sample_value) / this->_sampling_period); } -inline double +double FastErf::get_erf_linear_interpolation(double xp) const { #if 0 @@ -65,7 +65,7 @@ FastErf::get_erf_linear_interpolation(double xp) const return erf_values_vec[lower] + (xp_in_index - lower) * (erf_values_vec[lower + 1] - erf_values_vec[lower]); } -inline double +double FastErf::get_erf_nearest_neighbour_interpolation(double xp) const { #if 0 @@ -77,19 +77,19 @@ FastErf::get_erf_nearest_neighbour_interpolation(double xp) const return erf_values_vec[static_cast(std::round((xp + this->_maximum_sample_value) / this->_sampling_period))]; } -inline void +void FastErf::set_num_samples(const int num_samples) { this->_num_samples = num_samples; } -inline int +int FastErf::get_num_samples() const { return this->_num_samples; } -int +double FastErf::get_maximum_sample_value() const { return this->_maximum_sample_value; From 5864938fa8dcfde91cc63a4199f767f1ee181a99 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Tue, 14 May 2024 18:12:41 +0100 Subject: [PATCH 38/78] [CMake] force C++ version for more recent ROOT --- CMakeLists.txt | 8 +++++++- documentation/release_6.1.htm | 5 +++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 14ba3da322..ba37282df6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -121,7 +121,13 @@ if(NOT DISABLE_CERN_ROOT) find_package(CERN_ROOT) if (CERN_ROOT_FOUND) message(STATUS "ROOT Version: ${CERN_ROOT_VERSION}") - if (${CERN_ROOT_VERSION} VERSION_GREATER 6.23.99) + if (${CERN_ROOT_VERSION} VERSION_GREATER 6.29.99) + message(STATUS "ROOT Version is >= 6.30. Setting the minimum CXX version to 20.") + UseCXX(20) + elseif (${CERN_ROOT_VERSION} VERSION_GREATER 6.27.99) + message(STATUS "ROOT Version is >= 6.28. Setting the minimum CXX version to 17.") + UseCXX(17) + elseif (${CERN_ROOT_VERSION} VERSION_GREATER 6.23.99) message(STATUS "ROOT Version is >= 6.24. Setting the minimum CXX version to 14.") UseCXX(14) endif() diff --git a/documentation/release_6.1.htm b/documentation/release_6.1.htm index f0512e672d..3dd09ff393 100644 --- a/documentation/release_6.1.htm +++ b/documentation/release_6.1.htm @@ -135,8 +135,9 @@

    Other code changes

    Build system

      -
    • -
    • +
    • + Force C++ version according to CERN ROOT versions: ROOT 6.28.10 needs C++17 and 6.30.2 needs C++20. +

    Test changes

    From c813638064a4d7644382773b6c2134ae5981f7db Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Tue, 14 May 2024 22:13:56 +0100 Subject: [PATCH 39/78] update to 6.1 and minor doc improvements --- CMakeLists.txt | 4 ++-- VERSION.txt | 2 +- documentation/STIR-UsersGuide.tex | 5 +++-- documentation/STIR-developers-overview.tex | 20 +++++++++++++------- 4 files changed, 19 insertions(+), 12 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ba37282df6..2511aa596f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,9 +54,9 @@ endif() ####### Set Version number etc set(VERSION_MAJOR 6) -set(VERSION_MINOR 0) +set(VERSION_MINOR 1) set(VERSION_PATCH 0) -set(VERSION 060000) # only used in STIRConfig.h.in and swig/CMakeLists.txt +set(VERSION 060100) # only used in STIRConfig.h.in and swig/CMakeLists.txt set(STIR_VERSION ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}) diff --git a/VERSION.txt b/VERSION.txt index 09b254e90c..dfda3e0b4f 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -6.0.0 +6.1.0 diff --git a/documentation/STIR-UsersGuide.tex b/documentation/STIR-UsersGuide.tex index fd404074a3..975d03d006 100644 --- a/documentation/STIR-UsersGuide.tex +++ b/documentation/STIR-UsersGuide.tex @@ -44,7 +44,7 @@ \\[3cm] \textbf{{\huge User's Guide\\ - Version 6.0}} + Version 6.1}} \end{center} \end{spacing} @@ -444,7 +444,8 @@ \subsubsection{ { \subsubsubsection{Cygwin on Windows} } -\textit{Cygwin support has not been tested since about 2018 but likely still works. Instructions below might be out-of-date though.} +\textit{Cygwin support has not been tested since about 2018 but likely still works. Instructions below are likely out-of-date though. +We highly recommend to use WSL instead.} If you are using Windows but would like to have nearly everything that Linux/Unix has to offer, Cygwin could help. Check out http://cygwin.com. diff --git a/documentation/STIR-developers-overview.tex b/documentation/STIR-developers-overview.tex index 85e07a3cea..9297a938a6 100644 --- a/documentation/STIR-developers-overview.tex +++ b/documentation/STIR-developers-overview.tex @@ -60,8 +60,8 @@ \section{ doxygen on the source files that come in the STIR distribution). \subsection{Language support} -STIR is written in C++ and currently requires C++-11, but it is compatible with newer versions of the C++ standard. -We will enforce C++-14 from STIR 6.0. +STIR is written in C++ and currently requires C++-14, but it is compatible with newer versions of the C++ standard. +We will enforce C++-16 from STIR 6.2. Python and MATLAB support is provided via \R2Lurl{http://www.swig.org/}{SWIG}. This means that Python/MATLAB interfaces follow the C++ classes closely, although some differences are required as these languages do not support templates for instance. @@ -170,8 +170,8 @@ \subsection{ view. \end{itemize} -Note that in STIR version 6.0, Time-of-Flight (TOF) will be supported. This introduces another -index. However, \texttt{Sinogram} and \texttt{Viewgram} will remain 2D objects, and \texttt{Segment*} 3D. +Note that since STIR version 6.0, Time-of-Flight (TOF) is supported. This introduces another +index. However, \texttt{Sinogram} and \texttt{Viewgram} remain 2D objects, and \texttt{Segment*} 3D. This will also be the case once we have layers and energy windows. In STIR 5.2, we have therefore introduced new classes \texttt{SinogramIndices}, \texttt{ViewgramIndices}\footnote{Replacing \texttt{ViewSegmentNumbers} in previous versions of STIR.} @@ -897,7 +897,7 @@ \subsubsection{ \end{itemize} -At the moment, we have three derived classes: +At the moment, we have the following derived classes: \begin{itemize} \item \texttt{ProjMatrixByBinUsingRayTracing} uses essentially the same @@ -916,6 +916,10 @@ \subsubsection{ part of the projection matrix needs to be stored. Our current implementation does not yet provide a very compact format for storing the elements (although they are of course stored sparsely). +\item +\texttt{ProjMatrixByBinSPECTUB} is for parallel hole SPECT +\item +\texttt{ProjMatrixByBinPinholeSPECTUB} is for multi-pinhole SPECT \end{itemize} \begin{figure}[htbp] @@ -956,8 +960,10 @@ \subsection{Objective functions} that is computed is generally not the gradient of the log-likelihood that corresponds to the forward projector. However, one hopes that it still points towards the optimum. -The corresponding objective function is implemented in the class\\ -\texttt{PoissonLogLikelihoodWithLinearModelForMeanAndProjData}. +The corresponding objective function is implemented in the classes\\ +\texttt{PoissonLogLikelihoodWithLinearModelForMeanAndProjData} for +projection data and \texttt{PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin} +for list-mode data. There are classes for dynamic and gated data as well. There can be different objective function that use common operations. For instance, the objective function could implement a least squares From a7bbe64729d99357c545b333f67966fcadf47026 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Wed, 15 May 2024 11:50:56 +0100 Subject: [PATCH 40/78] minor changes preparing for 6.1 [ci skip] --- documentation/release_6.1.htm | 64 ++++++++-------------- examples/python/.gitignore | 2 + recon_test_pack/README.txt | 2 +- recon_test_pack/run_test_listmode_recon.sh | 2 +- 4 files changed, 28 insertions(+), 42 deletions(-) create mode 100644 examples/python/.gitignore diff --git a/documentation/release_6.1.htm b/documentation/release_6.1.htm index 3dd09ff393..6fc96a47e7 100644 --- a/documentation/release_6.1.htm +++ b/documentation/release_6.1.htm @@ -7,27 +7,30 @@

    Summary of changes in STIR release 6.1

    -

    This version is 95% backwards compatible with STIR 6.0 for the user (see below). -Developers might need to make code changes as -detailed below. -

    -

    Overall summary

    -

    -

    +

    + This version is 100% backwards compatible with STIR 6.0 for the user, except for the bug-fix in the RDP (see below). +

    +

    Overall summary

    +

    + This version adds capability of using Parallelproj (CPU and GPU versions) for TOF data. In addition, + the list-mode objective function has several improvements, including speed-up by using multi-threading + if caching was not enabled. +

    -

    Of course, there is also the usual code-cleanup and -improvements to the documentation. -

    +

    + Of course, there is also the usual code-cleanup and improvements to the documentation. +

    -

    This release contains mainly code written by Kris Thielemans (UCL). -

    +

    + This release contains mainly code written by Nicole Jurjew (UCL) and Kris Thielemans (UCL). +

    Patch release info

    • - 6.1.0 released ?/?/2020
      - GitHub Milestone 6.0 + 6.1.0 released 15/05/2024
      + GitHub Milestone 6.1
    • -Last modified: 7 February 2024 +Last modified: 16 May 2024 From 4439d5f3fdd73b3deb98b83aedc255eecc630a4d Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Thu, 16 May 2024 13:47:02 +0100 Subject: [PATCH 50/78] change comment to avoid confusing doxygen [ci skip] Using /// means that doxygen will interpret it, which is not what we wanted here. --- src/recon_buildblock/QuadraticPrior.cxx | 2 +- src/recon_buildblock/RelativeDifferencePrior.cxx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/recon_buildblock/QuadraticPrior.cxx b/src/recon_buildblock/QuadraticPrior.cxx index f9c8561986..2a070a294f 100644 --- a/src/recon_buildblock/QuadraticPrior.cxx +++ b/src/recon_buildblock/QuadraticPrior.cxx @@ -655,7 +655,7 @@ QuadraticPrior::accumulate_Hessian_times_input(DiscretisedDensity<3, elem const int min_dx = max(weights[0][0].get_min_index(), min_x - x); const int max_dx = min(weights[0][0].get_max_index(), max_x - x); - /// At this point, we have j = [z][y][x] + // At this point, we have j = [z][y][x] // The next for loops will have k = [z+dz][y+dy][x+dx] // The following computes //(H_{wf} y)_j = diff --git a/src/recon_buildblock/RelativeDifferencePrior.cxx b/src/recon_buildblock/RelativeDifferencePrior.cxx index c61fe1be04..072bcaf376 100644 --- a/src/recon_buildblock/RelativeDifferencePrior.cxx +++ b/src/recon_buildblock/RelativeDifferencePrior.cxx @@ -577,7 +577,7 @@ RelativeDifferencePrior::accumulate_Hessian_times_input(DiscretisedDensit const int min_dx = max(weights[0][0].get_min_index(), min_x - x); const int max_dx = min(weights[0][0].get_max_index(), max_x - x); - /// At this point, we have j = [z][y][x] + // At this point, we have j = [z][y][x] // The next for loops will have k = [z+dz][y+dy][x+dx] // The following computes //(H_{wf} y)_j = From 0d6516b9a22fbd92d5d0b1e9fc43f79d76dce4f1 Mon Sep 17 00:00:00 2001 From: NikEfth Date: Thu, 16 May 2024 22:50:19 -0400 Subject: [PATCH 51/78] New utility to convert ProjData to FanProjData3D. * Also minor corrections. --- src/buildblock/ML_norm.cxx | 40 ++++++++--- src/buildblock/Scanner.cxx | 2 +- src/include/stir/ML_norm.h | 2 +- src/utilities/CMakeLists.txt | 1 + .../construct_fanProjData_fromProjData.cxx | 70 +++++++++++++++++++ 5 files changed, 103 insertions(+), 12 deletions(-) create mode 100644 src/utilities/construct_fanProjData_fromProjData.cxx diff --git a/src/buildblock/ML_norm.cxx b/src/buildblock/ML_norm.cxx index bc8c109109..0916953782 100644 --- a/src/buildblock/ML_norm.cxx +++ b/src/buildblock/ML_norm.cxx @@ -1090,19 +1090,27 @@ make_fan_data_remove_gaps_help(FanProjData& fan_data, fan_data = FanProjData(num_physical_rings, num_physical_detectors_per_ring, new_max_delta, 2 * new_half_fan_size + 1); shared_ptr> segment_ptr; - Bin bin; - for (bin.segment_num() = proj_data.get_min_segment_num(); bin.segment_num() <= proj_data.get_max_segment_num(); - ++bin.segment_num()) + int max_views = num_detectors_per_ring / 2; + for (int iseg = proj_data.get_min_segment_num(); iseg <= proj_data.get_max_segment_num(); + ++iseg) { - segment_ptr.reset(new SegmentBySinogram(proj_data.get_segment_by_sinogram(bin.segment_num()))); - - for (bin.axial_pos_num() = proj_data.get_min_axial_pos_num(bin.segment_num()); - bin.axial_pos_num() <= proj_data.get_max_axial_pos_num(bin.segment_num()); - ++bin.axial_pos_num()) - for (bin.view_num() = 0; bin.view_num() < num_detectors_per_ring / 2; bin.view_num()++) - for (bin.tangential_pos_num() = -half_fan_size; bin.tangential_pos_num() <= half_fan_size; ++bin.tangential_pos_num()) + segment_ptr.reset(new SegmentBySinogram(proj_data.get_segment_by_sinogram(iseg))); + std::cout << " > Segment num: " << iseg<< std::endl; + int min_axial = proj_data.get_min_axial_pos_num(iseg); + int max_axial = proj_data.get_max_axial_pos_num(iseg); +#ifdef STIR_OPENMP +#pragma omp parallel for collapse(3) schedule(dynamic) +#endif + for (int iaxial = min_axial; iaxial <=max_axial ; ++iaxial) + { + for (int iview = 0; iview < max_views; iview++) + { + for (int itang = -half_fan_size; itang <= half_fan_size; ++itang) { + + Bin bin(iseg, iview, iaxial, itang); + int ra = 0, a = 0; int rb = 0, b = 0; @@ -1128,9 +1136,21 @@ make_fan_data_remove_gaps_help(FanProjData& fan_data, continue; int new_rb = rb - (rb / num_axial_crystals_per_block) * num_virtual_axial_crystals_per_block; +#ifdef STIR_OPENMP +# pragma omp critical(FANPROJDATAWRITE) +#endif + try + { fan_data(new_ra, new_a, new_rb, new_b) = fan_data(new_rb, new_b, new_ra, new_a) = (*segment_ptr)[bin.axial_pos_num()][bin.view_num()][bin.tangential_pos_num()]; + } + catch(...) + { + + } } + } + } } } diff --git a/src/buildblock/Scanner.cxx b/src/buildblock/Scanner.cxx index dd6ef256ad..6eb62abd8a 100644 --- a/src/buildblock/Scanner.cxx +++ b/src/buildblock/Scanner.cxx @@ -1248,7 +1248,7 @@ Scanner::Scanner(Type scanner_type) 5, // int num_transaxial_blocks_per_bucket_v, 6, // int num_axial_crystals_per_block_v, 7,// int num_transaxial_crystals_per_block_v, - 252, // int num_axial_crystals_per_singles_unit_v, + 84, // int num_axial_crystals_per_singles_unit_v, 35, // int num_transaxial_crystals_per_singles_unit_v, 1, 0.0F, 511.F, diff --git a/src/include/stir/ML_norm.h b/src/include/stir/ML_norm.h index 5806012e87..917ba27041 100644 --- a/src/include/stir/ML_norm.h +++ b/src/include/stir/ML_norm.h @@ -160,7 +160,7 @@ class GeoData3D : public Array<4, float> int num_detectors_per_ring; }; -class FanProjData : private Array<4, float> +class FanProjData : public Array<4, float> { public: FanProjData(); diff --git a/src/utilities/CMakeLists.txt b/src/utilities/CMakeLists.txt index 98259cd137..4664423637 100644 --- a/src/utilities/CMakeLists.txt +++ b/src/utilities/CMakeLists.txt @@ -21,6 +21,7 @@ set(${dir_EXE_SOURCES} manip_projdata.cxx display_projdata.cxx do_linear_regression.cxx + construct_fanProjData_fromProjData postfilter.cxx compare_projdata.cxx compare_image.cxx diff --git a/src/utilities/construct_fanProjData_fromProjData.cxx b/src/utilities/construct_fanProjData_fromProjData.cxx new file mode 100644 index 0000000000..70434fd68b --- /dev/null +++ b/src/utilities/construct_fanProjData_fromProjData.cxx @@ -0,0 +1,70 @@ +// +// +/*! + \file + \ingroup utilities + +\brief Construct FanProjData from ProjData + +\author Nikos Efthimiou + + */ + +#include "stir/ProjDataInfoCylindricalNoArcCorr.h" +#include "stir/ProjData.h" +#include "stir/utilities.h" +#include "stir/NumericType.h" +#include "stir/ByteOrder.h" +#include "stir/ParsingObject.h" +#include "stir/FilePath.h" +#include "stir/ML_norm.h" +#include "stir/IO/write_data.h" + +#include +#include +#include "stir/warning.h" +#include "stir/error.h" + +using std::ios; +using std::iostream; +using std::streamoff; +using std::fstream; +using std::cout; +using std::cerr; +using std::endl; +using std::vector; + +USING_NAMESPACE_STIR + +static void print_usage_and_exit() +{ + std::cerr << "\nUsage construct_fanProjData_fromProjData [output_filename] [input_projdata_filename]\n" < in_proj_data_sptr = ProjData::read_from_file(argv[2]); + + const std::string output_file_name = argv[1]; + FanProjData out_fan_data; + make_fan_data_remove_gaps(out_fan_data, *in_proj_data_sptr); + + shared_ptr fan_stream( + new std::fstream(output_file_name, std::ios::out | std::ios::binary)); + // Array<1, float> value(1); + float scale = 1.f; + + info("Writing to disk..."); + if(write_data(*fan_stream, out_fan_data, NumericType::Type::FLOAT,scale, + ByteOrder::Order::little_endian) == Succeeded::no) + error("Error writing FanProjData\n"); + + info("Finished writing."); + return EXIT_SUCCESS; +} From 5c1af3f9f3d8f5fc2257cfe98044958e6a57e33b Mon Sep 17 00:00:00 2001 From: NikEfth Date: Fri, 17 May 2024 01:29:36 -0400 Subject: [PATCH 52/78] Added option to load the model fanprojdata from the disk. *. This saves us the time to convert the model projdata to fansums. --- src/buildblock/ML_norm.cxx | 181 +++++++++++++----- src/include/stir/ML_norm.h | 4 +- ...L_estimate_component_based_normalisation.h | 5 +- ...estimate_component_based_normalisation.cxx | 18 +- src/utilities/find_ML_normfactors3D.cxx | 41 +++- 5 files changed, 196 insertions(+), 53 deletions(-) diff --git a/src/buildblock/ML_norm.cxx b/src/buildblock/ML_norm.cxx index 0916953782..ba5e36bc34 100644 --- a/src/buildblock/ML_norm.cxx +++ b/src/buildblock/ML_norm.cxx @@ -29,6 +29,9 @@ #include "stir/stream.h" #include "stir/warning.h" #include "stir/error.h" +#include "stir/NumericType.h" +#include "stir/ByteOrder.h" +#include "stir/IO/read_data.h" #ifdef STIR_OPENMP # include @@ -1058,7 +1061,8 @@ make_fan_data_remove_gaps_help(FanProjData& fan_data, int max_delta, int fan_size, const TProjDataInfo& proj_data_info, - const ProjData& proj_data) + const ProjData& proj_data, + const std::string read_from_filename) { if (proj_data.get_proj_data_info_sptr()->is_tof_data()) error("make_fan_data: Incompatible with TOF data. Abort."); @@ -1089,73 +1093,158 @@ make_fan_data_remove_gaps_help(FanProjData& fan_data, const int num_physical_rings = num_rings - (num_axial_blocks - 1) * num_virtual_axial_crystals_per_block; fan_data = FanProjData(num_physical_rings, num_physical_detectors_per_ring, new_max_delta, 2 * new_half_fan_size + 1); - shared_ptr> segment_ptr; + if(read_from_filename != "") + { + info("Loading model fansums from the disk ..."); + shared_ptr fan_stream( + new std::fstream(read_from_filename, std::ios::in | std::ios::binary)); + + float scale = 1.f; - int max_views = num_detectors_per_ring / 2; - for (int iseg = proj_data.get_min_segment_num(); iseg <= proj_data.get_max_segment_num(); - ++iseg) + info("Reading from disk..."); + if(read_data(*fan_stream, fan_data, NumericType::Type::FLOAT,scale, + ByteOrder::Order::little_endian) == Succeeded::no) + error("Error writing FanProjData\n"); + } + else { - segment_ptr.reset(new SegmentBySinogram(proj_data.get_segment_by_sinogram(iseg))); - std::cout << " > Segment num: " << iseg<< std::endl; - int min_axial = proj_data.get_min_axial_pos_num(iseg); - int max_axial = proj_data.get_max_axial_pos_num(iseg); + + shared_ptr> segment_ptr; + + int max_views = num_detectors_per_ring / 2; + for (int iseg = proj_data.get_min_segment_num(); iseg <= proj_data.get_max_segment_num(); + ++iseg) + { + segment_ptr.reset(new SegmentBySinogram(proj_data.get_segment_by_sinogram(iseg))); + std::cout << " > Segment num: " << iseg<< std::endl; + int min_axial = proj_data.get_min_axial_pos_num(iseg); + int max_axial = proj_data.get_max_axial_pos_num(iseg); #ifdef STIR_OPENMP #pragma omp parallel for collapse(3) schedule(dynamic) #endif - for (int iaxial = min_axial; iaxial <=max_axial ; ++iaxial) - { - for (int iview = 0; iview < max_views; iview++) - { - for (int itang = -half_fan_size; itang <= half_fan_size; ++itang) + for (int iaxial = min_axial; iaxial <=max_axial ; ++iaxial) { + for (int iview = 0; iview < max_views; iview++) + { + for (int itang = -half_fan_size; itang <= half_fan_size; ++itang) + { - Bin bin(iseg, iview, iaxial, itang); + Bin bin(iseg, iview, iaxial, itang); - int ra = 0, a = 0; - int rb = 0, b = 0; + int ra = 0, a = 0; + int rb = 0, b = 0; - proj_data_info.get_det_pair_for_bin(a, ra, b, rb, bin); - int a_in_block = a % num_transaxial_crystals_per_block; + proj_data_info.get_det_pair_for_bin(a, ra, b, rb, bin); + int a_in_block = a % num_transaxial_crystals_per_block; - if (a_in_block >= num_physical_transaxial_crystals_per_block) - continue; - int new_a = a - (a / num_transaxial_crystals_per_block) * num_virtual_transaxial_crystals_per_block; + if (a_in_block >= num_physical_transaxial_crystals_per_block) + continue; + int new_a = a - (a / num_transaxial_crystals_per_block) * num_virtual_transaxial_crystals_per_block; - int ra_in_block = ra % num_axial_crystals_per_block; - if (ra_in_block >= num_physical_axial_crystals_per_block) - continue; - int new_ra = ra - (ra / num_axial_crystals_per_block) * num_virtual_axial_crystals_per_block; + int ra_in_block = ra % num_axial_crystals_per_block; + if (ra_in_block >= num_physical_axial_crystals_per_block) + continue; + int new_ra = ra - (ra / num_axial_crystals_per_block) * num_virtual_axial_crystals_per_block; - int b_in_block = b % num_transaxial_crystals_per_block; - if (b_in_block >= num_physical_transaxial_crystals_per_block) - continue; - int new_b = b - (b / num_transaxial_crystals_per_block) * num_virtual_transaxial_crystals_per_block; + int b_in_block = b % num_transaxial_crystals_per_block; + if (b_in_block >= num_physical_transaxial_crystals_per_block) + continue; + int new_b = b - (b / num_transaxial_crystals_per_block) * num_virtual_transaxial_crystals_per_block; - int rb_in_block = rb % num_axial_crystals_per_block; - if (rb_in_block >= num_physical_axial_crystals_per_block) - continue; - int new_rb = rb - (rb / num_axial_crystals_per_block) * num_virtual_axial_crystals_per_block; + int rb_in_block = rb % num_axial_crystals_per_block; + if (rb_in_block >= num_physical_axial_crystals_per_block) + continue; + int new_rb = rb - (rb / num_axial_crystals_per_block) * num_virtual_axial_crystals_per_block; #ifdef STIR_OPENMP # pragma omp critical(FANPROJDATAWRITE) #endif - try - { - fan_data(new_ra, new_a, new_rb, new_b) = fan_data(new_rb, new_b, new_ra, new_a) - = (*segment_ptr)[bin.axial_pos_num()][bin.view_num()][bin.tangential_pos_num()]; - } - catch(...) - { - + try + { + fan_data(new_ra, new_a, new_rb, new_b) = fan_data(new_rb, new_b, new_ra, new_a) + = (*segment_ptr)[bin.axial_pos_num()][bin.view_num()][bin.tangential_pos_num()]; + } + catch(...) + { + + } + } } } - } } } } + +void +load_fan_data(FanProjData& fan_data, const ProjData& proj_data, + const std::string fan_filename) +{ + int num_rings; + int num_detectors_per_ring; + int fan_size; + int max_delta; + + if (proj_data.get_proj_data_info_sptr()->is_tof_data()) + error("make_fan_data: Incompatible with TOF data. Abort."); + + const ProjDataInfo& proj_data_info = *proj_data.get_proj_data_info_sptr(); + get_fan_info(num_rings, num_detectors_per_ring, max_delta, fan_size, proj_data_info); + + // if (proj_data.get_proj_data_info_sptr()->get_scanner_ptr()->get_scanner_geometry() == "Cylindrical") + // { + // auto proj_data_info_ptr = dynamic_cast(&proj_data_info); + // } + // else + // { + // auto proj_data_info_ptr = dynamic_cast(&proj_data_info); + // } + + if (proj_data.get_proj_data_info_sptr()->is_tof_data()) + error("make_fan_data: Incompatible with TOF data. Abort."); + + const int half_fan_size = fan_size / 2; + const int num_virtual_axial_crystals_per_block = proj_data_info.get_scanner_sptr()->get_num_virtual_axial_crystals_per_block(); + + const int num_virtual_transaxial_crystals_per_block + = proj_data_info.get_scanner_sptr()->get_num_virtual_transaxial_crystals_per_block(); + + const int num_transaxial_blocks = proj_data_info.get_scanner_sptr()->get_num_transaxial_blocks(); + const int num_axial_blocks = proj_data_info.get_scanner_sptr()->get_num_axial_blocks(); + const int num_transaxial_crystals_per_block = proj_data_info.get_scanner_sptr()->get_num_transaxial_crystals_per_block(); + const int num_axial_crystals_per_block = proj_data_info.get_scanner_sptr()->get_num_axial_crystals_per_block(); + + // const int num_physical_transaxial_crystals_per_block + // = num_transaxial_crystals_per_block - num_virtual_transaxial_crystals_per_block; + + // const int num_physical_axial_crystals_per_block = num_axial_crystals_per_block - num_virtual_axial_crystals_per_block; + + const int num_transaxial_blocks_in_fansize = fan_size / (num_transaxial_crystals_per_block); + const int new_fan_size = fan_size - num_transaxial_blocks_in_fansize * num_virtual_transaxial_crystals_per_block; + const int new_half_fan_size = new_fan_size / 2; + const int num_axial_blocks_in_max_delta = max_delta / (num_axial_crystals_per_block); + const int new_max_delta = max_delta - (num_axial_blocks_in_max_delta)*num_virtual_axial_crystals_per_block; + const int num_physical_detectors_per_ring + = num_detectors_per_ring - num_transaxial_blocks * num_virtual_transaxial_crystals_per_block; + const int num_physical_rings = num_rings - (num_axial_blocks - 1) * num_virtual_axial_crystals_per_block; + fan_data = FanProjData(num_physical_rings, num_physical_detectors_per_ring, new_max_delta, 2 * new_half_fan_size + 1); + + info("Loading model fansums from the disk ..."); + shared_ptr fan_stream( + new std::fstream(fan_filename, std::ios::in | std::ios::binary)); + + float scale = 1.f; + + info("Reading from disk..."); + if(read_data(*fan_stream, fan_data, NumericType::Type::FLOAT,scale, + ByteOrder::Order::little_endian) == Succeeded::no) + error("Error writing FanProjData\n"); + +} + void -make_fan_data_remove_gaps(FanProjData& fan_data, const ProjData& proj_data) +make_fan_data_remove_gaps(FanProjData& fan_data, const ProjData& proj_data, + const std::string model_fan_filename) { int num_rings; int num_detectors_per_ring; @@ -1173,14 +1262,14 @@ make_fan_data_remove_gaps(FanProjData& fan_data, const ProjData& proj_data) auto proj_data_info_ptr = dynamic_cast(&proj_data_info); make_fan_data_remove_gaps_help( - fan_data, num_rings, num_detectors_per_ring, max_delta, fan_size, *proj_data_info_ptr, proj_data); + fan_data, num_rings, num_detectors_per_ring, max_delta, fan_size, *proj_data_info_ptr, proj_data, model_fan_filename); } else { auto proj_data_info_ptr = dynamic_cast(&proj_data_info); make_fan_data_remove_gaps_help( - fan_data, num_rings, num_detectors_per_ring, max_delta, fan_size, *proj_data_info_ptr, proj_data); + fan_data, num_rings, num_detectors_per_ring, max_delta, fan_size, *proj_data_info_ptr, proj_data, model_fan_filename); } } diff --git a/src/include/stir/ML_norm.h b/src/include/stir/ML_norm.h index 917ba27041..ca6a2fd366 100644 --- a/src/include/stir/ML_norm.h +++ b/src/include/stir/ML_norm.h @@ -205,7 +205,9 @@ class FanProjData : public Array<4, float> typedef FanProjData BlockData3D; -void make_fan_data_remove_gaps(FanProjData& fan_data, const ProjData& proj_data); +void make_fan_data_remove_gaps(FanProjData& fan_data, const ProjData& proj_data, const std::string model_fan_filename = ""); + +void load_fan_data(FanProjData& fan_data, const ProjData& proj_data, const std::string fan_filename = ""); void set_fan_data_add_gaps(ProjData& proj_data, const FanProjData& fan_data, const float gap_value = 0.F); diff --git a/src/include/stir/recon_buildblock/ML_estimate_component_based_normalisation.h b/src/include/stir/recon_buildblock/ML_estimate_component_based_normalisation.h index f7fa35ce6e..206a4d335d 100644 --- a/src/include/stir/recon_buildblock/ML_estimate_component_based_normalisation.h +++ b/src/include/stir/recon_buildblock/ML_estimate_component_based_normalisation.h @@ -32,6 +32,9 @@ void ML_estimate_component_based_normalisation(const std::string& out_filename_p bool do_block, bool do_symmetry_per_block, bool do_KL, - bool do_display); + bool do_display, + bool use_lm_cache, + bool use_mode_fansums, + std::string model_fansums_filename); END_NAMESPACE_STIR diff --git a/src/recon_buildblock/ML_estimate_component_based_normalisation.cxx b/src/recon_buildblock/ML_estimate_component_based_normalisation.cxx index a22ed6b0b9..10cf79f5e4 100644 --- a/src/recon_buildblock/ML_estimate_component_based_normalisation.cxx +++ b/src/recon_buildblock/ML_estimate_component_based_normalisation.cxx @@ -32,6 +32,10 @@ #include #include #include +#include "stir/IO/read_data.h" +#include "stir/FilePath.h" +#include +#include START_NAMESPACE_STIR @@ -45,7 +49,10 @@ ML_estimate_component_based_normalisation(const std::string& out_filename_prefix bool do_block, bool do_symmetry_per_block, bool do_KL, - bool do_display) + bool do_display, + bool use_lm_cache, + bool use_model_fansums, + std::string model_fansums_filename) { const int num_transaxial_blocks = measured_data.get_proj_data_info_sptr()->get_scanner_sptr()->get_num_transaxial_blocks(); @@ -101,7 +108,14 @@ ML_estimate_component_based_normalisation(const std::string& out_filename_prefix BlockData3D measured_block_data(num_axial_blocks, num_transaxial_blocks, num_axial_blocks - 1, num_transaxial_blocks - 1); BlockData3D norm_block_data(num_axial_blocks, num_transaxial_blocks, num_axial_blocks - 1, num_transaxial_blocks - 1); - make_fan_data_remove_gaps(model_fan_data, model_data); + if(!use_model_fansums) + make_fan_data_remove_gaps(model_fan_data, model_data); + else + { + load_fan_data(model_fan_data, model_data, model_fansums_filename); + } + + return; //Stop here for now! : Debug { // next could be local if KL is not computed below FanProjData measured_fan_data; diff --git a/src/utilities/find_ML_normfactors3D.cxx b/src/utilities/find_ML_normfactors3D.cxx index 2e78c0cbf8..060316863d 100644 --- a/src/utilities/find_ML_normfactors3D.cxx +++ b/src/utilities/find_ML_normfactors3D.cxx @@ -25,13 +25,17 @@ #include #include +#include "stir/FilePath.h" + static void print_usage_and_exit(const std::string& program_name) { std::cerr << "Usage: " << program_name - << " [--display | --print-KL | --include-block-timing-model | --for-symmetry-per-block] \\\n" + << " [--display | --print-KL | --include-block-timing-model | --for-symmetry-per-block | --model-in-fansums | --use-listmode-cache] \\\n" << " out_filename_prefix measured_data model num_iterations num_eff_iterations\n" - << " set num_iterations to 0 to do only efficiencies\n"; + << " set num_iterations to 0 to do only efficiencies\n" + << " NOTE: If you already have the model fansums they need to be in the same path as the projdata with the same name and suffix .fsum\n"; + exit(EXIT_FAILURE); } @@ -51,6 +55,10 @@ main(int argc, char** argv) bool do_block = false; bool do_symmetry_per_block = false; + bool use_lm_cache = false; + bool use_model_fansums = false; + std::string model_fansums_filename; + // first process command line options while (argc > 0 && argv[0][0] == '-' && argc >= 1) { @@ -84,6 +92,18 @@ main(int argc, char** argv) --argc; ++argv; } + else if (strcmp(argv[0], "--model-in-fansums") == 0) + { + use_model_fansums = true; + --argc; + ++argv; + } + else if (strcmp(argv[0], "--use-listmode-cache") == 0) + { + use_lm_cache = true; + --argc; + ++argv; + } else print_usage_and_exit(program_name); } @@ -98,7 +118,20 @@ main(int argc, char** argv) } const int num_eff_iterations = atoi(argv[5]); const int num_iterations = atoi(argv[4]); + shared_ptr model_data = ProjData::read_from_file(argv[3]); + FilePath fp(argv[3] ); + fp.replace_extension(".fsum"); + if (!fp.exists(fp.get_as_string())) + { + warning("We could not find a fansum model. Defaulting to ProjData."); + use_model_fansums = false; + } + else + model_fansums_filename = fp.get_as_string(); + + // std::cout << fp.get_as_string() << std::endl; + // return 0; shared_ptr measured_data = ProjData::read_from_file(argv[2]); const std::string out_filename_prefix = argv[1]; @@ -114,7 +147,9 @@ main(int argc, char** argv) do_block, do_symmetry_per_block, do_KL, - do_display); + do_display, + use_lm_cache, + use_model_fansums, model_fansums_filename); timer.stop(); info(boost::format("CPU time %1% secs") % timer.value()); From a743a3224c448db18bee4254244a054c42ccbbc9 Mon Sep 17 00:00:00 2001 From: Nikos Efthimiou Date: Fri, 17 May 2024 11:21:10 -0400 Subject: [PATCH 53/78] Update src/buildblock/Scanner.cxx Co-authored-by: Kris Thielemans --- src/buildblock/Scanner.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/buildblock/Scanner.cxx b/src/buildblock/Scanner.cxx index 6eb62abd8a..0c4a90a9c1 100644 --- a/src/buildblock/Scanner.cxx +++ b/src/buildblock/Scanner.cxx @@ -1275,7 +1275,7 @@ Scanner::Scanner(Type scanner_type) 5, // int num_transaxial_blocks_per_bucket_v, 6, // int num_axial_crystals_per_block_v, 7,// int num_transaxial_crystals_per_block_v, - 252, // int num_axial_crystals_per_singles_unit_v, + 84, // int num_axial_crystals_per_singles_unit_v, 35, // int num_transaxial_crystals_per_singles_unit_v, 1, 0.0F, 511.F, From c05dc3e22d24e4c9237ab7c7dd39b6e040bf859c Mon Sep 17 00:00:00 2001 From: NikEfth Date: Fri, 17 May 2024 11:50:04 -0400 Subject: [PATCH 54/78] Fixes for test_time_of_flight. We try to fix problems introduces in the TOF tests by the new coincidence window convension. --- src/buildblock/ProjDataInfo.cxx | 4 ++-- src/include/stir/ProjDataInfo.inl | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/buildblock/ProjDataInfo.cxx b/src/buildblock/ProjDataInfo.cxx index f102d34542..4a70ab404e 100644 --- a/src/buildblock/ProjDataInfo.cxx +++ b/src/buildblock/ProjDataInfo.cxx @@ -178,11 +178,11 @@ ProjDataInfo::set_tof_mash_factor(const int new_num) tof_mash_factor = new_num; if (tof_mash_factor > scanner_ptr->get_max_num_timing_poss()) { - error("ProjDataInfo::set_tof_mash_factor: TOF mashing factor (" + std::to_string(tof_mash_factor) + warning("ProjDataInfo::set_tof_mash_factor: TOF mashing factor (" + std::to_string(tof_mash_factor) + +") must be smaller than or equal to the scanner's number of max timing bins (" + std::to_string(scanner_ptr->get_max_num_timing_poss()) + ")."); } - else if (tof_mash_factor == num_tof_bins) + else if (tof_mash_factor == scanner_ptr->get_max_num_timing_poss()) // This is a special case that we just want boundaries for the coincidence window. { diff --git a/src/include/stir/ProjDataInfo.inl b/src/include/stir/ProjDataInfo.inl index 69c6f82d78..9d0108b5b5 100644 --- a/src/include/stir/ProjDataInfo.inl +++ b/src/include/stir/ProjDataInfo.inl @@ -196,10 +196,10 @@ ProjDataInfo::is_tof_data() const // First case: if tof_mash_factor == 0, scanner is not tof ready and no tof data if (tof_mash_factor == 0) { - if (num_tof_bins != 1) - { - warning("Non-TOF data with inconsistent Time-of-Flight bin number - Note. As we are updating the scanner template style please make sure to have one TOF position (whole coincidence window for nonTOF scanners)."); - } + // if (num_tof_bins != 1) + // { + // warning("Non-TOF data with inconsistent Time-of-Flight bin number - Note. As we are updating the scanner template style please make sure to have one TOF position (whole coincidence window for nonTOF scanners)."); + // } return false; } // Second case: when tof_mash_factor is strictly positive, it means we have TOF data From da59b78774281e2c07ff4fafa504969c911b018a Mon Sep 17 00:00:00 2001 From: NikEfth Date: Fri, 17 May 2024 12:04:39 -0400 Subject: [PATCH 55/78] minor fix --- src/buildblock/ProjDataInfo.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/buildblock/ProjDataInfo.cxx b/src/buildblock/ProjDataInfo.cxx index 4a70ab404e..3fad0f4ca4 100644 --- a/src/buildblock/ProjDataInfo.cxx +++ b/src/buildblock/ProjDataInfo.cxx @@ -320,7 +320,7 @@ ProjDataInfo::ProjDataInfo(const shared_ptr& scanner_ptr_v, max_tof_pos_num = 0; tof_increament_in_mm = 0.f; tof_mash_factor = 0; - num_tof_bins = 0; + num_tof_bins = 1; } // TOF version. From fc362350f960a2cde74da66b1975d7af4bcd9b75 Mon Sep 17 00:00:00 2001 From: NikEfth Date: Fri, 17 May 2024 12:38:25 -0400 Subject: [PATCH 56/78] Fix to pass recon tests --- ...WithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx index a71ac1a929..04330bbe25 100644 --- a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx +++ b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx @@ -669,7 +669,7 @@ PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBinproj_data_info_sptr->get_min_tof_pos_num() : 0; // int max_timing_pos_num = use_tofsens ? this->proj_data_info_sptr->get_max_tof_pos_num() : 0; // if (min_timing_pos_num < 0 || max_timing_pos_num > 1) - if ( this->proj_data_info_sptr->get_num_tof_poss() > 1) + if ( this->sens_proj_data_info_sptr->get_num_tof_poss() > 1) error("TOF code for sensitivity needs work"); this->sens_backprojector_sptr->start_accumulating_in_new_target(); From 21597d09136ab9dc58e6430371a35bb1f17530f3 Mon Sep 17 00:00:00 2001 From: NikEfth Date: Fri, 17 May 2024 15:49:15 -0400 Subject: [PATCH 57/78] find ML normalization from listmode data. * This is mostly ready. I get a segfault after making the data. --- src/buildblock/FilePath.cxx | 4 +- src/buildblock/ML_norm.cxx | 301 ++++++++++++++++-- src/include/stir/ML_norm.h | 7 +- ...estimate_component_based_normalisation.cxx | 42 ++- src/utilities/find_ML_normfactors3D.cxx | 18 +- 5 files changed, 312 insertions(+), 60 deletions(-) diff --git a/src/buildblock/FilePath.cxx b/src/buildblock/FilePath.cxx index 598e6dcda1..f1948c5105 100644 --- a/src/buildblock/FilePath.cxx +++ b/src/buildblock/FilePath.cxx @@ -72,7 +72,7 @@ FilePath::is_directory() const struct stat info; if (stat(my_string.c_str(), &info) != 0) - error(boost::format("FilePath: Cannot access %1%.") % my_string); + warning(boost::format("FilePath: Cannot access %1%.") % my_string); else if (info.st_mode & S_IFDIR) return true; #endif @@ -92,7 +92,7 @@ FilePath::is_regular_file() const struct stat info; if (stat(my_string.c_str(), &info) != 0) - error(boost::format("FilePath: Cannot access %1%.") % my_string); + warning(boost::format("FilePath: Cannot access %1%.") % my_string); else if (info.st_mode & S_IFREG) return true; #endif diff --git a/src/buildblock/ML_norm.cxx b/src/buildblock/ML_norm.cxx index ba5e36bc34..1fd034b28d 100644 --- a/src/buildblock/ML_norm.cxx +++ b/src/buildblock/ML_norm.cxx @@ -32,6 +32,7 @@ #include "stir/NumericType.h" #include "stir/ByteOrder.h" #include "stir/IO/read_data.h" +#include "stir/FilePath.h" #ifdef STIR_OPENMP # include @@ -43,6 +44,69 @@ using std::max; START_NAMESPACE_STIR + //! TODO: Move this function somewhere else. All cache functions should be together + std::string get_cache_path() +{ + return FilePath::get_current_working_directory(); +} + +//! TODO: Move this function somewhere else. All cache functions should be together +std::string get_cache_filename(unsigned int file_id) +{ + std::string cache_filename = "my_CACHE" + std::to_string(file_id) + ".bin"; + FilePath icache(cache_filename, false); + icache.prepend_directory_name(get_cache_path()); + return icache.get_as_string(); +} + +//! TODO: Move this function somewhere else. All cache functions should be together +bool load_listmode_cache_file( + unsigned int file_id, std::vector& record_cache) +{ + FilePath icache(get_cache_filename(file_id), false); + + record_cache.clear(); + + if (icache.is_regular_file()) + { + info(boost::format("Loading Listmode cache from disk %1%") % icache.get_as_string()); + std::ifstream fin(icache.get_as_string(), std::ios::in | std::ios::binary | std::ios::ate); + + const std::size_t num_records = fin.tellg() / sizeof(Bin); + try + { + record_cache.reserve(num_records + 1); // add 1 to avoid reallocation when overruning (see below) + } + catch (...) + { + error("Listmode: cannot allocate cache for " + std::to_string(num_records + 1) + " records"); + } + if (!fin) + error("Error opening cache file \"" + icache.get_as_string() + "\" for reading."); + + fin.clear(); + fin.seekg(0); + + while (!fin.eof()) + { + BinAndCorr tmp; + fin.read((char*)&tmp, sizeof(Bin)); + record_cache.push_back(tmp); + } + // The while will push one junk record + record_cache.pop_back(); + fin.close(); + } + else + { + warning("Cannot find Listmode cache on disk. Please recompute it or do not set the max cache size. Abort."); + return false; // need to return something to avoid compiler warning + } + + info(boost::format("Cached Events: %1% ") % record_cache.size(), 2); + return true; +} + DetPairData::DetPairData() {} @@ -1051,6 +1115,8 @@ set_det_pair_data(ProjData& proj_data, const DetPairData& det_pair_data, const i } } + + /// **** This function make fan_data from projecion file while removing the intermodule gaps **** //// /// *** fan_data doesn't have gaps, proj_data has gaps *** /// template @@ -1061,8 +1127,7 @@ make_fan_data_remove_gaps_help(FanProjData& fan_data, int max_delta, int fan_size, const TProjDataInfo& proj_data_info, - const ProjData& proj_data, - const std::string read_from_filename) + const ProjData& proj_data) { if (proj_data.get_proj_data_info_sptr()->is_tof_data()) error("make_fan_data: Incompatible with TOF data. Abort."); @@ -1093,20 +1158,7 @@ make_fan_data_remove_gaps_help(FanProjData& fan_data, const int num_physical_rings = num_rings - (num_axial_blocks - 1) * num_virtual_axial_crystals_per_block; fan_data = FanProjData(num_physical_rings, num_physical_detectors_per_ring, new_max_delta, 2 * new_half_fan_size + 1); - if(read_from_filename != "") - { - info("Loading model fansums from the disk ..."); - shared_ptr fan_stream( - new std::fstream(read_from_filename, std::ios::in | std::ios::binary)); - - float scale = 1.f; - info("Reading from disk..."); - if(read_data(*fan_stream, fan_data, NumericType::Type::FLOAT,scale, - ByteOrder::Order::little_endian) == Succeeded::no) - error("Error writing FanProjData\n"); - } - else { shared_ptr> segment_ptr; @@ -1214,10 +1266,10 @@ load_fan_data(FanProjData& fan_data, const ProjData& proj_data, const int num_transaxial_crystals_per_block = proj_data_info.get_scanner_sptr()->get_num_transaxial_crystals_per_block(); const int num_axial_crystals_per_block = proj_data_info.get_scanner_sptr()->get_num_axial_crystals_per_block(); - // const int num_physical_transaxial_crystals_per_block - // = num_transaxial_crystals_per_block - num_virtual_transaxial_crystals_per_block; + // const int num_physical_transaxial_crystals_per_block + // = num_transaxial_crystals_per_block - num_virtual_transaxial_crystals_per_block; - // const int num_physical_axial_crystals_per_block = num_axial_crystals_per_block - num_virtual_axial_crystals_per_block; + // const int num_physical_axial_crystals_per_block = num_axial_crystals_per_block - num_virtual_axial_crystals_per_block; const int num_transaxial_blocks_in_fansize = fan_size / (num_transaxial_crystals_per_block); const int new_fan_size = fan_size - num_transaxial_blocks_in_fansize * num_virtual_transaxial_crystals_per_block; @@ -1229,22 +1281,211 @@ load_fan_data(FanProjData& fan_data, const ProjData& proj_data, const int num_physical_rings = num_rings - (num_axial_blocks - 1) * num_virtual_axial_crystals_per_block; fan_data = FanProjData(num_physical_rings, num_physical_detectors_per_ring, new_max_delta, 2 * new_half_fan_size + 1); - info("Loading model fansums from the disk ..."); - shared_ptr fan_stream( - new std::fstream(fan_filename, std::ios::in | std::ios::binary)); + info("Loading model fansums from the disk ..."); + shared_ptr fan_stream( + new std::fstream(fan_filename, std::ios::in | std::ios::binary)); + + float scale = 1.f; + + info("Reading from disk..."); + if(read_data(*fan_stream, fan_data, NumericType::Type::FLOAT,scale, + ByteOrder::Order::little_endian) == Succeeded::no) + error("Error writing FanProjData\n"); + +} + +void +make_all_fan_data_from_cache( + Array<2, float>& data_fan_sums, + GeoData3D& geo_data, + const ProjData& proj_data) +{ + + int num_rings; + int num_detectors_per_ring; + int fan_size; + int max_delta; + get_fan_info(num_rings, num_detectors_per_ring, max_delta, fan_size, *proj_data.get_proj_data_info_sptr()); + + auto proj_data_info_ptr + = dynamic_cast(&(*proj_data.get_proj_data_info_sptr())); + + data_fan_sums.fill(0); + geo_data.fill(0); + + + const int half_fan_size = fan_size / 2; + const int num_virtual_axial_crystals_per_block = proj_data_info_ptr->get_scanner_sptr()->get_num_virtual_axial_crystals_per_block(); + + const int num_virtual_transaxial_crystals_per_block + = proj_data_info_ptr->get_scanner_sptr()->get_num_virtual_transaxial_crystals_per_block(); + + const int num_transaxial_blocks = proj_data_info_ptr->get_scanner_sptr()->get_num_transaxial_blocks(); + const int num_axial_blocks = proj_data_info_ptr->get_scanner_sptr()->get_num_axial_blocks(); + const int num_transaxial_crystals_per_block = proj_data_info_ptr->get_scanner_sptr()->get_num_transaxial_crystals_per_block(); + const int num_axial_crystals_per_block = proj_data_info_ptr->get_scanner_sptr()->get_num_axial_crystals_per_block(); + + const int num_physical_transaxial_crystals_per_block + = num_transaxial_crystals_per_block - num_virtual_transaxial_crystals_per_block; + + const int num_physical_axial_crystals_per_block = num_axial_crystals_per_block - num_virtual_axial_crystals_per_block; + + const int num_transaxial_blocks_in_fansize = fan_size / (num_transaxial_crystals_per_block); + const int new_fan_size = fan_size - num_transaxial_blocks_in_fansize * num_virtual_transaxial_crystals_per_block; + const int new_half_fan_size = new_fan_size / 2; + const int num_axial_blocks_in_max_delta = max_delta / (num_axial_crystals_per_block); + const int new_max_delta = max_delta - (num_axial_blocks_in_max_delta)*num_virtual_axial_crystals_per_block; + const int num_physical_detectors_per_ring + = num_detectors_per_ring - num_transaxial_blocks * num_virtual_transaxial_crystals_per_block; + const int num_physical_rings = num_rings - (num_axial_blocks - 1) * num_virtual_axial_crystals_per_block; + + //This now is the old "work" from make_geo_data + FanProjData fan_data = FanProjData(num_physical_rings, num_physical_detectors_per_ring, new_max_delta, 2 * new_half_fan_size + 1); + + // + const int num_axial_detectors = fan_data.get_num_rings(); + const int num_transaxial_detectors = fan_data.get_num_detectors_per_ring(); + // const int num_axial_crystals_per_block = geo_data.get_num_axial_crystals_per_block(); + // const int num_transaxial_crystals_per_block = geo_data.get_half_num_transaxial_crystals_per_block() * 2; + // const int num_transaxial_blocks = num_transaxial_detectors / num_transaxial_crystals_per_block; + // const int num_axial_blocks = num_axial_detectors / num_axial_crystals_per_block; + + fan_data.fill(0); + + int ibatch = 0; + std::vector record_cache; + + while(true)//While we keep getting cache files. + { + + if (!load_listmode_cache_file(ibatch, record_cache)) + { + info("No more cache files in directory. Finished"); + break; + } + +#ifdef STIR_OPENMP +# pragma omp for schedule(dynamic) +#endif + for(long int ievent = 0; ievent < static_cast(record_cache.size()); ++ievent) + { + + auto& record = record_cache.at(ievent); + if (record.my_bin.get_bin_value() == 0.0f) // shouldn't happen really, but a check probably doesn't hurt + continue; + + if (ievent % 1000000L == 0) + std::cout << "\r" << ievent << " events used" << std::flush; + + const Bin& measured_bin = record.my_bin; + int ra = 0, a = 0; + int rb = 0, b = 0; - float scale = 1.f; + proj_data_info_ptr->get_det_pair_for_bin(a, ra, b, rb, measured_bin); - info("Reading from disk..."); - if(read_data(*fan_stream, fan_data, NumericType::Type::FLOAT,scale, - ByteOrder::Order::little_endian) == Succeeded::no) - error("Error writing FanProjData\n"); + data_fan_sums[ra][a] += 1; + data_fan_sums[rb][b] += 1; + + // I don't think we need this. + int a_in_block = a % num_transaxial_crystals_per_block; + if (a_in_block >= num_physical_transaxial_crystals_per_block) + continue; + int new_a = a - (a / num_transaxial_crystals_per_block) * num_virtual_transaxial_crystals_per_block; + + int ra_in_block = ra % num_axial_crystals_per_block; + if (ra_in_block >= num_physical_axial_crystals_per_block) + continue; + int new_ra = ra - (ra / num_axial_crystals_per_block) * num_virtual_axial_crystals_per_block; + + int b_in_block = b % num_transaxial_crystals_per_block; + if (b_in_block >= num_physical_transaxial_crystals_per_block) + continue; + int new_b = b - (b / num_transaxial_crystals_per_block) * num_virtual_transaxial_crystals_per_block; + + int rb_in_block = rb % num_axial_crystals_per_block; + if (rb_in_block >= num_physical_axial_crystals_per_block) + continue; + int new_rb = rb - (rb / num_axial_crystals_per_block) * num_virtual_axial_crystals_per_block; + + if (new_ra >= fan_data.get_min_ra() && new_ra <= fan_data.get_max_ra() && + new_a >= fan_data.get_min_a() && new_a <= fan_data.get_max_a() && + new_rb >= max(ra, fan_data.get_min_b(new_ra)) && new_rb <= fan_data.get_max_rb(new_ra) && + new_b > fan_data.get_min_b(new_a) && new_b <= fan_data.get_max_b(new_a) ) + { + const int ma = num_transaxial_detectors - 1 - new_a; + const int mb = (2 * num_transaxial_detectors - 1 - new_b) % num_transaxial_detectors; + const int mra = num_axial_detectors - 1 - new_ra; + const int mrb = (num_axial_detectors - 1 - new_rb); + + if ( new_ra != mra && new_rb != mrb) + { + fan_data( new_ra, new_a, new_rb, new_b) +=1; + fan_data( new_ra, ma, new_rb, new_b) +=1; + fan_data( mra, new_a, mrb, new_b) +=1; + fan_data( mra, ma, mrb, mb) +=1; + } + else + { + fan_data( new_ra, new_a, new_rb, new_b) +=1; + fan_data( new_ra, ma, new_rb, mb) +=1; + } + } + } + + ibatch++; + } + +#ifdef STIR_OPENMP +#pragma omp parallel for collapse(2) schedule(dynamic) +#endif + for (int ra = 0; ra < num_axial_crystals_per_block; ++ra) + // for (int a = 0; a <= num_transaxial_detectors/2; ++a) + for (int a = 0; a < num_transaxial_crystals_per_block / 2; ++a) + // loop rb from ra to avoid double counting + // for (int rb = fan_data.get_min_ra(); rb <= fan_data.get_max_ra(); ++rb) + for (int rb = max(ra, fan_data.get_min_rb(ra)); rb <= fan_data.get_max_rb(ra); ++rb) + for (int b = fan_data.get_min_b(a); b <= fan_data.get_max_b(a); ++b) + { + + // rotation + + for (int axial_block_num = 0; axial_block_num < num_axial_blocks; ++axial_block_num) + { + + for (int transaxial_block_num = 0; transaxial_block_num < num_transaxial_blocks; ++transaxial_block_num) + { + + const int transaxial_det_inc = transaxial_block_num * num_transaxial_crystals_per_block; + const int new_det_num_a = (a + transaxial_det_inc) % num_transaxial_detectors; + const int new_det_num_b = (b + transaxial_det_inc) % num_transaxial_detectors; + const int axial_det_inc = axial_block_num * num_axial_crystals_per_block; + const int new_ring_num_a = ra + axial_det_inc; + const int new_ring_num_b = rb + axial_det_inc; + + if (fan_data.is_in_data(new_ring_num_a, new_det_num_a, new_ring_num_b, new_det_num_b)) + { +#ifdef STIR_OPENMP +# pragma omp critical(FANPROJDATAWRITE) +#endif + try + { + geo_data(ra, a, rb, b % num_transaxial_detectors) + += fan_data(new_ring_num_a, new_det_num_a, new_ring_num_b, new_det_num_b); + } + catch(...) + { + + } + } + } + } + } } + void -make_fan_data_remove_gaps(FanProjData& fan_data, const ProjData& proj_data, - const std::string model_fan_filename) +make_fan_data_remove_gaps(FanProjData& fan_data, const ProjData& proj_data) { int num_rings; int num_detectors_per_ring; @@ -1262,14 +1503,14 @@ make_fan_data_remove_gaps(FanProjData& fan_data, const ProjData& proj_data, auto proj_data_info_ptr = dynamic_cast(&proj_data_info); make_fan_data_remove_gaps_help( - fan_data, num_rings, num_detectors_per_ring, max_delta, fan_size, *proj_data_info_ptr, proj_data, model_fan_filename); + fan_data, num_rings, num_detectors_per_ring, max_delta, fan_size, *proj_data_info_ptr, proj_data); } else { auto proj_data_info_ptr = dynamic_cast(&proj_data_info); make_fan_data_remove_gaps_help( - fan_data, num_rings, num_detectors_per_ring, max_delta, fan_size, *proj_data_info_ptr, proj_data, model_fan_filename); + fan_data, num_rings, num_detectors_per_ring, max_delta, fan_size, *proj_data_info_ptr, proj_data); } } diff --git a/src/include/stir/ML_norm.h b/src/include/stir/ML_norm.h index ca6a2fd366..77fad0694e 100644 --- a/src/include/stir/ML_norm.h +++ b/src/include/stir/ML_norm.h @@ -205,7 +205,12 @@ class FanProjData : public Array<4, float> typedef FanProjData BlockData3D; -void make_fan_data_remove_gaps(FanProjData& fan_data, const ProjData& proj_data, const std::string model_fan_filename = ""); +void make_all_fan_data_from_cache( + Array<2, float>& data_fan_sums, + GeoData3D& geo_data, + const ProjData& proj_data); + +void make_fan_data_remove_gaps(FanProjData& fan_data, const ProjData& proj_data); void load_fan_data(FanProjData& fan_data, const ProjData& proj_data, const std::string fan_filename = ""); diff --git a/src/recon_buildblock/ML_estimate_component_based_normalisation.cxx b/src/recon_buildblock/ML_estimate_component_based_normalisation.cxx index 10cf79f5e4..339891808e 100644 --- a/src/recon_buildblock/ML_estimate_component_based_normalisation.cxx +++ b/src/recon_buildblock/ML_estimate_component_based_normalisation.cxx @@ -105,8 +105,8 @@ ML_estimate_component_based_normalisation(const std::string& out_filename_prefix num_physical_rings, num_physical_detectors_per_ring); // inputes have to be modified - BlockData3D measured_block_data(num_axial_blocks, num_transaxial_blocks, num_axial_blocks - 1, num_transaxial_blocks - 1); - BlockData3D norm_block_data(num_axial_blocks, num_transaxial_blocks, num_axial_blocks - 1, num_transaxial_blocks - 1); + BlockData3D measured_block_data;//(num_axial_blocks, num_transaxial_blocks, num_axial_blocks - 1, num_transaxial_blocks - 1); + BlockData3D norm_block_data;//(num_axial_blocks, num_transaxial_blocks, num_axial_blocks - 1, num_transaxial_blocks - 1); if(!use_model_fansums) make_fan_data_remove_gaps(model_fan_data, model_data); @@ -115,35 +115,43 @@ ML_estimate_component_based_normalisation(const std::string& out_filename_prefix load_fan_data(model_fan_data, model_data, model_fansums_filename); } - return; //Stop here for now! : Debug { // next could be local if KL is not computed below FanProjData measured_fan_data; float threshold_for_KL; // compute factors dependent on the data { - make_fan_data_remove_gaps(measured_fan_data, measured_data); - /* TEMP FIX */ - for (int ra = model_fan_data.get_min_ra(); ra <= model_fan_data.get_max_ra(); ++ra) + if(!use_lm_cache) { - for (int a = model_fan_data.get_min_a(); a <= model_fan_data.get_max_a(); ++a) + make_fan_data_remove_gaps(measured_fan_data, measured_data); + + /* TEMP FIX */ + for (int ra = model_fan_data.get_min_ra(); ra <= model_fan_data.get_max_ra(); ++ra) { - for (int rb = std::max(ra, model_fan_data.get_min_rb(ra)); rb <= model_fan_data.get_max_rb(ra); ++rb) + for (int a = model_fan_data.get_min_a(); a <= model_fan_data.get_max_a(); ++a) { - for (int b = model_fan_data.get_min_b(a); b <= model_fan_data.get_max_b(a); ++b) - if (model_fan_data(ra, a, rb, b) == 0) - measured_fan_data(ra, a, rb, b) = 0; + for (int rb = std::max(ra, model_fan_data.get_min_rb(ra)); rb <= model_fan_data.get_max_rb(ra); ++rb) + { + for (int b = model_fan_data.get_min_b(a); b <= model_fan_data.get_max_b(a); ++b) + if (model_fan_data(ra, a, rb, b) == 0) + measured_fan_data(ra, a, rb, b) = 0; + } } } - } - threshold_for_KL = measured_fan_data.find_max() / 100000.F; - // display(measured_fan_data, "measured data"); + threshold_for_KL = measured_fan_data.find_max() / 100000.F; + // display(measured_fan_data, "measured data"); - make_fan_sum_data(data_fan_sums, measured_fan_data); - make_geo_data(measured_geo_data, measured_fan_data); - make_block_data(measured_block_data, measured_fan_data); + make_fan_sum_data(data_fan_sums, measured_fan_data); + make_geo_data(measured_geo_data, measured_fan_data); + make_block_data(measured_block_data, measured_fan_data); + } + else//Use LM cache to make all data, not blocks for now. + { + make_all_fan_data_from_cache( + data_fan_sums, measured_geo_data, measured_data); + } if (do_display) display(measured_block_data, "raw block data from measurements"); diff --git a/src/utilities/find_ML_normfactors3D.cxx b/src/utilities/find_ML_normfactors3D.cxx index 060316863d..05c28f7d6d 100644 --- a/src/utilities/find_ML_normfactors3D.cxx +++ b/src/utilities/find_ML_normfactors3D.cxx @@ -31,7 +31,7 @@ static void print_usage_and_exit(const std::string& program_name) { std::cerr << "Usage: " << program_name - << " [--display | --print-KL | --include-block-timing-model | --for-symmetry-per-block | --model-in-fansums | --use-listmode-cache] \\\n" + << " [--display | --print-KL | --include-block-timing-model | --for-symmetry-per-block | --model-in-fan-data | --use-listmode-cache] \\\n" << " out_filename_prefix measured_data model num_iterations num_eff_iterations\n" << " set num_iterations to 0 to do only efficiencies\n" << " NOTE: If you already have the model fansums they need to be in the same path as the projdata with the same name and suffix .fsum\n"; @@ -56,8 +56,8 @@ main(int argc, char** argv) bool do_symmetry_per_block = false; bool use_lm_cache = false; - bool use_model_fansums = false; - std::string model_fansums_filename; + bool use_model_fandata = false; + std::string model_fandata_filename; // first process command line options while (argc > 0 && argv[0][0] == '-' && argc >= 1) @@ -92,9 +92,9 @@ main(int argc, char** argv) --argc; ++argv; } - else if (strcmp(argv[0], "--model-in-fansums") == 0) + else if (strcmp(argv[0], "--model-in-fandata") == 0) { - use_model_fansums = true; + use_model_fandata = true; --argc; ++argv; } @@ -125,13 +125,11 @@ main(int argc, char** argv) if (!fp.exists(fp.get_as_string())) { warning("We could not find a fansum model. Defaulting to ProjData."); - use_model_fansums = false; + use_model_fandata = false; } else - model_fansums_filename = fp.get_as_string(); + model_fandata_filename = fp.get_as_string(); - // std::cout << fp.get_as_string() << std::endl; - // return 0; shared_ptr measured_data = ProjData::read_from_file(argv[2]); const std::string out_filename_prefix = argv[1]; @@ -149,7 +147,7 @@ main(int argc, char** argv) do_KL, do_display, use_lm_cache, - use_model_fansums, model_fansums_filename); + use_model_fandata, model_fandata_filename); timer.stop(); info(boost::format("CPU time %1% secs") % timer.value()); From 91625784d910fd04f1d79a36f1b4e27efbc614ac Mon Sep 17 00:00:00 2001 From: NikEfth Date: Fri, 17 May 2024 17:06:05 -0400 Subject: [PATCH 58/78] Parallelization changes and labels --- src/buildblock/ML_norm.cxx | 102 +++++++++++------- ...estimate_component_based_normalisation.cxx | 11 +- 2 files changed, 71 insertions(+), 42 deletions(-) diff --git a/src/buildblock/ML_norm.cxx b/src/buildblock/ML_norm.cxx index 1fd034b28d..1696d19ac4 100644 --- a/src/buildblock/ML_norm.cxx +++ b/src/buildblock/ML_norm.cxx @@ -1320,20 +1320,20 @@ make_all_fan_data_from_cache( const int num_virtual_transaxial_crystals_per_block = proj_data_info_ptr->get_scanner_sptr()->get_num_virtual_transaxial_crystals_per_block(); - const int num_transaxial_blocks = proj_data_info_ptr->get_scanner_sptr()->get_num_transaxial_blocks(); - const int num_axial_blocks = proj_data_info_ptr->get_scanner_sptr()->get_num_axial_blocks(); - const int num_transaxial_crystals_per_block = proj_data_info_ptr->get_scanner_sptr()->get_num_transaxial_crystals_per_block(); - const int num_axial_crystals_per_block = proj_data_info_ptr->get_scanner_sptr()->get_num_axial_crystals_per_block(); + int num_transaxial_blocks = proj_data_info_ptr->get_scanner_sptr()->get_num_transaxial_blocks(); + int num_axial_blocks = proj_data_info_ptr->get_scanner_sptr()->get_num_axial_blocks(); + int num_transaxial_crystals_per_block = proj_data_info_ptr->get_scanner_sptr()->get_num_transaxial_crystals_per_block(); + int num_axial_crystals_per_block = proj_data_info_ptr->get_scanner_sptr()->get_num_axial_crystals_per_block(); - const int num_physical_transaxial_crystals_per_block + int num_physical_transaxial_crystals_per_block = num_transaxial_crystals_per_block - num_virtual_transaxial_crystals_per_block; const int num_physical_axial_crystals_per_block = num_axial_crystals_per_block - num_virtual_axial_crystals_per_block; - const int num_transaxial_blocks_in_fansize = fan_size / (num_transaxial_crystals_per_block); + int num_transaxial_blocks_in_fansize = fan_size / (num_transaxial_crystals_per_block); const int new_fan_size = fan_size - num_transaxial_blocks_in_fansize * num_virtual_transaxial_crystals_per_block; const int new_half_fan_size = new_fan_size / 2; - const int num_axial_blocks_in_max_delta = max_delta / (num_axial_crystals_per_block); + int num_axial_blocks_in_max_delta = max_delta / (num_axial_crystals_per_block); const int new_max_delta = max_delta - (num_axial_blocks_in_max_delta)*num_virtual_axial_crystals_per_block; const int num_physical_detectors_per_ring = num_detectors_per_ring - num_transaxial_blocks * num_virtual_transaxial_crystals_per_block; @@ -1345,10 +1345,10 @@ make_all_fan_data_from_cache( // const int num_axial_detectors = fan_data.get_num_rings(); const int num_transaxial_detectors = fan_data.get_num_detectors_per_ring(); - // const int num_axial_crystals_per_block = geo_data.get_num_axial_crystals_per_block(); - // const int num_transaxial_crystals_per_block = geo_data.get_half_num_transaxial_crystals_per_block() * 2; - // const int num_transaxial_blocks = num_transaxial_detectors / num_transaxial_crystals_per_block; - // const int num_axial_blocks = num_axial_detectors / num_axial_crystals_per_block; + num_axial_crystals_per_block = geo_data.get_num_axial_crystals_per_block(); + num_transaxial_crystals_per_block = geo_data.get_half_num_transaxial_crystals_per_block() * 2; + num_transaxial_blocks = num_transaxial_detectors / num_transaxial_crystals_per_block; + num_axial_blocks = num_axial_detectors / num_axial_crystals_per_block; fan_data.fill(0); @@ -1383,8 +1383,16 @@ make_all_fan_data_from_cache( proj_data_info_ptr->get_det_pair_for_bin(a, ra, b, rb, measured_bin); - data_fan_sums[ra][a] += 1; - data_fan_sums[rb][b] += 1; +#ifdef STIR_OPENMP +# pragma omp critical(DATAFANWRITE) +#endif + try + { + data_fan_sums[ra][a] += 1; + data_fan_sums[rb][b] += 1; + } + catch(...) + {} // I don't think we need this. int a_in_block = a % num_transaxial_crystals_per_block; @@ -1419,15 +1427,31 @@ make_all_fan_data_from_cache( if ( new_ra != mra && new_rb != mrb) { - fan_data( new_ra, new_a, new_rb, new_b) +=1; - fan_data( new_ra, ma, new_rb, new_b) +=1; - fan_data( mra, new_a, mrb, new_b) +=1; - fan_data( mra, ma, mrb, mb) +=1; +#ifdef STIR_OPENMP +# pragma omp critical(FANPROJDATAWRITE) +#endif + try + { + fan_data( new_ra, new_a, new_rb, new_b) +=1; + fan_data( new_ra, ma, new_rb, new_b) +=1; + fan_data( mra, new_a, mrb, new_b) +=1; + fan_data( mra, ma, mrb, mb) +=1; + } + catch(...) + {} } else { - fan_data( new_ra, new_a, new_rb, new_b) +=1; - fan_data( new_ra, ma, new_rb, mb) +=1; +#ifdef STIR_OPENMP +# pragma omp critical(FANPROJDATAWRITE) +#endif + try + { + fan_data( new_ra, new_a, new_rb, new_b) +=1; + fan_data( new_ra, ma, new_rb, mb) +=1; + } + catch(...) + {} } } } @@ -1435,26 +1459,27 @@ make_all_fan_data_from_cache( ibatch++; } + record_cache.clear(); + + //TODO duplicate geo. + #ifdef STIR_OPENMP -#pragma omp parallel for collapse(2) schedule(dynamic) +#pragma omp parallel for collapse(4) schedule(dynamic) #endif for (int ra = 0; ra < num_axial_crystals_per_block; ++ra) // for (int a = 0; a <= num_transaxial_detectors/2; ++a) for (int a = 0; a < num_transaxial_crystals_per_block / 2; ++a) - // loop rb from ra to avoid double counting - // for (int rb = fan_data.get_min_ra(); rb <= fan_data.get_max_ra(); ++rb) - for (int rb = max(ra, fan_data.get_min_rb(ra)); rb <= fan_data.get_max_rb(ra); ++rb) - for (int b = fan_data.get_min_b(a); b <= fan_data.get_max_b(a); ++b) - { - - // rotation - - for (int axial_block_num = 0; axial_block_num < num_axial_blocks; ++axial_block_num) - { - - for (int transaxial_block_num = 0; transaxial_block_num < num_transaxial_blocks; ++transaxial_block_num) + for (int axial_block_num = 0; axial_block_num < num_axial_blocks; ++axial_block_num) + { + for (int transaxial_block_num = 0; transaxial_block_num < num_transaxial_blocks; ++transaxial_block_num) + { + // loop rb from ra to avoid double counting + // for (int rb = fan_data.get_min_ra(); rb <= fan_data.get_max_ra(); ++rb) + for (int rb = max(ra, fan_data.get_min_rb(ra)); rb <= fan_data.get_max_rb(ra); ++rb) + for (int b = fan_data.get_min_b(a); b <= fan_data.get_max_b(a); ++b) { + // rotation const int transaxial_det_inc = transaxial_block_num * num_transaxial_crystals_per_block; const int new_det_num_a = (a + transaxial_det_inc) % num_transaxial_detectors; const int new_det_num_b = (b + transaxial_det_inc) % num_transaxial_detectors; @@ -1469,8 +1494,8 @@ make_all_fan_data_from_cache( #endif try { - geo_data(ra, a, rb, b % num_transaxial_detectors) - += fan_data(new_ring_num_a, new_det_num_a, new_ring_num_b, new_det_num_b); + geo_data(ra, a, rb, b % num_transaxial_detectors) + += fan_data(new_ring_num_a, new_det_num_a, new_ring_num_b, new_det_num_b); } catch(...) { @@ -1480,7 +1505,6 @@ make_all_fan_data_from_cache( } } } - } @@ -1705,15 +1729,11 @@ apply_geo_norm(FanProjData& fan_data, const GeoData3D& geo_data, const bool appl for (int rb = max(ra, fan_data.get_min_rb(ra)); rb <= fan_data.get_max_rb(ra); ++rb) for (int b = fan_data.get_min_b(a); b <= fan_data.get_max_b(a); ++b) { - // rotation - for (int axial_block_num = 0; axial_block_num < num_axial_blocks; ++axial_block_num) { - for (int transaxial_block_num = 0; transaxial_block_num < num_transaxial_blocks; ++transaxial_block_num) { - const int transaxial_det_inc = transaxial_block_num * num_transaxial_crystals_per_block; const int new_det_num_a = (a + transaxial_det_inc) % num_transaxial_detectors; const int new_det_num_b = (b + transaxial_det_inc) % num_transaxial_detectors; @@ -1734,11 +1754,9 @@ apply_geo_norm(FanProjData& fan_data, const GeoData3D& geo_data, const bool appl work(new_ring_num_a, ma, new_ring_num_b, mb) = geo_data(ra, a, rb, b % num_transaxial_detectors); if (work.is_in_data(mra, new_det_num_a, mrb, new_det_num_b)) - work(mra, new_det_num_a, mrb, new_det_num_b) = geo_data(ra, a, rb, b % num_transaxial_detectors); if (work.is_in_data(mra, ma, mrb, mb)) - work(mra, ma, mrb, mb) = geo_data(ra, a, rb, b % num_transaxial_detectors); } } @@ -1984,6 +2002,7 @@ iterate_efficiencies(DetectorEfficiencies& efficiencies, const Array<2, float>& assert(model.get_max_ra() == data_fan_sums.get_max_index()); assert(model.get_min_a() == data_fan_sums[data_fan_sums.get_min_index()].get_min_index()); assert(model.get_max_a() == data_fan_sums[data_fan_sums.get_min_index()].get_max_index()); + for (int ra = model.get_min_ra(); ra <= model.get_max_ra(); ++ra) for (int a = model.get_min_a(); a <= model.get_max_a(); ++a) { @@ -1995,6 +2014,7 @@ iterate_efficiencies(DetectorEfficiencies& efficiencies, const Array<2, float>& for (int rb = model.get_min_rb(ra); rb <= model.get_max_rb(ra); ++rb) for (int b = model.get_min_b(a); b <= model.get_max_b(a); ++b) denominator += efficiencies[rb][b % num_detectors_per_ring] * model(ra, a, rb, b); + efficiencies[ra][a] = data_fan_sums[ra][a] / denominator; } } diff --git a/src/recon_buildblock/ML_estimate_component_based_normalisation.cxx b/src/recon_buildblock/ML_estimate_component_based_normalisation.cxx index 339891808e..45126b1d29 100644 --- a/src/recon_buildblock/ML_estimate_component_based_normalisation.cxx +++ b/src/recon_buildblock/ML_estimate_component_based_normalisation.cxx @@ -181,21 +181,30 @@ ML_estimate_component_based_normalisation(const std::string& out_filename_prefix for (int iter_num = 1; iter_num <= std::max(num_iterations, 1); ++iter_num) { + std::cout << "Iteration number: " << iter_num << std::endl; if (iter_num == 1) { + std::cout << "Calculating sums: " << iter_num << std::endl; efficiencies.fill(sqrt(data_fan_sums.sum() / model_fan_data.sum())); + std::cout << "Finished sums." << iter_num << std::endl; norm_geo_data.fill(1); norm_block_data.fill(1); } // efficiencies { fan_data = model_fan_data; + std::cout << "Applying geo norm..." << std::endl; apply_geo_norm(fan_data, norm_geo_data); - apply_block_norm(fan_data, norm_block_data); + if(do_block) + { + std::cout << "Applying block norm..." << std::endl; + apply_block_norm(fan_data, norm_block_data); + } if (do_display) display(fan_data, "model*geo*block"); for (int eff_iter_num = 1; eff_iter_num <= num_eff_iterations; ++eff_iter_num) { + std::cout << "Efficiency iteration number: " << iter_num << std::endl; iterate_efficiencies(efficiencies, data_fan_sums, fan_data); { char* out_filename = new char[out_filename_prefix.size() + 30]; From 069231c17cb8823e442195a4f0f7fa75cad7e7e6 Mon Sep 17 00:00:00 2001 From: NikEfth Date: Fri, 17 May 2024 18:05:00 -0400 Subject: [PATCH 59/78] Important fix. --- .../ML_estimate_component_based_normalisation.cxx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/recon_buildblock/ML_estimate_component_based_normalisation.cxx b/src/recon_buildblock/ML_estimate_component_based_normalisation.cxx index 45126b1d29..16a73396be 100644 --- a/src/recon_buildblock/ML_estimate_component_based_normalisation.cxx +++ b/src/recon_buildblock/ML_estimate_component_based_normalisation.cxx @@ -105,8 +105,8 @@ ML_estimate_component_based_normalisation(const std::string& out_filename_prefix num_physical_rings, num_physical_detectors_per_ring); // inputes have to be modified - BlockData3D measured_block_data;//(num_axial_blocks, num_transaxial_blocks, num_axial_blocks - 1, num_transaxial_blocks - 1); - BlockData3D norm_block_data;//(num_axial_blocks, num_transaxial_blocks, num_axial_blocks - 1, num_transaxial_blocks - 1); + BlockData3D measured_block_data(num_axial_blocks, num_transaxial_blocks, num_axial_blocks - 1, num_transaxial_blocks - 1); + BlockData3D norm_block_data(num_axial_blocks, num_transaxial_blocks, num_axial_blocks - 1, num_transaxial_blocks - 1); if(!use_model_fansums) make_fan_data_remove_gaps(model_fan_data, model_data); From 74fe00b0ed7685c493e987e55eb5f48b9ca3abf4 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Fri, 17 May 2024 22:37:07 +0100 Subject: [PATCH 60/78] OpenMP parallelise Array::sum/find_max/find_min implement the easy reduction clauses --- src/include/stir/Array.inl | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/include/stir/Array.inl b/src/include/stir/Array.inl index 98fce7eb12..bc4f12cd5b 100644 --- a/src/include/stir/Array.inl +++ b/src/include/stir/Array.inl @@ -157,6 +157,11 @@ Array::size_all() const { this->check_state(); size_t acc = 0; +#ifdef STIR_OPENMP +# if _OPENMP >= 201107 +# pragma omp parallel for reduction(+ : acc) +# endif +#endif for (int i = this->get_min_index(); i <= this->get_max_index(); i++) acc += this->num[i].size_all(); return acc; @@ -169,6 +174,11 @@ Array::sum() const this->check_state(); elemT acc; assign(acc, 0); +#ifdef STIR_OPENMP +# if _OPENMP >= 201107 +# pragma omp parallel for reduction(+ : acc) +# endif +#endif for (int i = this->get_min_index(); i <= this->get_max_index(); i++) acc += this->num[i].sum(); return acc; @@ -181,6 +191,11 @@ Array::sum_positive() const this->check_state(); elemT acc; assign(acc, 0); +#ifdef STIR_OPENMP +# if _OPENMP >= 201107 +# pragma omp parallel for reduction(+ : acc) +# endif +#endif for (int i = this->get_min_index(); i <= this->get_max_index(); i++) acc += this->num[i].sum_positive(); return acc; @@ -194,6 +209,11 @@ Array::find_max() const if (this->size() > 0) { elemT maxval = this->num[this->get_min_index()].find_max(); +#ifdef STIR_OPENMP +# if _OPENMP >= 201107 +# pragma omp parallel for reduction(max : maxval) +# endif +#endif for (int i = this->get_min_index() + 1; i <= this->get_max_index(); i++) { maxval = std::max(this->num[i].find_max(), maxval); @@ -215,6 +235,11 @@ Array::find_min() const if (this->size() > 0) { elemT minval = this->num[this->get_min_index()].find_min(); +#ifdef STIR_OPENMP +# if _OPENMP >= 201107 +# pragma omp parallel for reduction(min : minval) +# endif +#endif for (int i = this->get_min_index() + 1; i <= this->get_max_index(); i++) { minval = std::min(this->num[i].find_min(), minval); From b297080bf02302ff312ba9e02dcf1a029293003a Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Fri, 17 May 2024 23:08:41 +0100 Subject: [PATCH 61/78] parallelise some functions in ML_norm --- src/buildblock/ML_norm.cxx | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/src/buildblock/ML_norm.cxx b/src/buildblock/ML_norm.cxx index 1696d19ac4..cda339a7a8 100644 --- a/src/buildblock/ML_norm.cxx +++ b/src/buildblock/ML_norm.cxx @@ -444,10 +444,15 @@ iterate_efficiencies(Array<1, float>& efficiencies, const Array<1, float>& data_ else { // const float denominator = inner_product(efficiencies,model[a]); - float denominator = 0; + double denominator = 0; +#ifdef STIR_OPENMP +# if _OPENMP >= 201107 +# pragma omp parallel for reduction(+ : denominator) +# endif +#endif for (int b = model.get_min_index(a); b <= model.get_max_index(a); ++b) - denominator += efficiencies[b % num_detectors] * model(a, b); - efficiencies[a] = data_fan_sums[a] / denominator; + denominator += static_cast(efficiencies[b % num_detectors] * model(a, b)); + efficiencies[a] = data_fan_sums[a] / static_cast(denominator); } } } @@ -2010,12 +2015,17 @@ iterate_efficiencies(DetectorEfficiencies& efficiencies, const Array<2, float>& efficiencies[ra][a] = 0; else { - float denominator = 0; + double denominator = 0; +#ifdef STIR_OPENMP +# if _OPENMP >= 201107 +# pragma omp parallel for collapse(2) reduction(+ : denominator) +# endif +#endif for (int rb = model.get_min_rb(ra); rb <= model.get_max_rb(ra); ++rb) for (int b = model.get_min_b(a); b <= model.get_max_b(a); ++b) - denominator += efficiencies[rb][b % num_detectors_per_ring] * model(ra, a, rb, b); + denominator += static_cast(efficiencies[rb][b % num_detectors_per_ring] * model(ra, a, rb, b)); - efficiencies[ra][a] = data_fan_sums[ra][a] / denominator; + efficiencies[ra][a] = data_fan_sums[ra][a] / static_cast(denominator); } } } @@ -2039,12 +2049,17 @@ iterate_efficiencies(DetectorEfficiencies& efficiencies, efficiencies[ra][a] = 0; else { - float denominator = 0; + double denominator = 0; +#ifdef STIR_OPENMP +# if _OPENMP >= 201107 +# pragma omp parallel for collapse(2) reduction(+ : denominator) +# endif +#endif for (int rb = max(ra - max_ring_diff, 0); rb <= min(ra + max_ring_diff, num_rings - 1); ++rb) for (int b = a + num_detectors_per_ring / 2 - half_fan_size; b <= a + num_detectors_per_ring / 2 + half_fan_size; ++b) denominator += efficiencies[rb][b % num_detectors_per_ring]; - efficiencies[ra][a] = data_fan_sums[ra][a] / denominator; + efficiencies[ra][a] = data_fan_sums[ra][a] / static_cast(denominator); } #ifdef WRITE_ALL { From 75b9d6620d0d70bf7359077fc134578689d4a63b Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Fri, 17 May 2024 23:10:23 +0100 Subject: [PATCH 62/78] ML_norm estimation: avoid doing some block calculations if !do_block --- ...estimate_component_based_normalisation.cxx | 42 +++++++++++-------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/src/recon_buildblock/ML_estimate_component_based_normalisation.cxx b/src/recon_buildblock/ML_estimate_component_based_normalisation.cxx index 16a73396be..2e59cc731d 100644 --- a/src/recon_buildblock/ML_estimate_component_based_normalisation.cxx +++ b/src/recon_buildblock/ML_estimate_component_based_normalisation.cxx @@ -108,11 +108,11 @@ ML_estimate_component_based_normalisation(const std::string& out_filename_prefix BlockData3D measured_block_data(num_axial_blocks, num_transaxial_blocks, num_axial_blocks - 1, num_transaxial_blocks - 1); BlockData3D norm_block_data(num_axial_blocks, num_transaxial_blocks, num_axial_blocks - 1, num_transaxial_blocks - 1); - if(!use_model_fansums) + if (!use_model_fansums) make_fan_data_remove_gaps(model_fan_data, model_data); else { - load_fan_data(model_fan_data, model_data, model_fansums_filename); + load_fan_data(model_fan_data, model_data, model_fansums_filename); } { @@ -122,7 +122,7 @@ ML_estimate_component_based_normalisation(const std::string& out_filename_prefix // compute factors dependent on the data { - if(!use_lm_cache) + if (!use_lm_cache) { make_fan_data_remove_gaps(measured_fan_data, measured_data); @@ -145,14 +145,14 @@ ML_estimate_component_based_normalisation(const std::string& out_filename_prefix make_fan_sum_data(data_fan_sums, measured_fan_data); make_geo_data(measured_geo_data, measured_fan_data); - make_block_data(measured_block_data, measured_fan_data); + if (do_block) + make_block_data(measured_block_data, measured_fan_data); } - else//Use LM cache to make all data, not blocks for now. + else // Use LM cache to make all data, not blocks for now. { - make_all_fan_data_from_cache( - data_fan_sums, measured_geo_data, measured_data); + make_all_fan_data_from_cache(data_fan_sums, measured_geo_data, measured_data); } - if (do_display) + if (do_display && do_block) display(measured_block_data, "raw block data from measurements"); /* { @@ -195,7 +195,7 @@ ML_estimate_component_based_normalisation(const std::string& out_filename_prefix fan_data = model_fan_data; std::cout << "Applying geo norm..." << std::endl; apply_geo_norm(fan_data, norm_geo_data); - if(do_block) + if (do_block) { std::cout << "Applying block norm..." << std::endl; apply_block_norm(fan_data, norm_block_data); @@ -225,7 +225,8 @@ ML_estimate_component_based_normalisation(const std::string& out_filename_prefix // now restore for further iterations fan_data = model_fan_data; apply_geo_norm(fan_data, norm_geo_data); - apply_block_norm(fan_data, norm_block_data); + if (do_block) + apply_block_norm(fan_data, norm_block_data); } if (do_display) { @@ -235,7 +236,8 @@ ML_estimate_component_based_normalisation(const std::string& out_filename_prefix // now restore for further iterations fan_data = model_fan_data; apply_geo_norm(fan_data, norm_geo_data); - apply_block_norm(fan_data, norm_block_data); + if (do_block) + apply_block_norm(fan_data, norm_block_data); } } } // end efficiencies @@ -244,7 +246,8 @@ ML_estimate_component_based_normalisation(const std::string& out_filename_prefix fan_data = model_fan_data; apply_efficiencies(fan_data, efficiencies); - apply_block_norm(fan_data, norm_block_data); + if (do_block) + apply_block_norm(fan_data, norm_block_data); if (do_geo) iterate_geo_norm(norm_geo_data, measured_geo_data, fan_data); @@ -285,7 +288,8 @@ ML_estimate_component_based_normalisation(const std::string& out_filename_prefix apply_efficiencies(fan_data, efficiencies); apply_geo_norm(fan_data, norm_geo_data); if (do_block) - iterate_block_norm(norm_block_data, measured_block_data, fan_data); + { + iterate_block_norm(norm_block_data, measured_block_data, fan_data); #if 0 { // check for (int a=0; a Date: Fri, 17 May 2024 23:30:35 +0100 Subject: [PATCH 63/78] some ML_norm parallelisations --- src/buildblock/ML_norm.cxx | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/buildblock/ML_norm.cxx b/src/buildblock/ML_norm.cxx index cda339a7a8..f1a785db6e 100644 --- a/src/buildblock/ML_norm.cxx +++ b/src/buildblock/ML_norm.cxx @@ -1767,6 +1767,11 @@ apply_geo_norm(FanProjData& fan_data, const GeoData3D& geo_data, const bool appl } } +#ifdef STIR_OPENMP +# if _OPENMP >= 201107 +# pragma omp parallel for collapse(2) +# endif +#endif for (int ra = fan_data.get_min_ra(); ra <= fan_data.get_max_ra(); ++ra) for (int a = fan_data.get_min_a(); a <= fan_data.get_max_a(); ++a) // for (int rb = fan_data.get_min_ra(); rb <= fan_data.get_max_ra(); ++rb) @@ -1777,8 +1782,18 @@ apply_geo_norm(FanProjData& fan_data, const GeoData3D& geo_data, const bool appl if (fan_data(ra, a, rb, b) == 0) continue; if (apply) +#ifdef STIR_OPENMP +# if _OPENMP >= 201107 +# pragma omp atomic +# endif +#endif fan_data(ra, a, rb, b) *= work(ra, a, rb, b % num_transaxial_detectors); else +#ifdef STIR_OPENMP +# if _OPENMP >= 201107 +# pragma omp atomic +# endif +#endif fan_data(ra, a, rb, b) /= work(ra, a, rb, b % num_transaxial_detectors); } } @@ -1787,6 +1802,11 @@ void apply_efficiencies(FanProjData& fan_data, const DetectorEfficiencies& efficiencies, const bool apply) { const int num_detectors_per_ring = fan_data.get_num_detectors_per_ring(); +#ifdef STIR_OPENMP +# if _OPENMP >= 201107 +# pragma omp parallel for collapse(2) +# endif +#endif for (int ra = fan_data.get_min_ra(); ra <= fan_data.get_max_ra(); ++ra) for (int a = fan_data.get_min_a(); a <= fan_data.get_max_a(); ++a) // loop rb from ra to avoid double counting @@ -1796,8 +1816,18 @@ apply_efficiencies(FanProjData& fan_data, const DetectorEfficiencies& efficienci if (fan_data(ra, a, rb, b) == 0) continue; if (apply) +#ifdef STIR_OPENMP +# if _OPENMP >= 201107 +# pragma omp atomic +# endif +#endif fan_data(ra, a, rb, b) *= efficiencies[ra][a] * efficiencies[rb][b % num_detectors_per_ring]; else +#ifdef STIR_OPENMP +# if _OPENMP >= 201107 +# pragma omp atomic +# endif +#endif fan_data(ra, a, rb, b) /= efficiencies[ra][a] * efficiencies[rb][b % num_detectors_per_ring]; } } From c54c8a58aab720498e840889698a39ebac26ab98 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Fri, 17 May 2024 23:32:10 +0100 Subject: [PATCH 64/78] set KL_threshold to avoid problems --- .../ML_estimate_component_based_normalisation.cxx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/recon_buildblock/ML_estimate_component_based_normalisation.cxx b/src/recon_buildblock/ML_estimate_component_based_normalisation.cxx index 2e59cc731d..2132b760ca 100644 --- a/src/recon_buildblock/ML_estimate_component_based_normalisation.cxx +++ b/src/recon_buildblock/ML_estimate_component_based_normalisation.cxx @@ -151,6 +151,7 @@ ML_estimate_component_based_normalisation(const std::string& out_filename_prefix else // Use LM cache to make all data, not blocks for now. { make_all_fan_data_from_cache(data_fan_sums, measured_geo_data, measured_data); + threshold_for_KL = data_fan_sums.find_max() / 100000000.F; } if (do_display && do_block) display(measured_block_data, "raw block data from measurements"); From 6c411d812b24d5d27cfe152349fd5687cc272c26 Mon Sep 17 00:00:00 2001 From: NikEfth Date: Fri, 17 May 2024 21:45:12 -0400 Subject: [PATCH 65/78] More parallelization. --- recon_test_pack/root_header.hroot | 1 + src/CMakeLists.txt | 3 +- src/buildblock/ML_norm.cxx | 121 ++++++++++++------ src/include/stir/ML_norm.h | 2 +- ...estimate_component_based_normalisation.cxx | 48 ++++++- 5 files changed, 132 insertions(+), 43 deletions(-) diff --git a/recon_test_pack/root_header.hroot b/recon_test_pack/root_header.hroot index afc654912e..d90108dc46 100644 --- a/recon_test_pack/root_header.hroot +++ b/recon_test_pack/root_header.hroot @@ -14,6 +14,7 @@ View offset (degrees) := 0 Maximum number of (unmashed) TOF time bins := 5 Size of unmashed TOF time bins (ps) := 820.0 TOF timing resolution (ps) := 400.0 +TOF mashing factor:= 1 GATE scanner type := GATE_Cylindrical_PET GATE_Cylindrical_PET Parameters := diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7a731c2e63..ce9c4e6fed 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -252,7 +252,8 @@ endif() # create an object library with all the "registries" # Warning: dependencies for object libraries are not transitive! -add_library(stir_registries OBJECT ${STIR_REGISTRIES}) +add_library(stir_registries OBJECT ${STIR_REGISTRIES} + include/stir/LmCache.h) # TODO, really should use stir_libs.cmake target_include_directories(stir_registries PRIVATE ${STIR_INCLUDE_DIR}) target_include_directories(stir_registries PRIVATE ${Boost_INCLUDE_DIR}) diff --git a/src/buildblock/ML_norm.cxx b/src/buildblock/ML_norm.cxx index f1a785db6e..443e7428c0 100644 --- a/src/buildblock/ML_norm.cxx +++ b/src/buildblock/ML_norm.cxx @@ -1299,7 +1299,7 @@ load_fan_data(FanProjData& fan_data, const ProjData& proj_data, } -void +float make_all_fan_data_from_cache( Array<2, float>& data_fan_sums, GeoData3D& geo_data, @@ -1318,6 +1318,7 @@ make_all_fan_data_from_cache( data_fan_sums.fill(0); geo_data.fill(0); + long int return_value = 0; const int half_fan_size = fan_size / 2; const int num_virtual_axial_crystals_per_block = proj_data_info_ptr->get_scanner_sptr()->get_num_virtual_axial_crystals_per_block(); @@ -1395,6 +1396,7 @@ make_all_fan_data_from_cache( { data_fan_sums[ra][a] += 1; data_fan_sums[rb][b] += 1; + return_value += 2; } catch(...) {} @@ -1466,50 +1468,94 @@ make_all_fan_data_from_cache( record_cache.clear(); - //TODO duplicate geo. + std::vector> local_geo_sptrs; + int num_threads = 20; +#ifdef STIR_OPENMP +# pragma omp single + { + std::cout << "Converting fan data to geo data..." << std::endl; + std::cout << "We will be using " << num_threads << " threads... If your machine does not support that please contact sb from STIR" << std::endl; + local_geo_sptrs.resize(num_threads, shared_ptr()); + + for (int i = 0; i < num_threads; i++) + { + std::cout << "Allocated data to geo data... " << i<< std::endl; + local_geo_sptrs[i].reset(new GeoData3D(geo_data.size(), + geo_data[0].size(), + geo_data[0][0].size(), + geo_data[0][0][0].size())); + local_geo_sptrs[i]->fill(0); + } + } + #endif + +std::cout << "Starting geo loops... " << std::endl; #ifdef STIR_OPENMP -#pragma omp parallel for collapse(4) schedule(dynamic) +#pragma omp parallel for schedule(dynamic) num_threads(num_threads) #endif for (int ra = 0; ra < num_axial_crystals_per_block; ++ra) - // for (int a = 0; a <= num_transaxial_detectors/2; ++a) - for (int a = 0; a < num_transaxial_crystals_per_block / 2; ++a) - for (int axial_block_num = 0; axial_block_num < num_axial_blocks; ++axial_block_num) - { - for (int transaxial_block_num = 0; transaxial_block_num < num_transaxial_blocks; ++transaxial_block_num) - { - // loop rb from ra to avoid double counting - // for (int rb = fan_data.get_min_ra(); rb <= fan_data.get_max_ra(); ++rb) - for (int rb = max(ra, fan_data.get_min_rb(ra)); rb <= fan_data.get_max_rb(ra); ++rb) - for (int b = fan_data.get_min_b(a); b <= fan_data.get_max_b(a); ++b) - { - - // rotation - const int transaxial_det_inc = transaxial_block_num * num_transaxial_crystals_per_block; - const int new_det_num_a = (a + transaxial_det_inc) % num_transaxial_detectors; - const int new_det_num_b = (b + transaxial_det_inc) % num_transaxial_detectors; - const int axial_det_inc = axial_block_num * num_axial_crystals_per_block; - const int new_ring_num_a = ra + axial_det_inc; - const int new_ring_num_b = rb + axial_det_inc; + { - if (fan_data.is_in_data(new_ring_num_a, new_det_num_a, new_ring_num_b, new_det_num_b)) - { #ifdef STIR_OPENMP -# pragma omp critical(FANPROJDATAWRITE) + const int thread_num = omp_get_thread_num(); +#else + const int thread_num = 0; #endif - try - { - geo_data(ra, a, rb, b % num_transaxial_detectors) - += fan_data(new_ring_num_a, new_det_num_a, new_ring_num_b, new_det_num_b); - } - catch(...) - { - - } - } - } + std::cout <<"ra: " << ra << std::endl; + // for (int a = 0; a <= num_transaxial_detectors/2; ++a) + // std::cout << "Converting fan data to geo data..." << std::endl; + + for (int a = 0; a < num_transaxial_crystals_per_block / 2; ++a) + for (int axial_block_num = 0; axial_block_num < num_axial_blocks; ++axial_block_num) + { + for (int transaxial_block_num = 0; transaxial_block_num < num_transaxial_blocks; ++transaxial_block_num) + { + // loop rb from ra to avoid double counting + // for (int rb = fan_data.get_min_ra(); rb <= fan_data.get_max_ra(); ++rb) + for (int rb = max(ra, fan_data.get_min_rb(ra)); rb <= fan_data.get_max_rb(ra); ++rb) + for (int b = fan_data.get_min_b(a); b <= fan_data.get_max_b(a); ++b) + { + const int transaxial_det_inc = transaxial_block_num * num_transaxial_crystals_per_block; + const int new_det_num_a = (a + transaxial_det_inc) % num_transaxial_detectors; + const int new_det_num_b = (b + transaxial_det_inc) % num_transaxial_detectors; + const int axial_det_inc = axial_block_num * num_axial_crystals_per_block; + const int new_ring_num_a = ra + axial_det_inc; + const int new_ring_num_b = rb + axial_det_inc; + + if (fan_data.is_in_data(new_ring_num_a, new_det_num_a, new_ring_num_b, new_det_num_b)) + { +// #ifdef STIR_OPENMP +// # pragma omp critical(FANPROJDATAWRITE) +// #endif + try + { + (*local_geo_sptrs[thread_num])(ra, a, rb, b % num_transaxial_detectors) + += fan_data(new_ring_num_a, new_det_num_a, new_ring_num_b, new_det_num_b); + } + catch(...) + { + + } + } + } } } + } + + std::cout<<"Now flattening the geo data ..." << std::endl; +#ifdef STIR_OPENMP + // flatten data constructed by threads + { + for (int i = 0; i < static_cast(local_geo_sptrs.size()); ++i) + if (!is_null_ptr(local_geo_sptrs[i])) // only accumulate if a thread filled something in + geo_data += *(local_geo_sptrs[i]); + } +#endif + + return return_value; +// omp_set_num_threads(omp_get_max_threads()); +// std::cout<<"Returned the omp threads to " << omp_get_num_threads() << std::endl; } @@ -1725,6 +1771,9 @@ apply_geo_norm(FanProjData& fan_data, const GeoData3D& geo_data, const bool appl const int num_transaxial_blocks = num_transaxial_detectors / num_transaxial_crystals_per_block; const int num_axial_blocks = num_axial_detectors / num_axial_crystals_per_block; + // std::vector> local_geo_sptrs; + // int num_threads = 20; + FanProjData work = fan_data; work.fill(0); diff --git a/src/include/stir/ML_norm.h b/src/include/stir/ML_norm.h index 77fad0694e..e6edad0be6 100644 --- a/src/include/stir/ML_norm.h +++ b/src/include/stir/ML_norm.h @@ -205,7 +205,7 @@ class FanProjData : public Array<4, float> typedef FanProjData BlockData3D; -void make_all_fan_data_from_cache( +float make_all_fan_data_from_cache( Array<2, float>& data_fan_sums, GeoData3D& geo_data, const ProjData& proj_data); diff --git a/src/recon_buildblock/ML_estimate_component_based_normalisation.cxx b/src/recon_buildblock/ML_estimate_component_based_normalisation.cxx index 2132b760ca..b5736bad99 100644 --- a/src/recon_buildblock/ML_estimate_component_based_normalisation.cxx +++ b/src/recon_buildblock/ML_estimate_component_based_normalisation.cxx @@ -93,6 +93,7 @@ ML_estimate_component_based_normalisation(const std::string& out_filename_prefix FanProjData model_fan_data; FanProjData fan_data; + std::cout<<"Allocating ..." << std::endl; DetectorEfficiencies data_fan_sums(IndexRange2D(num_physical_rings, num_physical_detectors_per_ring)); DetectorEfficiencies efficiencies(IndexRange2D(num_physical_rings, num_physical_detectors_per_ring)); @@ -100,13 +101,21 @@ ML_estimate_component_based_normalisation(const std::string& out_filename_prefix num_physical_transaxial_crystals_per_basic_unit / 2, num_physical_rings, num_physical_detectors_per_ring); // inputes have to be modified + GeoData3D norm_geo_data(num_physical_axial_crystals_per_basic_unit, num_physical_transaxial_crystals_per_basic_unit / 2, num_physical_rings, num_physical_detectors_per_ring); // inputes have to be modified - BlockData3D measured_block_data(num_axial_blocks, num_transaxial_blocks, num_axial_blocks - 1, num_transaxial_blocks - 1); - BlockData3D norm_block_data(num_axial_blocks, num_transaxial_blocks, num_axial_blocks - 1, num_transaxial_blocks - 1); + BlockData3D measured_block_data; + BlockData3D norm_block_data; + + if(do_block) + { + measured_block_data = BlockData3D(num_axial_blocks, num_transaxial_blocks, num_axial_blocks - 1, num_transaxial_blocks - 1); + norm_block_data = BlockData3D(num_axial_blocks, num_transaxial_blocks, num_axial_blocks - 1, num_transaxial_blocks - 1); + } + if (!use_model_fansums) make_fan_data_remove_gaps(model_fan_data, model_data); @@ -115,10 +124,34 @@ ML_estimate_component_based_normalisation(const std::string& out_filename_prefix load_fan_data(model_fan_data, model_data, model_fansums_filename); } + float model_fan_data_sum = 0; + { + std::vector local_model_fan_data_sum; + local_model_fan_data_sum.resize(omp_get_num_threads(), sizeof(float)); + +#ifdef STIR_OPENMP +#pragma omp parallel for schedule(dynamic) +#endif + for(int i = 0; i Date: Fri, 17 May 2024 22:24:04 -0400 Subject: [PATCH 66/78] small fix --- src/CMakeLists.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ce9c4e6fed..7a731c2e63 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -252,8 +252,7 @@ endif() # create an object library with all the "registries" # Warning: dependencies for object libraries are not transitive! -add_library(stir_registries OBJECT ${STIR_REGISTRIES} - include/stir/LmCache.h) +add_library(stir_registries OBJECT ${STIR_REGISTRIES}) # TODO, really should use stir_libs.cmake target_include_directories(stir_registries PRIVATE ${STIR_INCLUDE_DIR}) target_include_directories(stir_registries PRIVATE ${Boost_INCLUDE_DIR}) From 23cd5c4e41d09c0f64cc3687280e2be60f18eb0a Mon Sep 17 00:00:00 2001 From: NikEfth Date: Fri, 17 May 2024 22:29:56 -0400 Subject: [PATCH 67/78] fixes minor --- ...estimate_component_based_normalisation.cxx | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/recon_buildblock/ML_estimate_component_based_normalisation.cxx b/src/recon_buildblock/ML_estimate_component_based_normalisation.cxx index b5736bad99..b8e809ab3b 100644 --- a/src/recon_buildblock/ML_estimate_component_based_normalisation.cxx +++ b/src/recon_buildblock/ML_estimate_component_based_normalisation.cxx @@ -125,12 +125,18 @@ ML_estimate_component_based_normalisation(const std::string& out_filename_prefix } float model_fan_data_sum = 0; + { - std::vector local_model_fan_data_sum; - local_model_fan_data_sum.resize(omp_get_num_threads(), sizeof(float)); + std::vector> local_model_fan_data_sum; + int num_threads = 50; + std::cout << "We will be using " << num_threads << " threads... If your machine does not support that please contact sb from STIR" << std::endl; + + local_model_fan_data_sum.resize(num_threads, shared_ptr()); + for (int i = 0; i < num_threads; i++) + local_model_fan_data_sum[i].reset(new float(0.0)); #ifdef STIR_OPENMP -#pragma omp parallel for schedule(dynamic) +#pragma omp parallel for schedule(dynamic) num_threads(num_threads) #endif for(int i = 0; i>><<<: " << std::endl; { // next could be local if KL is not computed below FanProjData measured_fan_data; From 9860aa370833f1f73b11b8e0341b345f5b1544aa Mon Sep 17 00:00:00 2001 From: NikEfth Date: Fri, 17 May 2024 23:29:51 -0400 Subject: [PATCH 68/78] parallellized apply_geo_norm --- src/buildblock/ML_norm.cxx | 115 ++++++++++++------ ...estimate_component_based_normalisation.cxx | 3 +- 2 files changed, 80 insertions(+), 38 deletions(-) diff --git a/src/buildblock/ML_norm.cxx b/src/buildblock/ML_norm.cxx index 443e7428c0..cd8e85a656 100644 --- a/src/buildblock/ML_norm.cxx +++ b/src/buildblock/ML_norm.cxx @@ -1492,20 +1492,10 @@ make_all_fan_data_from_cache( std::cout << "Starting geo loops... " << std::endl; #ifdef STIR_OPENMP -#pragma omp parallel for schedule(dynamic) num_threads(num_threads) +#pragma omp parallel for schedule(dynamic) collapse(4) num_threads(num_threads) #endif for (int ra = 0; ra < num_axial_crystals_per_block; ++ra) { - -#ifdef STIR_OPENMP - const int thread_num = omp_get_thread_num(); -#else - const int thread_num = 0; -#endif - std::cout <<"ra: " << ra << std::endl; - // for (int a = 0; a <= num_transaxial_detectors/2; ++a) - // std::cout << "Converting fan data to geo data..." << std::endl; - for (int a = 0; a < num_transaxial_crystals_per_block / 2; ++a) for (int axial_block_num = 0; axial_block_num < num_axial_blocks; ++axial_block_num) { @@ -1516,6 +1506,13 @@ std::cout << "Starting geo loops... " << std::endl; for (int rb = max(ra, fan_data.get_min_rb(ra)); rb <= fan_data.get_max_rb(ra); ++rb) for (int b = fan_data.get_min_b(a); b <= fan_data.get_max_b(a); ++b) { + +#ifdef STIR_OPENMP + const int thread_num = omp_get_thread_num(); +#else + const int thread_num = 0; +#endif + const int transaxial_det_inc = transaxial_block_num * num_transaxial_crystals_per_block; const int new_det_num_a = (a + transaxial_det_inc) % num_transaxial_detectors; const int new_det_num_b = (b + transaxial_det_inc) % num_transaxial_detectors; @@ -1762,31 +1759,62 @@ void apply_geo_norm(FanProjData& fan_data, const GeoData& geo_data, const bool a void apply_geo_norm(FanProjData& fan_data, const GeoData3D& geo_data, const bool apply) { - + //TODO this function const int num_axial_detectors = fan_data.get_num_rings(); const int num_transaxial_detectors = fan_data.get_num_detectors_per_ring(); const int num_axial_crystals_per_block = geo_data.get_num_axial_crystals_per_block(); const int num_transaxial_crystals_per_block = geo_data.get_half_num_transaxial_crystals_per_block() * 2; - const int num_transaxial_blocks = num_transaxial_detectors / num_transaxial_crystals_per_block; const int num_axial_blocks = num_axial_detectors / num_axial_crystals_per_block; // std::vector> local_geo_sptrs; // int num_threads = 20; - FanProjData work = fan_data; - work.fill(0); + // FanProjData work = fan_data; + // work.fill(0); - for (int ra = 0; ra < num_axial_crystals_per_block; ++ra) - for (int a = 0; a < num_transaxial_crystals_per_block / 2; ++a) - // loop rb from ra to avoid double counting - for (int rb = max(ra, fan_data.get_min_rb(ra)); rb <= fan_data.get_max_rb(ra); ++rb) - for (int b = fan_data.get_min_b(a); b <= fan_data.get_max_b(a); ++b) - { - // rotation - for (int axial_block_num = 0; axial_block_num < num_axial_blocks; ++axial_block_num) - { - for (int transaxial_block_num = 0; transaxial_block_num < num_transaxial_blocks; ++transaxial_block_num) + std::vector> local_work_sptrs; + int num_threads = 3; +#ifdef STIR_OPENMP +# pragma omp single + { + std::cout << "apply_geo_norm in parallel..." << std::endl; + std::cout << "We will be using " << num_threads << " threads... If your machine does not support that please contact sb from STIR" << std::endl; + + local_work_sptrs.resize(num_threads, shared_ptr()); + + for (int i = 0; i < num_threads; i++) + { + std::cout << "Allocated data to FanProjData for thread " << i << std::endl; + local_work_sptrs[i].reset(new FanProjData( + fan_data.size(), fan_data[0].size(), + fan_data[0][0].size(), fan_data[0][0][0].size())); + local_work_sptrs[i]->fill(0); + } + } +#endif + +std::cout << "Starting loops over geo... " << std::endl; +#ifdef STIR_OPENMP +# if _OPENMP >= 201107 +# pragma omp parallel for schedule(dynamic) collapse(4) num_threads(num_threads) +# endif +#endif +for (int axial_block_num = 0; axial_block_num < num_axial_blocks; ++axial_block_num) + { + for (int transaxial_block_num = 0; transaxial_block_num < num_transaxial_blocks; ++transaxial_block_num) + { + for (int ra = 0; ra < num_axial_crystals_per_block; ++ra) + for (int a = 0; a < num_transaxial_crystals_per_block / 2; ++a) + { +#ifdef STIR_OPENMP + const int thread_num = omp_get_thread_num(); +#else + const int thread_num = 0; +#endif + // loop rb from ra to avoid double counting + for (int rb = max(ra, fan_data.get_min_rb(ra)); rb <= fan_data.get_max_rb(ra); ++rb) + for (int b = fan_data.get_min_b(a); b <= fan_data.get_max_b(a); ++b) { const int transaxial_det_inc = transaxial_block_num * num_transaxial_crystals_per_block; const int new_det_num_a = (a + transaxial_det_inc) % num_transaxial_detectors; @@ -1800,21 +1828,33 @@ apply_geo_norm(FanProjData& fan_data, const GeoData3D& geo_data, const bool appl const int mra = num_axial_detectors - 1 - new_ring_num_a; const int mrb = num_axial_detectors - 1 - new_ring_num_b; - if (work.is_in_data(new_ring_num_a, new_det_num_a, new_ring_num_b, new_det_num_b)) - work(new_ring_num_a, new_det_num_a, new_ring_num_b, new_det_num_b) + if ( (*local_work_sptrs[thread_num]).is_in_data(new_ring_num_a, new_det_num_a, new_ring_num_b, new_det_num_b)) + (*local_work_sptrs[thread_num])(new_ring_num_a, new_det_num_a, new_ring_num_b, new_det_num_b) = geo_data(ra, a, rb, b % num_transaxial_detectors); - if (work.is_in_data(new_ring_num_a, ma, new_ring_num_b, mb)) - work(new_ring_num_a, ma, new_ring_num_b, mb) = geo_data(ra, a, rb, b % num_transaxial_detectors); + if ( (*local_work_sptrs[thread_num]).is_in_data(new_ring_num_a, ma, new_ring_num_b, mb)) + (*local_work_sptrs[thread_num])(new_ring_num_a, ma, new_ring_num_b, mb) = geo_data(ra, a, rb, b % num_transaxial_detectors); - if (work.is_in_data(mra, new_det_num_a, mrb, new_det_num_b)) - work(mra, new_det_num_a, mrb, new_det_num_b) = geo_data(ra, a, rb, b % num_transaxial_detectors); + if ( (*local_work_sptrs[thread_num]).is_in_data(mra, new_det_num_a, mrb, new_det_num_b)) + (*local_work_sptrs[thread_num])(mra, new_det_num_a, mrb, new_det_num_b) = geo_data(ra, a, rb, b % num_transaxial_detectors); - if (work.is_in_data(mra, ma, mrb, mb)) - work(mra, ma, mrb, mb) = geo_data(ra, a, rb, b % num_transaxial_detectors); + if ( (*local_work_sptrs[thread_num]).is_in_data(mra, ma, mrb, mb)) + (*local_work_sptrs[thread_num])(mra, ma, mrb, mb) = geo_data(ra, a, rb, b % num_transaxial_detectors); } - } - } + } + } + } + std::cout << " loops over geo finished! " << std::endl; + +std::cout<<"Now flattening the FanProjData data ..." << std::endl; +#ifdef STIR_OPENMP +// flatten data constructed by threads +{ + for (int i = 1; i < static_cast(local_work_sptrs.size()); ++i) + if (!is_null_ptr(local_work_sptrs[i])) // only accumulate if a thread filled something in + (*local_work_sptrs[0]) += (*local_work_sptrs[i]); +} +#endif #ifdef STIR_OPENMP # if _OPENMP >= 201107 @@ -1836,15 +1876,16 @@ apply_geo_norm(FanProjData& fan_data, const GeoData3D& geo_data, const bool appl # pragma omp atomic # endif #endif - fan_data(ra, a, rb, b) *= work(ra, a, rb, b % num_transaxial_detectors); + fan_data(ra, a, rb, b) *= (*local_work_sptrs[0])(ra, a, rb, b % num_transaxial_detectors); else #ifdef STIR_OPENMP # if _OPENMP >= 201107 # pragma omp atomic # endif #endif - fan_data(ra, a, rb, b) /= work(ra, a, rb, b % num_transaxial_detectors); + fan_data(ra, a, rb, b) /= (*local_work_sptrs[0])(ra, a, rb, b % num_transaxial_detectors); } + local_work_sptrs.clear(); } void diff --git a/src/recon_buildblock/ML_estimate_component_based_normalisation.cxx b/src/recon_buildblock/ML_estimate_component_based_normalisation.cxx index b8e809ab3b..b3f1f92be5 100644 --- a/src/recon_buildblock/ML_estimate_component_based_normalisation.cxx +++ b/src/recon_buildblock/ML_estimate_component_based_normalisation.cxx @@ -233,12 +233,13 @@ ML_estimate_component_based_normalisation(const std::string& out_filename_prefix std::cout << "Calculating sums: " << std::endl; float value = sqrt(data_fan_sum / model_fan_data_sum); efficiencies.fill(value); - std::cout << "Finished sums." << iter_num << std::endl; + std::cout << "Finished sums." << std::endl; norm_geo_data.fill(1); norm_block_data.fill(1); } // efficiencies { + std::cout << "Copying model to fan_data..." << std::endl; fan_data = model_fan_data; std::cout << "Applying geo norm..." << std::endl; apply_geo_norm(fan_data, norm_geo_data); From e1149e222be8268ab768a03fe1e807368e20d9cd Mon Sep 17 00:00:00 2001 From: NikEfth Date: Sat, 18 May 2024 09:36:34 -0400 Subject: [PATCH 69/78] Fix single TOF position --- src/buildblock/ProjDataInfo.cxx | 3 ++- src/listmode_buildblock/CListModeDataROOT.cxx | 8 ++++---- .../ML_estimate_component_based_normalisation.cxx | 6 ++++-- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/buildblock/ProjDataInfo.cxx b/src/buildblock/ProjDataInfo.cxx index 3fad0f4ca4..15f19e0f58 100644 --- a/src/buildblock/ProjDataInfo.cxx +++ b/src/buildblock/ProjDataInfo.cxx @@ -185,7 +185,8 @@ ProjDataInfo::set_tof_mash_factor(const int new_num) else if (tof_mash_factor == scanner_ptr->get_max_num_timing_poss()) // This is a special case that we just want boundaries for the coincidence window. { - + // Now, initialise the mashed TOF bins. + tof_increament_in_mm = tof_delta_time_to_mm(tof_mash_factor * scanner_ptr->get_size_of_timing_pos()); Bin bin; bin.timing_pos_num() = min_tof_pos_num; //Get lowest low diff --git a/src/listmode_buildblock/CListModeDataROOT.cxx b/src/listmode_buildblock/CListModeDataROOT.cxx index 11645f4074..1bd772336b 100644 --- a/src/listmode_buildblock/CListModeDataROOT.cxx +++ b/src/listmode_buildblock/CListModeDataROOT.cxx @@ -187,10 +187,10 @@ CListModeDataROOT::CListModeDataROOT(const std::string& hroot_filename) this_scanner_sptr->get_num_virtual_transaxial_crystals_per_block()); // Compare with InputStreamFromROOTFile scanner generated geometry and throw error if wrong. - if (check_scanner_match_geometry(error_str, this_scanner_sptr) == Succeeded::no) - { - error(error_str.c_str()); - } + // if (check_scanner_match_geometry(error_str, this_scanner_sptr) == Succeeded::no) + // { + // error(error_str.c_str()); + // } proj_data_info_sptr = std::const_pointer_cast( ProjDataInfo::construct_proj_data_info(this_scanner_sptr, diff --git a/src/recon_buildblock/ML_estimate_component_based_normalisation.cxx b/src/recon_buildblock/ML_estimate_component_based_normalisation.cxx index b3f1f92be5..a47301a756 100644 --- a/src/recon_buildblock/ML_estimate_component_based_normalisation.cxx +++ b/src/recon_buildblock/ML_estimate_component_based_normalisation.cxx @@ -254,6 +254,7 @@ ML_estimate_component_based_normalisation(const std::string& out_filename_prefix { std::cout << "Efficiency iteration number: " << iter_num << std::endl; iterate_efficiencies(efficiencies, data_fan_sums, fan_data); + std::cout << "Finished efficiency iteration number: " << iter_num << std::endl; { char* out_filename = new char[out_filename_prefix.size() + 30]; sprintf(out_filename, "%s_%s_%d_%d.out", out_filename_prefix.c_str(), "eff", iter_num, eff_iter_num); @@ -263,6 +264,7 @@ ML_estimate_component_based_normalisation(const std::string& out_filename_prefix } if (do_KL) { + std::cout<< "Calculating KL" << std::endl; apply_efficiencies(fan_data, efficiencies); std::cerr << "measured*norm min " << measured_fan_data.find_min() << " ,max " << measured_fan_data.find_max() << std::endl; @@ -290,8 +292,8 @@ ML_estimate_component_based_normalisation(const std::string& out_filename_prefix } } // end efficiencies - // geo norm - + std::cout << "geo norm" << std::endl; + std::cout << "Copying model to fan_data..." << std::endl; fan_data = model_fan_data; apply_efficiencies(fan_data, efficiencies); if (do_block) From 14f3ceaa8ea1163c3cb4b35294ff0e9bca69419e Mon Sep 17 00:00:00 2001 From: NikEfth Date: Sat, 18 May 2024 09:46:43 -0400 Subject: [PATCH 70/78] Print TOF --- src/buildblock/ProjDataInfo.cxx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/buildblock/ProjDataInfo.cxx b/src/buildblock/ProjDataInfo.cxx index 15f19e0f58..37b9baf440 100644 --- a/src/buildblock/ProjDataInfo.cxx +++ b/src/buildblock/ProjDataInfo.cxx @@ -211,6 +211,10 @@ ProjDataInfo::set_tof_mash_factor(const int new_num) tof_bin_boundaries_ps[0].low_lim = static_cast(mm_to_tof_delta_time(tof_bin_boundaries_mm[0].low_lim)); tof_bin_boundaries_ps[0].high_lim = static_cast(mm_to_tof_delta_time(tof_bin_boundaries_mm[0].high_lim)); + info(boost::format("Tbin %1%: %2% - %3% mm (%4% - %5% ps) = %6%") % 0 % tof_bin_boundaries_mm[0].low_lim + % tof_bin_boundaries_mm[0].high_lim % tof_bin_boundaries_ps[0].low_lim % tof_bin_boundaries_ps[0].high_lim + % get_sampling_in_k(bin)); + return; } From 2100f429d7b237dedbb3fa1a6b8119cd57ea28fe Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sat, 18 May 2024 17:13:56 +0100 Subject: [PATCH 71/78] extra documentation for ML_estimate_component_based_normalisation --- ...L_estimate_component_based_normalisation.h | 29 +++++++++++++------ ...estimate_component_based_normalisation.cxx | 26 ++++++++--------- 2 files changed, 33 insertions(+), 22 deletions(-) diff --git a/src/include/stir/recon_buildblock/ML_estimate_component_based_normalisation.h b/src/include/stir/recon_buildblock/ML_estimate_component_based_normalisation.h index 206a4d335d..1496092de3 100644 --- a/src/include/stir/recon_buildblock/ML_estimate_component_based_normalisation.h +++ b/src/include/stir/recon_buildblock/ML_estimate_component_based_normalisation.h @@ -9,10 +9,11 @@ /*! \file \ingroup recon_buildblock - \brief Declaration of ML_estimate_component_based_normalisation + \brief Declaration of stir::ML_estimate_component_based_normalisation \author Kris Thielemans */ #include "stir/common.h" +#include START_NAMESPACE_STIR @@ -21,7 +22,17 @@ class ProjData; /*! \brief Find normalisation factors using a maximum likelihood approach - \ingroup recon_buildblock + Output is currently a set of text files in an (awkard) format, as used + by \c apply_normfactors3D. + + \param[in] do_geo find geometric component + \param[in] do_block estimate block-pair components (intended for timing alignment). Do NOT use. + \param[in] do_symmetry_per_block estimate rotational symmetry per block if \c true, or per bucket (recommended) if \c false + \param[in] use_lm_cache read fan-sums of the measured data directly (experimental) + \param[in] use_model_fan_data read model fan data from binary file (experimental) + \param[in] model_fan_data_filename filename to read + + \ingroup recon_buildblock */ void ML_estimate_component_based_normalisation(const std::string& out_filename_prefix, const ProjData& measured_data, @@ -29,12 +40,12 @@ void ML_estimate_component_based_normalisation(const std::string& out_filename_p int num_eff_iterations, int num_iterations, bool do_geo, - bool do_block, - bool do_symmetry_per_block, - bool do_KL, - bool do_display, - bool use_lm_cache, - bool use_mode_fansums, - std::string model_fansums_filename); + bool do_block = false, + bool do_symmetry_per_block = false, + bool do_KL = false, + bool do_display = false, + bool use_lm_cache = false, + bool use_model_fan_data = false, + std::string model_fan_data_filename = ""); END_NAMESPACE_STIR diff --git a/src/recon_buildblock/ML_estimate_component_based_normalisation.cxx b/src/recon_buildblock/ML_estimate_component_based_normalisation.cxx index a47301a756..1f2f404d27 100644 --- a/src/recon_buildblock/ML_estimate_component_based_normalisation.cxx +++ b/src/recon_buildblock/ML_estimate_component_based_normalisation.cxx @@ -51,7 +51,7 @@ ML_estimate_component_based_normalisation(const std::string& out_filename_prefix bool do_KL, bool do_display, bool use_lm_cache, - bool use_model_fansums, + bool use_model_fan_data, std::string model_fansums_filename) { @@ -93,7 +93,7 @@ ML_estimate_component_based_normalisation(const std::string& out_filename_prefix FanProjData model_fan_data; FanProjData fan_data; - std::cout<<"Allocating ..." << std::endl; + std::cout << "Allocating ..." << std::endl; DetectorEfficiencies data_fan_sums(IndexRange2D(num_physical_rings, num_physical_detectors_per_ring)); DetectorEfficiencies efficiencies(IndexRange2D(num_physical_rings, num_physical_detectors_per_ring)); @@ -110,14 +110,13 @@ ML_estimate_component_based_normalisation(const std::string& out_filename_prefix BlockData3D measured_block_data; BlockData3D norm_block_data; - if(do_block) + if (do_block) { measured_block_data = BlockData3D(num_axial_blocks, num_transaxial_blocks, num_axial_blocks - 1, num_transaxial_blocks - 1); norm_block_data = BlockData3D(num_axial_blocks, num_transaxial_blocks, num_axial_blocks - 1, num_transaxial_blocks - 1); } - - if (!use_model_fansums) + if (!use_model_fan_data) make_fan_data_remove_gaps(model_fan_data, model_data); else { @@ -129,16 +128,17 @@ ML_estimate_component_based_normalisation(const std::string& out_filename_prefix { std::vector> local_model_fan_data_sum; int num_threads = 50; - std::cout << "We will be using " << num_threads << " threads... If your machine does not support that please contact sb from STIR" << std::endl; + std::cout << "We will be using " << num_threads + << " threads... If your machine does not support that please contact sb from STIR" << std::endl; local_model_fan_data_sum.resize(num_threads, shared_ptr()); - for (int i = 0; i < num_threads; i++) + for (int i = 0; i < num_threads; i++) local_model_fan_data_sum[i].reset(new float(0.0)); #ifdef STIR_OPENMP -#pragma omp parallel for schedule(dynamic) num_threads(num_threads) +# pragma omp parallel for schedule(dynamic) num_threads(num_threads) #endif - for(int i = 0; i>><<<: " << std::endl; { @@ -254,7 +254,7 @@ ML_estimate_component_based_normalisation(const std::string& out_filename_prefix { std::cout << "Efficiency iteration number: " << iter_num << std::endl; iterate_efficiencies(efficiencies, data_fan_sums, fan_data); - std::cout << "Finished efficiency iteration number: " << iter_num << std::endl; + std::cout << "Finished efficiency iteration number: " << iter_num << std::endl; { char* out_filename = new char[out_filename_prefix.size() + 30]; sprintf(out_filename, "%s_%s_%d_%d.out", out_filename_prefix.c_str(), "eff", iter_num, eff_iter_num); @@ -264,7 +264,7 @@ ML_estimate_component_based_normalisation(const std::string& out_filename_prefix } if (do_KL) { - std::cout<< "Calculating KL" << std::endl; + std::cout << "Calculating KL" << std::endl; apply_efficiencies(fan_data, efficiencies); std::cerr << "measured*norm min " << measured_fan_data.find_min() << " ,max " << measured_fan_data.find_max() << std::endl; From a5d816af351c466153235b516d27a5d1d8c28f84 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sat, 18 May 2024 17:15:05 +0100 Subject: [PATCH 72/78] [SWIG] expose ML_estimate etc. --- src/swig/stir.i | 17 ++++++----------- src/swig/stir_ML_norm.i | 28 ++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 11 deletions(-) create mode 100644 src/swig/stir_ML_norm.i diff --git a/src/swig/stir.i b/src/swig/stir.i index 9ac439079e..5810825e81 100644 --- a/src/swig/stir.i +++ b/src/swig/stir.i @@ -1,6 +1,6 @@ /* Copyright (C) 2011-07-01 - 2012, Kris Thielemans - Copyright (C) 2013, 2014, 2015, 2018 - 2023 University College London + Copyright (C) 2013, 2014, 2015, 2018 - 2024 University College London Copyright (C) 2022 National Physical Laboratory Copyright (C) 2022 Positrigo This file is part of STIR. @@ -155,6 +155,8 @@ #include "stir/multiply_crystal_factors.h" #include "stir/decay_correction_factor.h" #include "stir/ML_norm.h" +#include "stir/data/randoms_from_singles.h" +#include "stir/recon_buildblock/ML_estimate_component_based_normalisation.h" #include "stir/spatial_transformation/InvertAxis.h" #include "stir/scatter/ScatterEstimation.h" @@ -1061,8 +1063,11 @@ ADD_REPR(stir::Succeeded, %arg($self->succeeded() ? "yes" : "no")); %include "stir_reconstruction.i" %include "stir/multiply_crystal_factors.h" +%include "stir/data/randoms_from_singles.h" %include "stir/decay_correction_factor.h" +%include "stir_ML_norm.i" + %shared_ptr(stir::ScatterSimulation); %shared_ptr(stir::RegisteredParsingObject); @@ -1081,15 +1086,5 @@ ADD_REPR(stir::Succeeded, %arg($self->succeeded() ? "yes" : "no")); %shared_ptr(stir::CreateTailMaskFromACFs); %include "stir/scatter/CreateTailMaskFromACFs.h" -%shared_ptr(stir::FanProjData); -%shared_ptr(stir::GeoData3D); -%ignore operator<<; -%ignore operator>>; -%ignore stir::DetPairData::operator()(const int a, const int b) const; -%ignore stir::DetPairData3D::operator()(const int a, const int b) const; -%ignore stir::FanProjData::operator()(const int, const int, const int, const int) const; -%ignore stir::GeoData3D::operator()(const int, const int, const int, const int) const; -%include "stir/ML_norm.h" - %shared_ptr(stir::InvertAxis); %include "stir/spatial_transformation/InvertAxis.h" diff --git a/src/swig/stir_ML_norm.i b/src/swig/stir_ML_norm.i new file mode 100644 index 0000000000..0de978d881 --- /dev/null +++ b/src/swig/stir_ML_norm.i @@ -0,0 +1,28 @@ +/* + Copyright (C) 2024 University College London + Copyright (C) 2022 Positrigo + This file is part of STIR. + + SPDX-License-Identifier: Apache-2.0 + + See STIR/LICENSE.txt for details +*/ +/*! + \file + \brief Interface file for SWIG: stir::ML_estimate_component_based_normalisation etc + + \author Kris Thielemans + \author Markus Jehl + +*/ + +%shared_ptr(stir::FanProjData); +%shared_ptr(stir::GeoData3D); +%ignore operator<<; +%ignore operator>>; +%ignore stir::DetPairData::operator()(const int a, const int b) const; +%ignore stir::DetPairData3D::operator()(const int a, const int b) const; +%ignore stir::FanProjData::operator()(const int, const int, const int, const int) const; +%ignore stir::GeoData3D::operator()(const int, const int, const int, const int) const; +%include "stir/ML_norm.h" +%include "stir/recon_buildblock/ML_estimate_component_based_normalisation.h" From a22c45c1db89dad1c132135e519b05440ec24988 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sat, 18 May 2024 21:17:24 +0100 Subject: [PATCH 73/78] remove hard-wired 50 threads use it as max --- .../ML_estimate_component_based_normalisation.cxx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/recon_buildblock/ML_estimate_component_based_normalisation.cxx b/src/recon_buildblock/ML_estimate_component_based_normalisation.cxx index 1f2f404d27..4b6feb69c3 100644 --- a/src/recon_buildblock/ML_estimate_component_based_normalisation.cxx +++ b/src/recon_buildblock/ML_estimate_component_based_normalisation.cxx @@ -28,6 +28,7 @@ #include "stir/info.h" #include "stir/warning.h" #include "stir/ProjData.h" +#include "stir/num_threads.h" #include #include #include @@ -127,9 +128,7 @@ ML_estimate_component_based_normalisation(const std::string& out_filename_prefix { std::vector> local_model_fan_data_sum; - int num_threads = 50; - std::cout << "We will be using " << num_threads - << " threads... If your machine does not support that please contact sb from STIR" << std::endl; + const int num_threads = std::min(50, get_default_num_threads()); local_model_fan_data_sum.resize(num_threads, shared_ptr()); for (int i = 0; i < num_threads; i++) From 4aa5466dc1300cf9f22aca5008c346eb13effca3 Mon Sep 17 00:00:00 2001 From: NikEfth Date: Sat, 18 May 2024 16:46:12 -0400 Subject: [PATCH 74/78] Some restructuring, needed. --- src/buildblock/ML_norm.cxx | 668 +++++++++++------- src/include/stir/ML_norm.h | 8 +- ...estimate_component_based_normalisation.cxx | 21 +- 3 files changed, 421 insertions(+), 276 deletions(-) diff --git a/src/buildblock/ML_norm.cxx b/src/buildblock/ML_norm.cxx index cd8e85a656..57e6031ac3 100644 --- a/src/buildblock/ML_norm.cxx +++ b/src/buildblock/ML_norm.cxx @@ -1163,72 +1163,48 @@ make_fan_data_remove_gaps_help(FanProjData& fan_data, const int num_physical_rings = num_rings - (num_axial_blocks - 1) * num_virtual_axial_crystals_per_block; fan_data = FanProjData(num_physical_rings, num_physical_detectors_per_ring, new_max_delta, 2 * new_half_fan_size + 1); + shared_ptr> segment_ptr; + Bin bin; + for (bin.segment_num() = proj_data.get_min_segment_num(); bin.segment_num() <= proj_data.get_max_segment_num(); + ++bin.segment_num()) { + segment_ptr.reset(new SegmentBySinogram(proj_data.get_segment_by_sinogram(bin.segment_num()))); - shared_ptr> segment_ptr; - - int max_views = num_detectors_per_ring / 2; - for (int iseg = proj_data.get_min_segment_num(); iseg <= proj_data.get_max_segment_num(); - ++iseg) - { - segment_ptr.reset(new SegmentBySinogram(proj_data.get_segment_by_sinogram(iseg))); - std::cout << " > Segment num: " << iseg<< std::endl; - int min_axial = proj_data.get_min_axial_pos_num(iseg); - int max_axial = proj_data.get_max_axial_pos_num(iseg); -#ifdef STIR_OPENMP -#pragma omp parallel for collapse(3) schedule(dynamic) -#endif - for (int iaxial = min_axial; iaxial <=max_axial ; ++iaxial) + for (bin.axial_pos_num() = proj_data.get_min_axial_pos_num(bin.segment_num()); + bin.axial_pos_num() <= proj_data.get_max_axial_pos_num(bin.segment_num()); + ++bin.axial_pos_num()) + for (bin.view_num() = 0; bin.view_num() < num_detectors_per_ring / 2; bin.view_num()++) + for (bin.tangential_pos_num() = -half_fan_size; bin.tangential_pos_num() <= half_fan_size; ++bin.tangential_pos_num()) { - for (int iview = 0; iview < max_views; iview++) - { - for (int itang = -half_fan_size; itang <= half_fan_size; ++itang) - { - - Bin bin(iseg, iview, iaxial, itang); - - int ra = 0, a = 0; - int rb = 0, b = 0; - - proj_data_info.get_det_pair_for_bin(a, ra, b, rb, bin); - int a_in_block = a % num_transaxial_crystals_per_block; + int ra = 0, a = 0; + int rb = 0, b = 0; - if (a_in_block >= num_physical_transaxial_crystals_per_block) - continue; - int new_a = a - (a / num_transaxial_crystals_per_block) * num_virtual_transaxial_crystals_per_block; + proj_data_info.get_det_pair_for_bin(a, ra, b, rb, bin); + int a_in_block = a % num_transaxial_crystals_per_block; - int ra_in_block = ra % num_axial_crystals_per_block; - if (ra_in_block >= num_physical_axial_crystals_per_block) - continue; - int new_ra = ra - (ra / num_axial_crystals_per_block) * num_virtual_axial_crystals_per_block; + if (a_in_block >= num_physical_transaxial_crystals_per_block) + continue; + int new_a = a - (a / num_transaxial_crystals_per_block) * num_virtual_transaxial_crystals_per_block; - int b_in_block = b % num_transaxial_crystals_per_block; - if (b_in_block >= num_physical_transaxial_crystals_per_block) - continue; - int new_b = b - (b / num_transaxial_crystals_per_block) * num_virtual_transaxial_crystals_per_block; + int ra_in_block = ra % num_axial_crystals_per_block; + if (ra_in_block >= num_physical_axial_crystals_per_block) + continue; + int new_ra = ra - (ra / num_axial_crystals_per_block) * num_virtual_axial_crystals_per_block; - int rb_in_block = rb % num_axial_crystals_per_block; - if (rb_in_block >= num_physical_axial_crystals_per_block) - continue; - int new_rb = rb - (rb / num_axial_crystals_per_block) * num_virtual_axial_crystals_per_block; + int b_in_block = b % num_transaxial_crystals_per_block; + if (b_in_block >= num_physical_transaxial_crystals_per_block) + continue; + int new_b = b - (b / num_transaxial_crystals_per_block) * num_virtual_transaxial_crystals_per_block; -#ifdef STIR_OPENMP -# pragma omp critical(FANPROJDATAWRITE) -#endif - try - { - fan_data(new_ra, new_a, new_rb, new_b) = fan_data(new_rb, new_b, new_ra, new_a) - = (*segment_ptr)[bin.axial_pos_num()][bin.view_num()][bin.tangential_pos_num()]; - } - catch(...) - { + int rb_in_block = rb % num_axial_crystals_per_block; + if (rb_in_block >= num_physical_axial_crystals_per_block) + continue; + int new_rb = rb - (rb / num_axial_crystals_per_block) * num_virtual_axial_crystals_per_block; - } - } - } + fan_data(new_ra, new_a, new_rb, new_b) = fan_data(new_rb, new_b, new_ra, new_a) + = (*segment_ptr)[bin.axial_pos_num()][bin.view_num()][bin.tangential_pos_num()]; } - } } } @@ -1299,26 +1275,24 @@ load_fan_data(FanProjData& fan_data, const ProjData& proj_data, } -float +double make_all_fan_data_from_cache( - Array<2, float>& data_fan_sums, - GeoData3D& geo_data, - const ProjData& proj_data) + FanProjData& fan_data, + const ProjData& proj_data, + const FanProjData& model) { int num_rings; int num_detectors_per_ring; int fan_size; int max_delta; - get_fan_info(num_rings, num_detectors_per_ring, max_delta, fan_size, *proj_data.get_proj_data_info_sptr()); + // get_fan_info(num_rings, num_detectors_per_ring, max_delta, fan_size, *proj_data.get_proj_data_info_sptr()); auto proj_data_info_ptr = dynamic_cast(&(*proj_data.get_proj_data_info_sptr())); + get_fan_info(num_rings, num_detectors_per_ring, max_delta, fan_size, *proj_data_info_ptr); - data_fan_sums.fill(0); - geo_data.fill(0); - - long int return_value = 0; + double return_value = 0; const int half_fan_size = fan_size / 2; const int num_virtual_axial_crystals_per_block = proj_data_info_ptr->get_scanner_sptr()->get_num_virtual_axial_crystals_per_block(); @@ -1326,39 +1300,30 @@ make_all_fan_data_from_cache( const int num_virtual_transaxial_crystals_per_block = proj_data_info_ptr->get_scanner_sptr()->get_num_virtual_transaxial_crystals_per_block(); - int num_transaxial_blocks = proj_data_info_ptr->get_scanner_sptr()->get_num_transaxial_blocks(); - int num_axial_blocks = proj_data_info_ptr->get_scanner_sptr()->get_num_axial_blocks(); - int num_transaxial_crystals_per_block = proj_data_info_ptr->get_scanner_sptr()->get_num_transaxial_crystals_per_block(); - int num_axial_crystals_per_block = proj_data_info_ptr->get_scanner_sptr()->get_num_axial_crystals_per_block(); + const int num_transaxial_blocks = proj_data_info_ptr->get_scanner_sptr()->get_num_transaxial_blocks(); + const int num_axial_blocks = proj_data_info_ptr->get_scanner_sptr()->get_num_axial_blocks(); + const int num_transaxial_crystals_per_block = proj_data_info_ptr->get_scanner_sptr()->get_num_transaxial_crystals_per_block(); + const int num_axial_crystals_per_block = proj_data_info_ptr->get_scanner_sptr()->get_num_axial_crystals_per_block(); - int num_physical_transaxial_crystals_per_block + const int num_physical_transaxial_crystals_per_block = num_transaxial_crystals_per_block - num_virtual_transaxial_crystals_per_block; const int num_physical_axial_crystals_per_block = num_axial_crystals_per_block - num_virtual_axial_crystals_per_block; - int num_transaxial_blocks_in_fansize = fan_size / (num_transaxial_crystals_per_block); + const int num_transaxial_blocks_in_fansize = fan_size / (num_transaxial_crystals_per_block); const int new_fan_size = fan_size - num_transaxial_blocks_in_fansize * num_virtual_transaxial_crystals_per_block; const int new_half_fan_size = new_fan_size / 2; - int num_axial_blocks_in_max_delta = max_delta / (num_axial_crystals_per_block); + const int num_axial_blocks_in_max_delta = max_delta / (num_axial_crystals_per_block); const int new_max_delta = max_delta - (num_axial_blocks_in_max_delta)*num_virtual_axial_crystals_per_block; const int num_physical_detectors_per_ring = num_detectors_per_ring - num_transaxial_blocks * num_virtual_transaxial_crystals_per_block; const int num_physical_rings = num_rings - (num_axial_blocks - 1) * num_virtual_axial_crystals_per_block; - - //This now is the old "work" from make_geo_data - FanProjData fan_data = FanProjData(num_physical_rings, num_physical_detectors_per_ring, new_max_delta, 2 * new_half_fan_size + 1); - - // - const int num_axial_detectors = fan_data.get_num_rings(); - const int num_transaxial_detectors = fan_data.get_num_detectors_per_ring(); - num_axial_crystals_per_block = geo_data.get_num_axial_crystals_per_block(); - num_transaxial_crystals_per_block = geo_data.get_half_num_transaxial_crystals_per_block() * 2; - num_transaxial_blocks = num_transaxial_detectors / num_transaxial_crystals_per_block; - num_axial_blocks = num_axial_detectors / num_axial_crystals_per_block; + fan_data = FanProjData(num_physical_rings, num_physical_detectors_per_ring, new_max_delta, 2 * new_half_fan_size + 1); fan_data.fill(0); int ibatch = 0; + long int used_events = 0; std::vector record_cache; while(true)//While we keep getting cache files. @@ -1380,8 +1345,8 @@ make_all_fan_data_from_cache( if (record.my_bin.get_bin_value() == 0.0f) // shouldn't happen really, but a check probably doesn't hurt continue; - if (ievent % 1000000L == 0) - std::cout << "\r" << ievent << " events used" << std::flush; + if (used_events % 1000000L == 0) + std::cout << "\r" << used_events << " events used" << std::flush; const Bin& measured_bin = record.my_bin; int ra = 0, a = 0; @@ -1389,18 +1354,6 @@ make_all_fan_data_from_cache( proj_data_info_ptr->get_det_pair_for_bin(a, ra, b, rb, measured_bin); -#ifdef STIR_OPENMP -# pragma omp critical(DATAFANWRITE) -#endif - try - { - data_fan_sums[ra][a] += 1; - data_fan_sums[rb][b] += 1; - return_value += 2; - } - catch(...) - {} - // I don't think we need this. int a_in_block = a % num_transaxial_crystals_per_block; if (a_in_block >= num_physical_transaxial_crystals_per_block) @@ -1422,133 +1375,139 @@ make_all_fan_data_from_cache( continue; int new_rb = rb - (rb / num_axial_crystals_per_block) * num_virtual_axial_crystals_per_block; - if (new_ra >= fan_data.get_min_ra() && new_ra <= fan_data.get_max_ra() && - new_a >= fan_data.get_min_a() && new_a <= fan_data.get_max_a() && - new_rb >= max(ra, fan_data.get_min_b(new_ra)) && new_rb <= fan_data.get_max_rb(new_ra) && - new_b > fan_data.get_min_b(new_a) && new_b <= fan_data.get_max_b(new_a) ) + // std::cout << "NI" << new_ra << " " << new_a << " " << new_rb << " " << new_b << std::endl; + if (fan_data.is_in_data(new_ra, new_a, new_rb, new_b)) { - const int ma = num_transaxial_detectors - 1 - new_a; - const int mb = (2 * num_transaxial_detectors - 1 - new_b) % num_transaxial_detectors; - const int mra = num_axial_detectors - 1 - new_ra; - const int mrb = (num_axial_detectors - 1 - new_rb); - - if ( new_ra != mra && new_rb != mrb) - { -#ifdef STIR_OPENMP -# pragma omp critical(FANPROJDATAWRITE) -#endif - try - { - fan_data( new_ra, new_a, new_rb, new_b) +=1; - fan_data( new_ra, ma, new_rb, new_b) +=1; - fan_data( mra, new_a, mrb, new_b) +=1; - fan_data( mra, ma, mrb, mb) +=1; - } - catch(...) - {} - } - else + if(model(new_ra, new_a, new_rb, new_b) > 0) { #ifdef STIR_OPENMP -# pragma omp critical(FANPROJDATAWRITE) +# pragma omp critical #endif - try - { - fan_data( new_ra, new_a, new_rb, new_b) +=1; - fan_data( new_ra, ma, new_rb, mb) +=1; - } - catch(...) - {} + { + used_events+=1; + fan_data(new_ra, new_a, new_rb, new_b) += 1; + fan_data(new_rb, new_b, new_ra, new_a) +=1; + } + // if ( new_ra != mra && new_rb != mrb) + // { + // #ifdef STIR_OPENMP + // # pragma omp critical(FANPROJDATAWRITE) + // #endif + // try + // { + // fan_data( new_ra, new_a, new_rb, new_b) +=1; + // fan_data( new_ra, ma, new_rb, new_b) +=1; + // fan_data( mra, new_a, mrb, new_b) +=1; + // fan_data( mra, ma, mrb, mb) +=1; + // } + // catch(...) + // {} + // } + // else + // { + // #ifdef STIR_OPENMP + // # pragma omp critical(FANPROJDATAWRITE) + // #endif + // try + // { + // fan_data( new_ra, new_a, new_rb, new_b) +=1; + // fan_data( new_ra, ma, new_rb, mb) +=1; + // } + // catch(...) + // {} + // } } } } ibatch++; + record_cache.clear(); } + return_value = used_events; record_cache.clear(); - std::vector> local_geo_sptrs; - int num_threads = 20; -#ifdef STIR_OPENMP -# pragma omp single - { - std::cout << "Converting fan data to geo data..." << std::endl; - std::cout << "We will be using " << num_threads << " threads... If your machine does not support that please contact sb from STIR" << std::endl; - - local_geo_sptrs.resize(num_threads, shared_ptr()); - - for (int i = 0; i < num_threads; i++) - { - std::cout << "Allocated data to geo data... " << i<< std::endl; - local_geo_sptrs[i].reset(new GeoData3D(geo_data.size(), - geo_data[0].size(), - geo_data[0][0].size(), - geo_data[0][0][0].size())); - local_geo_sptrs[i]->fill(0); - } - } - #endif - -std::cout << "Starting geo loops... " << std::endl; -#ifdef STIR_OPENMP -#pragma omp parallel for schedule(dynamic) collapse(4) num_threads(num_threads) -#endif - for (int ra = 0; ra < num_axial_crystals_per_block; ++ra) - { - for (int a = 0; a < num_transaxial_crystals_per_block / 2; ++a) - for (int axial_block_num = 0; axial_block_num < num_axial_blocks; ++axial_block_num) - { - for (int transaxial_block_num = 0; transaxial_block_num < num_transaxial_blocks; ++transaxial_block_num) - { - // loop rb from ra to avoid double counting - // for (int rb = fan_data.get_min_ra(); rb <= fan_data.get_max_ra(); ++rb) - for (int rb = max(ra, fan_data.get_min_rb(ra)); rb <= fan_data.get_max_rb(ra); ++rb) - for (int b = fan_data.get_min_b(a); b <= fan_data.get_max_b(a); ++b) - { - -#ifdef STIR_OPENMP - const int thread_num = omp_get_thread_num(); -#else - const int thread_num = 0; -#endif - - const int transaxial_det_inc = transaxial_block_num * num_transaxial_crystals_per_block; - const int new_det_num_a = (a + transaxial_det_inc) % num_transaxial_detectors; - const int new_det_num_b = (b + transaxial_det_inc) % num_transaxial_detectors; - const int axial_det_inc = axial_block_num * num_axial_crystals_per_block; - const int new_ring_num_a = ra + axial_det_inc; - const int new_ring_num_b = rb + axial_det_inc; - - if (fan_data.is_in_data(new_ring_num_a, new_det_num_a, new_ring_num_b, new_det_num_b)) - { +// std::vector> local_geo_sptrs; +// int num_threads = 10; +// #ifdef STIR_OPENMP +// # pragma omp single +// { +// std::cout << "Converting fan data to geo data..." << std::endl; +// std::cout << "We will be using " << num_threads << " threads... If your machine does not support that please contact sb from STIR" << std::endl; + +// local_geo_sptrs.resize(num_threads, shared_ptr()); + +// for (int i = 0; i < num_threads; i++) +// { +// std::cout << "Allocated data to geo data... " << i<< std::endl; +// local_geo_sptrs[i].reset(new GeoData3D(geo_data.size(), +// geo_data[0].size(), +// geo_data[0][0].size(), +// geo_data[0][0][0].size())); +// local_geo_sptrs[i]->fill(0); +// } +// } +// #endif + +// std::cout << "Starting geo loops... " << std::endl; // #ifdef STIR_OPENMP -// # pragma omp critical(FANPROJDATAWRITE) +// #pragma omp parallel for schedule(dynamic) collapse(4) num_threads(num_threads) // #endif - try - { - (*local_geo_sptrs[thread_num])(ra, a, rb, b % num_transaxial_detectors) - += fan_data(new_ring_num_a, new_det_num_a, new_ring_num_b, new_det_num_b); - } - catch(...) - { +// for (int ra = 0; ra < num_axial_crystals_per_block; ++ra) +// { +// for (int a = 0; a < num_transaxial_crystals_per_block / 2; ++a) +// for (int axial_block_num = 0; axial_block_num < num_axial_blocks; ++axial_block_num) +// { +// for (int transaxial_block_num = 0; transaxial_block_num < num_transaxial_blocks; ++transaxial_block_num) +// { +// // loop rb from ra to avoid double counting +// // for (int rb = fan_data.get_min_ra(); rb <= fan_data.get_max_ra(); ++rb) +// for (int rb = max(ra, fan_data.get_min_rb(ra)); rb <= fan_data.get_max_rb(ra); ++rb) +// for (int b = fan_data.get_min_b(a); b <= fan_data.get_max_b(a); ++b) +// { - } - } - } - } - } - } +// #ifdef STIR_OPENMP +// const int thread_num = omp_get_thread_num(); +// #else +// const int thread_num = 0; +// #endif - std::cout<<"Now flattening the geo data ..." << std::endl; -#ifdef STIR_OPENMP - // flatten data constructed by threads - { - for (int i = 0; i < static_cast(local_geo_sptrs.size()); ++i) - if (!is_null_ptr(local_geo_sptrs[i])) // only accumulate if a thread filled something in - geo_data += *(local_geo_sptrs[i]); - } -#endif +// const int transaxial_det_inc = transaxial_block_num * num_transaxial_crystals_per_block; +// const int new_det_num_a = (a + transaxial_det_inc) % num_transaxial_detectors; +// const int new_det_num_b = (b + transaxial_det_inc) % num_transaxial_detectors; +// const int axial_det_inc = axial_block_num * num_axial_crystals_per_block; +// const int new_ring_num_a = ra + axial_det_inc; +// const int new_ring_num_b = rb + axial_det_inc; + +// if (fan_data.is_in_data(new_ring_num_a, new_det_num_a, new_ring_num_b, new_det_num_b)) +// { +// // #ifdef STIR_OPENMP +// // # pragma omp critical(FANPROJDATAWRITE) +// // #endif +// try +// { +// (*local_geo_sptrs[thread_num])(ra, a, rb, b % num_transaxial_detectors) +// += fan_data(new_ring_num_a, new_det_num_a, new_ring_num_b, new_det_num_b); +// } +// catch(...) +// { + +// } +// } +// } +// } +// } + // } + +// std::cout<<"Now flattening the geo data ..." << std::endl; +// #ifdef STIR_OPENMP +// // flatten data constructed by threads +// { +// for (int i = 0; i < static_cast(local_geo_sptrs.size()); ++i) +// if (!is_null_ptr(local_geo_sptrs[i])) // only accumulate if a thread filled something in +// geo_data += *(local_geo_sptrs[i]); +// } +// #endif return return_value; // omp_set_num_threads(omp_get_max_threads()); @@ -1759,7 +1718,7 @@ void apply_geo_norm(FanProjData& fan_data, const GeoData& geo_data, const bool a void apply_geo_norm(FanProjData& fan_data, const GeoData3D& geo_data, const bool apply) { - //TODO this function + const int num_axial_detectors = fan_data.get_num_rings(); const int num_transaxial_detectors = fan_data.get_num_detectors_per_ring(); const int num_axial_crystals_per_block = geo_data.get_num_axial_crystals_per_block(); @@ -1767,12 +1726,6 @@ apply_geo_norm(FanProjData& fan_data, const GeoData3D& geo_data, const bool appl const int num_transaxial_blocks = num_transaxial_detectors / num_transaxial_crystals_per_block; const int num_axial_blocks = num_axial_detectors / num_axial_crystals_per_block; - // std::vector> local_geo_sptrs; - // int num_threads = 20; - - // FanProjData work = fan_data; - // work.fill(0); - std::vector> local_work_sptrs; int num_threads = 3; #ifdef STIR_OPENMP @@ -1855,7 +1808,7 @@ std::cout<<"Now flattening the FanProjData data ..." << std::endl; (*local_work_sptrs[0]) += (*local_work_sptrs[i]); } #endif - +std::cout<<"Loop over fan_data ..." << std::endl; #ifdef STIR_OPENMP # if _OPENMP >= 201107 # pragma omp parallel for collapse(2) @@ -1886,12 +1839,14 @@ std::cout<<"Now flattening the FanProjData data ..." << std::endl; fan_data(ra, a, rb, b) /= (*local_work_sptrs[0])(ra, a, rb, b % num_transaxial_detectors); } local_work_sptrs.clear(); + std::cout<<"Finished Loop over fan_data ..." << std::endl; } void apply_efficiencies(FanProjData& fan_data, const DetectorEfficiencies& efficiencies, const bool apply) { const int num_detectors_per_ring = fan_data.get_num_detectors_per_ring(); + #ifdef STIR_OPENMP # if _OPENMP >= 201107 # pragma omp parallel for collapse(2) @@ -1925,9 +1880,48 @@ apply_efficiencies(FanProjData& fan_data, const DetectorEfficiencies& efficienci void make_fan_sum_data(Array<2, float>& data_fan_sums, const FanProjData& fan_data) { + std::vector>> local_fan_sptrs; + int num_threads = 30; +#ifdef STIR_OPENMP +# pragma omp single + { + std::cout << "Converting fan data to fan sum data..." << std::endl; + std::cout << "We will be using " << num_threads << " threads... If your machine does not support that please contact sb from STIR" << std::endl; + + local_fan_sptrs.resize(num_threads, shared_ptr>()); + + for (int i = 0; i < num_threads; i++) + { + std::cout << "Allocated data to fan sum data... " << i<< std::endl; + local_fan_sptrs[i].reset(new Array<2, float>(data_fan_sums)); + local_fan_sptrs[i]->fill(0); + } + } +#endif +#ifdef STIR_OPENMP +#pragma omp parallel for schedule(dynamic) collapse(2) num_threads(num_threads) +#endif for (int ra = fan_data.get_min_ra(); ra <= fan_data.get_max_ra(); ++ra) for (int a = fan_data.get_min_a(); a <= fan_data.get_max_a(); ++a) - data_fan_sums[ra][a] = fan_data.sum(ra, a); + { +#ifdef STIR_OPENMP + const int thread_num = omp_get_thread_num(); +#else + const int thread_num = 0; +#endif + (*local_fan_sptrs[thread_num])[ra][a] = fan_data.sum(ra, a); + } + + data_fan_sums.fill(0.f); + std::cout<<"Now flattening the fan sums ..." << std::endl; +#ifdef STIR_OPENMP + // flatten data constructed by threads + { + for (int i = 0; i < static_cast(local_fan_sptrs.size()); ++i) + data_fan_sums += (*local_fan_sptrs[i]); + } +#endif + local_fan_sptrs.clear(); } template @@ -2037,59 +2031,149 @@ make_geo_data(GeoData3D& geo_data, const FanProjData& fan_data) // transaxial and axial mirroring + std::vector> local_geo_sptrs; + int num_threads = 3; + #ifdef STIR_OPENMP + # pragma omp single + { + std::cout << "Converting fan data to geo data..." << std::endl; + std::cout << "We will be using " << num_threads << " threads... If your machine does not support that please contact sb from STIR" << std::endl; + + local_geo_sptrs.resize(num_threads, shared_ptr()); + + for (int i = 0; i < num_threads; i++) + { + std::cout << "Allocated data to geo data... " << i<< std::endl; + local_geo_sptrs[i].reset(new FanProjData(fan_data.size(), + fan_data[0].size(), + fan_data[0][0].size(), + fan_data[0][0][0].size())); + local_geo_sptrs[i]->fill(0); + } + } + #endif + FanProjData work = fan_data; work.fill(0); - - for (int ra = fan_data.get_min_ra(); ra <= fan_data.get_max_ra(); ++ra) - for (int a = fan_data.get_min_a(); a <= fan_data.get_max_a(); ++a) - // 1// for (int rb = fan_data.get_min_ra(); rb <= fan_data.get_max_ra(); ++rb) - for (int rb = max(ra, fan_data.get_min_rb(ra)); rb <= fan_data.get_max_rb(ra); ++rb) - for (int b = fan_data.get_min_b(a); b <= fan_data.get_max_b(a); ++b) + std::cout << "Starting geo loops... This is slow" << std::endl; +#ifdef STIR_OPENMP +#pragma omp parallel for schedule(dynamic) collapse(2) num_threads(num_threads) +#endif + for (int ra = fan_data.get_min_ra(); ra <= fan_data.get_max_ra(); ++ra) + { + for (int a = fan_data.get_min_a(); a <= fan_data.get_max_a(); ++a) { + // 1// for (int rb = fan_data.get_min_ra(); rb <= fan_data.get_max_ra(); ++rb) + for (int rb = max(ra, fan_data.get_min_rb(ra)); rb <= fan_data.get_max_rb(ra); ++rb) + { + for (int b = fan_data.get_min_b(a); b <= fan_data.get_max_b(a); ++b) + { - const int ma = num_transaxial_detectors - 1 - a; - const int mb = (2 * num_transaxial_detectors - 1 - b) % num_transaxial_detectors; - const int mra = num_axial_detectors - 1 - ra; - const int mrb = (num_axial_detectors - 1 - rb); - - if (ra != mra && rb != mrb) - work(ra, a, rb, b) - = fan_data(ra, a, rb, b) + fan_data(ra, ma, rb, mb) + fan_data(mra, a, mrb, b) + fan_data(mra, ma, mrb, mb); - else - work(ra, a, rb, b) = fan_data(ra, a, rb, b) + fan_data(ra, ma, rb, mb); +#ifdef STIR_OPENMP + const int thread_num = omp_get_thread_num(); +#else + const int thread_num = 0; +#endif + const int ma = num_transaxial_detectors - 1 - a; + const int mb = (2 * num_transaxial_detectors - 1 - b) % num_transaxial_detectors; + const int mra = num_axial_detectors - 1 - ra; + const int mrb = (num_axial_detectors - 1 - rb); + + if (ra != mra && rb != mrb) + (*local_geo_sptrs[thread_num])(ra, a, rb, b) + = fan_data(ra, a, rb, b) + fan_data(ra, ma, rb, mb) + fan_data(mra, a, mrb, b) + fan_data(mra, ma, mrb, mb); + else + (*local_geo_sptrs[thread_num])(ra, a, rb, b) = fan_data(ra, a, rb, b) + fan_data(ra, ma, rb, mb); + } + } } + } - geo_data.fill(0); + std::cout<<"Now flattening the geo data ...This is very slow" << std::endl; + #ifdef STIR_OPENMP + // flatten data constructed by threads + { + for (int i = 0; i < static_cast(local_geo_sptrs.size()); ++i) + if (!is_null_ptr(local_geo_sptrs[i])) // only accumulate if a thread filled something in + work+= (*local_geo_sptrs[i]); + } + #endif - for (int ra = 0; ra < num_axial_crystals_per_block; ++ra) - // for (int a = 0; a <= num_transaxial_detectors/2; ++a) - for (int a = 0; a < num_transaxial_crystals_per_block / 2; ++a) - // loop rb from ra to avoid double counting - // for (int rb = fan_data.get_min_ra(); rb <= fan_data.get_max_ra(); ++rb) - for (int rb = max(ra, fan_data.get_min_rb(ra)); rb <= fan_data.get_max_rb(ra); ++rb) - for (int b = fan_data.get_min_b(a); b <= fan_data.get_max_b(a); ++b) - { + local_geo_sptrs.clear(); + geo_data.fill(0); - // rotation + std::vector> local_gg_sptrs; + num_threads = 30; +#ifdef STIR_OPENMP +# pragma omp single - for (int axial_block_num = 0; axial_block_num < num_axial_blocks; ++axial_block_num) - { + { + std::cout << "Converting fan data to fan GEO data..." << std::endl; + std::cout << "We will be using " << num_threads << " threads... If your machine does not support that please contact sb from STIR" << std::endl; - for (int transaxial_block_num = 0; transaxial_block_num < num_transaxial_blocks; ++transaxial_block_num) - { + local_gg_sptrs.resize(num_threads, shared_ptr()); - const int transaxial_det_inc = transaxial_block_num * num_transaxial_crystals_per_block; - const int new_det_num_a = (a + transaxial_det_inc) % num_transaxial_detectors; - const int new_det_num_b = (b + transaxial_det_inc) % num_transaxial_detectors; - const int axial_det_inc = axial_block_num * num_axial_crystals_per_block; - const int new_ring_num_a = ra + axial_det_inc; - const int new_ring_num_b = rb + axial_det_inc; - if (fan_data.is_in_data(new_ring_num_a, new_det_num_a, new_ring_num_b, new_det_num_b)) - geo_data(ra, a, rb, b % num_transaxial_detectors) - += work(new_ring_num_a, new_det_num_a, new_ring_num_b, new_det_num_b); - } - } + for (int i = 0; i < num_threads; i++) + { + std::cout << "Allocated data to fan sum data... " << i<< std::endl; + local_gg_sptrs[i].reset(new GeoData3D(geo_data.size(), + geo_data[0].size(), + geo_data[0][0].size(), + geo_data[0][0][0].size())); + local_gg_sptrs[i]->fill(0); } + } +#endif + +#ifdef STIR_OPENMP +#pragma omp parallel for schedule(dynamic) collapse(4) num_threads(num_threads -1 ) +#endif + for (int axial_block_num = 0; axial_block_num < num_axial_blocks; ++axial_block_num) + { + for (int transaxial_block_num = 0; transaxial_block_num < num_transaxial_blocks; ++transaxial_block_num) + { + for (int ra = 0; ra < num_axial_crystals_per_block; ++ra) + // for (int a = 0; a <= num_transaxial_detectors/2; ++a) + for (int a = 0; a < num_transaxial_crystals_per_block / 2; ++a) + { + // loop rb from ra to avoid double counting + // for (int rb = fan_data.get_min_ra(); rb <= fan_data.get_max_ra(); ++rb) + for (int rb = max(ra, fan_data.get_min_rb(ra)); rb <= fan_data.get_max_rb(ra); ++rb) + for (int b = fan_data.get_min_b(a); b <= fan_data.get_max_b(a); ++b) + { + // rotation + const int transaxial_det_inc = transaxial_block_num * num_transaxial_crystals_per_block; + const int new_det_num_a = (a + transaxial_det_inc) % num_transaxial_detectors; + const int new_det_num_b = (b + transaxial_det_inc) % num_transaxial_detectors; + const int axial_det_inc = axial_block_num * num_axial_crystals_per_block; + const int new_ring_num_a = ra + axial_det_inc; + const int new_ring_num_b = rb + axial_det_inc; + if (fan_data.is_in_data(new_ring_num_a, new_det_num_a, new_ring_num_b, new_det_num_b)) + { +#ifdef STIR_OPENMP + const int thread_num = omp_get_thread_num(); +#else + const int thread_num = 0; +#endif + (*local_gg_sptrs[thread_num])(ra, a, rb, b % num_transaxial_detectors) + += work(new_ring_num_a, new_det_num_a, new_ring_num_b, new_det_num_b); + } + } + } + } + } + + std::cout<<"Now flattening the final geo data ..." << std::endl; +#ifdef STIR_OPENMP + // flatten data constructed by threads + { + for (int i = 0; i < static_cast(local_gg_sptrs.size()); ++i) + if (!is_null_ptr(local_gg_sptrs[i])) // only accumulate if a thread filled something in + geo_data += (*local_gg_sptrs[i]); + } +#endif + + local_gg_sptrs.clear(); } void @@ -2129,8 +2213,12 @@ iterate_efficiencies(DetectorEfficiencies& efficiencies, const Array<2, float>& assert(model.get_max_a() == data_fan_sums[data_fan_sums.get_min_index()].get_max_index()); for (int ra = model.get_min_ra(); ra <= model.get_max_ra(); ++ra) + { + std::cout <<">> ra: " << ra << " from "<< model.get_min_ra() << " to " <a: " << a << " from "<< model.get_min_a() << " to " <& efficiencies[ra][a] = data_fan_sums[ra][a] / static_cast(denominator); } } + } } // version without model @@ -2208,24 +2297,69 @@ iterate_geo_norm(GeoData3D& norm_geo_data, const GeoData3D& measured_geo_data, c make_geo_data(norm_geo_data, model); // norm_geo_data = measured_geo_data / norm_geo_data; + std::vector> local_geo_sptrs; + int num_threads = 10; +#ifdef STIR_OPENMP +# pragma omp single + { + std::cout << "Converting fan data to geo data..." << std::endl; + std::cout << "We will be using " << num_threads << " threads... If your machine does not support that please contact sb from STIR" << std::endl; + + local_geo_sptrs.resize(num_threads, shared_ptr()); + + for (int i = 0; i < num_threads; i++) + { + std::cout << "Allocated data to geo data... " << i<< std::endl; + local_geo_sptrs[i].reset(new GeoData3D(norm_geo_data.size(), + norm_geo_data[0].size(), + norm_geo_data[0][0].size(), + norm_geo_data[0][0][0].size())); + local_geo_sptrs[i]->fill(0); + } + } +#endif + const int num_axial_crystals_per_block = measured_geo_data.get_num_axial_crystals_per_block(); const int num_transaxial_crystals_per_block = measured_geo_data.get_half_num_transaxial_crystals_per_block() * 2; const float threshold = measured_geo_data.find_max() / 10000.F; + std::cout << "Iterating over geo norm" << std::endl; +#ifdef STIR_OPENMP +#pragma omp parallel for schedule(dynamic) collapse(2) num_threads(num_threads) +#endif for (int ra = 0; ra < num_axial_crystals_per_block; ++ra) - for (int a = 0; a < num_transaxial_crystals_per_block / 2; ++a) - // loop rb from ra to avoid double counting - // for (int rb = model.get_min_ra(); rb <= model.get_max_ra(); ++rb) - for (int rb = max(ra, model.get_min_rb(ra)); rb <= model.get_max_rb(ra); ++rb) - for (int b = model.get_min_b(a); b <= model.get_max_b(a); ++b) - { + { + for (int a = 0; a < num_transaxial_crystals_per_block / 2; ++a) + { + // loop rb from ra to avoid double counting + // for (int rb = model.get_min_ra(); rb <= model.get_max_ra(); ++rb) +#ifdef STIR_OPENMP + const int thread_num = omp_get_thread_num(); +#else + const int thread_num = 0; +#endif + for (int rb = max(ra, model.get_min_rb(ra)); rb <= model.get_max_rb(ra); ++rb) + for (int b = model.get_min_b(a); b <= model.get_max_b(a); ++b) + { - norm_geo_data(ra, a, rb, b) = (measured_geo_data(ra, a, rb, b) >= threshold - || measured_geo_data(ra, a, rb, b) < 10000 * norm_geo_data(ra, a, rb, b)) - ? measured_geo_data(ra, a, rb, b) / norm_geo_data(ra, a, rb, b) - : 0; - } + (*local_geo_sptrs[thread_num])(ra, a, rb, b) = (measured_geo_data(ra, a, rb, b) >= threshold + || measured_geo_data(ra, a, rb, b) < 10000 * norm_geo_data(ra, a, rb, b)) + ? measured_geo_data(ra, a, rb, b) / norm_geo_data(ra, a, rb, b) + : 0; + } + } + } + + std::cout<<"Now flattening the geo data ..." << std::endl; +#ifdef STIR_OPENMP + // flatten data constructed by threads + { + for (int i = 0; i < static_cast(local_geo_sptrs.size()); ++i) + if (!is_null_ptr(local_geo_sptrs[i])) // only accumulate if a thread filled something in + norm_geo_data += *(local_geo_sptrs[i]); + } +#endif } void diff --git a/src/include/stir/ML_norm.h b/src/include/stir/ML_norm.h index e6edad0be6..54e01b1a04 100644 --- a/src/include/stir/ML_norm.h +++ b/src/include/stir/ML_norm.h @@ -205,10 +205,10 @@ class FanProjData : public Array<4, float> typedef FanProjData BlockData3D; -float make_all_fan_data_from_cache( - Array<2, float>& data_fan_sums, - GeoData3D& geo_data, - const ProjData& proj_data); +double make_all_fan_data_from_cache( + FanProjData& fan_data, + const ProjData& proj_data, + const FanProjData& model); void make_fan_data_remove_gaps(FanProjData& fan_data, const ProjData& proj_data); diff --git a/src/recon_buildblock/ML_estimate_component_based_normalisation.cxx b/src/recon_buildblock/ML_estimate_component_based_normalisation.cxx index a47301a756..34a153bca8 100644 --- a/src/recon_buildblock/ML_estimate_component_based_normalisation.cxx +++ b/src/recon_buildblock/ML_estimate_component_based_normalisation.cxx @@ -124,7 +124,7 @@ ML_estimate_component_based_normalisation(const std::string& out_filename_prefix load_fan_data(model_fan_data, model_data, model_fansums_filename); } - float model_fan_data_sum = 0; + double model_fan_data_sum = 0.f; { std::vector> local_model_fan_data_sum; @@ -159,7 +159,7 @@ ML_estimate_component_based_normalisation(const std::string& out_filename_prefix // next could be local if KL is not computed below FanProjData measured_fan_data; float threshold_for_KL; - float data_fan_sum = 0; + double data_fan_sum = 0; // compute factors dependent on the data { @@ -195,8 +195,18 @@ ML_estimate_component_based_normalisation(const std::string& out_filename_prefix { // TODO: The TEMP fix is not applied. So until then, keep the model source slightly bigger than the actual measuremnts // to avoid divisions by zero. - data_fan_sum = make_all_fan_data_from_cache(data_fan_sums, measured_geo_data, measured_data); - threshold_for_KL = data_fan_sums.find_max() / 100000000.F; + data_fan_sum = make_all_fan_data_from_cache(measured_fan_data, measured_data, model_fan_data); + float dd = data_fan_sums.find_max(); + std::cout << "Data fan sum max " << dd << std::endl; + threshold_for_KL =dd / 100000000.F; + + std::cout << "1. Making fan sum data .. " << std::endl; + make_fan_sum_data(data_fan_sums, measured_fan_data); + std::cout << "fan sum data min/max " << data_fan_sums.find_min() << " " << data_fan_sums.find_max() << std::endl; + + std::cout << "2. Making fan geo data .. " << std::endl; + make_geo_data(measured_geo_data, measured_fan_data); + std::cout << "measured_geo_data min/max " << measured_geo_data.find_min() << " " << measured_geo_data.find_max() << std::endl; } if (do_display && do_block) display(measured_block_data, "raw block data from measurements"); @@ -232,6 +242,7 @@ ML_estimate_component_based_normalisation(const std::string& out_filename_prefix { std::cout << "Calculating sums: " << std::endl; float value = sqrt(data_fan_sum / model_fan_data_sum); + std::cout << "Ratio: " << value << std::endl; efficiencies.fill(value); std::cout << "Finished sums." << std::endl; norm_geo_data.fill(1); @@ -332,7 +343,7 @@ ML_estimate_component_based_normalisation(const std::string& out_filename_prefix display(fan_data, "geo norm"); } - // block norm + if(do_block) //norm { fan_data = model_fan_data; apply_efficiencies(fan_data, efficiencies); From 53c9d058765fc7af22b621f55d038d3ee262aae4 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sun, 19 May 2024 14:05:09 +0100 Subject: [PATCH 75/78] fix writing of single-TOF bin data --- src/IO/interfile.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/IO/interfile.cxx b/src/IO/interfile.cxx index e7565fb549..74001308c4 100644 --- a/src/IO/interfile.cxx +++ b/src/IO/interfile.cxx @@ -1201,7 +1201,7 @@ write_basic_interfile_PDFS_header(const string& header_file_name, const string& // it's PET data if we get here // N.E. Added timing locations - const bool is_TOF = pdfs.get_proj_data_info_sptr()->get_num_tof_poss() > 1; + const bool is_TOF = pdfs.get_proj_data_info_sptr()->is_tof_data(); output_header << "number of dimensions := " + std::to_string(is_TOF ? 5 : 4) + "\n"; // TODO support more ? From 0c18fb6994ca86f3fd1f8922ce0036ecde4ee547 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sun, 19 May 2024 14:15:51 +0100 Subject: [PATCH 76/78] another fix on tof data --- src/buildblock/ProjDataFromStream.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/buildblock/ProjDataFromStream.cxx b/src/buildblock/ProjDataFromStream.cxx index bcb47f9c65..13c9a0b71d 100644 --- a/src/buildblock/ProjDataFromStream.cxx +++ b/src/buildblock/ProjDataFromStream.cxx @@ -79,7 +79,7 @@ ProjDataFromStream::ProjDataFromStream(shared_ptr const& exam_in assert(storage_order != Unsupported); assert(!(data_type == NumericType::UNKNOWN_TYPE)); - if (proj_data_info_sptr->get_num_tof_poss() > 1) + if (proj_data_info_sptr->is_tof_data()) activate_TOF(); } From 7eea9175d0e0ee9b98962fe8bc93959911051a63 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sat, 1 Jun 2024 19:15:19 +0100 Subject: [PATCH 77/78] fix Interfile writing of TOF data with 1 TOF bin We need to preserve the TOF character, such that coincidence window etc is taken into account. --- src/IO/interfile.cxx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/IO/interfile.cxx b/src/IO/interfile.cxx index 74001308c4..e4251b1123 100644 --- a/src/IO/interfile.cxx +++ b/src/IO/interfile.cxx @@ -1225,12 +1225,16 @@ write_basic_interfile_PDFS_header(const string& header_file_name, const string& */ { case ProjDataFromStream::Segment_View_AxialPos_TangPos: { + if (is_TOF) // TOF data with 1 timing position + order_of_timing_poss = 5; order_of_segment = 4; order_of_view = 3; order_of_z = 2; break; } case ProjDataFromStream::Segment_AxialPos_View_TangPos: { + if (is_TOF) // TOF data with 1 timing position + order_of_timing_poss = 5; order_of_segment = 4; order_of_view = 2; order_of_z = 3; @@ -1252,8 +1256,7 @@ write_basic_interfile_PDFS_header(const string& header_file_name, const string& if (order_of_timing_poss > 0) { output_header << "matrix axis label [" << order_of_timing_poss << "] := timing positions\n"; - output_header << "!matrix size [" << order_of_timing_poss << "] := " << pdfs.get_timing_poss_sequence_in_stream().size() - << "\n"; + output_header << "!matrix size [" << order_of_timing_poss << "] := " << pdfs.get_num_tof_poss() << "\n"; } output_header << "matrix axis label [" << order_of_segment << "] := segment\n"; From d26b68a29b13bfb2e9eaefea6ebb0c52c5b20b68 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sat, 1 Jun 2024 20:02:02 +0100 Subject: [PATCH 78/78] replace std::cout calls with info() --- src/include/stir/Scanner.h | 3 +- ...estimate_component_based_normalisation.cxx | 36 +++++++++---------- .../construct_fanProjData_fromProjData.cxx | 2 +- 3 files changed, 19 insertions(+), 22 deletions(-) diff --git a/src/include/stir/Scanner.h b/src/include/stir/Scanner.h index d8a751498e..efb5718c7a 100644 --- a/src/include/stir/Scanner.h +++ b/src/include/stir/Scanner.h @@ -87,8 +87,7 @@ class Succeeded; coincidence window then the reconstruction will essential be nonTOF but the projector will restrict the size of the LOR to the size of the coincidence window. \li \c The scanner will be classified as TOF enabled when the numer of TOF bins and -TOF bin size are >1 and >0, respectively. If the timing resolution is not set that will be fine -as long as the final TOF possitions is 1. Then we just restict the size of the LOR. +TOF bin size are >1 and >0, respectively. If the timing resolution is not set, we will attempt to handle this if the number TOF positions is 1 (the projector will then just use the bin-size to restrict the size of the LOR). A further complication is that some scanners (including many Siemens scanners) insert virtual crystals in the sinogram data (corresponding to gaps between diff --git a/src/recon_buildblock/ML_estimate_component_based_normalisation.cxx b/src/recon_buildblock/ML_estimate_component_based_normalisation.cxx index 11693ef312..0cf9ba34c8 100644 --- a/src/recon_buildblock/ML_estimate_component_based_normalisation.cxx +++ b/src/recon_buildblock/ML_estimate_component_based_normalisation.cxx @@ -94,7 +94,7 @@ ML_estimate_component_based_normalisation(const std::string& out_filename_prefix FanProjData model_fan_data; FanProjData fan_data; - std::cout << "Allocating ..." << std::endl; + info("Allocating memory ...", 3); DetectorEfficiencies data_fan_sums(IndexRange2D(num_physical_rings, num_physical_detectors_per_ring)); DetectorEfficiencies efficiencies(IndexRange2D(num_physical_rings, num_physical_detectors_per_ring)); @@ -151,9 +151,8 @@ ML_estimate_component_based_normalisation(const std::string& out_filename_prefix model_fan_data_sum += *local_model_fan_data_sum[i]; local_model_fan_data_sum.clear(); - std::cout << "Model sum: " << model_fan_data_sum << std::endl; + info("Model sum of fan data: " + std::to_string(model_fan_data_sum), 3); } - std::cout << ">>><<<: " << std::endl; { // next could be local if KL is not computed below FanProjData measured_fan_data; @@ -196,16 +195,16 @@ ML_estimate_component_based_normalisation(const std::string& out_filename_prefix // to avoid divisions by zero. data_fan_sum = make_all_fan_data_from_cache(measured_fan_data, measured_data, model_fan_data); float dd = data_fan_sums.find_max(); - std::cout << "Data fan sum max " << dd << std::endl; + info("Data fan sum max: " + std::to_string(dd), 3); threshold_for_KL =dd / 100000000.F; - std::cout << "1. Making fan sum data .. " << std::endl; + info("1. Making fan sum data .. ", 3); make_fan_sum_data(data_fan_sums, measured_fan_data); - std::cout << "fan sum data min/max " << data_fan_sums.find_min() << " " << data_fan_sums.find_max() << std::endl; + info("fan sum data min/max " + std::to_string(data_fan_sums.find_min()) + "/" + std::to_string(data_fan_sums.find_max()), 3); - std::cout << "2. Making fan geo data .. " << std::endl; + info("2. Making fan geo data .. ", 3); make_geo_data(measured_geo_data, measured_fan_data); - std::cout << "measured_geo_data min/max " << measured_geo_data.find_min() << " " << measured_geo_data.find_max() << std::endl; + info("measured_geo_data min/max " + std::to_string(measured_geo_data.find_min()) + "/" + std::to_string(measured_geo_data.find_max()), 3);; } if (do_display && do_block) display(measured_block_data, "raw block data from measurements"); @@ -239,32 +238,31 @@ ML_estimate_component_based_normalisation(const std::string& out_filename_prefix std::cout << "Iteration number: " << iter_num << std::endl; if (iter_num == 1) { - std::cout << "Calculating sums: " << std::endl; + info("Calculating sums om fan_data ...", 3); float value = sqrt(data_fan_sum / model_fan_data_sum); - std::cout << "Ratio: " << value << std::endl; + info("Ratio: " + std::to_string(value), 3); efficiencies.fill(value); - std::cout << "Finished sums." << std::endl; norm_geo_data.fill(1); norm_block_data.fill(1); } // efficiencies { - std::cout << "Copying model to fan_data..." << std::endl; + info("Copying model to fan_data...", 3); fan_data = model_fan_data; - std::cout << "Applying geo norm..." << std::endl; + info("Applying geo norm...", 3); apply_geo_norm(fan_data, norm_geo_data); if (do_block) { - std::cout << "Applying block norm..." << std::endl; + info("Applying block norm...", 3); apply_block_norm(fan_data, norm_block_data); } if (do_display) display(fan_data, "model*geo*block"); for (int eff_iter_num = 1; eff_iter_num <= num_eff_iterations; ++eff_iter_num) { - std::cout << "Efficiency iteration number: " << iter_num << std::endl; + info("Efficiency iteration number: " + std::to_string(iter_num), 3); iterate_efficiencies(efficiencies, data_fan_sums, fan_data); - std::cout << "Finished efficiency iteration number: " << iter_num << std::endl; + info("Finished efficiency iteration", 3); { char* out_filename = new char[out_filename_prefix.size() + 30]; sprintf(out_filename, "%s_%s_%d_%d.out", out_filename_prefix.c_str(), "eff", iter_num, eff_iter_num); @@ -274,7 +272,7 @@ ML_estimate_component_based_normalisation(const std::string& out_filename_prefix } if (do_KL) { - std::cout << "Calculating KL" << std::endl; + info("Calculating KL", 3); apply_efficiencies(fan_data, efficiencies); std::cerr << "measured*norm min " << measured_fan_data.find_min() << " ,max " << measured_fan_data.find_max() << std::endl; @@ -302,8 +300,8 @@ ML_estimate_component_based_normalisation(const std::string& out_filename_prefix } } // end efficiencies - std::cout << "geo norm" << std::endl; - std::cout << "Copying model to fan_data..." << std::endl; + info("Starting geo norm", 3); + info("Copying model to fan_data...", 3); fan_data = model_fan_data; apply_efficiencies(fan_data, efficiencies); if (do_block) diff --git a/src/utilities/construct_fanProjData_fromProjData.cxx b/src/utilities/construct_fanProjData_fromProjData.cxx index 70434fd68b..d416b1d60b 100644 --- a/src/utilities/construct_fanProjData_fromProjData.cxx +++ b/src/utilities/construct_fanProjData_fromProjData.cxx @@ -4,7 +4,7 @@ \file \ingroup utilities -\brief Construct FanProjData from ProjData +\brief Construct stir::FanProjData from stir::ProjData \author Nikos Efthimiou