diff --git a/documentation/release_6.3.htm b/documentation/release_6.3.htm
index dc6cbad02..f7d2296d6 100644
--- a/documentation/release_6.3.htm
+++ b/documentation/release_6.3.htm
@@ -60,12 +60,17 @@
New functionality
Changed functionality
+
+ Rework MLEstimateComponentBasedNormalisation
into a class.
+ This allows for more flexibility in the use of the class without calling CLI executables.
+ PR # 1499
+
+
-
Default ECAT scanner configurations updated to use a negative intrinsic tilt.
-
Bug fixes
-
@@ -115,6 +120,12 @@
Test changes
C++ tests
+ -
+ Added
MLEstimateComponentBasedNormalisationTest
that tests the basic functionality of the
+ MLEstimateComponentBasedNormalisation
class and creation of normalisation factors from
+ the calculated PET components.
+ PR # 1499
+
recon_test_pack
diff --git a/src/buildblock/ML_norm.cxx b/src/buildblock/ML_norm.cxx
index bc8c10910..986ed3b7a 100644
--- a/src/buildblock/ML_norm.cxx
+++ b/src/buildblock/ML_norm.cxx
@@ -987,6 +987,10 @@ get_fan_info(int& num_rings, int& num_detectors_per_ring, int& max_ring_diff, in
{
error("Can only process data without axial compression (i.e. span=1)\n");
}
+ if (proj_data_info_ptr->is_tof_data())
+ {
+ error("make_fan_data: Incompatible with TOF data. Abort.");
+ }
num_rings = proj_data_info.get_scanner_ptr()->get_num_rings();
num_detectors_per_ring = proj_data_info.get_scanner_ptr()->get_num_detectors_per_ring();
const int half_fan_size = min(proj_data_info.get_max_tangential_pos_num(), -proj_data_info.get_min_tangential_pos_num());
@@ -1141,23 +1145,19 @@ make_fan_data_remove_gaps(FanProjData& fan_data, const ProjData& proj_data)
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);
+ const 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);
}
else
{
- auto proj_data_info_ptr = dynamic_cast(&proj_data_info);
+ const 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);
diff --git a/src/include/stir/recon_buildblock/BinNormalisationPETFromComponents.h b/src/include/stir/recon_buildblock/BinNormalisationPETFromComponents.h
index 714ffca9b..67f6e0a18 100644
--- a/src/include/stir/recon_buildblock/BinNormalisationPETFromComponents.h
+++ b/src/include/stir/recon_buildblock/BinNormalisationPETFromComponents.h
@@ -34,7 +34,7 @@ START_NAMESPACE_STIR
Components currently supported are crystal efficiencies, geometric factors
(constrained by symmetry) and block data. The latter were introduced to
cope with timing alignment issues between blocks, but are generally
- not recommended in the current estimation process (by ML_estimate_component_based_normalisation)
+ not recommended in the current estimation process (by MLEstimateComponentBasedNormalisation)
as the model allows for too much freedom.
The detection efficiency of a crystal pair is modelled as
@@ -126,15 +126,33 @@ class BinNormalisationPETFromComponents : public BinNormalisation
void
allocate(shared_ptr, bool do_eff, bool do_geo, bool do_block = false, bool do_symmetry_per_block = false);
- DetectorEfficiencies& crystal_efficiencies()
+
+ void set_crystal_efficiencies(const DetectorEfficiencies& eff)
+ {
+ efficiencies = eff;
+ }
+
+ DetectorEfficiencies& get_crystal_efficiencies()
{
return efficiencies;
}
- GeoData3D& geometric_factors()
+
+ void set_geometric_factors(const GeoData3D& geo)
+ {
+ geo_data = geo;
+ }
+
+ GeoData3D& get_geometric_factors()
{
return geo_data;
}
- BlockData3D& block_factors()
+
+ void set_block_factors(const BlockData3D& block)
+ {
+ block_data = block;
+ }
+
+ BlockData3D& get_block_factors()
{
return block_data;
}
diff --git a/src/include/stir/recon_buildblock/MLEstimateComponentBasedNormalisation.h b/src/include/stir/recon_buildblock/MLEstimateComponentBasedNormalisation.h
new file mode 100644
index 000000000..d7bb12a6d
--- /dev/null
+++ b/src/include/stir/recon_buildblock/MLEstimateComponentBasedNormalisation.h
@@ -0,0 +1,316 @@
+/*
+ Copyright (C) 2022, University College London
+ Copyright (C) 2024, Robert Twyman Skelly
+ This file is part of STIR.
+
+ SPDX-License-Identifier: Apache-2.0
+
+ See STIR/LICENSE.txt for details
+*/
+/*!
+ \file
+ \ingroup recon_buildblock
+ \brief Declaration of stir::ML_estimate_component_based_normalisation
+ \author Kris Thielemans
+ \author Robert Twyman Skelly
+ */
+#include "BinNormalisationPETFromComponents.h"
+#include "stir/common.h"
+#include
+#include "stir/ProjData.h"
+#include "stir/ML_norm.h"
+#include "stir/ProjDataInMemory.h"
+
+START_NAMESPACE_STIR
+
+/*!
+ \brief Find normalisation factors using a maximum likelihood approach
+ \ingroup recon_buildblock
+ \param out_filename_prefix The prefix for the output files
+ \param measured_data The measured projection data
+ \param model_data The model projection data
+ \param num_eff_iterations The number of (sub-)efficiency iterations to perform per iteration of the algorithm
+ \param num_iterations The number of algorithm iterations to perform
+ \param do_geo Whether to perform geo normalization calculations
+ \param do_block Whether to perform block normalization calculations
+ \param do_symmetry_per_block Whether to perform block normalization calculations with symmetry
+ \param do_KL Whether to perform Kullback-Leibler divergence calculations and display the KL value. This can be slow.
+ \param do_display Whether to display the progress of the algorithm.
+ \param do_save_to_file Whether to save the each iteration of the efficiencies, geo data and block data to file.
+*/
+void ML_estimate_component_based_normalisation(const std::string& out_filename_prefix,
+ const ProjData& measured_projdata,
+ const ProjData& model_projdata,
+ 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 do_save_to_file = true);
+
+/*!
+ \brief Find normalisation factors using a maximum likelihood approach
+ \ingroup recon_buildblock
+*/
+class MLEstimateComponentBasedNormalisation
+{
+public:
+ /*!
+ \brief Constructor
+ \param out_filename_prefix_v The prefix for the output files
+ \param measured_projdata_v The measured projection data
+ \param model_projdata_v The model projection data
+ \param num_eff_iterations_v The number of (sub-)efficiency iterations to perform per iteration of the algorithm
+ \param num_iterations_v The number of algorithm iterations to perform
+ \param do_geo_v Whether to perform geo normalization calculations
+ \param do_block_v Whether to perform block normalization calculations
+ \param do_symmetry_per_block_v Whether to perform block normalization calculations with symmetry
+ \param do_KL_v Whether to perform Kullback-Leibler divergence calculations and display the KL value. This can be slow.
+ \param do_display_v Whether to display the progress of the algorithm.
+ \param do_save_to_file_v Whether to save the each iteration of the efficiencies, geo data and block data to file.
+ */
+ MLEstimateComponentBasedNormalisation(std::string out_filename_prefix_v,
+ const ProjData& measured_projdata_v,
+ const ProjData& model_projdata_v,
+ int num_eff_iterations_v,
+ int num_iterations_v,
+ bool do_geo_v,
+ bool do_block_v,
+ bool do_symmetry_per_block_v,
+ bool do_KL_v,
+ bool do_display_v,
+ bool do_save_to_file_v);
+
+ /*!
+ \brief Run the normalisation estimation algorithm using the parameters provided in the constructor.
+ */
+ void process();
+
+ //! Check if the data has been processed
+ bool get_data_is_processed() const;
+
+ //! Get the efficiencies, requires process() to be called first
+ DetectorEfficiencies get_efficiencies() const;
+ //! Get the geo data, requires process() to be called first and do_geo to be true
+ GeoData3D get_geo_data() const;
+ //! Get the block data, requires process() to be called first and do_block to be true
+ BlockData3D get_block_data() const;
+
+ //! Construct a BinNormalisationPETFromComponents from the calculated efficiencies, geo data and block data
+ BinNormalisationPETFromComponents construct_bin_norm_from_pet_components() const;
+
+ /*!
+ \brief Get the output filename prefix.
+ \return The output filename prefix.
+ */
+ std::string get_output_filename_prefix() const { return out_filename_prefix; }
+
+ /*!
+ \brief Set the output filename prefix.
+ \param out_filename_prefix The new output filename prefix.
+ */
+ void set_output_filename_prefix(const std::string& out_filename_prefix);
+
+ /*!
+ \brief Get the number of efficiency iterations.
+ There are the inner iterations of the algorithm, num_eff_iterations are performed per iteration.
+ \return The number of efficiency iterations.
+ */
+ int get_num_eff_iterations() const { return num_eff_iterations; }
+
+ /*!
+ \brief Set the number of efficiency iterations.
+ There are the inner iterations of the algorithm, num_eff_iterations are performed per iteration.
+ \param num_eff_iterations The new number of efficiency iterations.
+ */
+ void set_num_eff_iterations(int num_eff_iterations);
+
+ /*!
+ \brief Get the number of iterations.
+ These are the outer iterations of the algorithm.
+ \return The number of iterations.
+ */
+ int get_num_iterations() const { return num_iterations; }
+
+ /*!
+ \brief Set the number of iterations.
+ These are the outer iterations of the algorithm.
+ \param num_iterations The new number of iterations.
+ */
+ void set_num_iterations(int num_iterations);
+
+ /*!
+ \brief Check if geo normalization is enabled.
+ \return True if geo normalization is enabled, false otherwise.
+ */
+ bool get_enable_geo_norm_calculation() const { return do_geo; }
+
+ /*!
+ \brief Enable or disable geo normalization.
+ \param do_geo True to enable geo normalization, false to disable.
+ */
+ void set_enable_geo_norm_calculation(bool do_geo);
+
+ /*!
+ \brief Check if block normalization is enabled.
+ \return True if block normalization is enabled, false otherwise.
+ */
+ bool get_enable_block_norm_calculation() const { return do_block; }
+
+ /*!
+ \brief Enable or disable block normalization.
+ \param do_block True to enable block normalization, false to disable.
+ */
+ void set_enable_block_norm_calculation(bool do_block);
+
+ /*!
+ \brief Check if symmetry per block is enabled.
+ \return True if symmetry per block is enabled, false otherwise.
+ */
+ bool get_enable_symmetry_per_block() const { return do_symmetry_per_block; }
+
+ /*!
+ \brief Enable or disable symmetry per block.
+ \param do_symmetry_per_block True to enable symmetry per block, false to disable.
+ */
+ void set_enable_symmetry_per_block(bool do_symmetry_per_block);
+
+ /*!
+ \brief Check if KL divergence calculation is enabled.
+ This does not impact the calculation of the normalisation factors, only the display of the KL value.
+ \return True if KL divergence calculation is enabled, false otherwise.
+ */
+ bool get_do_kl_calculation() const { return do_KL; }
+
+ /*!
+ \brief Enable or disable KL divergence calculation.
+ This does not impact the calculation of the normalisation factors, it only prints the KL value to console.
+ \param do_kl True to enable KL divergence calculation, false to disable.
+ */
+ void set_do_kl_calculation(bool do_kl);
+
+ /*!
+ \brief Check if display is enabled.
+ \return True if display is enabled, false otherwise.
+ */
+ bool get_write_display_data() const { return do_display; }
+
+ /*!
+ \brief Enable or disable display.
+ \param do_display True to enable display, false to disable.
+ */
+ void set_write_display_data(bool do_display);
+
+ /*!
+ \brief Check if saving to file is enabled.
+ \return True if saving to file is enabled, false otherwise.
+ */
+ bool get_write_intermediates_to_file() const { return do_save_to_file; }
+
+ /*!
+ \brief Enable or disable saving to file.
+ \param do_save_to_file True to enable saving to file, false to disable.
+ */
+ void set_write_intermediates_to_file(bool do_save_to_file);
+
+private:
+ /*!
+ \brief Performs an efficiency iteration to update the efficiancies from the data_fan_sums and model.
+ Additionally, handles the saving of the efficiencies iteration to file, KL calculation and display.
+ \param[in] iter_num The iteration number
+ \param[in] eff_iter_num The efficiency iteration number
+ */
+ void efficiency_iteration(int iter_num, int eff_iter_num);
+
+ /*!
+ \brief Performs a geo normalization iteration to update the geo data from the measured_geo_data and model_data.
+ Additionally, handles the saving of the geo data iteration to file, KL calculation and display.
+ \param[in] iter_num The iteration number
+ */
+ void geo_normalization_iteration(int iter_num);
+
+ /*!
+ \brief Performs a block normalization iteration to update the block data from the measured_block_data and model_data.
+ Additionally, handles the saving of the block data iteration to file, KL calculation and display.
+ * @param iter_num The iteration number
+ */
+ void block_normalization_iteration(int iter_num);
+
+ /*!
+ \brief Write the efficiencies to a file (regardless of the value of do_save_to_file)
+ \param[in] iter_num The iteration number
+ \param[in] eff_iter_num The efficiency iteration number
+ */
+ void write_efficiencies_to_file(int iter_num, int eff_iter_num) const;
+
+ /*!
+ \brief Write the efficiencies to a file (regardless of the value of do_save_to_file)
+ \param[in] iter_num The iteration number
+ */
+ void write_geo_data_to_file(int iter_num) const;
+
+ /*!
+ \brief Write the efficiencies to a file (regardless of the value of do_save_to_file)
+ \param[in] iter_num The iteration number
+ */
+ void write_block_data_to_file(int iter_num) const;
+
+ /*!
+ \brief Computes the threshold for the Kullback-Leibler divergence calculation. This is a purely heuristic value.
+ \return The threshold value
+ */
+ float compute_threshold_for_KL();
+
+ // Constructor parameters
+ //! The prefix for the output files
+ std::string out_filename_prefix;
+ //! The number of (sub-)efficiency iterations to perform per iteration of the algorithm
+ int num_eff_iterations;
+ //! The number of algorithm iterations to perform
+ int num_iterations;
+ //! Whether to perform geo normalization calculations
+ bool do_geo;
+ //! Whether to perform block normalization calculations
+ bool do_block;
+ //! Whether to perform block normalization calculations with symmetry
+ bool do_symmetry_per_block;
+ //! Whether to perform Kullback-Leibler divergence calculations and display the KL value. This can be slow.
+ bool do_KL;
+ //! Whether to display the progress of the algorithm.
+ bool do_display;
+ //! Whether to save each iteration of the efficiencies, geo data and block data to file.
+ //! This will only work if the out_filename_prefix is set.
+ bool do_save_to_file;
+
+ //! The projdata info of the measured data
+ std::shared_ptr projdata_info;
+
+ // Calculated variables
+ //! The efficiencies calculated by the algorithm
+ DetectorEfficiencies norm_efficiencies;
+ //! The geo data calculated by the algorithm, if do_geo is true
+ BlockData3D norm_block_data;
+ //! The block data calculated by the algorithm, if do_block is true
+ GeoData3D norm_geo_data;
+
+ //! The threshold for the Kullback-Leibler divergence calculation
+ float threshold_for_KL;
+ FanProjData model_fan_data;
+ FanProjData fan_data;
+ DetectorEfficiencies data_fan_sums;
+ BlockData3D measured_block_data;
+ GeoData3D measured_geo_data;
+
+ //! Boolean to check if the data has been processed, see has_data_been_processed()
+ bool data_processed = false;
+
+ // do_KL specific varaibles
+ FanProjData measured_fan_data;
+ DetectorEfficiencies fan_sums;
+ GeoData3D geo_data;
+ BlockData3D block_data;
+};
+
+END_NAMESPACE_STIR
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
deleted file mode 100644
index 9fcbc0650..000000000
--- a/src/include/stir/recon_buildblock/ML_estimate_component_based_normalisation.h
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- Copyright (C) 2022, University College London
- This file is part of STIR.
-
- SPDX-License-Identifier: Apache-2.0
-
- See STIR/LICENSE.txt for details
-*/
-/*!
- \file
- \ingroup recon_buildblock
- \brief Declaration of stir::ML_estimate_component_based_normalisation
- \author Kris Thielemans
- */
-#include "stir/common.h"
-#include
-
-START_NAMESPACE_STIR
-
-class ProjData;
-
-/*!
- \brief Find normalisation factors using a maximum likelihood approach
-
- \ingroup recon_buildblock
-*/
-void ML_estimate_component_based_normalisation(const std::string& out_filename_prefix,
- const ProjData& measured_data,
- const ProjData& model_data,
- int num_eff_iterations,
- int num_iterations,
- bool do_geo,
- bool do_block,
- bool do_symmetry_per_block,
- bool do_KL,
- bool do_display);
-
-END_NAMESPACE_STIR
diff --git a/src/recon_buildblock/CMakeLists.txt b/src/recon_buildblock/CMakeLists.txt
index ae64f14fc..c14375051 100644
--- a/src/recon_buildblock/CMakeLists.txt
+++ b/src/recon_buildblock/CMakeLists.txt
@@ -61,7 +61,7 @@ set(${dir_LIB_SOURCES}
BinNormalisationFromAttenuationImage.cxx
BinNormalisationSPECT.cxx
BinNormalisationPETFromComponents.cxx
- ML_estimate_component_based_normalisation.cxx
+ MLEstimateComponentBasedNormalisation.cxx
GeneralisedPrior.cxx
ProjDataRebinning.cxx
FourierRebinning.cxx
diff --git a/src/recon_buildblock/MLEstimateComponentBasedNormalisation.cxx b/src/recon_buildblock/MLEstimateComponentBasedNormalisation.cxx
new file mode 100644
index 000000000..51e8e5ae9
--- /dev/null
+++ b/src/recon_buildblock/MLEstimateComponentBasedNormalisation.cxx
@@ -0,0 +1,488 @@
+/*
+ Copyright (C) 2001- 2008, Hammersmith Imanet Ltd
+ Copyright (C) 2019-2020, 2022, University College London
+ Copyright (C) 2016-2017, PETsys Electronics
+ Copyright (C) 2021, Gefei Chen
+ Copyright (C) 2024, Robert Twyman Skelly
+ This file is part of STIR.
+
+ SPDX-License-Identifier: Apache-2.0
+
+ See STIR/LICENSE.txt for details
+*/
+/*!
+
+ \file
+ \ingroup recon_buildblock
+ \brief Implementation of ML_estimate_component_based_normalisation
+
+ \author Kris Thielemans
+ \author Tahereh Niknejad
+ \author Gefei Chen
+ \author Robert Twyman Skelly
+ */
+#include "stir/recon_buildblock/MLEstimateComponentBasedNormalisation.h"
+#include "stir/ML_norm.h"
+#include "stir/Scanner.h"
+#include "stir/display.h"
+#include "stir/info.h"
+#include "stir/ProjData.h"
+#include "stir/stream.h"
+#include
+#include
+
+START_NAMESPACE_STIR
+
+void
+ML_estimate_component_based_normalisation(const std::string& out_filename_prefix,
+ const ProjData& measured_projdata,
+ const ProjData& model_projdata,
+ const int num_eff_iterations,
+ const int num_iterations,
+ const bool do_geo,
+ const bool do_block,
+ const bool do_symmetry_per_block,
+ const bool do_KL,
+ const bool do_display,
+ const bool do_save_to_file)
+{
+ MLEstimateComponentBasedNormalisation estimator(out_filename_prefix,
+ measured_projdata,
+ model_projdata,
+ num_eff_iterations,
+ num_iterations,
+ do_geo,
+ do_block,
+ do_symmetry_per_block,
+ do_KL,
+ do_display,
+ do_save_to_file);
+ estimator.process();
+}
+
+MLEstimateComponentBasedNormalisation::
+MLEstimateComponentBasedNormalisation(std::string out_filename_prefix_v,
+ const ProjData& measured_projdata_v,
+ const ProjData& model_projdata_v,
+ const int num_eff_iterations_v,
+ const int num_iterations_v,
+ const bool do_geo_v,
+ const bool do_block_v,
+ const bool do_symmetry_per_block_v,
+ const bool do_KL_v,
+ const bool do_display_v,
+ const bool do_save_to_file_v)
+ : out_filename_prefix(std::move(out_filename_prefix_v)),
+ num_eff_iterations(num_eff_iterations_v),
+ num_iterations(num_iterations_v),
+ do_geo(do_geo_v),
+ do_block(do_block_v),
+ do_symmetry_per_block(do_symmetry_per_block_v),
+ do_KL(do_KL_v),
+ do_display(do_display_v),
+ do_save_to_file(do_save_to_file_v)
+{
+ if (*measured_projdata_v.get_proj_data_info_sptr() != *model_projdata_v.get_proj_data_info_sptr())
+ {
+ error("MLEstimateComponentBasedNormalisation: measured and model data have different ProjDataInfo");
+ }
+
+ projdata_info = measured_projdata_v.get_proj_data_info_sptr();
+ const int num_transaxial_blocks
+ = measured_projdata_v.get_proj_data_info_sptr()->get_scanner_sptr()->get_num_transaxial_blocks();
+ const int num_axial_blocks = measured_projdata_v.get_proj_data_info_sptr()->get_scanner_sptr()->get_num_axial_blocks();
+ const int virtual_axial_crystals
+ = measured_projdata_v.get_proj_data_info_sptr()->get_scanner_sptr()->get_num_virtual_axial_crystals_per_block();
+ const int virtual_transaxial_crystals
+ = measured_projdata_v.get_proj_data_info_sptr()->get_scanner_sptr()->get_num_virtual_transaxial_crystals_per_block();
+ const int num_physical_rings = measured_projdata_v.get_proj_data_info_sptr()->get_scanner_sptr()->get_num_rings()
+ - (num_axial_blocks - 1) * virtual_axial_crystals;
+ const int num_physical_detectors_per_ring
+ = measured_projdata_v.get_proj_data_info_sptr()->get_scanner_sptr()->get_num_detectors_per_ring()
+ - num_transaxial_blocks * virtual_transaxial_crystals;
+ const int num_transaxial_buckets
+ = measured_projdata_v.get_proj_data_info_sptr()->get_scanner_sptr()->get_num_transaxial_buckets();
+ const int num_axial_buckets = measured_projdata_v.get_proj_data_info_sptr()->get_scanner_sptr()->get_num_axial_buckets();
+ const int num_transaxial_blocks_per_bucket
+ = measured_projdata_v.get_proj_data_info_sptr()->get_scanner_sptr()->get_num_transaxial_blocks_per_bucket();
+ const int num_axial_blocks_per_bucket
+ = measured_projdata_v.get_proj_data_info_sptr()->get_scanner_sptr()->get_num_axial_blocks_per_bucket();
+
+ int num_physical_transaxial_crystals_per_basic_unit
+ = measured_projdata_v.get_proj_data_info_sptr()->get_scanner_sptr()->get_num_transaxial_crystals_per_block()
+ - virtual_transaxial_crystals;
+ int num_physical_axial_crystals_per_basic_unit
+ = measured_projdata_v.get_proj_data_info_sptr()->get_scanner_sptr()->get_num_axial_crystals_per_block()
+ - virtual_axial_crystals;
+
+ // If there are multiple buckets, we increase the symmetry size to a bucket. Otherwise, we use a block.
+ if (do_symmetry_per_block == false)
+ {
+ num_physical_transaxial_crystals_per_basic_unit *= num_transaxial_blocks_per_bucket;
+ num_physical_axial_crystals_per_basic_unit *= num_axial_blocks_per_bucket;
+ }
+
+ // Setup the data structures given the PET scanner geometry
+ data_fan_sums = DetectorEfficiencies(IndexRange2D(num_physical_rings, num_physical_detectors_per_ring));
+ norm_efficiencies = DetectorEfficiencies(IndexRange2D(num_physical_rings, num_physical_detectors_per_ring));
+
+ measured_geo_data = GeoData3D(num_physical_axial_crystals_per_basic_unit,
+ num_physical_transaxial_crystals_per_basic_unit / 2,
+ num_physical_rings,
+ num_physical_detectors_per_ring);
+ norm_geo_data = GeoData3D(num_physical_axial_crystals_per_basic_unit,
+ num_physical_transaxial_crystals_per_basic_unit / 2,
+ num_physical_rings,
+ num_physical_detectors_per_ring);
+
+ 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);
+
+ make_fan_data_remove_gaps(model_fan_data, model_projdata_v);
+ make_fan_data_remove_gaps(measured_fan_data, measured_projdata_v);
+
+ threshold_for_KL = compute_threshold_for_KL();
+
+ 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_display)
+ {
+ display(measured_block_data, "raw block data from measurements");
+ }
+
+ // Compute the do_KL specific variables from the measured data
+ fan_sums = DetectorEfficiencies(IndexRange2D(num_physical_rings, num_physical_detectors_per_ring));
+ geo_data = GeoData3D(num_physical_axial_crystals_per_basic_unit,
+ num_physical_transaxial_crystals_per_basic_unit / 2,
+ num_physical_rings,
+ num_physical_detectors_per_ring);
+ block_data = BlockData3D(num_axial_blocks, num_transaxial_blocks, num_axial_blocks - 1, num_transaxial_blocks - 1);
+}
+
+void
+MLEstimateComponentBasedNormalisation::process()
+{
+ if (do_display)
+ {
+ display(model_fan_data, "model");
+ }
+
+ // Initialize the efficiencies, geo data and block data to 1
+ norm_efficiencies.fill(sqrt(data_fan_sums.sum() / model_fan_data.sum()));
+ norm_geo_data.fill(1);
+ norm_block_data.fill(1);
+
+ for (int iter_num = 1; iter_num <= std::max(num_iterations, 1); ++iter_num)
+ {
+ fan_data = model_fan_data;
+ apply_geo_norm(fan_data, norm_geo_data);
+ apply_block_norm(fan_data, norm_block_data);
+ if (do_display)
+ {
+ display(fan_data, "model*geo*block");
+ }
+
+ // Internal Efficiency iterations loop
+ for (int eff_iter_num = 1; eff_iter_num <= num_eff_iterations; ++eff_iter_num)
+ {
+ efficiency_iteration(iter_num, eff_iter_num);
+ }
+
+ if (do_geo)
+ {
+ geo_normalization_iteration(iter_num); // Calculate geo norm iteration
+ }
+ if (do_block)
+ {
+ block_normalization_iteration(iter_num); // Calculate block norm iteration
+ }
+ if (do_KL)
+ {
+ // print KL for fansums
+ make_fan_sum_data(fan_sums, fan_data);
+ make_geo_data(geo_data, fan_data);
+ make_block_data(block_data, measured_fan_data);
+ info(boost::format("KL on fans: %1%, %2%") % KL(measured_fan_data, fan_data, 0) % KL(measured_geo_data, geo_data, 0));
+ }
+ }
+ data_processed = true;
+}
+bool
+MLEstimateComponentBasedNormalisation::get_data_is_processed() const
+{
+ return data_processed;
+}
+
+DetectorEfficiencies
+MLEstimateComponentBasedNormalisation::get_efficiencies() const
+{
+ if (!this->get_data_is_processed())
+ {
+ error("MLEstimateComponentBasedNormalisation::get_efficiencies: data has not been processed yet");
+ }
+ return norm_efficiencies;
+}
+
+GeoData3D
+MLEstimateComponentBasedNormalisation::get_geo_data() const
+{
+ if (!this->get_data_is_processed())
+ {
+ error("MLEstimateComponentBasedNormalisation::get_geo_data: data has not been processed yet");
+ }
+ if (!do_geo)
+ {
+ error("MLEstimateComponentBasedNormalisation::get_geo_data: geo data was not calculated");
+ }
+ return norm_geo_data;
+}
+
+BlockData3D
+MLEstimateComponentBasedNormalisation::get_block_data() const
+{
+ if (!this->get_data_is_processed())
+ {
+ error("MLEstimateComponentBasedNormalisation::get_block_data: data has not been processed yet");
+ }
+ if (!do_block)
+ {
+ error("MLEstimateComponentBasedNormalisation::get_block_data: block data was not calculated");
+ }
+ return norm_block_data;
+}
+
+BinNormalisationPETFromComponents
+MLEstimateComponentBasedNormalisation::construct_bin_norm_from_pet_components() const
+{
+ if (!this->get_data_is_processed())
+ {
+ error("MLEstimateComponentBasedNormalisation::construct_bin_norm_from_pet_components: data has not been processed yet");
+ }
+ auto bin_norm = BinNormalisationPETFromComponents();
+ bin_norm.allocate(projdata_info, true, do_geo, do_block, do_symmetry_per_block);
+ bin_norm.set_crystal_efficiencies(norm_efficiencies);
+ if (do_geo)
+ {
+ bin_norm.set_geometric_factors(norm_geo_data);
+ }
+ if (do_block)
+ {
+ bin_norm.set_block_factors(norm_block_data);
+ }
+ return bin_norm;
+}
+
+void
+MLEstimateComponentBasedNormalisation::set_output_filename_prefix(const std::string& out_filename_prefix)
+{
+ this->out_filename_prefix = out_filename_prefix;
+}
+
+void
+MLEstimateComponentBasedNormalisation::set_num_eff_iterations(const int num_eff_iterations)
+{
+ data_processed = false;
+ this->num_eff_iterations = num_eff_iterations;
+}
+
+void
+MLEstimateComponentBasedNormalisation::set_num_iterations(const int num_iterations)
+{
+ data_processed = false;
+ this->num_iterations = num_iterations;
+}
+
+void
+MLEstimateComponentBasedNormalisation::set_enable_geo_norm_calculation(const bool do_geo)
+{
+ data_processed = false;
+ this->do_geo = do_geo;
+}
+
+void
+MLEstimateComponentBasedNormalisation::set_enable_block_norm_calculation(const bool do_block)
+{
+ data_processed = false;
+ this->do_block = do_block;
+}
+
+void
+MLEstimateComponentBasedNormalisation::set_enable_symmetry_per_block(const bool do_symmetry_per_block)
+{
+ data_processed = false;
+ this->do_symmetry_per_block = do_symmetry_per_block;
+}
+
+void
+MLEstimateComponentBasedNormalisation::set_do_kl_calculation(const bool do_kl)
+{
+ data_processed = false;
+ do_KL = do_kl;
+}
+
+void
+MLEstimateComponentBasedNormalisation::set_write_display_data(const bool do_display)
+{
+ data_processed = false;
+ this->do_display = do_display;
+}
+
+void
+MLEstimateComponentBasedNormalisation::set_write_intermediates_to_file(const bool do_save_to_file)
+{
+ data_processed = false;
+ this->do_save_to_file = do_save_to_file;
+}
+
+void
+MLEstimateComponentBasedNormalisation::efficiency_iteration(const int iter_num, const int eff_iter_num)
+{
+ iterate_efficiencies(norm_efficiencies, data_fan_sums, fan_data);
+ if (do_save_to_file)
+ {
+ write_efficiencies_to_file(iter_num, eff_iter_num);
+ }
+ if (do_KL)
+ {
+ apply_efficiencies(fan_data, norm_efficiencies);
+ std::cerr << "measured*norm min " << measured_fan_data.find_min() << " ,max " << measured_fan_data.find_max() << std::endl;
+ std::cerr << "model*norm min " << fan_data.find_min() << " ,max " << fan_data.find_max() << std::endl;
+ if (do_display)
+ display(fan_data, "model_times_norm");
+ info(boost::format("KL %1%") % KL(measured_fan_data, fan_data, threshold_for_KL));
+ // 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_display)
+ {
+ fan_data.fill(1);
+ apply_efficiencies(fan_data, norm_efficiencies);
+ display(fan_data, "eff norm");
+ // 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);
+ }
+}
+
+void
+MLEstimateComponentBasedNormalisation::geo_normalization_iteration(const int iter_num)
+{
+
+ fan_data = model_fan_data; // Reset fan_data to model_data
+ apply_efficiencies(fan_data, norm_efficiencies); // Apply efficiencies
+ apply_block_norm(fan_data, norm_block_data); // Apply block norm
+ iterate_geo_norm(norm_geo_data, measured_geo_data, fan_data);
+
+ if (do_save_to_file)
+ {
+ write_geo_data_to_file(iter_num);
+ }
+ if (do_KL)
+ {
+ apply_geo_norm(fan_data, norm_geo_data);
+ info(boost::format("KL %1%") % KL(measured_fan_data, fan_data, threshold_for_KL));
+ }
+ if (do_display)
+ {
+ fan_data.fill(1);
+ apply_geo_norm(fan_data, norm_geo_data);
+ display(fan_data, "geo norm");
+ }
+}
+
+void
+MLEstimateComponentBasedNormalisation::block_normalization_iteration(const int iter_num)
+{
+
+ fan_data = model_fan_data; // Reset fan_data to model_data
+ apply_efficiencies(fan_data, norm_efficiencies); // Apply efficiencies
+ apply_geo_norm(fan_data, norm_geo_data); // Apply geo norm
+ iterate_block_norm(norm_block_data, measured_block_data, fan_data); // Iterate block norm calculation
+
+ if (do_save_to_file)
+ {
+ write_block_data_to_file(iter_num);
+ }
+ if (do_KL)
+ {
+ apply_block_norm(fan_data, norm_block_data);
+ info(boost::format("KL %1%") % KL(measured_fan_data, fan_data, threshold_for_KL));
+ }
+ if (do_display)
+ {
+ fan_data.fill(1);
+ apply_block_norm(fan_data, norm_block_data);
+ display(norm_block_data, "raw block norm");
+ display(fan_data, "block norm");
+ }
+}
+
+void
+MLEstimateComponentBasedNormalisation::write_efficiencies_to_file(const int iter_num, const int eff_iter_num) const
+{
+ 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);
+ std::ofstream out(out_filename);
+ out << norm_efficiencies;
+}
+
+void
+MLEstimateComponentBasedNormalisation::write_geo_data_to_file(const int iter_num) const
+{
+ char* out_filename = new char[out_filename_prefix.size() + 30];
+ sprintf(out_filename, "%s_%s_%d.out", out_filename_prefix.c_str(), "geo", iter_num);
+ std::ofstream out(out_filename);
+ out << norm_geo_data;
+}
+
+void
+MLEstimateComponentBasedNormalisation::write_block_data_to_file(const int iter_num) const
+{
+ char* out_filename = new char[out_filename_prefix.size() + 30];
+ sprintf(out_filename, "%s_%s_%d.out", out_filename_prefix.c_str(), "block", iter_num);
+ std::ofstream out(out_filename);
+ out << norm_block_data;
+}
+
+float
+MLEstimateComponentBasedNormalisation::compute_threshold_for_KL()
+{
+ // Set the max found value to -inf
+ float max_elem = -std::numeric_limits::infinity();
+
+ const auto min_ra = model_fan_data.get_min_ra();
+ const auto max_ra = model_fan_data.get_max_ra();
+ const auto min_a = model_fan_data.get_min_a();
+ const auto max_a = model_fan_data.get_max_a();
+
+ for (auto ra = min_ra; ra <= max_ra; ++ra)
+ {
+ const auto min_rb = std::max(ra, model_fan_data.get_min_rb(ra));
+ const auto max_rb = model_fan_data.get_max_rb(ra);
+
+ for (auto a = min_a; a <= max_a; ++a)
+ {
+ const auto min_b = model_fan_data.get_min_b(a);
+ const auto max_b = model_fan_data.get_max_b(a);
+
+ for (auto rb = min_rb; rb <= max_rb; ++rb)
+ {
+ for (auto b = min_b; b <= max_b; ++b)
+ {
+ if (model_fan_data(ra, a, rb, b) == 0)
+ {
+ max_elem = std::max(max_elem, measured_fan_data(ra, a, rb, b));
+ }
+ }
+ }
+ }
+ }
+
+ return max_elem / 100000.F;
+}
+
+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
deleted file mode 100644
index a22ed6b0b..000000000
--- a/src/recon_buildblock/ML_estimate_component_based_normalisation.cxx
+++ /dev/null
@@ -1,308 +0,0 @@
-/*
- Copyright (C) 2001- 2008, Hammersmith Imanet Ltd
- Copyright (C) 2019-2020, 2022, University College London
- Copyright (C) 2016-2017, PETsys Electronics
- Copyright (C) 2021, Gefei Chen
- This file is part of STIR.
-
- SPDX-License-Identifier: Apache-2.0
-
- See STIR/LICENSE.txt for details
-*/
-/*!
-
- \file
- \ingroup recon_buildblock
- \brief Implementation of ML_estimate_component_based_normalisation
-
- \author Kris Thielemans
- \author Tahereh Niknejad
- \author Gefei Chen
- */
-#include "stir/recon_buildblock/ML_estimate_component_based_normalisation.h"
-
-#include "stir/ML_norm.h"
-#include "stir/Scanner.h"
-#include "stir/stream.h"
-#include "stir/display.h"
-#include "stir/info.h"
-#include "stir/warning.h"
-#include "stir/ProjData.h"
-#include
-#include
-#include
-#include
-
-START_NAMESPACE_STIR
-
-void
-ML_estimate_component_based_normalisation(const std::string& out_filename_prefix,
- const ProjData& measured_data,
- const ProjData& model_data,
- int num_eff_iterations,
- int num_iterations,
- bool do_geo,
- bool do_block,
- bool do_symmetry_per_block,
- bool do_KL,
- bool do_display)
-{
-
- const int num_transaxial_blocks = measured_data.get_proj_data_info_sptr()->get_scanner_sptr()->get_num_transaxial_blocks();
- const int num_axial_blocks = measured_data.get_proj_data_info_sptr()->get_scanner_sptr()->get_num_axial_blocks();
- const int virtual_axial_crystals
- = measured_data.get_proj_data_info_sptr()->get_scanner_sptr()->get_num_virtual_axial_crystals_per_block();
- const int virtual_transaxial_crystals
- = measured_data.get_proj_data_info_sptr()->get_scanner_sptr()->get_num_virtual_transaxial_crystals_per_block();
- const int num_physical_rings = measured_data.get_proj_data_info_sptr()->get_scanner_sptr()->get_num_rings()
- - (num_axial_blocks - 1) * virtual_axial_crystals;
- const int num_physical_detectors_per_ring
- = measured_data.get_proj_data_info_sptr()->get_scanner_sptr()->get_num_detectors_per_ring()
- - num_transaxial_blocks * virtual_transaxial_crystals;
- const int num_transaxial_buckets = measured_data.get_proj_data_info_sptr()->get_scanner_sptr()->get_num_transaxial_buckets();
- const int num_axial_buckets = measured_data.get_proj_data_info_sptr()->get_scanner_sptr()->get_num_axial_buckets();
- const int num_transaxial_blocks_per_bucket
- = measured_data.get_proj_data_info_sptr()->get_scanner_sptr()->get_num_transaxial_blocks_per_bucket();
- const int num_axial_blocks_per_bucket
- = measured_data.get_proj_data_info_sptr()->get_scanner_sptr()->get_num_axial_blocks_per_bucket();
-
- int num_physical_transaxial_crystals_per_basic_unit
- = measured_data.get_proj_data_info_sptr()->get_scanner_sptr()->get_num_transaxial_crystals_per_block()
- - virtual_transaxial_crystals;
- int num_physical_axial_crystals_per_basic_unit
- = measured_data.get_proj_data_info_sptr()->get_scanner_sptr()->get_num_axial_crystals_per_block() - virtual_axial_crystals;
- // If there are multiple buckets, we increase the symmetry size to a bucket. Otherwise, we use a block.
- if (do_symmetry_per_block == false)
- {
- if (num_transaxial_buckets > 1)
- {
- num_physical_transaxial_crystals_per_basic_unit *= num_transaxial_blocks_per_bucket;
- }
- if (num_axial_buckets > 1)
- {
- num_physical_axial_crystals_per_basic_unit *= num_axial_blocks_per_bucket;
- }
- }
-
- FanProjData model_fan_data;
- FanProjData fan_data;
- DetectorEfficiencies data_fan_sums(IndexRange2D(num_physical_rings, num_physical_detectors_per_ring));
- DetectorEfficiencies efficiencies(IndexRange2D(num_physical_rings, num_physical_detectors_per_ring));
-
- GeoData3D measured_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
- 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);
-
- make_fan_data_remove_gaps(model_fan_data, model_data);
- {
- // 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)
- {
- for (int a = model_fan_data.get_min_a(); a <= model_fan_data.get_max_a(); ++a)
- {
- 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");
-
- 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_display)
- display(measured_block_data, "raw block data from measurements");
-
- /* {
- char *out_filename = new char[20];
- sprintf(out_filename, "%s_%d.out",
- "fan", ax_pos_num);
- std::ofstream out(out_filename);
- out << data_fan_sums;
- delete[] out_filename;
- }
- */
- }
-
- // std::cerr << "model min " << model_fan_data.find_min() << " ,max " << model_fan_data.find_max() << std::endl;
- if (do_display)
- display(model_fan_data, "model");
-#if 0
- {
- shared_ptr out_proj_data_ptr =
- new ProjDataInterfile(model_data.get_proj_data_info_sptr()->clone,
- output_file_name);
-
- set_fan_data(*out_proj_data_ptr, model_fan_data);
- }
-#endif
-
- for (int iter_num = 1; iter_num <= std::max(num_iterations, 1); ++iter_num)
- {
- if (iter_num == 1)
- {
- efficiencies.fill(sqrt(data_fan_sums.sum() / model_fan_data.sum()));
- norm_geo_data.fill(1);
- norm_block_data.fill(1);
- }
- // efficiencies
- {
- fan_data = model_fan_data;
- apply_geo_norm(fan_data, norm_geo_data);
- 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)
- {
- iterate_efficiencies(efficiencies, data_fan_sums, fan_data);
- {
- 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);
- std::ofstream out(out_filename);
- out << efficiencies;
- delete[] out_filename;
- }
- if (do_KL)
- {
- apply_efficiencies(fan_data, efficiencies);
- std::cerr << "measured*norm min " << measured_fan_data.find_min() << " ,max " << measured_fan_data.find_max()
- << std::endl;
- std::cerr << "model*norm min " << fan_data.find_min() << " ,max " << fan_data.find_max() << std::endl;
- if (do_display)
- display(fan_data, "model_times_norm");
- info(boost::format("KL %1%") % KL(measured_fan_data, fan_data, threshold_for_KL));
- // 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_display)
- {
- fan_data.fill(1);
- apply_efficiencies(fan_data, efficiencies);
- display(fan_data, "eff norm");
- // 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);
- }
- }
- } // end efficiencies
-
- // geo norm
-
- fan_data = model_fan_data;
- apply_efficiencies(fan_data, efficiencies);
- apply_block_norm(fan_data, norm_block_data);
-
- if (do_geo)
- iterate_geo_norm(norm_geo_data, measured_geo_data, fan_data);
-
-#if 0
-
- { // check
- for (int a=0; a(*Scanner::get_scanner_from_name("Discovery 690"));
+ const auto exam_info = std::make_shared(ImagingModality::PT);
+ exam_info->patient_position = PatientPosition(PatientPosition::HFS);
+
+ const shared_ptr projdata_info(ProjDataInfo::ProjDataInfoCTI(
+ /*scanner_ptr*/ scanner,
+ /*span*/ 1,
+ /*max_delta*/ 23,
+ /*views*/ scanner->get_num_detectors_per_ring() / 2,
+ /*tang_pos*/ scanner->get_default_num_arccorrected_bins(),
+ /*arc_corrected*/ false,
+ /*tof_mash_factor*/ -1));
+
+ const auto measured_projdata = std::make_shared(ProjDataInMemory(exam_info, projdata_info, false));
+ measured_projdata->fill(1.F);
+
+ const auto model_projdata = std::make_shared(*measured_projdata);
+ model_projdata->fill(2.F);
+
+ constexpr int num_eff_iterations = 6;
+ constexpr int num_iterations = 2;
+ constexpr bool do_geo = false;
+ constexpr bool do_block = false;
+ constexpr bool do_symmetry_per_block = false;
+ constexpr bool do_KL = false;
+ constexpr bool do_display = false;
+ constexpr bool do_save_to_file = false;
+ auto ml_estimator = MLEstimateComponentBasedNormalisation("",
+ *measured_projdata,
+ *model_projdata,
+ num_eff_iterations,
+ num_iterations,
+ do_geo,
+ do_block,
+ do_symmetry_per_block,
+ do_KL,
+ do_display,
+ do_save_to_file);
+ ml_estimator.process();
+
+ // Check the efficiencies, with measured data as uniform 1s and model data as uniform 2s, expect this to be ~0.707
+ auto efficiencies = ml_estimator.get_efficiencies();
+ check_if_less(efficiencies.find_max(), 0.75, "The max value of the efficiencies is greater than expected");
+ check_if_less(0.65, efficiencies.find_max(), "The max value of the efficiencies is less than expected");
+ check_if_less(efficiencies.find_min(), 0.75, "The min value of the efficiencies is greater than expected");
+ check_if_less(0.65, efficiencies.find_min(), "The min value of the efficiencies is less than expected");
+
+ auto bin_normalization = ml_estimator.construct_bin_norm_from_pet_components();
+ bin_normalization.set_up(measured_projdata->get_exam_info_sptr(), measured_projdata->get_proj_data_info_sptr());
+
+ // Compute the projdata
+ ProjDataInMemory normalization_projdata(*measured_projdata);
+ normalization_projdata.fill(1.F);
+ bin_normalization.apply(normalization_projdata);
+
+ // Check the normalization factors, with measured data as uniform 1s and model data as uniform 2s, expect this to be 2.0f
+ const auto prev_tolerance = get_tolerance();
+ set_tolerance(1e-3);
+ check_if_equal(normalization_projdata.find_max(), 2.f, "The max value of the normalization projdata is not 2.0");
+ check_if_equal(normalization_projdata.find_min(), 2.f, "The min value of the normalization projdata is not 0.0");
+ set_tolerance(prev_tolerance);
+ }
+};
+END_NAMESPACE_STIR
+
+USING_NAMESPACE_STIR
+
+int
+main()
+{
+ Verbosity::set(1);
+ MLEstimateComponentBasedNormalisationTest tests;
+ tests.run_tests();
+ return tests.main_return_value();
+}
diff --git a/src/utilities/apply_normfactors3D.cxx b/src/utilities/apply_normfactors3D.cxx
index a235aea2b..0be120d05 100644
--- a/src/utilities/apply_normfactors3D.cxx
+++ b/src/utilities/apply_normfactors3D.cxx
@@ -73,7 +73,7 @@ main(int argc, char** argv)
char* in_filename = new char[in_filename_prefix.size() + 30];
sprintf(in_filename, "%s_%s_%d_%d.out", in_filename_prefix.c_str(), "eff", iter_num, eff_iter_num);
std::ifstream in(in_filename);
- in >> norm.crystal_efficiencies();
+ in >> norm.get_crystal_efficiencies();
if (!in)
{
warning("Error reading %s, using all 1s instead\n", in_filename);
@@ -89,7 +89,7 @@ main(int argc, char** argv)
char* in_filename = new char[in_filename_prefix.size() + 30];
sprintf(in_filename, "%s_%s_%d.out", in_filename_prefix.c_str(), "geo", iter_num);
std::ifstream in(in_filename);
- in >> norm.geometric_factors();
+ in >> norm.get_geometric_factors();
if (!in)
{
warning("Error reading %s, using all 1s instead\n", in_filename);
@@ -106,7 +106,7 @@ main(int argc, char** argv)
char* in_filename = new char[in_filename_prefix.size() + 30];
sprintf(in_filename, "%s_%s_%d.out", in_filename_prefix.c_str(), "block", iter_num);
std::ifstream in(in_filename);
- in >> norm.block_factors();
+ in >> norm.get_block_factors();
if (!in)
{
warning("Error reading %s, using all 1s instead\n", in_filename);
diff --git a/src/utilities/find_ML_normfactors3D.cxx b/src/utilities/find_ML_normfactors3D.cxx
index 2e78c0cbf..f7bdcd998 100644
--- a/src/utilities/find_ML_normfactors3D.cxx
+++ b/src/utilities/find_ML_normfactors3D.cxx
@@ -17,7 +17,7 @@
Just a wrapper around ML_estimate_component_based_normalisation
\author Kris Thielemans
*/
-#include "stir/recon_buildblock/ML_estimate_component_based_normalisation.h"
+#include "stir/recon_buildblock/MLEstimateComponentBasedNormalisation.h"
#include "stir/CPUTimer.h"
#include "stir/info.h"
#include "stir/ProjData.h"
@@ -114,7 +114,8 @@ main(int argc, char** argv)
do_block,
do_symmetry_per_block,
do_KL,
- do_display);
+ do_display,
+ /*do_save_to_file*/ true);
timer.stop();
info(boost::format("CPU time %1% secs") % timer.value());