diff --git a/recon_test_pack/run_tests_modelling.sh b/recon_test_pack/run_tests_modelling.sh index 07c9cad875..77d8dfec50 100755 --- a/recon_test_pack/run_tests_modelling.sh +++ b/recon_test_pack/run_tests_modelling.sh @@ -126,7 +126,7 @@ generate_image ${INPUTDIR}generate_p5.par fi assemble_images p0005-p5.${imgext} p0005.hv p5.hv # test -extract_single_images_from_parametric_image p0005-p5_%d.hv p0005-p5.${imgext} +extract_single_images_from_parametric_image p0005-p5_{}.hv p0005-p5.${imgext} if compare_image p0005-p5_1.hv p0005.hv; then : # ok else @@ -145,14 +145,14 @@ get_dynamic_images_from_parametric_images dyn_from_p0005-p5.hv p0005-p5.${imgext # run and test Patlak estimation apply_patlak_to_images indirect_Patlak.hv dyn_from_p0005-p5.hv ${INPUTDIR}PatlakPlot.par echo "Test Patlak round-trip" -extract_single_images_from_parametric_image indirect_Patlak_img_%d.hv indirect_Patlak.hv +extract_single_images_from_parametric_image indirect_Patlak_img_{}.hv indirect_Patlak.hv echo "indirect to original" for par in 1 2; do compare_image indirect_Patlak_img_${par}.hv p0005-p5_${par}.hv done # Create the appropriate proj_data files -extract_single_images_from_dynamic_image dyn_from_p0005-p5_img_f%dg1d0b0.hv dyn_from_p0005-p5.hv +extract_single_images_from_dynamic_image dyn_from_p0005-p5_img_f{}g1d0b0.hv dyn_from_p0005-p5.hv # if [ ! -r fwd_dyn_from_p0005-p5.S ]; then for fr in `count 23 28`; do @@ -184,14 +184,49 @@ for direct in OSMAPOSL OSSPS ; do INPUT=fwd_dyn_from_p0005-p5.hs INIT=indirect_Patlak.hv ${MPIRUN} P${direct} P${direct}.par > P${direct}.log 2>&1 echo "Compare the direct parametric images to the original ones" - extract_single_images_from_parametric_image p0005-p5_img_f%dg1d0b0.hv p0005-p5.${imgext} - extract_single_images_from_parametric_image P${direct}_${ITER}_img_f%dg1d0b0.hv P${direct}_${ITER}.hv + extract_single_images_from_parametric_image p0005-p5_img_f{}g1d0b0.hv p0005-p5.${imgext} + extract_single_images_from_parametric_image P${direct}_${ITER}_img_f{}g1d0b0.hv P${direct}_${ITER}.hv for par in 1 2; do compare_image -t .01 P${direct}_${ITER}_img_f${par}g1d0b0.hv p0005-p5_img_f${par}g1d0b0.hv done echo "Comparison is OK" done # POSMAPOSL POSSPS# + +for nested in NESTPOSMAPOSL ; do #NESTGPOSMAPOSL ; do + cp ${INPUTDIR}${nested}.par . + echo "Test the nested ${nested} Patlak Plot reconstruction" + # rm -f ${nested}.log + INPUT=fwd_dyn_from_p0005-p5.hs INIT=indirect_Patlak.hv ${MPIRUN} ${nested} ${nested}.par > ${nested}.log 2>&1 + + case ${nested} in + NESTPOSMAPOSL) + par_map="1:1 2:2" + ;; + NESTGPOSMAPOSL) + par_map="1:1 3:2" + ;; + *) + echo "Unknown algorithm ${nested}" >&2 + exit 1 + ;; + esac + + echo "Compare the nested direct parametric images to the original ones" + extract_single_images_from_parametric_image p0005-p5_img_f{}g1d0b0.hv p0005-p5.${imgext} + extract_single_images_from_parametric_image ${nested}_${ITER}_img_f{}g1d0b0.hv ${nested}_${ITER}.hv + + for pair in ${par_map}; do + recon_par=${pair%%:*} + truth_par=${pair##*:} + compare_image -t .01 \ + ${nested}_${ITER}_img_f${recon_par}g1d0b0.hv \ + p0005-p5_img_f${truth_par}g1d0b0.hv + done + + echo "Comparison is OK" + +done # NESTPOSMAPOSL NESTGPOSMAPOSL echo "Test the utility: 'mult_model_with_dyn_images'" echo "Multiply the dynamic images with the model matrix to get images in the parametric space." diff --git a/recon_test_pack/test_modelling_input/NESTGPOSMAPOSL.par b/recon_test_pack/test_modelling_input/NESTGPOSMAPOSL.par new file mode 100644 index 0000000000..45cd7cbd0a --- /dev/null +++ b/recon_test_pack/test_modelling_input/NESTGPOSMAPOSL.par @@ -0,0 +1,100 @@ +OSMAPOSLParameters := + +objective function type:=PoissonNestedLogLikelihoodWithGeneralizedPatlakAndDynamicProjectionData +PoissonNestedLogLikelihoodWithGeneralizedPatlakAndDynamicProjectionData Parameters := + +input file := ${INPUT} + +; if disabled, defaults to maximum segment number in the file +maximum absolute segment number to process := ${MAXSEG} +; see User's Guide to see when you need this +zero end planes of segment 0:= 0 + +;zoom := .15645 +;xy output image size (in pixels) := 21 +;z output image size (in pixels) := 17 +;z offset (in mm) := 0 + +projector pair type := Matrix + Projector Pair Using Matrix Parameters := + Matrix type := Ray Tracing + Ray tracing matrix parameters := + End Ray tracing matrix parameters := + End Projector Pair Using Matrix Parameters := + +Bin Normalisation type := From ProjData + Bin Normalisation From ProjData := + normalisation projdata filename := all_ones.hs + End Bin Normalisation From ProjData := + +; we need this for backwards compatibility with the testing script +use subset sensitivities:=0 +sensitivity filename:= sens.img +; if next is set to 1, sensitivity will be recomputed +; and written to file (if "sensitivity filename" is set) +recompute sensitivity := 1 + +; specify additive projection data to handle randoms or so +; see User's Guide for more info +additive sinograms := 0 + +; patlak related files +Kinetic Model type := Generalized Patlak Plot +Generalized Patlak Plot Parameters := +time frame definition filename := time.fdef +starting frame := 23 +calibration factor := 9432.31 +blood data filename := plasma.if +Time Shift := 0 +In total counts := 1 +;In correct scale := 0 +kloss lower bound := 0 +kloss upper bound := 0 +; Default: end_of_frame = 0 +;mid_of_frame = 1 +frame reference time: = 0 +number of kloss samples := 3 +end Generalized Patlak Plot Parameters := + +;; Nested subiterations (regular nested iterations using the Generalized Patlak matrix) +number of nested subiterations := 0 +;; run the nested linear (standard) Patlak EM to convergence first, +;; and use that as the starting point for the nested generalized Patlak EM, +;; Global initial subiterations for initialization (using standard Patlak model, +;; i.e. ModelMatrix) before switching to Generalized Patlak iterations +number of global initialization subiterations := 40 +;; Nested subiterations for initialization (using standard Patlak model, +;; i.e. ModelMatrix instead of GenearlizedPatlakMatrix) +number of nested initialization subiterations := 1 +;; Choose whether both standard and generalized Patlak models +;; (1) will be used in an alternating fashion for the initialization or +;; (0) only the standard Patlak model will be utilized. +alternating initialization mode := 0 +; maximum nested relative change := +; minimum nested relative change := + +End PoissonNestedLogLikelihoodWithGeneralizedPatlakAndDynamicProjectionData Parameters:= + +; Number of subsets should be a divisor of num_views/4 +number of subsets:=${NUMSUBS} +; Use for starting the numbering from something else than 1 +start at subiteration number:=1 +; Use if you want to start from another subset than 0 (but why?) +start at subset:= 0 +number of subiterations:= ${ITER} +save estimates at subiteration intervals:= ${SAVITER} + +initial estimate := ${INIT} +; enable this when you read an initial estimate with negative data +enforce initial positivity condition:=1 + +inter-update filter subiteration interval:= 0 +inter-update filter type := None + +inter-iteration filter subiteration interval:= 0 + +post-filter type := None + +output filename prefix:= NESTGPOSMAPOSL + +END := \ No newline at end of file diff --git a/recon_test_pack/test_modelling_input/NESTPOSMAPOSL.par b/recon_test_pack/test_modelling_input/NESTPOSMAPOSL.par new file mode 100644 index 0000000000..8b3f958a9f --- /dev/null +++ b/recon_test_pack/test_modelling_input/NESTPOSMAPOSL.par @@ -0,0 +1,87 @@ +OSMAPOSLParameters := + +objective function type:=PoissonNestedLogLikelihoodWithLinearKineticModelAndDynamicProjectionData +PoissonNestedLogLikelihoodWithLinearKineticModelAndDynamicProjectionData Parameters := + +input file := ${INPUT} + +; if disabled, defaults to maximum segment number in the file +maximum absolute segment number to process := ${MAXSEG} +; see User's Guide to see when you need this +zero end planes of segment 0:= 0 + +;zoom := .15645 +;xy output image size (in pixels) := 21 +;z output image size (in pixels) := 17 +;z offset (in mm) := 0 + +projector pair type := Matrix + Projector Pair Using Matrix Parameters := + Matrix type := Ray Tracing + Ray tracing matrix parameters := + End Ray tracing matrix parameters := + End Projector Pair Using Matrix Parameters := + +Bin Normalisation type := From ProjData + Bin Normalisation From ProjData := + normalisation projdata filename := all_ones.hs + End Bin Normalisation From ProjData := + +; we need this for backwards compatibility with the testing script +use subset sensitivities:=0 +sensitivity filename:= sens.img +; if next is set to 1, sensitivity will be recomputed +; and written to file (if "sensitivity filename" is set) +recompute sensitivity := 1 + +; specify additive projection data to handle randoms or so +; see User's Guide for more info +additive sinograms := 0 + +; patlak related files +Kinetic Model type := Patlak Plot +Patlak Plot Parameters := +time frame definition filename := time.fdef +starting frame := 23 +calibration factor := 9432.31 +blood data filename := plasma.if +Time Shift := 0 +In total counts := 1 +;In correct scale := 0 +end Patlak Plot Parameters := + +;; These are unique parameters in Nested methods +;; 20 subsets as per: Whole-body direct 4D parametric PET imaging employing nested +;; generalized Patlak expectation–maximization reconstruction, Phys Med Biol 2016 +number of nested subiterations := 1 +; restrict updates (larger nested relative updates will be thresholded) +; maximum nested relative change := +; restrict updates (smaller nested relative updates will be thresholded) +; minimum nested relative change := + + +End PoissonNestedLogLikelihoodWithLinearKineticModelAndDynamicProjectionData Parameters:= + +; Number of subsets should be a divisor of num_views/4 +number of subsets:=${NUMSUBS} +; Use for starting the numbering from something else than 1 +start at subiteration number:=1 +; Use if you want to start from another subset than 0 (but why?) +start at subset:= 0 +number of subiterations:= ${ITER} +save estimates at subiteration intervals:= ${SAVITER} + +initial estimate := ${INIT} +; enable this when you read an initial estimate with negative data +enforce initial positivity condition:=1 + +inter-update filter subiteration interval:= 0 +inter-update filter type := None + +inter-iteration filter subiteration interval:= 0 + +post-filter type := None + +output filename prefix:=NESTPOSMAPOSL + +END := \ No newline at end of file diff --git a/src/IO/CMakeLists.txt b/src/IO/CMakeLists.txt index e78d97d363..7dce036701 100644 --- a/src/IO/CMakeLists.txt +++ b/src/IO/CMakeLists.txt @@ -23,6 +23,7 @@ set(${dir_LIB_SOURCES} InterfileHeader.cxx InterfilePDFSHeaderSPECT.cxx InputFileFormatRegistry.cxx + InterfileDynamicDiscretisedDensityOutputFileFormat.cxx ) if (NOT MINI_STIR) diff --git a/src/IO/ECAT7ParametricDensityOutputFileFormat.cxx b/src/IO/ECAT7ParametricDensityOutputFileFormat.cxx index b731d5d9c5..33b8d90b43 100644 --- a/src/IO/ECAT7ParametricDensityOutputFileFormat.cxx +++ b/src/IO/ECAT7ParametricDensityOutputFileFormat.cxx @@ -14,6 +14,7 @@ \brief Implementation of class stir::ecat::ecat7::ECAT7ParametricDensityOutputFileFormat \author Kris Thielemans + \author Nicolas A Karakatsanis */ #include "stir/IO/ECAT7ParametricDensityOutputFileFormat.h" @@ -155,7 +156,8 @@ ECAT7ParametricDensityOutputFileFormat::actual_write_to_fil return Succeeded::yes; } -template class ECAT7ParametricDensityOutputFileFormat; +template class ECAT7ParametricDensityOutputFileFormat; +template class ECAT7ParametricDensityOutputFileFormat; END_NAMESPACE_ECAT7 END_NAMESPACE_ECAT diff --git a/src/IO/IO_registries.cxx b/src/IO/IO_registries.cxx index 0667f8a022..6545d80eff 100644 --- a/src/IO/IO_registries.cxx +++ b/src/IO/IO_registries.cxx @@ -20,6 +20,8 @@ \author Kris Thielemans \author Berta Marti Fuster + \author Nicolas A Karakatsanis + \author Nikos Efthimiou */ #include "stir/IO/InterfileOutputFileFormat.h" @@ -93,9 +95,12 @@ static RegisterInputFileFormat idummy0(0); static ITKOutputFileFormat::RegisterIt dummyITK1; # endif static InterfileDynamicDiscretisedDensityOutputFileFormat::RegisterIt dummydynIntfOut; -static InterfileParametricDiscretisedDensityOutputFileFormat::RegisterIt dummyparIntfOut; +static InterfileParametricDiscretisedDensityOutputFileFormat::RegisterIt + dummyparIntfOut; +static InterfileParametricDiscretisedDensityOutputFileFormat::RegisterIt + dummyGenPatIntfIn; static MultiDynamicDiscretisedDensityOutputFileFormat::RegisterIt dummydynMultiOut; -static MultiParametricDiscretisedDensityOutputFileFormat::RegisterIt dummyparMultiOut; +static MultiParametricDiscretisedDensityOutputFileFormat::RegisterIt dummyparMultiOut; //! Support for SAFIR listmode file format static RegisterInputFileFormat> LMdummySAFIR(4); @@ -119,7 +124,8 @@ END_NAMESPACE_ECAT6 START_NAMESPACE_ECAT7 static ECAT7OutputFileFormat::RegisterIt dummy3; static ECAT7DynamicDiscretisedDensityOutputFileFormat::RegisterIt dummydynecat7In; -static ECAT7ParametricDensityOutputFileFormat::RegisterIt dummyparecat7In; +static ECAT7ParametricDensityOutputFileFormat::RegisterIt dummyparecat7In; +static ECAT7ParametricDensityOutputFileFormat::RegisterIt dummyGenPatecat7In; END_NAMESPACE_ECAT7 END_NAMESPACE_ECAT # endif @@ -139,7 +145,8 @@ static RegisterInputFileFormat>>> idummy7(10000); # endif static RegisterInputFileFormat dyndummy_intf(1); -static RegisterInputFileFormat paradummy_intf(1); +static RegisterInputFileFormat> paradummy_intf(1); +static RegisterInputFileFormat> paradummy_int3f(1); static RegisterInputFileFormat dynim_dummy_multi(1); static RegisterInputFileFormat parim_dummy_multi(1); diff --git a/src/IO/InputFileFormatRegistry.cxx b/src/IO/InputFileFormatRegistry.cxx index 23e2e602e1..6a6f29f10c 100644 --- a/src/IO/InputFileFormatRegistry.cxx +++ b/src/IO/InputFileFormatRegistry.cxx @@ -13,6 +13,7 @@ \brief Instantiations for class stir::InputFileFormatRegistry \author Kris Thielemans + \author Nicolas A Karakatsanis */ @@ -28,8 +29,9 @@ START_NAMESPACE_STIR // instantiations template class InputFileFormatRegistry>; template class InputFileFormatRegistry; +template class InputFileFormatRegistry; template class InputFileFormatRegistry; template class InputFileFormatRegistry; template class InputFileFormatRegistry>>; -END_NAMESPACE_STIR +END_NAMESPACE_STIR \ No newline at end of file diff --git a/src/IO/InterfileHeader.cxx b/src/IO/InterfileHeader.cxx index 9df1f5f346..cdd9de2ce4 100644 --- a/src/IO/InterfileHeader.cxx +++ b/src/IO/InterfileHeader.cxx @@ -389,6 +389,9 @@ InterfileHeader::post_processing() exam_info_sptr->time_frame_definitions = TimeFrameDefinitions(image_relative_start_times, image_durations); + // Added for old implementations relying on this->time_frame_definitions variable + this->time_frame_definitions = exam_info_sptr->time_frame_definitions; + return false; } diff --git a/src/IO/InterfileParametricDiscretisedDensityOutputFileFormat.cxx b/src/IO/InterfileParametricDiscretisedDensityOutputFileFormat.cxx index f6843f778c..1024798f75 100644 --- a/src/IO/InterfileParametricDiscretisedDensityOutputFileFormat.cxx +++ b/src/IO/InterfileParametricDiscretisedDensityOutputFileFormat.cxx @@ -103,6 +103,7 @@ InterfileParamDiscDensity::actual_write_to_file(std::string& filename, #undef ParamDiscDensity #undef TEMPLATE -template class InterfileParametricDiscretisedDensityOutputFileFormat; +template class InterfileParametricDiscretisedDensityOutputFileFormat; +template class InterfileParametricDiscretisedDensityOutputFileFormat; END_NAMESPACE_STIR diff --git a/src/IO/MultiParametricDiscretisedDensityOutputFileFormat.cxx b/src/IO/MultiParametricDiscretisedDensityOutputFileFormat.cxx index b065304d85..2829cfaa71 100644 --- a/src/IO/MultiParametricDiscretisedDensityOutputFileFormat.cxx +++ b/src/IO/MultiParametricDiscretisedDensityOutputFileFormat.cxx @@ -126,6 +126,6 @@ ParamDiscDensityOutputFileFormat::actual_write_to_file(std::string& filename, #undef ParamDiscDensity #undef TEMPLATE -template class MultiParametricDiscretisedDensityOutputFileFormat; +template class MultiParametricDiscretisedDensityOutputFileFormat; END_NAMESPACE_STIR diff --git a/src/IO/OutputFileFormat.cxx b/src/IO/OutputFileFormat.cxx index 9b6d80212e..2a0b140e64 100644 --- a/src/IO/OutputFileFormat.cxx +++ b/src/IO/OutputFileFormat.cxx @@ -13,6 +13,7 @@ \brief Instantiations of the stir::OutputFileFormat class \author Kris Thielemans + \author Nicolas A Karakatsanis */ #include "stir/IO/OutputFileFormat.txx" @@ -29,6 +30,7 @@ START_NAMESPACE_STIR template class OutputFileFormat>; template class OutputFileFormat; -template class OutputFileFormat; +template class OutputFileFormat; +template class OutputFileFormat; END_NAMESPACE_STIR diff --git a/src/IO/OutputFileFormat_default.cxx b/src/IO/OutputFileFormat_default.cxx index 40c7cfe370..fb57f8a3dc 100644 --- a/src/IO/OutputFileFormat_default.cxx +++ b/src/IO/OutputFileFormat_default.cxx @@ -13,7 +13,7 @@ \ingroup IO \brief initialisation of the stir::OutputFileFormat::_default_sptr member \author Kris Thielemans - + \author Nicolas A Karakatsanis */ #include "stir/IO/InterfileOutputFileFormat.h" @@ -47,13 +47,23 @@ shared_ptr>> new InterfileParametricDiscretisedDensityOutputFileFormat<3,KineticParameters<2,float> >; # else template <> -shared_ptr> OutputFileFormat::_default_sptr( +shared_ptr> OutputFileFormat::_default_sptr( # ifdef HAVE_LLN_MATRIX new ecat::ecat7::ECAT7ParametricDensityOutputFileFormat # else - new InterfileParametricDiscretisedDensityOutputFileFormat + new InterfileParametricDiscretisedDensityOutputFileFormat +# endif +); + +template <> +shared_ptr> OutputFileFormat::_default_sptr( +# ifdef HAVE_LLN_MATRIX + new ecat::ecat7::ECAT7ParametricDensityOutputFileFormat +# else + new InterfileParametricDiscretisedDensityOutputFileFormat # endif ); + # endif # if 1 template <> diff --git a/src/IO/interfile.cxx b/src/IO/interfile.cxx index 03d63ac615..b297f179d0 100644 --- a/src/IO/interfile.cxx +++ b/src/IO/interfile.cxx @@ -17,6 +17,7 @@ \author Kris Thielemans \author Sanida Mustafovic + \author Nicolas A Karakatsanis \author PARAPET project \author Richard Brown \author Parisa Khateri @@ -215,7 +216,8 @@ read_interfile_dynamic_image(istream& input, const string& directory_for_data) #ifndef MINI_STIR -ParametricVoxelsOnCartesianGrid* +template +ParametricDiscretisedDensity>>* read_interfile_parametric_image(istream& input, const string& directory_for_data) { InterfileImageHeader hdr; @@ -232,9 +234,10 @@ read_interfile_parametric_image(istream& input, const string& directory_for_data voxel_size[2] = hdr.pixel_sizes[1]; voxel_size[3] = hdr.pixel_sizes[0]; - ParametricVoxelsOnCartesianGrid* parametric_dens_ptr - = new ParametricVoxelsOnCartesianGrid(ParametricVoxelsOnCartesianGridBaseType( - hdr.get_exam_info_sptr(), image_sptr->get_index_range(), image_sptr->get_origin(), voxel_size)); + ParametricDiscretisedDensity>>* parametric_dens_ptr + = new ParametricDiscretisedDensity>>( + VoxelsOnCartesianGrid>( + hdr.get_exam_info_sptr(), image_sptr->get_index_range(), image_sptr->get_origin(), voxel_size)); ifstream data_in; open_read_binary(data_in, full_data_file_name); @@ -259,23 +262,28 @@ read_interfile_parametric_image(istream& input, const string& directory_for_data if (hdr.image_scaling_factors[kin_param - 1][i] != 1) (*image_sptr)[i] *= static_cast(hdr.image_scaling_factors[kin_param - 1][i]); - // Check that we're dealing with VoxelsOnCartesianGrid - if (dynamic_cast*>(image_sptr.get()) == 0) - error("ParametricDiscretisedDensity::read_from_file only supports VoxelsOnCartesianGrid"); - - // Set the image for the given kinetic parameter - ParametricVoxelsOnCartesianGrid::SingleDiscretisedDensityType::const_full_iterator single_density_iter - = image_sptr->begin_all(); - ParametricVoxelsOnCartesianGrid::SingleDiscretisedDensityType::const_full_iterator end_single_density_iter - = image_sptr->end_all(); - ParametricVoxelsOnCartesianGrid::full_densel_iterator parametric_density_iter = parametric_dens_ptr->begin_all_densel(); - - while (single_density_iter != end_single_density_iter) - { - (*parametric_density_iter)[kin_param] = *single_density_iter; - ++single_density_iter; - ++parametric_density_iter; - } + auto img_sptr = dynamic_cast*>(image_sptr.get()); + if (is_null_ptr(img_sptr)) + error("read_interfile_parametric_image only supports VoxelsOnCartesianGrid"); + parametric_dens_ptr->update_parametric_image(*img_sptr, kin_param); + + // // Check that we're dealing with VoxelsOnCartesianGrid + // if (dynamic_cast*>(image_sptr.get()) == 0) + // error("ParametricDiscretisedDensity::read_from_file only supports VoxelsOnCartesianGrid"); + + // // Set the image for the given kinetic parameter + // ParametricVoxelsOnCartesianGrid::SingleDiscretisedDensityType::const_full_iterator single_density_iter + // = image_sptr->begin_all(); + // ParametricVoxelsOnCartesianGrid::SingleDiscretisedDensityType::const_full_iterator end_single_density_iter + // = image_sptr->end_all(); + // ParametricVoxelsOnCartesianGrid::full_densel_iterator parametric_density_iter = parametric_dens_ptr->begin_all_densel(); + + // while (single_density_iter != end_single_density_iter) + // { + // (*parametric_density_iter)[kin_param] = *single_density_iter; + // ++single_density_iter; + // ++parametric_density_iter; + // } } return parametric_dens_ptr; @@ -315,7 +323,8 @@ read_interfile_dynamic_image(const string& filename) #ifndef MINI_STIR -ParametricVoxelsOnCartesianGrid* +template +ParametricDiscretisedDensity>>* read_interfile_parametric_image(const string& filename) { ifstream image_stream(filename.c_str()); @@ -327,9 +336,111 @@ read_interfile_parametric_image(const string& filename) char directory_name[max_filename_length]; get_directory_name(directory_name, filename.c_str()); - return read_interfile_parametric_image(image_stream, directory_name); + return read_interfile_parametric_image(image_stream, directory_name); } +// // Nicolas A Karakatsanis +// VoxelsOnCartesianGrid* +// read_interfile_frame_image(std::istream& input, +// const unsigned int data_offset, +// InterfileImageHeader& ifheader, +// const string& directory_for_data) +// { +// /* +// InterfileImageHeader ifheader; +// if (!ifheader.parse(input)) +// { +// error("read_interfile_frame_image: Failed to properly parse the Interfile header file of the provided data stream"); +// return 0; +// } +// */ +// // prepend directory_for_data to the data_file_name from the header + +// char full_data_file_name[max_filename_length]; +// strcpy(full_data_file_name, ifheader.data_file_name.c_str()); +// prepend_directory_name(full_data_file_name, directory_for_data.c_str()); + +// std::cout << "Preparing to load the image data of current image with offset = " << data_offset << endl; +// ifstream data_in; +// open_read_binary(data_in, full_data_file_name); + +// std::cout << "Loading of image data stream for current image/frame was successful\n"; + +// CartesianCoordinate3D voxel_size(static_cast(ifheader.pixel_sizes[2]), +// static_cast(ifheader.pixel_sizes[1]), +// static_cast(ifheader.pixel_sizes[0])); + +// const int z_size = ifheader.matrix_size[2][0]; +// const int y_size = ifheader.matrix_size[1][0]; +// const int x_size = ifheader.matrix_size[0][0]; +// const BasicCoordinate<3, int> min_indices = make_coordinate(0, -y_size / 2, -x_size / 2); +// const BasicCoordinate<3, int> max_indices = min_indices + make_coordinate(z_size, y_size, x_size) - 1; + +// CartesianCoordinate3D origin(0, 0, 0); +// if (ifheader.first_pixel_offsets[2] != InterfileHeader::double_value_not_set) +// { +// // make sure that origin is such that +// // first_pixel_offsets = min_indices*voxel_size + origin +// origin = make_coordinate(float(ifheader.first_pixel_offsets[2]), +// float(ifheader.first_pixel_offsets[1]), +// float(ifheader.first_pixel_offsets[0])) +// - voxel_size * BasicCoordinate<3, float>(min_indices); +// // TODO remove +// if (norm(origin) > .01) +// warning("interfile parsing: setting origin to (z=%g,y=%g,x=%g)", origin.z(), origin.y(), origin.x()); +// } + +// std::cout << "The min. and max. indices as well as the origin of the current image have been determined.\n"; + +// VoxelsOnCartesianGrid* image_ptr +// = new VoxelsOnCartesianGrid(IndexRange<3>(min_indices, max_indices), origin, voxel_size); + +// std::cout << "Loading of image data buffer for current image was successful.\n"; + +// data_in.seekg(data_offset); + +// std::cout << "Shifting of the reference point of the current data stream was successful.\n"; + +// float scale = float(1); +// if (read_data(data_in, *image_ptr, ifheader.type_of_numbers, scale, ifheader.file_byte_order) == Succeeded::no || scale != 1) +// { +// error("read_interfile_image: error reading data or scale factor returned by read_data not equal to 1\n"); +// return 0; +// } + +// for (int i = 0; i < ifheader.matrix_size[2][0]; i++) +// if (ifheader.image_scaling_factors[0][i] != 1) +// (*image_ptr)[i] *= static_cast(ifheader.image_scaling_factors[0][i]); + +// std::cout << "Binary data stream was successfully loaded to the image buffer.\nReady to return the current image/frame data " +// "buffer pointer.\n\n"; + +// return image_ptr; +// } + +// // Nicolas A Karakatsanis +// VoxelsOnCartesianGrid* +// read_interfile_frame_image(const string& filename, const unsigned int data_offset) +// { +// ifstream image_stream(filename.c_str()); +// if (!image_stream) +// { +// error("read_interfile_frame_image: couldn't open file %s\n", filename.c_str()); +// } + +// InterfileImageHeader ifheader; +// if (!ifheader.parse(image_stream)) +// { +// error("read_interfile_frame_image: Failed to properly parse the Interfile header file of the provided data stream\n"); +// return 0; +// } + +// char directory_name[max_filename_length]; +// get_directory_name(directory_name, filename.c_str()); + +// return read_interfile_frame_image(image_stream, data_offset, ifheader, directory_name); +// } + #endif #if 0 @@ -830,14 +941,42 @@ write_basic_interfile(const string& filename, #ifndef MINI_STIR +// // Nicolas A Karakatsanis +// Succeeded +// write_basic_interfile(string& filename, +// const DiscretisedDensity<3, float>& image, +// const unsigned int param_num, +// const NumericType output_type, +// const float scale, +// const ByteOrder byte_order) +// { + +// // Construct a string stream from the integer denoting the frame index +// std::ostringstream frame_idx; +// frame_idx << param_num; + +// // Append the frame/parameter dependent extension to the original filename +// // string frame_ext = "_p"; +// filename += "_p"; +// filename += frame_idx.str(); + +// // dynamic_cast will throw an exception when it's not valid +// return write_basic_interfile( +// filename, dynamic_cast&>(image), output_type, scale, byte_order); +// } + +template Succeeded write_basic_interfile(const string& filename, - const ParametricVoxelsOnCartesianGrid& image, + const ParametricDiscretisedDensity>>& image, const NumericType output_type, const float scale, const ByteOrder byte_order) { + static_assert(num_params == 2 || num_params == 3, + "write_basic_interfile: unnamed kinetic parameter labels for this num_dimensions — add a case below"); + std::string data_name, header_name; interfile_create_filenames(filename, data_name, header_name); @@ -856,7 +995,10 @@ write_basic_interfile(const string& filename, // Tell it what the different kinetic parameters mean std::vector data_type_descriptions; + data_type_descriptions.push_back("slope"); + if constexpr (num_params == 3) + data_type_descriptions.push_back("kloss"); data_type_descriptions.push_back("intercept"); const Succeeded success = write_basic_interfile_image_header(header_name, @@ -1492,4 +1634,24 @@ template Succeeded write_basic_interfile<>(const string& filename, const float scale, const ByteOrder byte_order); +template Succeeded +write_basic_interfile<2>(const std::string& filename, + const ParametricDiscretisedDensity>>& image, + const NumericType output_type, + const float scale, + const ByteOrder byte_order); + +template Succeeded +write_basic_interfile<3>(const std::string& filename, + const ParametricDiscretisedDensity>>& image, + const NumericType output_type, + const float scale, + const ByteOrder byte_order); + +template ParametricDiscretisedDensity>>* +read_interfile_parametric_image<2>(const std::string& filename); + +template ParametricDiscretisedDensity>>* +read_interfile_parametric_image<3>(const std::string& filename); + END_NAMESPACE_STIR diff --git a/src/buildblock/ChainedDataProcessor.cxx b/src/buildblock/ChainedDataProcessor.cxx index 4319e147dc..e23a7d54a5 100644 --- a/src/buildblock/ChainedDataProcessor.cxx +++ b/src/buildblock/ChainedDataProcessor.cxx @@ -14,6 +14,7 @@ \brief Implementations for class stir::ChainedDataProcessor \author Kris Thielemans + \author Nicolas A Karakatsanis */ #include "stir/ChainedDataProcessor.h" @@ -113,5 +114,6 @@ const char* const ChainedDataProcessor::registered_name = "Chained Data P // have the above variable in a separate file, which you need to pass at link time template class ChainedDataProcessor>; -template class ChainedDataProcessor; +template class ChainedDataProcessor; +template class ChainedDataProcessor; END_NAMESPACE_STIR diff --git a/src/buildblock/DynamicProjData.cxx b/src/buildblock/DynamicProjData.cxx index e42040ac47..3d3098cd10 100644 --- a/src/buildblock/DynamicProjData.cxx +++ b/src/buildblock/DynamicProjData.cxx @@ -14,6 +14,7 @@ \brief Implementation of class stir::DynamicProjData \author Kris Thielemans \author Charalampos Tsoumpas + \author Nicolas A Karakatsanis \author Richard Brown */ @@ -209,6 +210,9 @@ static DynamicProjData* read_interfile_DPDFS(istream& input, const string& directory_for_data, const std::ios::openmode open_mode) { + // Nicolas A Karakatsanis - Add the printed message for better clarity and evaluation of implementation + info(format("DynamicProjData: Reading the Interfile projection data set located in directory\n{} ...\n", directory_for_data)); + InterfilePDFSHeader hdr; if (!hdr.parse(input)) diff --git a/src/buildblock/ThresholdMinToSmallPositiveValueDataProcessor.cxx b/src/buildblock/ThresholdMinToSmallPositiveValueDataProcessor.cxx index 18c596f447..daafe57891 100644 --- a/src/buildblock/ThresholdMinToSmallPositiveValueDataProcessor.cxx +++ b/src/buildblock/ThresholdMinToSmallPositiveValueDataProcessor.cxx @@ -86,7 +86,8 @@ const char* const ThresholdMinToSmallPositiveValueDataProcessor::register // have the above variable in a separate file, which you need to pass at link time template class ThresholdMinToSmallPositiveValueDataProcessor>; -template class ThresholdMinToSmallPositiveValueDataProcessor; +template class ThresholdMinToSmallPositiveValueDataProcessor; +template class ThresholdMinToSmallPositiveValueDataProcessor; // template class ThresholdMinToSmallPositiveValueDataProcessor< VoxelsOnCartesianGrid > >; // template class ThresholdMinToSmallPositiveValueDataProcessor< VoxelsOnCartesianGrid > >; // template class ThresholdMinToSmallPositiveValueDataProcessor< VoxelsOnCartesianGrid > >; diff --git a/src/buildblock/TimeGateDefinitions.cxx b/src/buildblock/TimeGateDefinitions.cxx index 5c7a8d0b71..097ae9209b 100644 --- a/src/buildblock/TimeGateDefinitions.cxx +++ b/src/buildblock/TimeGateDefinitions.cxx @@ -38,6 +38,13 @@ TimeGateDefinitions::get_gate_duration(unsigned int num) const return this->_gate_sequence[num - 1].second; } +// Nicolas A Karakatsanis +float +TimeGateDefinitions::get_gate_relative_duration(unsigned int num) const +{ + return this->_gate_relative_durations_sequence[num - 1]; +} + unsigned int TimeGateDefinitions::get_gate_num(unsigned int num) const { @@ -56,6 +63,34 @@ TimeGateDefinitions::get_num_time_gates() const return static_cast(this->_gate_sequence.size()); } +// Set function (Nicolas A Karakatsanis) +void +TimeGateDefinitions::set_gate_relative_durations(const vector>& gate_sequence) +{ + std::cout << std::endl + << "Setting the time fractions for each motion/gate relative to the total acquisition length of the current frame " + << std::endl + << std::endl; + if (gate_sequence.size() == 0) + error("TimeGateDefinitions::set_gate_relative_durations: Designated input gate_sequence has no gates"); + // Initialization for the total frame duration (of all motion/gates) + this->_acquisition_total_duration = 0; + + for (unsigned int current_gate = 1; current_gate <= gate_sequence.size(); ++current_gate) + this->_acquisition_total_duration += gate_sequence[current_gate - 1].second; + + if (this->_acquisition_total_duration != 0) + { + std::cout << std::endl << "Motion/Gate index Time fraction (relative to total frame duration):" << std::endl; + for (unsigned int current_gate = 1; current_gate <= gate_sequence.size(); ++current_gate) + { + float relative_duration = gate_sequence[current_gate - 1].second / this->_acquisition_total_duration; + std::cout << current_gate << " " << relative_duration << std::endl; + this->_gate_relative_durations_sequence.push_back(relative_duration); + } + } +} + TimeGateDefinitions::TimeGateDefinitions() {} @@ -67,6 +102,7 @@ TimeGateDefinitions::TimeGateDefinitions(const string& gdef_filename) void TimeGateDefinitions::read_gdef_file(const string& gdef_filename) { + this->_acquisition_total_duration = 0; ifstream in(gdef_filename.c_str()); if (!in) { @@ -98,6 +134,9 @@ TimeGateDefinitions::read_gdef_file(const string& gdef_filename) "3 50.5\n1 10\n10 7\n\n" "for 3rd gate of 50.5 secs, 1st gate of 10 secs, 10th gate of 7 secs.", gdef_filename.c_str()); + + // Nicolas A Karakatsanis: Calculate and set the time fraction for each motion/gate relative to the total frame duration + this->set_gate_relative_durations(this->_gate_sequence); } TimeGateDefinitions::TimeGateDefinitions(const vector>& gate_sequence) @@ -108,11 +147,17 @@ TimeGateDefinitions::TimeGateDefinitions(const vector return; this->_gate_sequence.resize(gate_sequence.size()); + + this->_acquisition_total_duration = 0; + for (unsigned int current_gate = 1; current_gate <= this->_gate_sequence.size(); ++current_gate) { this->_gate_sequence[current_gate - 1].first = gate_sequence[current_gate - 1].first; this->_gate_sequence[current_gate - 1].second = gate_sequence[current_gate - 1].second; } + + // Nicolas A Karakatsanis: Calculate and set the time fraction for each motion/gate relative to the total frame duration + this->set_gate_relative_durations(this->_gate_sequence); } TimeGateDefinitions::TimeGateDefinitions(const vector& gate_num_vector, const vector& duration_vector) @@ -126,6 +171,9 @@ TimeGateDefinitions::TimeGateDefinitions(const vector& gate_num_ve this->_gate_sequence[current_gate - 1].first = gate_num_vector[current_gate - 1]; this->_gate_sequence[current_gate - 1].second = duration_vector[current_gate - 1]; } + + // Nicolas A Karakatsanis: Calculate and set the time fraction for each motion/gate relative to the total frame duration + this->set_gate_relative_durations(this->_gate_sequence); } END_NAMESPACE_STIR diff --git a/src/buildblock/recon_array_functions.cxx b/src/buildblock/recon_array_functions.cxx index 290fdb8bba..2a186cbc4e 100644 --- a/src/buildblock/recon_array_functions.cxx +++ b/src/buildblock/recon_array_functions.cxx @@ -16,6 +16,7 @@ \author Matthew Jacobson \author Kris Thielemans + \author Nicolas A Karakatsanis \author PARAPET project @@ -376,6 +377,48 @@ accumulate_loglikelihood(Viewgram& projection_data, *accum += result; } +void +accumulate_loglikelihood(DiscretisedDensity<3, float>& outer_loop_dyn_image_estimate, + const DiscretisedDensity<3, float>& nested_loop_dyn_image_estimates, + double* accum) +{ + + assert(outer_loop_dyn_image_estimate.get_index_range() == nested_loop_dyn_image_estimates.get_index_range()); + + /* note for implementation: + First compute result for this image slice in a local variable, + then add to accum. + This avoids problems with adding small numbers to large numbers + For instance if there are a large number of bins in the image, + each with about the same contribution. After about 1e6 bins, the value of + accum would no longer change because of the finite precision. + */ + double result = 0; + const float small_value = max(outer_loop_dyn_image_estimate.find_max() * SMALL_NUM, 0.F); + const float max_quotient = 10000.F; + + for (int z = outer_loop_dyn_image_estimate.get_min_index(); z <= outer_loop_dyn_image_estimate.get_max_index(); z++) + { + double sub_result = 0; // use this for total result for this r, reducing numerical error + + for (int y = outer_loop_dyn_image_estimate[z].get_min_index(); y <= outer_loop_dyn_image_estimate[z].get_max_index(); y++) + for (int x = outer_loop_dyn_image_estimate[z][y].get_min_index(); + x <= outer_loop_dyn_image_estimate[z][y].get_max_index(); + x++) + { + const float new_estimate + = max(nested_loop_dyn_image_estimates[z][y][x], outer_loop_dyn_image_estimate[z][y][x] / max_quotient); + if (outer_loop_dyn_image_estimate[z][y][x] <= small_value) + sub_result += -double(new_estimate); + else + sub_result += double(outer_loop_dyn_image_estimate[z][y][x] * log(new_estimate) - new_estimate); + } + result += sub_result; + } + + *accum += result; +} + void multiply_and_add(DiscretisedDensity<3, float>& image_res, const DiscretisedDensity<3, float>& image_scaled, float scalar) { diff --git a/src/cmake/stir_dirs.cmake b/src/cmake/stir_dirs.cmake index db2bf6c198..23ae533cd4 100644 --- a/src/cmake/stir_dirs.cmake +++ b/src/cmake/stir_dirs.cmake @@ -85,6 +85,8 @@ if (NOT MINI_STIR) iterative/KOSMAPOSL iterative/OSSPS iterative/POSMAPOSL + iterative/NESTPOSMAPOSL + iterative/NESTGPOSMAPOSL iterative/POSSPS SimSET SimSET/scripts diff --git a/src/include/stir/DynamicDiscretisedDensity.h b/src/include/stir/DynamicDiscretisedDensity.h index 5d35d811fe..8b1cb5a4e8 100644 --- a/src/include/stir/DynamicDiscretisedDensity.h +++ b/src/include/stir/DynamicDiscretisedDensity.h @@ -6,8 +6,8 @@ \brief Declaration of class stir::DynamicDiscretisedDensity \author Kris Thielemans \author Charalampos Tsoumpas + \author Nicolas Karakatsanis \author Richard Brown - */ /* Copyright (C) 2005 - 2011-01-12, Hammersmith Imanet Ltd @@ -107,6 +107,36 @@ class DynamicDiscretisedDensity : public ExamData } } + //! Construct an empty DynamicDiscretisedDensity based on a shared_ptr > + DynamicDiscretisedDensity(const TimeFrameDefinitions& time_frame_definitions, + const unsigned int& num_conv_params, + const double scan_start_time_in_secs_since_1970, + const shared_ptr& scanner_sptr, + const shared_ptr& density_sptr) + { + _densities.resize(num_conv_params); + shared_ptr _exam_info_sptr; + if (is_null_ptr(density_sptr->get_exam_info_sptr())) + _exam_info_sptr.reset(new ExamInfo); + else + _exam_info_sptr = density_sptr->get_exam_info_sptr()->create_shared_clone(); + _exam_info_sptr->set_time_frame_definitions(time_frame_definitions); + + _exam_info_sptr->start_time_in_secs_since_1970 = scan_start_time_in_secs_since_1970; + this->exam_info_sptr = _exam_info_sptr; + + // _calibration_factor = -1.F; + // _isotope_halflife = -1.F; + + _scanner_sptr = scanner_sptr; + + for (unsigned int conv_point = 0; conv_point < num_conv_params; ++conv_point) + { + //! TODO: Introduce the ExamInfo + this->_densities[conv_point].reset(density_sptr->get_empty_discretised_density()); + } + } + DynamicDiscretisedDensity& operator=(const DynamicDiscretisedDensity& argument); /*! @name functions returning full_iterators @@ -177,6 +207,13 @@ class DynamicDiscretisedDensity : public ExamData this->exam_info_sptr = sptr; } + void resize_densities(const TimeFrameDefinitions& time_frame_definitions) + { + this->set_time_frame_definitions(time_frame_definitions); + unsigned int num_densities = this->get_time_frame_definitions().get_num_time_frames(); + this->_densities.resize(num_densities); + } + void set_scanner(const Scanner& scanner) { this->_scanner_sptr.reset(new Scanner(scanner)); } const TimeFrameDefinitions& get_time_frame_definitions() const; diff --git a/src/include/stir/GatedProjData.h b/src/include/stir/GatedProjData.h index aa9d168c41..6f973c79eb 100644 --- a/src/include/stir/GatedProjData.h +++ b/src/include/stir/GatedProjData.h @@ -15,8 +15,14 @@ \brief Declaration of class stir::GatedProjData \author Kris Thielemans \author Charalampos Tsoumpas + \author Nicolas A Karakatsanis */ +// Header guards were added by Nicolas K. to avoid multiple definition of the class from multiple obj functions +// (e.g. when motion is corrected in non-nested and nested implementation) +#ifndef __stir_GatedProjData_H__ +#define __stir_GatedProjData_H__ + #include "stir/MultipleProjData.h" #include "stir/TimeGateDefinitions.h" #include @@ -48,3 +54,5 @@ class GatedProjData : public MultipleProjData }; END_NAMESPACE_STIR + +#endif //__stir_GatedProjData_H__ \ No newline at end of file diff --git a/src/include/stir/IO/InterfileHeader.h b/src/include/stir/IO/InterfileHeader.h index ba21e4b1a0..cbd0589efe 100644 --- a/src/include/stir/IO/InterfileHeader.h +++ b/src/include/stir/IO/InterfileHeader.h @@ -16,6 +16,7 @@ \author Kris Thielemans \author Sanida Mustafovic + \author Nicolas A Karakatsanis \author PARAPET project \author Richard Brown \author Parisa Khateri @@ -33,6 +34,7 @@ #include "stir/ProjDataFromStream.h" #include "stir/ExamInfo.h" #include "stir/date_time_functions.h" +#include "stir/TimeFrameDefinitions.h" START_NAMESPACE_STIR @@ -186,6 +188,9 @@ class InterfileHeader : public MinimalInterfileHeader std::vector upper_en_window_thresholds; // end acquisition parameters + // Added for old implementations relying on time_frame_definitions variable + TimeFrameDefinitions time_frame_definitions; + protected: // version 3.3 had only a single offset. we'll internally replace it with data_offset_each_dataset unsigned long data_offset; diff --git a/src/include/stir/IO/InterfileParametricDiscretisedDensityInputFileFormat.h b/src/include/stir/IO/InterfileParametricDiscretisedDensityInputFileFormat.h index 9f13934cce..8989aa9efe 100644 --- a/src/include/stir/IO/InterfileParametricDiscretisedDensityInputFileFormat.h +++ b/src/include/stir/IO/InterfileParametricDiscretisedDensityInputFileFormat.h @@ -26,23 +26,39 @@ #include "stir/modelling/ParametricDiscretisedDensity.h" #include "stir/error.h" #include "stir/is_null_ptr.h" - +#include "stir/format.h" START_NAMESPACE_STIR //! Class for reading images in Interfile file-format. /*! \ingroup IO */ -class InterfileParametricDiscretisedDensityInputFileFormat : public InputFileFormat +template +class InterfileParametricDiscretisedDensityInputFileFormat + : public InputFileFormat>>> { +private: + typedef InputFileFormat>>> base_type; + public: - const std::string get_name() const override { return "Interfile"; } + const std::string get_name() const override { return format("Interfile{}param", num_params); } + + typedef typename base_type::data_type data_type; // <- restores unqualified `data_type` below protected: - bool actual_can_read(const FileSignature& signature, std::istream&) const override + bool actual_can_read(const FileSignature& signature, std::istream& input) const override { + const std::string sig(signature.get_signature()); //. todo should check if it's an image - return is_interfile_signature(signature.get_signature()); + if (!is_interfile_signature(signature.get_signature())) + return false; + // We should check how many parameters are declared in the header. + // We cannot use the signature, because it is limited to 1024 bytes + const std::streampos orig_pos = input.tellg(); + const int n = find_num_image_data_types(input); + input.clear(); // clear EOF before seeking back + input.seekg(orig_pos); + return n == num_params; } unique_ptr read_from_file(std::istream&) const override @@ -57,13 +73,31 @@ class InterfileParametricDiscretisedDensityInputFileFormat : public InputFileFor } unique_ptr read_from_file(const std::string& filename) const override { - unique_ptr ret(read_interfile_parametric_image(filename)); + unique_ptr ret(read_interfile_parametric_image(filename)); if (is_null_ptr(ret)) { error("failed to read an Interfile image from file \"%s\"", filename.c_str()); } return ret; } + +private: + static int find_num_image_data_types(std::istream& input) + { + const std::string key = "number of image data types"; + std::string line; + while (std::getline(input, line)) + { + const auto key_pos = line.find(key); + if (key_pos == std::string::npos) + continue; + const auto eq_pos = line.find(":=", key_pos); + if (eq_pos == std::string::npos) + continue; + return std::atoi(line.c_str() + eq_pos + 2); + } + return -1; + } }; END_NAMESPACE_STIR diff --git a/src/include/stir/IO/interfile.h b/src/include/stir/IO/interfile.h index 37108781b9..5eb6117237 100644 --- a/src/include/stir/IO/interfile.h +++ b/src/include/stir/IO/interfile.h @@ -20,6 +20,7 @@ \author Kris Thielemans \author Sanida Mustafovic + \author Nicolas A Karakatsanis \author PARAPET project \author Richard Brown @@ -32,6 +33,8 @@ #include "stir/Succeeded.h" #include "stir/ByteOrder.h" #include "stir/ArrayFwd.h" +#include "stir/IO/InterfileHeader.h" + #include #include @@ -109,11 +112,13 @@ DynamicDiscretisedDensity* read_interfile_dynamic_image(std::istream& input, con DynamicDiscretisedDensity* read_interfile_dynamic_image(const std::string& filename); /// Read parametric image -ParametricDiscretisedDensity>>* +template +ParametricDiscretisedDensity>>* read_interfile_parametric_image(std::istream& input, const std::string& directory_for_data); /// Read parametric image -ParametricDiscretisedDensity>>* +template +ParametricDiscretisedDensity>>* read_interfile_parametric_image(const std::string& filename); //! This outputs an Interfile header for an image. @@ -234,11 +239,13 @@ Succeeded write_basic_interfile(const std::string& filename, const float scale = 0, const ByteOrder byte_order = ByteOrder::native); -Succeeded write_basic_interfile(const std::string& filename, - const ParametricDiscretisedDensity>>& image, - const NumericType output_type = NumericType::FLOAT, - const float scale = 0, - const ByteOrder byte_order = ByteOrder::native); +template +Succeeded +write_basic_interfile(const std::string& filename, + const ParametricDiscretisedDensity>>& image, + const NumericType output_type = NumericType::FLOAT, + const float scale = 0, + const ByteOrder byte_order = ByteOrder::native); Succeeded write_basic_interfile(const std::string& filename, const DynamicDiscretisedDensity& image, diff --git a/src/include/stir/TimeGateDefinitions.h b/src/include/stir/TimeGateDefinitions.h index 3f35422fe3..274d081692 100644 --- a/src/include/stir/TimeGateDefinitions.h +++ b/src/include/stir/TimeGateDefinitions.h @@ -61,13 +61,21 @@ class TimeGateDefinitions \a gate_num_of_this_duration to 0 allows skipping a time period of the corresponding \a duration_in_secs. */ + void read_gdef_file(const std::string& gdef_filename); + // Nicolas A Karakatsanis: Calculate and set the time fraction for each motion/gate relative to the total frame duration + void set_gate_relative_durations(const std::vector>& gate_sequence); + //! \name get info for a gate //@{ double get_gate_duration(unsigned int num) const; + unsigned int get_gate_num(unsigned int num) const; + // Get the time fraction for each motion/gate relative to the total acquisition duration + float get_gate_relative_duration(unsigned int num) const; // Nicolas A Karakatsanis + //@} //! Get number of gates @@ -78,6 +86,13 @@ class TimeGateDefinitions private: //! Stores start and end time for each gate std::vector> _gate_sequence; + + // Total duration of all gates (total acquisition length) (Nicolas A Karakatsanis) + float _acquisition_total_duration; + + //! Stores the time fractions for all motions/gate (time fraction = gate_duration/acquisition_total_duration) (Nicolas A + //! Karakatsanis) + std::vector _gate_relative_durations_sequence; }; END_NAMESPACE_STIR diff --git a/src/include/stir/modelling/GeneralizedPatlakMatrix.h b/src/include/stir/modelling/GeneralizedPatlakMatrix.h new file mode 100644 index 0000000000..52ce7b2277 --- /dev/null +++ b/src/include/stir/modelling/GeneralizedPatlakMatrix.h @@ -0,0 +1,182 @@ +// +/* + Copyright (C) 2006 - 2011, Hammersmith Imanet Ltd + This file is part of STIR. + + SPDX-License-Identifier: Apache-2.0 + + See STIR/LICENSE.txt for details +*/ +/*! + \file + \ingroup modelling + \brief Declaration of class stir::GeneralizedPatlakMatrix + \author Nicolas A Karakatsanis + +*/ + +#ifndef __stir_modelling_GeneralizedPatlakMatrix_H__ +#define __stir_modelling_GeneralizedPatlakMatrix_H__ + +#include "stir/Array.h" +#include "stir/BasicCoordinate.h" +#include "stir/VectorWithOffset.h" +#include "stir/DynamicDiscretisedDensity.h" +#include "stir/modelling/ParametricDiscretisedDensity.h" +#include "stir/Succeeded.h" +#include +#include + +START_NAMESPACE_STIR +//! A helper class to store the model matrix for a linear kinetic model +/*! \ingroup modelling + */ +template +class GeneralizedPatlakMatrix +{ +public: + inline GeneralizedPatlakMatrix(); //!< default constructor + + inline ~GeneralizedPatlakMatrix(); //!< default destructor + + /*! Implementation to read the model matrix from a text file + \warning In this way the information about the calibration _is_uncalibrated and the counts _is_converted is not passed. + */ + inline void read_from_file(const std::string input_string, int num_conv_params); + + //! Implementation to write the model matrix to a text file + inline Succeeded write_to_file(const std::string output_string, int num_conv_params); + + //! \name Functions to get parameters @{ + inline Array<2, float> get_model_array() const; + inline const VectorWithOffset get_model_array_sum() const; + inline VectorWithOffset get_time_vector() const; + //!@} + //! \name Functions to set parameters @{ + inline void set_model_array(const Array<2, float>& model_array); + + inline void set_Hfunction_array(const Array<2, float>& Hfunction_array); + inline void set_Ki_array(const Array<2, float>& Hfunction_array); + + inline void set_conv_sample_interval(const unsigned int conv_sampling_interval); + + inline void set_prefetched_sampling(const float kloss_start, const float kloss_end, const float kloss_nsamples); + + inline Array<2, float> get_Hfunction_array() const; + inline Array<2, float> get_Ki_array() const; + + inline void estimate_inverse_Hfunction(float& kloss_estimate, float& Hfunction_val) const; + + inline void estimate_denominator_Kifunction(float& Ki_denominator, float& kloss_val) const; + + inline void set_time_vector(const VectorWithOffset& time_vector); + //! Function to set _is_calibrated boolean true or false + inline void set_if_uncalibrated(const bool is_uncalibrated); + inline void set_if_in_correct_scale(const bool in_correct_scale); + inline void set_matrix_in_total_frame_counts(const bool is_converted_to_total_counts); + //!@} + + //! Function to give the threshold_value to the all elements of the model_array which lower value than the threshold_value. + inline void threshold_model_array(const float threshold_value); + + /*! Function to divide with the calibration factor the model array. + Calibrated ModelMatrix means that the counts are in kBq/ml, while uncalibrated means that it will be to the same units as the + reconstructed images. + */ + inline void uncalibrate(const float cal_factor); + + /*! Function to multiply with the scale factor the model array. + Scaled ModelMatrix means that the counts are already scaled to the correct, while not scaled means that it needs to be scaled. + */ + inline void scale_model_matrix(const float scale_factor); + + /*! Multiply with the duration to convert the count rate to total counts in the time frame. + Converted ModelMatrix means that it is in total counts in respect to the time_frame_duration, + while not converted sets the _is_converted to false and means that it will be in "mean count rate". + */ + inline void convert_to_total_frame_counts(const TimeFrameDefinitions& time_frame_definitions); + + /*! Multiplications of the model with the dynamic or the parametric images. + /todo Maybe it will be better to lie in a linear models class. + */ + //@{ + //! multiply (transpose) model-matrix with dynamic image and add result to original \c parametric_image + inline void multiply_dynamic_image_with_model_and_add_to_input(DynamicDiscretisedDensity& impulse_response_image, + const DynamicDiscretisedDensity& dynamic_image, + int num_conv_params) const; + //! multiply (transpose) model-matrix with dynamic image (overwriting original content of \c parametric_image) + /*! \todo current implementation first fills first argument with 0 and then calls + multiply_dynamic_image_with_model_and_add_to_input(). This is somewhat inefficient. + */ + inline void multiply_dynamic_image_with_model(DynamicDiscretisedDensity& impulse_response_image, + const DynamicDiscretisedDensity& dynamic_image, + int num_conv_params) const; + + inline void synthesize_impulse_response_from_parametric_image(DynamicDiscretisedDensity& impulse_response_image, + const Parametric3VoxelsOnCartesianGrid& parametric_image, + int num_conv_params) const; + + inline void multiply_impulse_response_with_model_and_add_to_input(DynamicDiscretisedDensity& dynamic_image, + const DynamicDiscretisedDensity& impulse_response_image, + int num_conv_params) const; + + inline void multiply_impulse_response_with_model(DynamicDiscretisedDensity& dynamic_image, + const DynamicDiscretisedDensity& impulse_response_image, + int num_conv_params) const; + + //! multiply model-matrix with parametric image and add result to original \c dynamic_image + inline void multiply_parametric_image_with_model_and_add_to_input(DynamicDiscretisedDensity& dynamic_image, + const Parametric3VoxelsOnCartesianGrid& parametric_image, + int num_conv_params) const; + //! multiply model-matrix with parametric image (overwriting original content of \c dynamic_image) + /*! \todo current implementation first fills first argument with 0 and then calls + multiply_dynamic_image_with_model_and_add_to_input(). This is somewhat inefficient. + */ + inline void multiply_parametric_image_with_model(DynamicDiscretisedDensity& dynamic_image, + const Parametric3VoxelsOnCartesianGrid& parametric_image, + int num_conv_params) const; + + inline void estimate_generalized_patlak_parameters_with_impulse_response_and_add_to_input( + Parametric3VoxelsOnCartesianGrid& parametric_image, + const DynamicDiscretisedDensity& impulse_response_image, + int num_conv_params) const; + + inline void + estimate_generalized_patlak_parameters_with_impulse_response(Parametric3VoxelsOnCartesianGrid& parametric_image, + const DynamicDiscretisedDensity& impulse_response_image, + int num_conv_params) const; + + inline void normalise_parametric_image_with_model_sum(Parametric3VoxelsOnCartesianGrid& parametric_image_out, + const Parametric3VoxelsOnCartesianGrid& parametric_image, + int num_conv_params) const; + + inline void estimate_nested_loop_parameters_with_model(Parametric3VoxelsOnCartesianGrid& parametric_image, + DynamicDiscretisedDensity& dynamic_image_nested_loop_estimate, + DynamicDiscretisedDensity& dynamic_image_update_factor, + const DynamicDiscretisedDensity& dynamic_image_reference, + int num_nested_subiterations, + float min_nested_rel_change, + float max_nested_rel_change, + int num_conv_params) const; + + //@} +private: + //! At the moment it has the form of _model_array[param_num][frame_num]. + Array<2, float> _model_array; + Array<2, float> _Hfunction_array; + Array<2, float> _Ki_array; + VectorWithOffset _time_vector; + bool _is_uncalibrated; + bool _in_correct_scale; + bool _is_converted_to_total_counts; + unsigned int _conv_sampling_interval; + float _kloss_start; + float _kloss_end; + unsigned int _kloss_nsamples; +}; + +END_NAMESPACE_STIR + +#include "stir/modelling/GeneralizedPatlakMatrix.inl" + +#endif //__stir_modelling_GeneralizedPatlakMatrix_H__ \ No newline at end of file diff --git a/src/include/stir/modelling/GeneralizedPatlakMatrix.inl b/src/include/stir/modelling/GeneralizedPatlakMatrix.inl new file mode 100644 index 0000000000..31d077ccd4 --- /dev/null +++ b/src/include/stir/modelling/GeneralizedPatlakMatrix.inl @@ -0,0 +1,909 @@ +// +/* + Copyright (C) 2006 - $Date: 2011-01-12 18:27:12 $, Hammersmith Imanet Ltd + This file is part of STIR. + + SPDX-License-Identifier: Apache-2.0 + + See STIR/LICENSE.txt for details +*/ +/*! + \file + \ingroup modelling + + \brief Implementations of inline functions of class stir::GeneralizedPatlakMatrix + + \author Nicolas A Karakatsanis + +*/ + +#include +#include +#include "stir/numerics/divide.h" +#include "stir/thresholding.h" + +using std::cerr; +using std::endl; + +START_NAMESPACE_STIR + +const float small_num = 0.000001F; + +//! default constructor +template +GeneralizedPatlakMatrix::GeneralizedPatlakMatrix() +{ + // Calibrated ModelMatrix means that the counts are in kBq/ml, while uncalibrated means that it will be to the same units as the + // reconstructed images. + this->_is_uncalibrated = false; + // Converted ModelMatrix means that it is in total counts in respect to the time_frame_duration, while false means that it will + // be in mean count rate. + this->_in_correct_scale = false; + this->_is_converted_to_total_counts = false; +} + +//! default destructor +template +GeneralizedPatlakMatrix::~GeneralizedPatlakMatrix() +{} + +//! Implementation to read the model matrix +template +void +GeneralizedPatlakMatrix::read_from_file(const std::string input_string, int num_conv_params) +{ + std::ifstream data_stream(input_string.c_str()); + unsigned int starting_frame, last_frame; + if (!data_stream) + error("cannot read model matrix from file.\n"); + else + { + data_stream >> starting_frame; + data_stream >> last_frame; + } + + BasicCoordinate<2, int> min_range; + BasicCoordinate<2, int> max_range; + min_range[1] = 1; + min_range[2] = starting_frame; + max_range[1] = num_conv_params; + max_range[2] = last_frame; + IndexRange<2> data_range(min_range, max_range); + Array<2, float> input_array(data_range); + while (true) + { + for (unsigned int frame_num = starting_frame; frame_num <= last_frame; ++frame_num) + for (int param_num = 1; param_num <= num_conv_params; ++param_num) + data_stream >> input_array[param_num][frame_num]; + if (!data_stream) + break; + } + this->_model_array = input_array; // I do not pass info if it is calibrated and if it includes time frame_duration, yet. +} + +//! Implementation to write the model matrix +template +Succeeded +GeneralizedPatlakMatrix::write_to_file(const std::string output_string, int num_conv_params) +{ + + BasicCoordinate<2, int> model_array_min, model_array_max; + if (!(this->_model_array).get_regular_range(model_array_min, model_array_max)) + error("Model array has not regular range"); + unsigned int starting_frame = model_array_min[2], last_frame = model_array_max[2]; + + std::ofstream data_stream(output_string.c_str(), std::ios::out); + if (!data_stream) + { + warning("GeneralizedPatlakMatrix::write_to_file: error opening output file %s\n", output_string.c_str()); + return Succeeded::no; + } + else + { + data_stream << starting_frame << " "; + data_stream << last_frame << " "; + } + + // It will be good to assert that there will be no writing error. + for (unsigned int frame_num = starting_frame; frame_num <= last_frame; ++frame_num) + { + data_stream << "\n"; + for (int param_num = 1; param_num <= num_conv_params; ++param_num) + data_stream << this->_model_array[param_num][frame_num] << " "; + } + data_stream.close(); + return Succeeded::yes; +} + +template +void +GeneralizedPatlakMatrix::set_model_array(const Array<2, float>& model_array) +{ + this->_model_array = model_array; +} + +template +Array<2, float> +GeneralizedPatlakMatrix::get_model_array() const +{ + return this->_model_array; +} + +template +void +GeneralizedPatlakMatrix::set_Hfunction_array(const Array<2, float>& Hfunction_array) +{ + this->_Hfunction_array = Hfunction_array; +} + +template +Array<2, float> +GeneralizedPatlakMatrix::get_Hfunction_array() const +{ + return this->_Hfunction_array; +} + +template +void +GeneralizedPatlakMatrix::set_Ki_array(const Array<2, float>& Ki_array) +{ + this->_Ki_array = Ki_array; +} + +template +Array<2, float> +GeneralizedPatlakMatrix::get_Ki_array() const +{ + return this->_Ki_array; +} + +template +void +GeneralizedPatlakMatrix::set_conv_sample_interval(const unsigned int conv_sampling_interval) +{ + this->_conv_sampling_interval = conv_sampling_interval; +} + +template +void +GeneralizedPatlakMatrix::set_prefetched_sampling(const float kloss_start, + const float kloss_end, + const float kloss_nsamples) +{ + this->_kloss_start = kloss_start; + this->_kloss_end = kloss_end; + this->_kloss_nsamples = kloss_nsamples; +} + +template +const VectorWithOffset +GeneralizedPatlakMatrix::get_model_array_sum() const +{ + BasicCoordinate<2, int> model_array_min, model_array_max; + if (!(this->_model_array).get_regular_range(model_array_min, model_array_max)) + error("Model array has not regular range"); + VectorWithOffset sum(model_array_min[1], model_array_max[1]); + for (int param_num = model_array_min[1]; param_num <= model_array_max[1]; ++param_num) + { + sum[param_num] = 0.F; + for (int frame_num = model_array_min[2]; frame_num <= model_array_max[2]; ++frame_num) + sum[param_num] += this->_model_array[param_num][frame_num]; + } + return sum; +} + +template +void +GeneralizedPatlakMatrix::threshold_model_array(const float threshold_value) +{ + BasicCoordinate<2, int> model_array_min, model_array_max; + if (!(this->_model_array).get_regular_range(model_array_min, model_array_max)) + error("Model array has not regular range"); + + for (int param_num = model_array_min[1]; param_num <= model_array_max[1]; ++param_num) + for (int frame_num = model_array_min[2]; frame_num <= model_array_max[2]; ++frame_num) + if (this->_model_array[param_num][frame_num] <= 0) + this->_model_array[param_num][frame_num] = threshold_value; +} + +template +void +GeneralizedPatlakMatrix::set_if_uncalibrated(const bool is_uncalibrated) +{ + this->_is_uncalibrated = is_uncalibrated; +} + +template +void +GeneralizedPatlakMatrix::set_if_in_correct_scale(const bool in_correct_scale) +{ + this->_in_correct_scale = in_correct_scale; +} + +template +void +GeneralizedPatlakMatrix::set_matrix_in_total_frame_counts(const bool is_converted_to_total_counts) +{ + this->_is_converted_to_total_counts = is_converted_to_total_counts; +} + +template +void +GeneralizedPatlakMatrix::uncalibrate(const float cal_factor) +{ + if (this->_is_uncalibrated) + warning("GeneralizedPatlakMatrix is already uncalibrated, so it will be not re-uncalibrated."); + else + { + BasicCoordinate<2, int> model_array_min, model_array_max; + if (!(this->_model_array).get_regular_range(model_array_min, model_array_max)) + error("Model array has not regular range"); + + for (int param_num = model_array_min[1]; param_num <= model_array_max[1]; ++param_num) + for (int frame_num = model_array_min[2]; frame_num <= model_array_max[2]; ++frame_num) + this->_model_array[param_num][frame_num] /= cal_factor; + + GeneralizedPatlakMatrix::set_if_uncalibrated(true); + } +} + +template +void +GeneralizedPatlakMatrix::scale_model_matrix(const float scale_factor) +{ + if (this->_in_correct_scale) + warning("GeneralizedPatlakMatrix is already scaled, so it will not be re-scaled. "); + else + { + BasicCoordinate<2, int> model_array_min, model_array_max; + if (!(this->_model_array).get_regular_range(model_array_min, model_array_max)) + error("Model array has not regular range"); + for (int param_num = model_array_min[1]; param_num <= model_array_max[1]; ++param_num) + for (int frame_num = model_array_min[2]; frame_num <= model_array_max[2]; ++frame_num) + this->_model_array[param_num][frame_num] *= scale_factor; + + this->_in_correct_scale = true; + } +} + +template +void +GeneralizedPatlakMatrix::convert_to_total_frame_counts(const TimeFrameDefinitions& time_frame_definitions) +{ + if (this->_is_converted_to_total_counts == true) + warning("GeneralizedPatlakMatrix is already converted to total counts, so it will not be re-converted. "); + else + { + BasicCoordinate<2, int> model_array_min, model_array_max; + if (!(this->_model_array).get_regular_range(model_array_min, model_array_max)) + error("Model array has not regular range"); + for (int param_num = model_array_min[1]; param_num <= model_array_max[1]; ++param_num) + for (int frame_num = model_array_min[2]; frame_num <= model_array_max[2]; ++frame_num) + this->_model_array[param_num][frame_num] *= static_cast(time_frame_definitions.get_duration(frame_num)); + + this->_is_converted_to_total_counts = true; + } +} + +template +void +GeneralizedPatlakMatrix::set_time_vector(const VectorWithOffset& time_vector) +{ + this->_time_vector = time_vector; +} + +template +VectorWithOffset +GeneralizedPatlakMatrix::get_time_vector() const +{ + return this->_time_vector; +} + +template +void +GeneralizedPatlakMatrix::estimate_inverse_Hfunction(float& kloss_estimate, float& Hfunction_val) const +{ + // Below we utilize the fact that Hfunction is monotonically decreasing + for (unsigned int kloss_index = 1; kloss_index <= this->_kloss_nsamples; ++kloss_index) + { + // If the second column of this->_Hfunction_array is already in log scale (which is the default case), + // then the Hfunction_val must also be converted to log scale, to conduct linear-log interpolation below + // Hfunction_val=log(Hfunction_val); + + if (Hfunction_val < this->_Hfunction_array[2][kloss_index]) + { + if (kloss_index < this->_kloss_nsamples) + continue; + else + { + kloss_estimate = this->_Hfunction_array[1][kloss_index]; + // cerr << "\nWARNING: Estimated H value (" << Hfunction_val << ") caused out-of-bounds kloss estimation: \n" + // << "[ub,lb]: [" << this->_kloss_start << "," << this->_kloss_end << "] , while est. kloss value=" << + // kloss_estimate << "\n\n"; + break; + } + } + else if (Hfunction_val > this->_Hfunction_array[2][kloss_index]) + { + if (kloss_index > 1) + { + // Simple averaging + // kloss_estimate=0.5*(this->_Hfunction_array[1][kloss_index-1]+this->_Hfunction_array[1][kloss_index]); + + // Linear interpolation, if second column of this->_Hfunction_array and Hfunction_val are in linear scale (not + // implemented now) + // Effectively Linear-log interpolation if second column of this->_Hfunction_array and Hfunction_val are in log + // scale (deafult now) + kloss_estimate = this->_Hfunction_array[1][kloss_index - 1] + + ((this->_Hfunction_array[1][kloss_index] - this->_Hfunction_array[1][kloss_index - 1]) + / (this->_Hfunction_array[2][kloss_index] - this->_Hfunction_array[2][kloss_index - 1])) + * (Hfunction_val - this->_Hfunction_array[2][kloss_index - 1]); + } + else + { + kloss_estimate = this->_Hfunction_array[1][kloss_index]; + // cerr << "\nWARNING: Estimated H value (" << Hfunction_val << ") caused out-of-bounds kloss estimation: \n" + // << "[ub,lb]: [" << this->_kloss_start << "," << this->_kloss_end << "] , while est. kloss value=" << + // kloss_estimate << "\n\n"; + } + break; + } + else + { + kloss_estimate = this->_Hfunction_array[1][kloss_index]; + break; + } + + // cerr << "estimate_inverse_Hfunction: estimated kloss = " << kloss_estimate << endl; + } +} + +template +void +GeneralizedPatlakMatrix::estimate_denominator_Kifunction(float& Ki_denominator, float& kloss_val) const +{ + // Below we utilize the fact that denominator_Kifunction is monotonically decreasing + for (unsigned int kloss_index = 1; kloss_index <= this->_kloss_nsamples; ++kloss_index) + { + if (kloss_val == this->_Ki_array[1][kloss_index]) + { + Ki_denominator = this->_Ki_array[2][kloss_index]; + break; + } + else if (kloss_val > this->_Ki_array[1][kloss_index]) + { + if (kloss_index < this->_kloss_nsamples) + continue; + else + { + Ki_denominator = this->_Ki_array[2][kloss_index]; + // cerr << "\nWARNING: Estimated Ki denominator value (" << Ki_denominator << ") caused out-of-bounds kloss + // estimation: \n" + // << "[ub,lb]: [" << this->_kloss_start << "," << this->_kloss_end << "] , while est. kloss value=" << + // this->_Ki_array[1][kloss_index] << "\n\n"; + break; + } + } + else if (kloss_val < this->_Ki_array[1][kloss_index]) + { + if (kloss_index > 1) + { + // Simple averaging + // Ki_denominator=0.5*(this->_Ki_array[2][kloss_index-1]+this->_Ki_array[2][kloss_index]); + + // Linear interpolation + Ki_denominator = this->_Ki_array[2][kloss_index - 1] + + ((this->_Ki_array[2][kloss_index] - this->_Ki_array[2][kloss_index - 1]) + / (this->_Ki_array[1][kloss_index] - this->_Ki_array[1][kloss_index - 1])) + * (kloss_val - this->_Ki_array[1][kloss_index - 1]); + + // Fast linear-log interpolation assuming column: this->_Ki_array[2][kloss_index] is in log scale already + // float log_Ki_denominator=this->_Ki_array[2][kloss_index-1] + ((this->_Ki_array[2][kloss_index] - + // this->_Ki_array[2][kloss_index-1])/(this->_Ki_array[1][kloss_index] - + // this->_Ki_array[1][kloss_index-1]))*(kloss_val - this->_Ki_array[1][kloss_index-1]); + // Ki_denominator=exp(log_Ki_denominator); + + // Linear-Log Interpolation + // float log_Ki_denominator=log(this->_Ki_array[2][kloss_index-1]) + ((log(this->_Ki_array[2][kloss_index]) - + // log(this->_Ki_array[2][kloss_index-1]))/(this->_Ki_array[1][kloss_index] - + // this->_Ki_array[1][kloss_index-1]))*(kloss_val - this->_Ki_array[1][kloss_index-1]); + // Ki_denominator=exp(log_Ki_denominator); + } + else + { + Ki_denominator = this->_Ki_array[2][kloss_index]; + // cerr << "\nWARNING: Estimated Ki denominator value (" << Ki_denominator << ") resulted from out-of-bounds kloss + // estimation: \n" + // << "[ub,lb]: [" << this->_kloss_start << "," << this->_kloss_end << "] , while est. kloss value=" << + // this->_Ki_array[1][kloss_index] << "\n\n"; + } + break; + } + } + + // cerr << "estimate_denominator_Kifunction: estimated Ki_denominator = " << Ki_denominator << endl; +} + +template +void +GeneralizedPatlakMatrix::multiply_dynamic_image_with_model_and_add_to_input( + DynamicDiscretisedDensity& impulse_response_image, const DynamicDiscretisedDensity& dynamic_image, int num_conv_params) const +{ + BasicCoordinate<2, int> model_array_min, model_array_max; + if (!this->_model_array.get_regular_range(model_array_min, model_array_max)) + error("Model array has not regular range"); + + // Assert that the sizes of the one frame of the dynamic image is equal with the parametric image size. + // ChT::ToDo::Might be better to assert that each of the dimensions sizes with their voxle sizes are equal. + // Could probably use has_same_characteristics()? + //! TODO: NE + // assert(dynamic_image[1].size_all() == parametric_image.size_all()); + assert(dynamic_image.get_time_frame_definitions().get_num_frames() == static_cast(model_array_max[2])); + assert(model_array_max[1] - model_array_min[1] + 1 == num_conv_params); + + const int min_k_index = dynamic_image[1].get_min_index(); + const int max_k_index = dynamic_image[1].get_max_index(); + for (int k = min_k_index; k <= max_k_index; ++k) + { + const int min_j_index = dynamic_image[1][k].get_min_index(); + const int max_j_index = dynamic_image[1][k].get_max_index(); + for (int j = min_j_index; j <= max_j_index; ++j) + { + const int min_i_index = dynamic_image[1][k][j].get_min_index(); + const int max_i_index = dynamic_image[1][k][j].get_max_index(); + for (int i = min_i_index; i <= max_i_index; ++i) + { + // Estimation of impulse response image (the last slice is the estimated V parameter) + for (int conv_param_num = model_array_min[1]; conv_param_num <= model_array_max[1]; ++conv_param_num) + for (int frame_num = model_array_min[2]; frame_num <= model_array_max[2]; ++frame_num) + impulse_response_image[conv_param_num][k][j][i] + += this->_model_array[conv_param_num][frame_num] * dynamic_image[frame_num][k][j][i]; + } + } + } +} + +template +void +GeneralizedPatlakMatrix::multiply_dynamic_image_with_model(DynamicDiscretisedDensity& impulse_response_image, + const DynamicDiscretisedDensity& dynamic_image, + int num_conv_params) const +{ + std::fill(impulse_response_image.begin_all(), impulse_response_image.end_all(), 0.F); + this->multiply_dynamic_image_with_model_and_add_to_input(impulse_response_image, dynamic_image, num_conv_params); +} + +template +void +GeneralizedPatlakMatrix::synthesize_impulse_response_from_parametric_image( + DynamicDiscretisedDensity& impulse_response_image, + const Parametric3VoxelsOnCartesianGrid& parametric_image, + int num_conv_params) const +{ + BasicCoordinate<2, int> model_array_min, model_array_max; + if (!(this->_model_array).get_regular_range(model_array_min, model_array_max)) + error("Model array does not have a regular range"); + + assert(model_array_max[1] - model_array_min[1] + 1 == num_conv_params); + + const int min_k_index = parametric_image.construct_single_density(1).get_min_index(); + const int max_k_index = parametric_image.construct_single_density(1).get_max_index(); + for (int k = min_k_index; k <= max_k_index; ++k) + { + const int min_j_index = (parametric_image.construct_single_density(1))[k].get_min_index(); + const int max_j_index = (parametric_image.construct_single_density(1))[k].get_max_index(); + for (int j = min_j_index; j <= max_j_index; ++j) + { + const int min_i_index = (parametric_image.construct_single_density(1))[k][j].get_min_index(); + const int max_i_index = (parametric_image.construct_single_density(1))[k][j].get_max_index(); + for (int i = min_i_index; i <= max_i_index; ++i) + { + for (int conv_param_num = model_array_min[1]; conv_param_num <= model_array_max[1] - 1; ++conv_param_num) + { + int actual_time_point = (conv_param_num - 1) * this->_conv_sampling_interval + 1; + impulse_response_image[conv_param_num][k][j][i] + = parametric_image[k][j][i][1] * exp(-parametric_image[k][j][i][2] * actual_time_point); + } + impulse_response_image[num_conv_params][k][j][i] = parametric_image[k][j][i][3]; + } + } + } +} + +template +void +GeneralizedPatlakMatrix::multiply_impulse_response_with_model_and_add_to_input( + DynamicDiscretisedDensity& dynamic_image, const DynamicDiscretisedDensity& impulse_response_image, int num_conv_params) const +{ + BasicCoordinate<2, int> model_array_min, model_array_max; + if (!(this->_model_array).get_regular_range(model_array_min, model_array_max)) + error("Model array does not have a regular range"); + + // Assert that the sizes of the one frame of the dynamic image is equal with the parametric image size. + // ChT::ToDo::Might be better to assert that each of the dimensions sizes with their voxle sizes are equal. + // Maybe this will be easier if I clone the single images for the two and then compare them. + assert(dynamic_image.get_time_frame_definitions().get_num_frames() == static_cast(model_array_max[2])); + assert(model_array_max[1] - model_array_min[1] + 1 == num_conv_params); + + const int min_k_index = dynamic_image[1].get_min_index(); + const int max_k_index = dynamic_image[1].get_max_index(); + for (int k = min_k_index; k <= max_k_index; ++k) + { + const int min_j_index = dynamic_image[1][k].get_min_index(); + const int max_j_index = dynamic_image[1][k].get_max_index(); + for (int j = min_j_index; j <= max_j_index; ++j) + { + const int min_i_index = dynamic_image[1][k][j].get_min_index(); + const int max_i_index = dynamic_image[1][k][j].get_max_index(); + for (int i = min_i_index; i <= max_i_index; ++i) + for (int frame_num = model_array_min[2]; frame_num <= model_array_max[2]; ++frame_num) + for (int conv_param_num = model_array_min[1]; conv_param_num <= model_array_max[1]; ++conv_param_num) + dynamic_image[frame_num][k][j][i] + += this->_model_array[conv_param_num][frame_num] * impulse_response_image[conv_param_num][k][j][i]; + } + } +} + +template +void +GeneralizedPatlakMatrix::multiply_impulse_response_with_model( + DynamicDiscretisedDensity& dynamic_image, const DynamicDiscretisedDensity& impulse_response_image, int num_conv_params) const +{ + std::fill(dynamic_image.begin_all(), dynamic_image.end_all(), 0.F); + this->multiply_impulse_response_with_model_and_add_to_input(dynamic_image, impulse_response_image, num_conv_params); +} + +template +void +GeneralizedPatlakMatrix::multiply_parametric_image_with_model_and_add_to_input( + DynamicDiscretisedDensity& dynamic_image, const Parametric3VoxelsOnCartesianGrid& parametric_image, int num_conv_params) const +{ + BasicCoordinate<2, int> model_array_min, model_array_max; + if (!(this->_model_array).get_regular_range(model_array_min, model_array_max)) + error("Model array does not have a regular range"); + + VectorWithOffset impulse_response_vector(model_array_min[1], model_array_max[1]); + // Assert that the sizes of the one frame of the dynamic image is equal with the parametric image size. + // ChT::ToDo::Might be better to assert that each of the dimensions sizes with their voxle sizes are equal. + // Maybe this will be easier if I clone the single images for the two and then compare them. + assert(dynamic_image[1].size_all() == parametric_image.size_all()); + assert(dynamic_image.get_time_frame_definitions().get_num_frames() == static_cast(model_array_max[2])); + assert(model_array_max[1] - model_array_min[1] + 1 == num_conv_params); + + const int min_k_index = dynamic_image[1].get_min_index(); + const int max_k_index = dynamic_image[1].get_max_index(); + for (int k = min_k_index; k <= max_k_index; ++k) + { + const int min_j_index = dynamic_image[1][k].get_min_index(); + const int max_j_index = dynamic_image[1][k].get_max_index(); + for (int j = min_j_index; j <= max_j_index; ++j) + { + const int min_i_index = dynamic_image[1][k][j].get_min_index(); + const int max_i_index = dynamic_image[1][k][j].get_max_index(); + for (int i = min_i_index; i <= max_i_index; ++i) + { + for (int conv_param_num = model_array_min[1]; conv_param_num <= model_array_max[1] - 1; ++conv_param_num) + { + int actual_time_point = (conv_param_num - 1) * this->_conv_sampling_interval + 1; + impulse_response_vector[conv_param_num] + = parametric_image[k][j][i][1] * exp(-parametric_image[k][j][i][2] * actual_time_point); + } + impulse_response_vector[num_conv_params] = parametric_image[k][j][i][3]; + for (int frame_num = model_array_min[2]; frame_num <= model_array_max[2]; ++frame_num) + for (int conv_param_num = model_array_min[1]; conv_param_num <= model_array_max[1]; ++conv_param_num) + dynamic_image[frame_num][k][j][i] + += this->_model_array[conv_param_num][frame_num] * impulse_response_vector[conv_param_num]; + } + } + } + + // Print out the min and max values of the last voxel impulse response vector + const float current_min_imp_response = *std::min_element(impulse_response_vector.begin(), impulse_response_vector.end()); + const float current_max_imp_response = *std::max_element(impulse_response_vector.begin(), impulse_response_vector.end()); + cerr << "Impulse response vector initialized from parametric image: " + << "(min, max): (" << current_min_imp_response << ", " << current_max_imp_response << ")" << endl; +} + +template +void +GeneralizedPatlakMatrix::multiply_parametric_image_with_model( + DynamicDiscretisedDensity& dynamic_image, const Parametric3VoxelsOnCartesianGrid& parametric_image, int num_conv_params) const +{ + std::fill(dynamic_image.begin_all(), dynamic_image.end_all(), 0.F); + this->multiply_parametric_image_with_model_and_add_to_input(dynamic_image, parametric_image, num_conv_params); +} + +template +void +GeneralizedPatlakMatrix::estimate_generalized_patlak_parameters_with_impulse_response_and_add_to_input( + Parametric3VoxelsOnCartesianGrid& parametric_image, + const DynamicDiscretisedDensity& impulse_response_image, + int num_conv_params) const +{ + // Initialization + BasicCoordinate<2, int> model_array_min, model_array_max; + if (!(this->_model_array).get_regular_range(model_array_min, model_array_max)) + error("Model array does not have a regular range"); + + const int min_k_index = parametric_image.construct_single_density(1).get_min_index(); + const int max_k_index = parametric_image.construct_single_density(1).get_max_index(); + assert(model_array_max[1] - model_array_min[1] + 1 == num_conv_params); + for (int k = min_k_index; k <= max_k_index; ++k) + { + const int min_j_index = (parametric_image.construct_single_density(1))[k].get_min_index(); + const int max_j_index = (parametric_image.construct_single_density(1))[k].get_max_index(); + for (int j = min_j_index; j <= max_j_index; ++j) + { + const int min_i_index = (parametric_image.construct_single_density(1))[k][j].get_min_index(); + const int max_i_index = (parametric_image.construct_single_density(1))[k][j].get_max_index(); + for (int i = min_i_index; i <= max_i_index; ++i) + { + float SUM1 = 0.F, SUM2 = 0.F, Hfunction = 0.F, kloss_estimate = 0.F, Ki_denominator = 0.F; + // Estimation of kloss parameter + for (int conv_param_num = model_array_min[1]; conv_param_num <= model_array_max[1] - 1; ++conv_param_num) + { + int actual_time_point = (conv_param_num - 1) * this->_conv_sampling_interval + 1; + SUM1 += actual_time_point * impulse_response_image[conv_param_num][k][j][i]; + SUM2 += impulse_response_image[conv_param_num][k][j][i]; + } + Hfunction = SUM1 / SUM2; + this->estimate_inverse_Hfunction(kloss_estimate, Hfunction); + parametric_image[k][j][i][2] += kloss_estimate; + + // Estimation of Ki parameter + // Either utilize a look-up table + // this->estimate_denominator_Kifunction(Ki_denominator,kloss_estimate); + + // or estimate the Ki denominator sum at each voxel + for (int conv_param_num = model_array_min[1]; conv_param_num <= model_array_max[1] - 1; ++conv_param_num) + { + int actual_time_point = (conv_param_num - 1) * this->_conv_sampling_interval + 1; + Ki_denominator += exp(-kloss_estimate * actual_time_point); + } + parametric_image[k][j][i][1] += SUM2 / Ki_denominator; + + // Estimation of V parameter + int conv_param_num = model_array_max[1]; + parametric_image[k][j][i][3] += impulse_response_image[conv_param_num][k][j][i]; + } + } + } +} + +template +void +GeneralizedPatlakMatrix::estimate_generalized_patlak_parameters_with_impulse_response( + Parametric3VoxelsOnCartesianGrid& parametric_image, + const DynamicDiscretisedDensity& impulse_response_image, + int num_conv_params) const +{ + std::fill(parametric_image.begin_all(), parametric_image.end_all(), 0.F); + this->estimate_generalized_patlak_parameters_with_impulse_response_and_add_to_input( + parametric_image, impulse_response_image, num_conv_params); +} + +template +void +GeneralizedPatlakMatrix::normalise_parametric_image_with_model_sum( + Parametric3VoxelsOnCartesianGrid& parametric_image_out, + const Parametric3VoxelsOnCartesianGrid& parametric_image, + int num_conv_params) const +{ + BasicCoordinate<2, int> model_array_min, model_array_max; + if (!(this->_model_array).get_regular_range(model_array_min, model_array_max)) + error("Model array has not regular range"); + + assert(parametric_image_out.size_all() == parametric_image.size_all()); + assert(model_array_max[1] - model_array_min[1] + 1 == num_conv_params); + + const int min_k_index = parametric_image.construct_single_density(1).get_min_index(); + const int max_k_index = parametric_image.construct_single_density(1).get_max_index(); + for (int k = min_k_index; k <= max_k_index; ++k) + { + const int min_j_index = (parametric_image.construct_single_density(1))[k].get_min_index(); + const int max_j_index = (parametric_image.construct_single_density(1))[k].get_max_index(); + for (int j = min_j_index; j <= max_j_index; ++j) + { + const int min_i_index = (parametric_image.construct_single_density(1))[k][j].get_min_index(); + const int max_i_index = (parametric_image.construct_single_density(1))[k][j].get_max_index(); + for (int i = min_i_index; i <= max_i_index; ++i) + { + parametric_image_out[k][j][i][1] = parametric_image[k][j][i][1] / ((this->get_model_array_sum())[1]); + parametric_image_out[k][j][i][2] = parametric_image[k][j][i][2] / ((this->get_model_array_sum())[2]); + } + } + } +} + +template +void +GeneralizedPatlakMatrix::estimate_nested_loop_parameters_with_model( + Parametric3VoxelsOnCartesianGrid& parametric_image, + DynamicDiscretisedDensity& dynamic_image_nested_loop_estimate, + DynamicDiscretisedDensity& dynamic_image_update_factor, + const DynamicDiscretisedDensity& dynamic_image_reference, + int num_nested_subiterations, + float min_nested_rel_change, + float max_nested_rel_change, + int num_conv_params) const +{ + // Initialization + BasicCoordinate<2, int> model_array_min, model_array_max; + if (!(this->_model_array).get_regular_range(model_array_min, model_array_max)) + error("Model array does not have a regular range"); + + VectorWithOffset impulse_response_vector(model_array_min[1], model_array_max[1]); + VectorWithOffset impulse_response_update_factor(model_array_min[1], model_array_max[1]); + VectorWithOffset impulse_response_estimate(model_array_min[1], model_array_max[1]); + VectorWithOffset model_sensitivity_vector(model_array_min[1], model_array_max[1]); + + // Assert that the sizes of the one frame of the dynamic image is equal with the parametric image size. + // ChT::ToDo::Might be better to assert that each of the dimensions sizes with their voxle sizes are equal. + // Maybe this will be easier if I clone the single images for the two and then compare them. + assert(dynamic_image_nested_loop_estimate[1].size_all() == parametric_image.size_all()); + assert(dynamic_image_nested_loop_estimate.get_time_frame_definitions().get_num_frames() + == static_cast(model_array_max[2])); + assert(model_array_max[1] - model_array_min[1] + 1 == num_conv_params); + + // nested EM loop + cerr << endl << "Entering nested loop " << endl; + for (int nested_subiterations_num = 1; nested_subiterations_num <= num_nested_subiterations; nested_subiterations_num++) + { + // Forward-projection to transfer from parametric space to impulse response space and then dynamic (time) space + const int min_k_index = dynamic_image_nested_loop_estimate[1].get_min_index(); + const int max_k_index = dynamic_image_nested_loop_estimate[1].get_max_index(); + for (int k = min_k_index; k <= max_k_index; ++k) + { + const int min_j_index = dynamic_image_nested_loop_estimate[1][k].get_min_index(); + const int max_j_index = dynamic_image_nested_loop_estimate[1][k].get_max_index(); + for (int j = min_j_index; j <= max_j_index; ++j) + { + const int min_i_index = dynamic_image_nested_loop_estimate[1][k][j].get_min_index(); + const int max_i_index = dynamic_image_nested_loop_estimate[1][k][j].get_max_index(); + for (int i = min_i_index; i <= max_i_index; ++i) + { + for (int conv_param_num = model_array_min[1]; conv_param_num <= model_array_max[1]; ++conv_param_num) + { + if (conv_param_num < model_array_max[1]) + impulse_response_vector[conv_param_num] + = parametric_image[k][j][i][1] * exp(-parametric_image[k][j][i][2] * conv_param_num); + else + impulse_response_vector[conv_param_num] = parametric_image[k][j][i][3]; + } + for (int frame_num = model_array_min[2]; frame_num <= model_array_max[2]; ++frame_num) + { + float sum_over_conv_param = 0.F; + for (int conv_param_num = model_array_min[1]; conv_param_num <= model_array_max[1]; ++conv_param_num) + sum_over_conv_param + += impulse_response_vector[conv_param_num] * this->_model_array[conv_param_num][frame_num]; + dynamic_image_nested_loop_estimate[frame_num][k][j][i] += sum_over_conv_param; + } + } + } + } + + // Use the outer loop dynamic image estimate as a reference and divide it by the nested loop dynamic image estimate + // to get the dynamic image update factor + dynamic_image_update_factor = dynamic_image_reference; + // loop over single_frame, utilize model_matrix and use the outer loop dynamic image estimate as a reference + for (int frame_num = model_array_min[2]; frame_num <= model_array_max[2]; ++frame_num) + divide(dynamic_image_update_factor[frame_num].begin_all(), + dynamic_image_update_factor[frame_num].end_all(), + dynamic_image_nested_loop_estimate[frame_num].begin_all(), + small_num); + + // Back-projection of the dynamic image update factor to get the impulse reponse update factor + // Also calculate sensitivity of the GeneralizedPatlakMatrix + for (int k = min_k_index; k <= max_k_index; ++k) + { + const int min_j_index = dynamic_image_update_factor[1][k].get_min_index(); + const int max_j_index = dynamic_image_update_factor[1][k].get_max_index(); + for (int j = min_j_index; j <= max_j_index; ++j) + { + const int min_i_index = dynamic_image_update_factor[1][k][j].get_min_index(); + const int max_i_index = dynamic_image_update_factor[1][k][j].get_max_index(); + for (int i = min_i_index; i <= max_i_index; ++i) + { + for (int conv_param_num = model_array_min[1]; conv_param_num <= model_array_max[1]; ++conv_param_num) + { + float sum_over_frames = 0.F; + float sensitivity_sum_over_frames = 0.F; + for (int frame_num = model_array_min[2]; frame_num <= model_array_max[2]; ++frame_num) + { + sum_over_frames + += this->_model_array[conv_param_num][frame_num] * dynamic_image_update_factor[frame_num][k][j][i]; + + // Also here we calculate the sensitivity of the GeneralizedPatlakMatrix + // TODO: No need to repeat sensitivity calculation for its voxel. This should be done elsewhere once and + // then passed to the method + sensitivity_sum_over_frames += this->_model_array[conv_param_num][frame_num]; + } + impulse_response_update_factor[conv_param_num] = sum_over_frames; + model_sensitivity_vector[conv_param_num] = sensitivity_sum_over_frames; + } + } + } + } + + // Sensitivity division of impulse reponse update factor + for (int conv_param_num = model_array_min[1]; conv_param_num <= model_array_max[1]; ++conv_param_num) + divide(impulse_response_update_factor.begin(), + impulse_response_update_factor.end(), + model_sensitivity_vector.begin(), + small_num); + + if (nested_subiterations_num != 1) + { + const float current_min_nested_gradient + = *std::min_element(impulse_response_update_factor.begin(), impulse_response_update_factor.end()); + const float current_max_nested_gradient + = *std::max_element(impulse_response_update_factor.begin(), impulse_response_update_factor.end()); + const float new_min_nested_gradient = static_cast(min_nested_rel_change); + const float new_max_nested_gradient = static_cast(max_nested_rel_change); + cerr << "Nested iteration: " << nested_subiterations_num << " sub-gradient(update image) old value (min, max): (" + << current_min_nested_gradient << ", " << current_max_nested_gradient << "), new value (min, max) (" + << std::max(current_min_nested_gradient, new_min_nested_gradient) << ", " + << std::min(current_max_nested_gradient, new_max_nested_gradient) << ")" << endl; + + threshold_upper_lower(impulse_response_update_factor.begin(), + impulse_response_update_factor.end(), + new_min_nested_gradient, + new_max_nested_gradient); + } + + // Update the nested estimates of impulse reponse function + for (int conv_param_num = model_array_min[1]; conv_param_num <= model_array_max[1]; ++conv_param_num) + impulse_response_estimate[conv_param_num] *= impulse_response_update_factor[conv_param_num]; + + // Print out the min and max values of the nested updated impulse reponse vector for each nested iteration + const float current_min_nested_updated_impulse_response + = *std::min_element(impulse_response_estimate.begin(), impulse_response_estimate.end()); + const float current_max_nested_updated_impulse_response + = *std::max_element(impulse_response_estimate.begin(), impulse_response_estimate.end()); + cerr << "Nested iteration: " << nested_subiterations_num << " Updated impulse reponse value (min, max) (" + << current_min_nested_updated_impulse_response << ", " << current_max_nested_updated_impulse_response << ")" << endl + << endl; + + // Current nested loop estimation of the kinetic parameters Ki, kloss and V, based on updated impulse reponse estimate + for (int k = min_k_index; k <= max_k_index; ++k) + { + const int min_j_index = dynamic_image_nested_loop_estimate[1][k].get_min_index(); + const int max_j_index = dynamic_image_nested_loop_estimate[1][k].get_max_index(); + for (int j = min_j_index; j <= max_j_index; ++j) + { + const int min_i_index = dynamic_image_nested_loop_estimate[1][k][j].get_min_index(); + const int max_i_index = dynamic_image_nested_loop_estimate[1][k][j].get_max_index(); + for (int i = min_i_index; i <= max_i_index; ++i) + { + float SUM1 = 0.F, SUM2 = 0.F, Hfunction = 0.F, kloss_estimate = 0.F, Ki_denominator = 0.F; + // Estimation of kloss parameter + for (int conv_param_num = model_array_min[1]; conv_param_num <= model_array_max[1] - 1; ++conv_param_num) + { + SUM1 += conv_param_num * impulse_response_estimate[conv_param_num]; + SUM2 += impulse_response_estimate[conv_param_num]; + } + Hfunction = SUM1 / SUM2; + estimate_inverse_Hfunction(kloss_estimate, Hfunction); + parametric_image[k][j][i][2] = kloss_estimate; + + // Estimation of Ki parameter + this->estimate_denominator_Kifunction(Ki_denominator, kloss_estimate); + parametric_image[k][j][i][1] = SUM2 / Ki_denominator; + + // Estimation of V parameter + int conv_param_num = model_array_max[1]; + parametric_image[k][j][i][3] = impulse_response_estimate[conv_param_num]; + } + } + } + + // Print out the min and max values of the nested updated impulse reponse vector for each nested iteration + const float current_min_nested_updated_image = *std::min_element(parametric_image.begin_all(), parametric_image.end_all()); + const float current_max_nested_updated_image = *std::max_element(parametric_image.begin_all(), parametric_image.end_all()); + cerr << "Nested iteration: " << nested_subiterations_num << " Updated parametric image value (min, max) (" + << current_min_nested_updated_image << ", " << current_max_nested_updated_image << ")" << endl + << endl; + } +} + +END_NAMESPACE_STIR diff --git a/src/include/stir/modelling/GeneralizedPatlakPlot.h b/src/include/stir/modelling/GeneralizedPatlakPlot.h new file mode 100644 index 0000000000..e6f37c239d --- /dev/null +++ b/src/include/stir/modelling/GeneralizedPatlakPlot.h @@ -0,0 +1,222 @@ +// +/* + Copyright (C) 2006 - 2011, Hammersmith Imanet Ltd + This file is part of STIR. + + SPDX-License-Identifier: Apache-2.0 + + See STIR/LICENSE.txt for details +*/ +/*! + \file + \ingroup modelling + \brief Implementation of functions of class stir::PatlakPlot + + \author Charalampos Tsoumpas +*/ + +#ifndef __stir_modelling_GeneralizedPatlakPlot_H__ +#define __stir_modelling_GeneralizedPatlakPlot_H__ + +#include "stir/modelling/KineticModel.h" +#include "stir/modelling/GeneralizedPatlakMatrix.h" +#include "stir/modelling/ModelMatrix.h" +#include "stir/Succeeded.h" +#include "stir/RegisteredParsingObject.h" + +#include "stir/modelling/PatlakPlot.h" +START_NAMESPACE_STIR + +//! +/*! + \ingroup modelling + \brief Generalized Patlak kinetic model + + Model suitable for irreversible tracers (such as FDG and FLT) AS WELL AS reversible tracers . See + + - Patlak C S, Blasberg R G, Fenstermacher J D (1985) + Graphical evaluation of blood-to-brain transfer constants from multiple-time uptake data, {J Cereb Blood Flow Metab + 3(1): p. 1-7. + + - Patlak C S, Blasberg R G (1985) + Experimental and Graphical evaluation of blood-to-brain transfer constant from multiple-time uptake data: + Generalizations, J Cereb Blood Flow Metab 5: p. 584-90. + + + \par Example .par file + \verbatim + Generalized Patlak Plot Parameters:= + + time frame definition filename := frames.txt + starting frame := 23 + calibration factor := 9000 + blood data filename := blood_file.txt + ; In seconds + Time Shift := 0 + In total counts := 1 + + end Generalized Patlak Plot Parameters:= + \endverbatim + + \warning + - The dynamic images will be calibrated only if the calibration factor is given. + - The [if_total_cnt] is set to true the Dynamic Image will have the total number of + counts while if set to false it will have the total_number_of_counts/get_duration(frame_num). + - The dynamic images will always be in decaying counts. + - The plasma data is assumed to be in decaying counts. + + \todo Should be derived from LinearModels, but when non-linear models will be introduced, as well. +*/ +class GeneralizedPatlakPlot : public RegisteredParsingObject +{ +private: + typedef RegisteredParsingObject base_type; + +public: + //! Name which will be used when parsing a GeneralizedPatlakPlot object + static const char* const registered_name; + + GeneralizedPatlakPlot(); //!< Default constructor (calls set_defaults()) + ~GeneralizedPatlakPlot() override; + /*! \name Functions to get parameters */ + //@{ + //! Simply gets model matrix, if it has been already stored. + GeneralizedPatlakMatrix<2> get_model_matrix() const; + //! Creates model matrix from plasma data (Must be already sorted in appropriate frames). + GeneralizedPatlakMatrix<2> get_model_matrix(const PlasmaData& complete_plasma_data, + const PlasmaData& plasma_frame_data, + const TimeFrameDefinitions& time_frame_definitions, + const unsigned int starting_frame); + + //! Simply gets model matrix, if it has been already stored. + ModelMatrix<2> get_initialization_model_matrix() const; + //! Creates initialization model matrix from plasma data (Must be already sorted in appropriate frames). + ModelMatrix<2> get_initialization_model_matrix(const PlasmaData& plasma_data, + const TimeFrameDefinitions& time_frame_definitions, + const unsigned int starting_frame); + + //! Returns the number of convolution parameters for the GeneralizedPatlakPlot matrix. + unsigned int get_num_conv_params() const; + //!@} + /*! \name Functions to set parameters*/ + //@{ + inline void set_with_initialization_loops(bool arg) { with_initialization_loops = arg; } + + void set_model_matrix(GeneralizedPatlakMatrix<2> model_matrix); //!< Simply set model matrix + void set_initialization_model_matrix(ModelMatrix<2> initialization_model_matrix); //!< Simply set initialization model matrix + //@} + + void set_Hfunction_matrix(GeneralizedPatlakMatrix<2> Hfunction_matrix); //!< Simply set Hfunction matrix + void set_Ki_matrix(GeneralizedPatlakMatrix<2> Ki_matrix); //!< Simply set Ki matrix + + //! Simply gets Hfunction matrix + GeneralizedPatlakMatrix<2> get_Hfunction_matrix() const; + GeneralizedPatlakMatrix<2> get_Ki_matrix() const; + + //! Multiplies the dynamic image with the model gradient. + /*! For a linear model the model gradient is the transpose of the model matrix. + So, the dynamic image is "projected" from time domain to the parameter domain. + + \todo Should be a virtual function declared in the KineticModel class. + */ + virtual void multiply_dynamic_image_with_model_gradient(DynamicDiscretisedDensity& impulse_response, + const DynamicDiscretisedDensity& dyn_image) const; + //! Multiplies the dynamic image with the model gradient and add to original \c parametric_image + /*! \todo Should be a virtual function declared in the KineticModel class. + */ + virtual void multiply_dynamic_image_with_model_gradient_and_add_to_input(DynamicDiscretisedDensity& impulse_response, + const DynamicDiscretisedDensity& dyn_image) const; + + virtual void get_impulse_response_from_parametric_image(DynamicDiscretisedDensity& impulse_response_image, + const Parametric3VoxelsOnCartesianGrid& par_image) const; + + virtual void get_dynamic_image_from_impulse_response(DynamicDiscretisedDensity& dyn_image, + const DynamicDiscretisedDensity& impulse_response_image) const; + + //! Multiplies the parametric image with the model matrix to get the corresponding dynamic image. + /*! \todo Should be a virtual function declared in the KineticModel class. + */ + virtual void get_dynamic_image_from_parametric_image(DynamicDiscretisedDensity& dyn_image, + const Parametric3VoxelsOnCartesianGrid& par_image) const; + + virtual void get_generalized_patlak_parameters_from_impulse_response(Parametric3VoxelsOnCartesianGrid& par_image, + const DynamicDiscretisedDensity& dyn_image, + const DynamicDiscretisedDensity& impulse_response) const; + + //! Multiplies the dynamic image with the initialization kinetic model gradient. + /*! For a linear model the model gradient is the transpose of the model matrix. + So, the dynamic image is "projected" from time domain to the parameter domain. + + \todo Should be a virtual function declared in the KineticModel class. + Only used for the initialization of the Generalized Patlak Model EM update estimates + */ + virtual void multiply_dynamic_image_with_initialization_model_gradient(Parametric3VoxelsOnCartesianGrid& parametric_image, + const DynamicDiscretisedDensity& dyn_image) const; + + //! Multiplies the dynamic image with the initialization kinetic model gradient and add to original \c parametric_image + /*! \todo Should be a virtual function declared in the KineticModel class. + // Only used for the initialization of the Generalized Patlak Model EM update estimates + */ + virtual void + multiply_dynamic_image_with_initialization_model_gradient_and_add_to_input(Parametric3VoxelsOnCartesianGrid& parametric_image, + const DynamicDiscretisedDensity& dyn_image) const; + + //! Multiplies the parametric image with the initialization kinetic model matrix to get the corresponding dynamic image. + /*! \todo Should be a virtual function declared in the KineticModel class. + // Only used for the initialization of the Generalized Patlak Model EM update estimates + */ + virtual void get_dynamic_image_from_initialization_parametric_image(DynamicDiscretisedDensity& dyn_image, + const Parametric3VoxelsOnCartesianGrid& par_image) const; + + virtual void estimate_nested_loop_parameters_with_model(Parametric3VoxelsOnCartesianGrid& parametric_image, + DynamicDiscretisedDensity& dynamic_image_nested_loop_estimate, + DynamicDiscretisedDensity& dynamic_image_update_factor, + const DynamicDiscretisedDensity& dynamic_image_reference, + float minimum_nested_relative_change, + float maximum_nested_relative_change, + int num_nested_subiterations) const; + + void set_defaults() override; + + Succeeded set_up() override; + //! Number of frames to apply the model + unsigned int _num_frames; + //! Interval between convolution samples (in order to downsample the convolution sampling) + unsigned int _conv_sample_interval; + unsigned int _num_conv_params; //!< Number of convolution parameters + //! Reference point of the last frame defined by user + unsigned int _last_frame_ref_time; + //! Lower bound for the search space of the estimated kloss parameter. + float _kloss_lb; + //! Upper bound for the search space of the estimated kloss parameter. + float _kloss_ub; + //! Number of samples for the search space of the estimated kloss parameter. + unsigned int _kloss_num_samples; + //! Stores the complete plasma data before distributing/sorting into frames + PlasmaData _not_complete_plasma_data; + +private: + bool with_initialization_loops; + //! Creating Generalized Model Matrix (Here printed in its transverse format) + //! NOTE1: It contains as many columns as the number of later frames participating in parameter estimation + //! It contains as many rows as the convolution points of the input function + //! + 1 last row consisting of the plasma counts for the corresponding later frame + //! "NOTE2: Last element of each column is the plasma counts for the corresponding later frame + void create_model_matrix() override; + //! Precalculates Hfunction matrix from private members + void create_Hfunction_matrix(); + void create_Ki_matrix(); + void initialise_keymap() override; + bool post_processing() override; + mutable GeneralizedPatlakMatrix<2> _model_matrix; + mutable ModelMatrix<2> _initialization_model_matrix; + mutable GeneralizedPatlakMatrix<2> _Hfunction_matrix; + mutable GeneralizedPatlakMatrix<2> _Ki_matrix; + bool _initialization_matrix_is_stored; // -remove + + std::shared_ptr linear_model; +}; + +END_NAMESPACE_STIR + +#endif //__stir_modelling_GeneralizedPatlakPlot_H__ \ No newline at end of file diff --git a/src/include/stir/modelling/KineticModel.h b/src/include/stir/modelling/KineticModel.h index 7a72548255..4b685dda8d 100644 --- a/src/include/stir/modelling/KineticModel.h +++ b/src/include/stir/modelling/KineticModel.h @@ -2,6 +2,7 @@ // /* Copyright (C) 2006 - 2009, Hammersmith Imanet Ltd + Copyright (C) 2026, University Medical Center Groningen This file is part of STIR. SPDX-License-Identifier: Apache-2.0 @@ -15,7 +16,7 @@ \brief Definition of class stir::KineticModel \author Charalampos Tsoumpas - + \author Nikos Efthimiou */ #ifndef __stir_modelling_KineticModel_H__ @@ -23,6 +24,12 @@ #include "stir/RegisteredObject.h" #include "stir/RegisteredParsingObject.h" +#include "stir/TimeFrameDefinitions.h" +#include "stir/modelling/PlasmaData.h" +#include "stir/Succeeded.h" +#include "stir/ExamInfo.h" + +#include "stir/shared_ptr.h" START_NAMESPACE_STIR @@ -38,20 +45,97 @@ class KineticModel : public RegisteredObject public: static const char* const registered_name; //! default constructor - KineticModel(); + KineticModel() { _already_setup = false; }; + KineticModel(const ExamInfo& _exam_info_arg) { _already_setup = false; }; //! default destructor - ~KineticModel() override; + virtual ~KineticModel(){}; // virtual float get_compartmental_activity_at_time(const int param_num, const int sample_num) const; // virtual float get_total_activity_at_time(const int sample_num) const; - virtual Succeeded set_up() = 0; + virtual Succeeded set_up(); + + /*! \name Functions to get parameters */ + //@{ + + //! Returns the frame that the GeneralizedPatlakPlot linearization is assumed to be valid. + inline unsigned int get_starting_frame() const; + //! Returns the TimeFrameDefinitions that the GeneralizedPatlakPlot linearization is assumed to be valid: ChT::Check + inline const TimeFrameDefinitions& get_time_frame_definitions() const; + //! Returns the number of the last frame available. + inline unsigned int get_ending_frame() const; + + inline const PlasmaData& get_plasma_data() const; + + inline float get_calibration_factor() const; + + inline const shared_ptr get_exam_info_sptr() const; + + inline int get_frame_reference_time() const; + //!@} + + /*! \name Functions to set parameters */ + //@{ + inline void set_plasma_data(PlasmaData& arg); + + inline void set_time_frame_definitions(TimeFrameDefinitions& arg); + + inline void set_starting_frame(unsigned int arg); + + inline void set_calibration_factor(float arg); + + inline void set_exam_info(const shared_ptr& exam_info_sptr); + + inline void set_radionuclide(const Radionuclide _radionuclide); + + inline void set_frame_reference_time(int _arg); + + //!@} + +protected: + virtual void initialise_keymap(); + virtual void set_defaults(); + virtual bool post_processing(); + //! + virtual void create_model_matrix(); + //! Switches between cardiac and brain data + bool _if_cardiac; + //! Calibration Factor, maybe to be removed. + float _cal_factor; + //! Shifts the time to fit the timing of Plasma Data with the Projection Data. + float _time_shift; + //! Switch to scale or not the model_matrix to the correct scale, according to the appropriate scale + bool _in_correct_scale; + //! Switch to choose the image values of the model to be in total counts or in mean counts. + bool _in_total_cnt; + //! Switch to choose the plasma values of the model to be in total counts or in mean counts. + bool _plasma_in_total_cnt; + //! Name of file in which the input function is stored + std::string _blood_data_filename; + //! Stores the plasma data into frames for brain studies + PlasmaData _plasma_frame_data; + //! name of file to get frame definitions + std::string _time_frame_definition_filename; + + bool _matrix_is_stored; + +private: + //! All setters will set this to false, reminding you call set_up() + bool _already_setup; + //! TimeFrameDefinitions + TimeFrameDefinitions _frame_defs; + //! Starting frame to apply the model + unsigned int _starting_frame; + + std::shared_ptr _exam_info_sptr; - // protected: - // void initialise_keymap(); + //! end_of_frame = 0 + //! mid_of_frame = 1 + int _frame_reference_time; }; END_NAMESPACE_STIR +#include "stir/modelling/KineticModel.inl" #endif //__stir_modelling_KineticModel_H__ diff --git a/src/include/stir/modelling/KineticModel.inl b/src/include/stir/modelling/KineticModel.inl new file mode 100644 index 0000000000..615ed5b666 --- /dev/null +++ b/src/include/stir/modelling/KineticModel.inl @@ -0,0 +1,106 @@ +/* + Copyright (C) 2026, University Medical Center Groningen + This file is part of STIR. + + SPDX-License-Identifier: Apache-2.0 + + See STIR/LICENSE.txt for details +*/ + +#include "stir/is_null_ptr.h" + +START_NAMESPACE_STIR + +unsigned int +KineticModel::get_starting_frame() const +{ + return _starting_frame; +} + +const TimeFrameDefinitions& +KineticModel::get_time_frame_definitions() const +{ + return _frame_defs; +} + +int +KineticModel::get_frame_reference_time() const +{ + return this->_frame_reference_time; +} + +unsigned int +KineticModel::get_ending_frame() const +{ + return get_time_frame_definitions().get_num_frames(); +} + +const PlasmaData& +KineticModel::get_plasma_data() const +{ + return _plasma_frame_data; +} + +float +KineticModel::get_calibration_factor() const +{ + return _cal_factor; +} + +const shared_ptr +KineticModel::get_exam_info_sptr() const +{ + return _exam_info_sptr; +} + +void +KineticModel::set_plasma_data(PlasmaData& arg) +{ + _already_setup = false; + _plasma_frame_data = arg; +} + +void +KineticModel::set_time_frame_definitions(TimeFrameDefinitions& arg) +{ + _already_setup = false; + _frame_defs = arg; +} + +void +KineticModel::set_starting_frame(unsigned int arg) +{ + _already_setup = false; + _starting_frame = arg; +} + +void +KineticModel::set_calibration_factor(float arg) +{ + _already_setup = false; + _cal_factor = arg; +} + +void +KineticModel::set_exam_info(const shared_ptr& exam_info_sptr) +{ + if (is_null_ptr(exam_info_sptr)) + error("KineticModel::set_exam_info: null exam info"); + _exam_info_sptr = exam_info_sptr; + set_radionuclide(exam_info_sptr->get_radionuclide()); +} + +void +KineticModel::set_radionuclide(Radionuclide radionuclide_arg) +{ + _already_setup = false; + this->_plasma_frame_data.set_isotope_halflife(radionuclide_arg.get_half_life()); +} + +void +KineticModel::set_frame_reference_time(int arg) +{ + this->_frame_reference_time = arg; +} + +END_NAMESPACE_STIR \ No newline at end of file diff --git a/src/include/stir/modelling/ModelMatrix.h b/src/include/stir/modelling/ModelMatrix.h index 5f152bd1d8..457ba809c5 100644 --- a/src/include/stir/modelling/ModelMatrix.h +++ b/src/include/stir/modelling/ModelMatrix.h @@ -13,7 +13,7 @@ \ingroup modelling \brief Declaration of class stir::ModelMatrix \author Charalampos Tsoumpas - + \author Nicolas A Karakatsanis */ #ifndef __stir_modelling_ModelMatrix_H__ @@ -61,6 +61,7 @@ class ModelMatrix //! Function to set _is_calibrated boolean true or false inline void set_is_uncalibrated(const bool is_uncalibrated); inline void set_is_in_correct_scale(const bool in_correct_scale); + inline void set_matrix_in_total_frame_counts(const bool is_converted_to_total_counts); //!@} //! Function to give the threshold_value to the all elements of the model_array which lower value than the threshold_value. @@ -109,7 +110,37 @@ class ModelMatrix inline void normalise_parametric_image_with_model_sum(ParametricVoxelsOnCartesianGrid& parametric_image_out, const ParametricVoxelsOnCartesianGrid& parametric_image) const; - //!@} + + /*! Multiplications of the initialization kinetic model matrix with the dynamic or the parametric images. + /todo Maybe it will be better to lie in a linear models class. + */ + //@{ + //! multiply (transpose) initialization kinetic model-matrix with dynamic image and add result to original \c parametric_image + inline void + multiply_dynamic_image_with_initialization_model_and_add_to_input(Parametric3VoxelsOnCartesianGrid& parametric_image, + const DynamicDiscretisedDensity& dynamic_image) const; + //! multiply (transpose) initialization kinetic model-matrix with dynamic image (overwriting original content of \c + //! parametric_image) + /*! \todo current implementation first fills first argument with 0 and then calls + multiply_dynamic_image_with_initialization kinetic model_and_add_to_input(). This is somewhat inefficient. + */ + inline void multiply_dynamic_image_with_initialization_model(Parametric3VoxelsOnCartesianGrid& parametric_image, + const DynamicDiscretisedDensity& dynamic_image) const; + //! multiply initialization kinetic model-matrix with parametric image and add result to original \c dynamic_image + inline void multiply_parametric_image_with_initialization_model_and_add_to_input( + DynamicDiscretisedDensity& dynamic_image, const Parametric3VoxelsOnCartesianGrid& parametric_image) const; + //! multiply initialization kinetic model-matrix with parametric image (overwriting original content of \c dynamic_image) + /*! \todo current implementation first fills first argument with 0 and then calls + multiply_dynamic_image_with_initialization_model_and_add_to_input(). This is somewhat inefficient. + */ + inline void multiply_parametric_image_with_initialization_model(DynamicDiscretisedDensity& dynamic_image, + const Parametric3VoxelsOnCartesianGrid& parametric_image) const; + + inline void + normalise_parametric_image_with_initialization_model_sum(Parametric3VoxelsOnCartesianGrid& parametric_image_out, + const Parametric3VoxelsOnCartesianGrid& parametric_image) const; + + //@} private: //! At the moment it has the form of _model_array[param_num][frame_num]. Array<2, float> _model_array; diff --git a/src/include/stir/modelling/ModelMatrix.inl b/src/include/stir/modelling/ModelMatrix.inl index 1277a8825d..82b06f0cdb 100644 --- a/src/include/stir/modelling/ModelMatrix.inl +++ b/src/include/stir/modelling/ModelMatrix.inl @@ -14,6 +14,7 @@ \brief Implementations of inline functions of class stir::ModelMatrix \author Charalampos Tsoumpas + \author Nicolas A Karakatsanis */ @@ -167,6 +168,13 @@ ModelMatrix::set_is_in_correct_scale(const bool in_correct_scale) this->_in_correct_scale = in_correct_scale; } +template +void +ModelMatrix::set_matrix_in_total_frame_counts(const bool is_converted_to_total_counts) +{ + this->_is_converted_to_total_counts = is_converted_to_total_counts; +} + template void ModelMatrix::uncalibrate(const float cal_factor) @@ -182,7 +190,6 @@ ModelMatrix::uncalibrate(const float cal_factor) for (int param_num = model_array_min[1]; param_num <= model_array_max[1]; ++param_num) for (int frame_num = model_array_min[2]; frame_num <= model_array_max[2]; ++frame_num) this->_model_array[param_num][frame_num] /= cal_factor; - ModelMatrix::set_is_uncalibrated(true); } } @@ -364,4 +371,139 @@ ModelMatrix::normalise_parametric_image_with_model_sum(ParametricVoxe } } +template +void +ModelMatrix::multiply_dynamic_image_with_initialization_model_and_add_to_input( + Parametric3VoxelsOnCartesianGrid& parametric_image, const DynamicDiscretisedDensity& dynamic_image) const +{ + BasicCoordinate<2, int> model_array_min, model_array_max; + if (!this->_model_array.get_regular_range(model_array_min, model_array_max)) + error("Model array has not regular range"); + + // Assert that the sizes of the one frame of the dynamic image is equal with the parametric image size. + // ChT::ToDo::Might be better to assert that each of the dimensions sizes with their voxle sizes are equal. + // Could probably use has_same_characteristics()? + assert(dynamic_image[1].size_all() == parametric_image.size_all()); + assert(dynamic_image.get_time_frame_definitions().get_num_frames() == static_cast(model_array_max[2])); + assert(model_array_max[1] - model_array_min[1] + 1 == num_param); + + const int min_k_index = dynamic_image[1].get_min_index(); + const int max_k_index = dynamic_image[1].get_max_index(); + for (int k = min_k_index; k <= max_k_index; ++k) + { + const int min_j_index = dynamic_image[1][k].get_min_index(); + const int max_j_index = dynamic_image[1][k].get_max_index(); + for (int j = min_j_index; j <= max_j_index; ++j) + { + const int min_i_index = dynamic_image[1][k][j].get_min_index(); + const int max_i_index = dynamic_image[1][k][j].get_max_index(); + for (int i = min_i_index; i <= max_i_index; ++i) + for (int param_num = model_array_min[1]; param_num <= model_array_max[1]; ++param_num) + { + float sum_over_frames = 0.F; + for (int frame_num = model_array_min[2]; frame_num <= model_array_max[2]; ++frame_num) + sum_over_frames += this->_model_array[param_num][frame_num] * dynamic_image[frame_num][k][j][i]; + if (param_num == 2) + { + parametric_image[k][j][i][param_num] += 0; + parametric_image[k][j][i][param_num + 1] += sum_over_frames; + } + else + parametric_image[k][j][i][param_num] += sum_over_frames; + } + } + } +} + +template +void +ModelMatrix::multiply_dynamic_image_with_initialization_model(Parametric3VoxelsOnCartesianGrid& parametric_image, + const DynamicDiscretisedDensity& dynamic_image) const +{ + std::fill(parametric_image.begin_all(), parametric_image.end_all(), 0.F); + this->multiply_dynamic_image_with_initialization_model_and_add_to_input(parametric_image, dynamic_image); +} + +template +void +ModelMatrix::multiply_parametric_image_with_initialization_model_and_add_to_input( + DynamicDiscretisedDensity& dynamic_image, const Parametric3VoxelsOnCartesianGrid& parametric_image) const +{ + BasicCoordinate<2, int> model_array_min, model_array_max; + if (!(this->_model_array).get_regular_range(model_array_min, model_array_max)) + error("Model array does not have a regular range"); + + // Assert that the sizes of the one frame of the dynamic image is equal with the parametric image size. + // ChT::ToDo::Might be better to assert that each of the dimensions sizes with their voxle sizes are equal. + // Maybe this will be easier if I clone the single images for the two and then compare them. + assert(dynamic_image[1].size_all() == parametric_image.size_all()); + assert(dynamic_image.get_time_frame_definitions().get_num_frames() == static_cast(model_array_max[2])); + assert(model_array_max[1] - model_array_min[1] + 1 == num_param); + + const int min_k_index = dynamic_image[1].get_min_index(); + const int max_k_index = dynamic_image[1].get_max_index(); + for (int k = min_k_index; k <= max_k_index; ++k) + { + const int min_j_index = dynamic_image[1][k].get_min_index(); + const int max_j_index = dynamic_image[1][k].get_max_index(); + for (int j = min_j_index; j <= max_j_index; ++j) + { + const int min_i_index = dynamic_image[1][k][j].get_min_index(); + const int max_i_index = dynamic_image[1][k][j].get_max_index(); + for (int i = min_i_index; i <= max_i_index; ++i) + for (int frame_num = model_array_min[2]; frame_num <= model_array_max[2]; ++frame_num) + { + float sum_over_param = 0.F; + for (int param_num = model_array_min[1]; param_num <= model_array_max[1]; ++param_num) + if (param_num == 2) + sum_over_param += parametric_image[k][j][i][param_num + 1] * this->_model_array[param_num][frame_num]; + else + sum_over_param += parametric_image[k][j][i][param_num] * this->_model_array[param_num][frame_num]; + dynamic_image[frame_num][k][j][i] = sum_over_param; + } + } + } +} + +template +void +ModelMatrix::multiply_parametric_image_with_initialization_model( + DynamicDiscretisedDensity& dynamic_image, const Parametric3VoxelsOnCartesianGrid& parametric_image) const +{ + std::fill(dynamic_image.begin_all(), dynamic_image.end_all(), 0.F); + this->multiply_parametric_image_with_initialization_model_and_add_to_input(dynamic_image, parametric_image); +} + +template +void +ModelMatrix::normalise_parametric_image_with_initialization_model_sum( + Parametric3VoxelsOnCartesianGrid& parametric_image_out, const Parametric3VoxelsOnCartesianGrid& parametric_image) const +{ + BasicCoordinate<2, int> model_array_min, model_array_max; + if (!(this->_model_array).get_regular_range(model_array_min, model_array_max)) + error("Model array has not regular range"); + + assert(parametric_image_out.size_all() == parametric_image.size_all()); + assert(model_array_max[1] - model_array_min[1] + 1 == num_param); + + const int min_k_index = parametric_image.construct_single_density(num_param).get_min_index(); + const int max_k_index = parametric_image.construct_single_density(num_param).get_max_index(); + for (int k = min_k_index; k <= max_k_index; ++k) + { + const int min_j_index = (parametric_image.construct_single_density(num_param))[k].get_min_index(); + const int max_j_index = (parametric_image.construct_single_density(num_param))[k].get_max_index(); + for (int j = min_j_index; j <= max_j_index; ++j) + { + const int min_i_index = (parametric_image.construct_single_density(num_param))[k][j].get_min_index(); + const int max_i_index = (parametric_image.construct_single_density(num_param))[k][j].get_max_index(); + for (int i = min_i_index; i <= max_i_index; ++i) + { + parametric_image_out[k][j][i][1] = parametric_image[k][j][i][1] / ((this->get_model_array_sum())[1]); + parametric_image_out[k][j][i][2] = 0; + parametric_image_out[k][j][i][3] = parametric_image[k][j][i][3] / ((this->get_model_array_sum())[2]); + } + } + } +} + END_NAMESPACE_STIR diff --git a/src/include/stir/modelling/ParametricDiscretisedDensity.h b/src/include/stir/modelling/ParametricDiscretisedDensity.h index 6e5b192b2e..a41a1ec879 100644 --- a/src/include/stir/modelling/ParametricDiscretisedDensity.h +++ b/src/include/stir/modelling/ParametricDiscretisedDensity.h @@ -16,8 +16,8 @@ \ingroup modelling \brief Declaration of class stir::ParametricDiscretisedDensity \author Kris Thielemans + \author Nicolas A Karakatsanis \author Richard Brown - */ #include "stir/DiscretisedDensity.h" @@ -166,10 +166,20 @@ class ParametricDiscretisedDensity : public DiscDensT }; //! Convenience typedef for base-type of Cartesian Voxelised Parametric Images with just two parameters -typedef VoxelsOnCartesianGrid> ParametricVoxelsOnCartesianGridBaseType; +typedef VoxelsOnCartesianGrid> Parametric2VoxelsOnCartesianGridBaseType; //! Convenience typedef for Cartesian Voxelised Parametric Images with just two parameters -typedef ParametricDiscretisedDensity ParametricVoxelsOnCartesianGrid; +typedef ParametricDiscretisedDensity Parametric2VoxelsOnCartesianGrid; + +//! Convenience typedef for base-type of Cartesian Voxelised Parametric Images with just three parameters (for Generalized Patlak +//! algorithm) +typedef VoxelsOnCartesianGrid> Parametric3VoxelsOnCartesianGridBaseType; + +//! Convenience typedef for Cartesian Voxelised Parametric Images with just three parameters (for Generalized Patlak algorithm) +typedef ParametricDiscretisedDensity Parametric3VoxelsOnCartesianGrid; + +//! type for 2 parameters, we need this for backwards compatibility +using ParametricVoxelsOnCartesianGrid = Parametric2VoxelsOnCartesianGrid; END_NAMESPACE_STIR //#include "stir/modelling/ParametricDiscretisedDensity.inl" diff --git a/src/include/stir/modelling/PatlakPlot.h b/src/include/stir/modelling/PatlakPlot.h index 7afd2ca3aa..d270a1647e 100644 --- a/src/include/stir/modelling/PatlakPlot.h +++ b/src/include/stir/modelling/PatlakPlot.h @@ -14,6 +14,7 @@ \brief Implementation of functions of class stir::PatlakPlot \author Charalampos Tsoumpas + \author Nicolas A Karakatsanis */ @@ -22,8 +23,7 @@ #include "stir/modelling/KineticModel.h" #include "stir/modelling/ModelMatrix.h" -#include "stir/modelling/PlasmaData.h" -#include "stir/Succeeded.h" + #include "stir/RegisteredParsingObject.h" START_NAMESPACE_STIR @@ -68,28 +68,31 @@ START_NAMESPACE_STIR \todo Should be derived from LinearModels, but when non-linear models will be introduced, as well. */ -class PatlakPlot : public RegisteredParsingObject +class PatlakPlot : public RegisteredParsingObject { +private: + typedef RegisteredParsingObject base_type; + public: //! Name which will be used when parsing a PatlakPlot object static const char* const registered_name; - PatlakPlot(); //!< Default constructor (calls set_defaults()) - ~PatlakPlot() override; //!< default destructor - /*! \name Functions to get parameters */ - //!@{ + //! Default constructor (calls set_defaults()) + PatlakPlot(); + + PatlakPlot(const shared_ptr& exam_info_sptr); + + ~PatlakPlot() override; + + /*! \name Functions to get parameters */ + //!@{ //! Simply gets model matrix, if it has been already stored. ModelMatrix<2> get_model_matrix() const; //! Creates model matrix from plasma data (Must be already sorted in appropriate frames). ModelMatrix<2> get_model_matrix(const PlasmaData& plasma_data, const TimeFrameDefinitions& time_frame_definitions, const unsigned int starting_frame); - //! Returns the frame that the PatlakPlot linearization is assumed to be valid. - unsigned int get_starting_frame() const; - //! Returns the number of the last frame available. - unsigned int get_ending_frame() const; - //! Returns the TimeFrameDefinitions that the PatlakPlot linearization is assumed to be valid: ChT::Check - TimeFrameDefinitions get_time_frame_definitions() const; + //!@} /*! \name Functions to set parameters*/ //!@{ @@ -116,6 +119,37 @@ class PatlakPlot : public RegisteredParsingObject virtual void get_dynamic_image_from_parametric_image(DynamicDiscretisedDensity& dyn_image, const ParametricVoxelsOnCartesianGrid& par_image) const; + //! Multiplies the dynamic image with the initialization kinetic model gradient. + /*! For a linear model the model gradient is the transpose of the model matrix. + So, the dynamic image is "projected" from time domain to the parameter domain. + + \todo Should be a virtual function declared in the KineticModel class. + Only intended for the initialization of the Generalized Patlak Model EM update estimates + Currently not used but retained for future potential usage. + The initialization of generalized Patlak nested estimates is performed by GeneralizedPatlakPlot equivalent method + */ + virtual void multiply_dynamic_image_with_initialization_model_gradient(Parametric3VoxelsOnCartesianGrid& parametric_image, + const DynamicDiscretisedDensity& dyn_image) const; + + //! Multiplies the dynamic image with the initialization kinetic model gradient and add to original \c parametric_image + /*! \todo Should be a virtual function declared in the KineticModel class. + Only intended for the initialization of the Generalized Patlak Model EM update estimates + Currently not used but retained for future potential usage. + The initialization of generalized Patlak nested estimates is performed by GeneralizedPatlakPlot equivalent method + */ + virtual void + multiply_dynamic_image_with_initialization_model_gradient_and_add_to_input(Parametric3VoxelsOnCartesianGrid& parametric_image, + const DynamicDiscretisedDensity& dyn_image) const; + + //! Multiplies the parametric image with the initialization kinetic model matrix to get the corresponding dynamic image. + /*! \todo Should be a virtual function declared in the KineticModel class. + Only intended for the initialization of the Generalized Patlak Model EM update estimates + Currently not used but retained for future potential usage. + The initialization of generalized Patlak nested estimates is performed by GeneralizedPatlakPlot equivalent method + */ + virtual void get_dynamic_image_from_initialization_parametric_image(DynamicDiscretisedDensity& dyn_image, + const Parametric3VoxelsOnCartesianGrid& par_image) const; + //! This is the common method used to estimate the parametric images from the dynamic images. /*! \todo There is currently no check if the time frame definitions from \a dyn_image are the same as the ones encoded in the model. @@ -126,25 +160,13 @@ class PatlakPlot : public RegisteredParsingObject Succeeded set_up() override; - bool _if_cardiac; //!< Switches between cardiac and brain data - unsigned int _starting_frame; //!< Starting frame to apply the model - float _cal_factor; //!< Calibration Factor, maybe to be removed. - float _time_shift; //!< Shifts the time to fit the timing of Plasma Data with the Projection Data. - bool _in_correct_scale; //!< Switch to scale or not the model_matrix to the correct scale, according to the appropriate scale - //!< factor. - bool _in_total_cnt; //!< Switch to choose the values of the model to be in total counts or in mean counts. - std::string _blood_data_filename; //!< Name of file in which the input function is stored - PlasmaData _plasma_frame_data; //!< Stores the plasma data into frames for brain studies - std::string _time_frame_definition_filename; //!< name of file to get frame definitions - TimeFrameDefinitions _frame_defs; //!< TimeFrameDefinitions - private: - void create_model_matrix(); //!< Creates model matrix from private members + //! Creates model matrix from private members + void create_model_matrix() override; + void initialise_keymap() override; bool post_processing() override; mutable ModelMatrix<2> _model_matrix; - bool _matrix_is_stored; - typedef RegisteredParsingObject base_type; }; END_NAMESPACE_STIR diff --git a/src/include/stir/modelling/PlasmaData.inl b/src/include/stir/modelling/PlasmaData.inl index ea74ff1995..08746dfd92 100644 --- a/src/include/stir/modelling/PlasmaData.inl +++ b/src/include/stir/modelling/PlasmaData.inl @@ -14,6 +14,7 @@ \brief Implementations of inline functions of class stir::PlasmaData \author Charalampos Tsoumpas + \author Nicolas A Karakatsanis */ #include "stir/decay_correction_factor.h" @@ -279,6 +280,34 @@ PlasmaData::get_sample_data_in_frames(TimeFrameDefinitions time_frame_def) plasma_data_in_frames.set_is_decay_corrected(this->_is_decay_corrected); plasma_data_in_frames.set_isotope_halflife(this->_isotope_halflife); plasma_data_in_frames.set_time_frame_definitions(plasma_fdef); + + PlasmaData::const_iterator cur_plasma_frame_iter; + + std::cout << "Plasma data after sorted in user-defined time frames.\n"; + std::cout << "Time frame " + << "Plasma counts " + << "Blood counts \n"; + + for (cur_plasma_frame_iter = plasma_data_in_frames.begin(); cur_plasma_frame_iter != plasma_data_in_frames.end(); + ++cur_plasma_frame_iter) + std::cout << cur_plasma_frame_iter->get_time_in_s() << " " << cur_plasma_frame_iter->get_plasma_counts_in_kBq() + << " " << cur_plasma_frame_iter->get_blood_counts_in_kBq() << " " + << "\n"; + + std::cout << "\n"; + + // for(cur_plasma_frame_iter=plasma_data_in_frames.begin() ; + // cur_plasma_frame_iter!=plasma_data_in_frames.end(); ++cur_plasma_frame_iter ) + // std::cout << "Current frame blood counts: " << cur_plasma_frame_iter->get_blood_counts_in_kBq() << "\n"; + + // std::cout << "\n"; + + // for(cur_plasma_frame_iter=plasma_data_in_frames.begin() ; + // cur_plasma_frame_iter!=plasma_data_in_frames.end(); ++cur_plasma_frame_iter ) + // std::cout << "Current frame times: " << cur_plasma_frame_iter->get_time_in_s() << "\n"; + + // std::cout << "\n"; + return plasma_data_in_frames; } diff --git a/src/include/stir/recon_array_functions.h b/src/include/stir/recon_array_functions.h index 9a56d1cb42..0c0ea4039a 100644 --- a/src/include/stir/recon_array_functions.h +++ b/src/include/stir/recon_array_functions.h @@ -22,6 +22,7 @@ \author Matthew Jacobson \author Kris Thielemans \author PARAPET project + \author Nicolas A Karakatsanis */ @@ -114,5 +115,11 @@ void accumulate_loglikelihood(Viewgram& projection_data, const int rim_truncation_sino, double* accum); +//! compute the log term of the loglikelihood function for given part of the image space +void accumulate_loglikelihood(DiscretisedDensity<3, float>& outer_loop_dyn_image_estimate, + const DiscretisedDensity<3, float>& nested_loop_dyn_image_estimate, + const int rim_truncation_sino, + double* accum); + END_NAMESPACE_STIR #endif // __recon_array_functions_h_ diff --git a/src/include/stir/recon_buildblock/GeneralisedObjectiveFunction.h b/src/include/stir/recon_buildblock/GeneralisedObjectiveFunction.h index 515a945cc3..ed33295715 100644 --- a/src/include/stir/recon_buildblock/GeneralisedObjectiveFunction.h +++ b/src/include/stir/recon_buildblock/GeneralisedObjectiveFunction.h @@ -19,6 +19,7 @@ \author Kris Thielemans \author Robert Twyman Skelly \author Sanida Mustafovic + \author Nicolas A Karakatsanis */ #ifndef __stir_recon_buildblock_GeneralisedObjectiveFunction_H__ @@ -279,6 +280,38 @@ class GeneralisedObjectiveFunction : public RegisteredObject last_nested_estimate_sptr; + //! Read-only access to the prior /*! \todo It would be nicer to not return a pointer. */ diff --git a/src/include/stir/recon_buildblock/PoissonNestedLogLikelihoodWithGeneralizedPatlakAndDynamicProjectionData.h b/src/include/stir/recon_buildblock/PoissonNestedLogLikelihoodWithGeneralizedPatlakAndDynamicProjectionData.h new file mode 100644 index 0000000000..73c80041f6 --- /dev/null +++ b/src/include/stir/recon_buildblock/PoissonNestedLogLikelihoodWithGeneralizedPatlakAndDynamicProjectionData.h @@ -0,0 +1,249 @@ +// +/* + Copyright (C) 2006 - 2011, Hammersmith Imanet Ltd + This file is part of STIR. + + SPDX-License-Identifier: Apache-2.0 + + See STIR/LICENSE.txt for details +*/ +/*! + \file + \ingroup GeneralisedObjectiveFunction + \ingroup modelling + \brief Declaration of class stir::PoissonNestedLogLikelihoodWithGeneralizedPatlakAndDynamicProjectionData + + \author Nicolas A Karakatsanis + +*/ + +#ifndef __stir_recon_buildblock_PoissonNestedLogLikelihoodWithGeneralizedPatlakAndDynamicProjectionData_H__ +#define __stir_recon_buildblock_PoissonNestedLogLikelihoodWithGeneralizedPatlakAndDynamicProjectionData_H__ +#include "stir/RegisteredParsingObject.h" +#include "stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.h" +#include "stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMean.h" +#include "stir/Array.h" +#include "stir/BasicCoordinate.h" +#include "stir/VectorWithOffset.h" +#include "stir/DynamicProjData.h" +#include "stir/DynamicDiscretisedDensity.h" +#include "stir/modelling/ParseAndCreateParametricDiscretisedDensityFrom.h" +#include "stir/modelling/ParametricDiscretisedDensity.h" +#include "stir/modelling/KineticParameters.h" +#include "stir/modelling/GeneralizedPatlakPlot.h" + +START_NAMESPACE_STIR + +/*! + \ingroup GeneralisedObjectiveFunction + \ingroup modelling + \brief a base class for LogLikelihood of independent Poisson variables + where the mean values are non-linear combinations of the kinetic parameters. + + \par Parameters for parsing + +*/ + +template +class PoissonNestedLogLikelihoodWithGeneralizedPatlakAndDynamicProjectionData + : public RegisteredParsingObject, + GeneralisedObjectiveFunction, + PoissonLogLikelihoodWithLinearModelForMean> +{ +private: + typedef RegisteredParsingObject, + GeneralisedObjectiveFunction, + PoissonLogLikelihoodWithLinearModelForMean> + base_type; + typedef PoissonLogLikelihoodWithLinearModelForMeanAndProjData> SingleFrameObjFunc; + VectorWithOffset _single_frame_obj_funcs; + +public: + //! Name which will be used when parsing a GeneralisedObjectiveFunction object + static const char* const registered_name; + + PoissonNestedLogLikelihoodWithGeneralizedPatlakAndDynamicProjectionData(); + + //! Returns a pointer to a newly allocated target object (with 0 data). + /*! Dimensions etc are set from the \a dyn_proj_data_sptr and other information set by parsing, + such as \c zoom, \c output_image_size_z etc. + */ + TargetT* construct_target_ptr() const override; + + // Computes the outer loop gradient after conducting nested iterations + /* At each nested iteration \current_estimate is updated and therefore it + is declared as TargetT and NOT as const TargetT + */ + + void actual_compute_subset_gradient_without_penalty(TargetT& gradient, + const TargetT& current_estimate, + const int subset_num, + const bool add_sensitivity) override; + +protected: + virtual void actual_compute_nested_sub_gradient_without_penalty(TargetT& gradient, + TargetT& current_estimate, + const int subset_num, + const bool add_sensitivity); + + // The nested EM reconstruction method using the standard Patlak model for initialization purposes + // Use this method in GeneralizedPatlak objective function class ONLY for EM initialization + virtual void + initialize_nested_loop_parameters_with_initialization_model(TargetT& parametric_gradient, + TargetT& current_estimate, + DynamicDiscretisedDensity& dyn_image_estimate, + DynamicDiscretisedDensity& dyn_image_reference_data, + DynamicDiscretisedDensity& dyn_image_nested_loop_estimate); + + // The principal nested EM reconstruction method that performs the generalized Patlak reconstruction + // It can be used either (1) for EM initialization, together with standard Patlak EM method, or (2) for regular nested EM + // reconstruction. + virtual void estimate_nested_loop_parameters_with_model(DynamicDiscretisedDensity& impulse_response_gradient, + TargetT& current_estimate, + DynamicDiscretisedDensity& dyn_image_estimate, + DynamicDiscretisedDensity& dyn_image_reference_data, + DynamicDiscretisedDensity& dyn_image_nested_loop_estimate); + + double actual_compute_objective_function_without_penalty(const TargetT& current_estimate, const int subset_num) override; + + Succeeded set_up_before_sensitivity(shared_ptr const& target_sptr) override; + + //! Add subset sensitivity to existing data + /*! \todo Current implementation does NOT add to the subset sensitivity, but overwrites + */ + void add_subset_sensitivity(TargetT& sensitivity, const int subset_num) const override; + + //! Add subset initialization sensitivity to existing initialization data + /*! \todo Current implementation does NOT add to the subset sensitivity, but overwrites + */ + virtual void add_subset_initialization_sensitivity(TargetT& initialization_sensitivity, const int subset_num) const; + + Succeeded actual_add_multiplication_with_approximate_sub_Hessian_without_penalty(TargetT& output, + const TargetT& input, + const int subset_num) const override; + +public: + /*! \name Functions to get parameters + \warning Be careful with changing shared pointers. If you modify the objects in + one place, all objects that use the shared pointer will be affected. + */ + //@{ + const DynamicProjData& get_dyn_proj_data() const; + const shared_ptr& get_dyn_proj_data_sptr() const; + const int get_max_segment_num_to_process() const; + const bool get_zero_seg0_end_planes() const; + const DynamicProjData& get_input_data() const override; + const DynamicProjData& get_additive_dyn_proj_data() const; + const shared_ptr& get_additive_dyn_proj_data_sptr() const; + const ProjectorByBinPair& get_projector_pair() const; + const shared_ptr& get_projector_pair_sptr() const; + const BinNormalisation& get_normalisation() const; + const shared_ptr& get_normalisation_sptr() const; + const DynamicDiscretisedDensity get_model_sensitivity_impulse_response() const; + const TargetT& get_initialization_model_sensitivity_image() const; + const shared_ptr& get_initialization_model_sensitivity_image_sptr() const; + //@} + + /*! \name Functions to set parameters + This can be used as alternative to the parsing mechanism. + \warning After using any of these, you have to call set_up(). + \warning Be careful with setting shared pointers. If you modify the objects in + one place, all objects that use the shared pointer will be affected. + */ + //@{ + void set_recompute_sensitivity(const bool); + void set_sensitivity_sptr(const shared_ptr&); + int set_num_subsets(const int num_subsets) override; + void set_input_data(const shared_ptr&) override; + void set_additive_proj_data_sptr(const shared_ptr&) override; + void set_normalisation_sptr(const shared_ptr&) override; + //@} +protected: + //! Filename with input projection data + std::string _input_filename; + + //! points to the object for the total input projection data + shared_ptr _dyn_proj_data_sptr; + + //! the maximum absolute ring difference number to use in the reconstruction + /*! convention: if -1, use get_max_segment_num()*/ + int _max_segment_num_to_process; + + /**********************/ + ParseAndCreateFrom target_parameter_parser; + + /**********************/ + //! the current subiteration index in the nested loop for EM estimation + // using the generalized Patlak model, i.e. GeneralizedPatlakPlot + int nested_subiterations_num; + + //! the current subiteration index in the nested loop for EM estimation + // using the standard Patlak model, i.e. PatlakPlot + // (for the purpose of proper initialization of the generalized Patlak model estimates) + int nested_initialization_subiterations_num; + + //! restrict updates (larger nested relative updates will be thresholded) + double maximum_nested_relative_change; + + //! restrict updates (smaller nested relative updates will be thresholded) + double minimum_nested_relative_change; + + //! Boolean value to determine whether the current global subiteration is performed under initialization mode + bool is_initialization_subiteration; + + //! Boolean value to determine whether the initialization mode will alternate between + // standard (first) and generalized (second) Patlak nested reconstruction within each initialization iteration + bool is_alternating_initialization_model; + + /********************************/ + //! name of file in which additive projection data are stored + std::string _additive_dyn_proj_data_filename; + //! points to the additive projection data + /*! the projection data in this file is bin-wise added to forward projection results*/ + shared_ptr _additive_dyn_proj_data_sptr; + /*! the normalisation or/and attenuation data */ + shared_ptr _normalisation_sptr; + //! Stores the projectors that are used for the computations + shared_ptr _projector_pair_ptr; + //! signals whether to zero the data in the end planes of the projection data + bool _zero_seg0_end_planes; + + // Patlak Plot Parameters + /*! the generalizedPatlak plot pointer where all the parameters are stored */ + shared_ptr _patlak_plot_sptr; + + //! dynamic image template + DynamicDiscretisedDensity _dyn_image_template; + DynamicDiscretisedDensity _imp_response_image_template; + + BasicCoordinate<2, int> model_array_min, model_array_max; + + // Define a vector for the GenralizedPatlak model sensitivity impulse response + DynamicDiscretisedDensity sensitivity_impulse_response_image; + DynamicDiscretisedDensity impulse_response; + + // Define a shared pointer for the (linear kinetic) model sensitivity image + shared_ptr initialization_model_sensitivity_image_sptr; + + void compute_initialization_model_sensitivity_image(const TargetT& param_image); + + bool actual_subsets_are_approximately_balanced(std::string& warning_message) const override; + + void compute_model_sensitivity_impulse_response(DynamicDiscretisedDensity& impulse_reponse); + + void add_subset_impulse_response_sensitivity(DynamicDiscretisedDensity& impulse_response, const int subset_num) const; + + //! Sets defaults for parsing + /*! Resets \c sensitivity_filename and \c sensitivity_sptr and + \c recompute_sensitivity to \c false. + */ + void set_defaults() override; + void initialise_keymap() override; + bool post_processing() override; +}; + +END_NAMESPACE_STIR + +//#include "stir/recon_buildblock/PoissonNestedLogLikelihoodWithGeneralizedPatlakAndDynamicProjectionData.inl" + +#endif \ No newline at end of file diff --git a/src/include/stir/recon_buildblock/PoissonNestedLogLikelihoodWithGeneralizedPatlakAndDynamicProjectionData.txx b/src/include/stir/recon_buildblock/PoissonNestedLogLikelihoodWithGeneralizedPatlakAndDynamicProjectionData.txx new file mode 100644 index 0000000000..4e354124c7 --- /dev/null +++ b/src/include/stir/recon_buildblock/PoissonNestedLogLikelihoodWithGeneralizedPatlakAndDynamicProjectionData.txx @@ -0,0 +1,1523 @@ +// +/* + Copyright (C) 2006 - 2011, Hammersmith Imanet Ltd + This file is part of STIR. + + SPDX-License-Identifier: Apache-2.0 + + See STIR/LICENSE.txt for details +*/ +/*! + \file + \ingroup GeneralisedObjectiveFunction + \ingroup modelling + \brief Implementation of class stir::PoissonNestedLogLikelihoodWithGeneralizedPatlakAndDynamicProjectionData + + \author Nicolas A Karakatsanis +*/ +#include "stir/DiscretisedDensity.h" +#include "stir/DynamicDiscretisedDensity.h" +#include "stir/modelling/ParametricDiscretisedDensity.h" +#include "stir/modelling/KineticParameters.h" + +#include "stir/Array.h" +#include "stir/is_null_ptr.h" +#include "stir/numerics/divide.h" +#include "stir/thresholding.h" +#include "stir/NumericInfo.h" +#include "stir/recon_buildblock/TrivialBinNormalisation.h" +#include "stir/Succeeded.h" +#include "stir/RelatedViewgrams.h" +#include "stir/stream.h" +#include "stir/recon_buildblock/ProjectorByBinPair.h" +#include "stir/CPUTimer.h" +#include "stir/format.h" +// for get_symmetries_ptr() +#include "stir/DataSymmetriesForViewSegmentNumbers.h" +// include the following to set defaults +#ifndef USE_PMRT +#include "stir/recon_buildblock/ForwardProjectorByBinUsingRayTracing.h" +#include "stir/recon_buildblock/BackProjectorByBinUsingInterpolation.h" +#else +#include "stir/recon_buildblock/ForwardProjectorByBinUsingProjMatrixByBin.h" +#include "stir/recon_buildblock/BackProjectorByBinUsingProjMatrixByBin.h" +#include "stir/recon_buildblock/ProjMatrixByBinUsingRayTracing.h" +#endif +#include "stir/recon_buildblock/ProjectorByBinPairUsingSeparateProjectors.h" + +#include "stir/Succeeded.h" +#include "stir/IO/OutputFileFormat.h" + +#include "stir/Viewgram.h" +#include "stir/recon_array_functions.h" +#include +#include +// For the Patlak Plot Modelling +#include "stir/modelling/GeneralizedPatlakMatrix.h" +#include "stir/modelling/ModelMatrix.h" +#include "stir/recon_buildblock/PoissonNestedLogLikelihoodWithGeneralizedPatlakAndDynamicProjectionData.h" + +#ifndef STIR_NO_NAMESPACES +using std::cerr; +using std::endl; +#endif + +START_NAMESPACE_STIR + +//const float small_num = 0.000001F; + +template +const char * const +PoissonNestedLogLikelihoodWithGeneralizedPatlakAndDynamicProjectionData:: +registered_name = +"PoissonNestedLogLikelihoodWithGeneralizedPatlakAndDynamicProjectionData"; + +template +void +PoissonNestedLogLikelihoodWithGeneralizedPatlakAndDynamicProjectionData:: +set_defaults() +{ + base_type::set_defaults(); + + this->_input_filename=""; + this->_max_segment_num_to_process=-1; + //num_views_to_add=1; // KT 20/06/2001 disabled + + this->_dyn_proj_data_sptr.reset(); + this->_zero_seg0_end_planes = 0; + + this->_additive_dyn_proj_data_filename = "0"; + this->_additive_dyn_proj_data_sptr.reset(); + +#ifndef USE_PMRT // set default for _projector_pair_ptr + shared_ptr forward_projector_ptr(new ForwardProjectorByBinUsingRayTracing()); + shared_ptr back_projector_ptr(new BackProjectorByBinUsingInterpolation()); +#else + shared_ptr PM(new ProjMatrixByBinUsingRayTracing()); + shared_ptr forward_projector_ptr(new ForwardProjectorByBinUsingProjMatrixByBin(PM)); + shared_ptr back_projector_ptr(new BackProjectorByBinUsingProjMatrixByBin(PM)); +#endif + + this->_projector_pair_ptr. + reset(new ProjectorByBinPairUsingSeparateProjectors(forward_projector_ptr, back_projector_ptr)); + this->_normalisation_sptr.reset(new TrivialBinNormalisation); + + this->target_parameter_parser.set_defaults(); + + // A counter measuring all the number of global (not nested) iterations (both initialization and regular recosntruction mode). + this->subiterations_counter=0; + + //Number of nested iterations + this->num_nested_initialization_subiterations=1; + this->num_nested_subiterations=1; + + this->maximum_nested_relative_change = NumericInfo().max_value(); + this->minimum_nested_relative_change = 0; + + // Modelling Stuff + this->_patlak_plot_sptr.reset(); //For kinetic modelling + + // Initializing generalized Patlak Ki, kloss and V parameters from standard Patlak Ki and V parameters (kloss is initialized with zero) + // The following parameter determines the number of complete 4D Standard Patlak iterations required for initialization + // Default value is 1 (i.e. only perform a single standard Patlak iteration (with as many nested iterations + // as defined from "this->this->num_nested_initialization_subiterations" at the first iteration and use generalized Patlak for the subsequent iterations) + this->num_initialization_subiterations=7; + + // A counter measuring the number of global (not nested) iterations executed under kinetic model initialization mode. + this->initialization_subiterations_counter=0; + + // The default choice is only to perform purely standard Patlak nested reconstruction, i.e. + // NOT to alternate between the standard (first) and generalized (afterwards) Patlak at initialization mode. + this->is_alternating_initialization_model=0; + + //Initializing generalized Patlak Ki, kloss and V parameters with a global value + //Currently not used, but it was retained for future usage + //this->global_param_initialization=1; +} + +template +void +PoissonNestedLogLikelihoodWithGeneralizedPatlakAndDynamicProjectionData:: +initialise_keymap() +{ + base_type::initialise_keymap(); + this->parser.add_start_key("PoissonNestedLogLikelihoodWithGeneralizedPatlakAndDynamicProjectionData Parameters"); + this->parser.add_stop_key("End PoissonNestedLogLikelihoodWithGeneralizedPatlakAndDynamicProjectionData Parameters"); + this->parser.add_key("input file",&this->_input_filename); + + // parser.add_key("mash x views", &num_views_to_add); // KT 20/06/2001 disabled + this->parser.add_key("maximum absolute segment number to process", &this->_max_segment_num_to_process); + this->parser.add_key("zero end planes of segment 0", &this->_zero_seg0_end_planes); + + this->target_parameter_parser.add_to_keymap(this->parser); + this->parser.add_parsing_key("Projector pair type", &this->_projector_pair_ptr); + + // Scatter correction + this->parser.add_key("additive sinograms",&this->_additive_dyn_proj_data_filename); + + // normalisation (and attenuation correction) + this->parser.add_parsing_key("Bin Normalisation type", &this->_normalisation_sptr); + + // Modelling Information + this->parser.add_parsing_key("Kinetic Model Type", &this->_patlak_plot_sptr); // Do sth with dynamic_cast to get the GeneralizedPatlakPlot + + // Nested subiterations (regular nested iterations using the Generalized Patlak matrix) + this->parser.add_key("number of nested subiterations", &this->num_nested_subiterations); + + // Regularization Information + // this->parser.add_parsing_key("prior type", &this->_prior_sptr); + + // Global initial subiterations for initialization (using standard Patlak model, i.e. ModelMatrix) before switching to Generalized Patlak iterations + this->parser.add_key("number of global initialization subiterations", &this->num_initialization_subiterations); + + // Nested subiterations for initialization (using standard Patlak model, i.e. ModelMatrix instead of GenearlizedPatlakMatrix) + this->parser.add_key("number of nested initialization subiterations", &this->num_nested_initialization_subiterations); + + // Choose whether both standard and generalized Patlak models + // (1) will be used in an alternating fashion for the initialization or + // (0) only the standard Patlak model will be utilized. + this->parser.add_key("alternating initialization mode", &this->is_alternating_initialization_model); + + //max and min allowed relative change between nested updates + this->parser.add_key("maximum nested relative change", &this->maximum_nested_relative_change); + this->parser.add_key("minimum nested relative change", &this->minimum_nested_relative_change); + + //Initializing generalized Patlak parameters Ki, kloss and V with a global value + //Currently not defined, but it is retained for future usage + //this->parser.add_key("parameters initialization global value", &this->global_param_initialization); +} + +template +bool +PoissonNestedLogLikelihoodWithGeneralizedPatlakAndDynamicProjectionData:: +post_processing() +{ + if (base_type::post_processing() == true) + return true; + if (this->_input_filename.length() == 0) + { + warning("You need to specify an input filename"); + return true; + } + +#if 0 // KT 20/06/2001 disabled as not functional yet + if (num_views_to_add!=1 && (num_views_to_add<=0 || num_views_to_add%2 != 0)) + { warning("The 'mash x views' key has an invalid value (must be 1 or even number)"); return true; } +#endif + + this->_dyn_proj_data_sptr = DynamicProjData::read_from_file(_input_filename); + if (is_null_ptr(this->_dyn_proj_data_sptr)) + { + warning(format("Error reading input file {}", _input_filename)); + return true; + } + + this->target_parameter_parser.check_values(); + + if (this->_additive_dyn_proj_data_filename != "0") + { + info(format("Reading additive projdata data {}", this->_additive_dyn_proj_data_filename)); + this->_additive_dyn_proj_data_sptr = DynamicProjData::read_from_file(this->_additive_dyn_proj_data_filename); + if (is_null_ptr(this->_additive_dyn_proj_data_sptr)) + { + warning(format("Error reading additive input file {}", _additive_dyn_proj_data_filename)); + return true; + } + + } + + if (!this->initial_data_filename.empty() && this->initial_data_filename != "1" + && this->num_initialization_subiterations > 0) + { + warning("An initial estimate was supplied and 'number of global initialization subiterations' is non-zero. " + "These serve the same purpose; the initialization sub-iterations will re-run standard Patlak " + "updates on an estimate that is presumably already initialised. " + "Skipping initialization loops"); + this->num_initialization_subiterations = 0; + } + return false; +} + +template +PoissonNestedLogLikelihoodWithGeneralizedPatlakAndDynamicProjectionData:: +PoissonNestedLogLikelihoodWithGeneralizedPatlakAndDynamicProjectionData() +{ + this->set_defaults(); +} + +template +TargetT * +PoissonNestedLogLikelihoodWithGeneralizedPatlakAndDynamicProjectionData:: +construct_target_ptr() const +{ + return this->target_parameter_parser.create(this->get_input_data()); +} +/*************************************************************** + subset balancing +***************************************************************/ + +template +bool +PoissonNestedLogLikelihoodWithGeneralizedPatlakAndDynamicProjectionData:: +actual_subsets_are_approximately_balanced(std::string& warning_message) const +{ // call actual_subsets_are_approximately_balanced( for first single_frame_obj_func ) + if (this->_patlak_plot_sptr->get_time_frame_definitions().get_num_frames() == 0 || this->_single_frame_obj_funcs.size() == 0) + error("PoissonNestedLogLikelihoodWithGeneralizedPatlakAndDynamicProjectionData:\n" + "actual_subsets_are_approximately_balanced called but not frames yet.\n"); + else if(this->_single_frame_obj_funcs.size() != 0) + { + bool frames_are_balanced=true; + for(unsigned int frame_num=this->_patlak_plot_sptr->get_starting_frame();frame_num<=this->_patlak_plot_sptr->get_time_frame_definitions().get_num_frames();++frame_num) + frames_are_balanced &= this->_single_frame_obj_funcs[frame_num].subsets_are_approximately_balanced(warning_message); + return frames_are_balanced; + } + else + warning("Something strange happened in PoissonNestedLogLikelihoodWithGeneralizedPatlakAndDynamicProjectionData:\n" + "actual_subsets_are_approximately_balanced called before setup()?\n"); + return + false; +} + +/*************************************************************** + get_ functions +***************************************************************/ +template +const DynamicProjData& +PoissonNestedLogLikelihoodWithGeneralizedPatlakAndDynamicProjectionData:: +get_dyn_proj_data() const +{ return *this->_dyn_proj_data_sptr; } + +template +const shared_ptr& +PoissonNestedLogLikelihoodWithGeneralizedPatlakAndDynamicProjectionData:: +get_dyn_proj_data_sptr() const +{ return this->_dyn_proj_data_sptr; } + +template +const int +PoissonNestedLogLikelihoodWithGeneralizedPatlakAndDynamicProjectionData:: +get_max_segment_num_to_process() const +{ return this->_max_segment_num_to_process; } + +template +const bool +PoissonNestedLogLikelihoodWithGeneralizedPatlakAndDynamicProjectionData:: +get_zero_seg0_end_planes() const +{ return this->_zero_seg0_end_planes; } + +template +const DynamicProjData& +PoissonNestedLogLikelihoodWithGeneralizedPatlakAndDynamicProjectionData:: +get_additive_dyn_proj_data() const +{ return *this->_additive_dyn_proj_data_sptr; } + +template +const shared_ptr& +PoissonNestedLogLikelihoodWithGeneralizedPatlakAndDynamicProjectionData:: +get_additive_dyn_proj_data_sptr() const +{ return this->_additive_dyn_proj_data_sptr; } + +template +const ProjectorByBinPair& +PoissonNestedLogLikelihoodWithGeneralizedPatlakAndDynamicProjectionData:: +get_projector_pair() const +{ return *this->_projector_pair_ptr; } + +template +const shared_ptr& +PoissonNestedLogLikelihoodWithGeneralizedPatlakAndDynamicProjectionData:: +get_projector_pair_sptr() const +{ return this->_projector_pair_ptr; } + +template +const BinNormalisation& +PoissonNestedLogLikelihoodWithGeneralizedPatlakAndDynamicProjectionData:: +get_normalisation() const +{ return *this->_normalisation_sptr; } + +template +const shared_ptr& +PoissonNestedLogLikelihoodWithGeneralizedPatlakAndDynamicProjectionData:: +get_normalisation_sptr() const +{ return this->_normalisation_sptr; } + + +template +const DynamicDiscretisedDensity +PoissonNestedLogLikelihoodWithGeneralizedPatlakAndDynamicProjectionData:: +get_model_sensitivity_impulse_response() const +{ + return this->sensitivity_impulse_response_image; +} + +template +const TargetT& +PoissonNestedLogLikelihoodWithGeneralizedPatlakAndDynamicProjectionData:: +get_initialization_model_sensitivity_image() const +{ + return *this->initialization_model_sensitivity_image_sptr; +} + +template +const shared_ptr& +PoissonNestedLogLikelihoodWithGeneralizedPatlakAndDynamicProjectionData:: +get_initialization_model_sensitivity_image_sptr() const +{ + return this->initialization_model_sensitivity_image_sptr; +} + +template +const DynamicProjData& +PoissonNestedLogLikelihoodWithGeneralizedPatlakAndDynamicProjectionData::get_input_data() const +{ + return *this->_dyn_proj_data_sptr; +} + +/*************************************************************** + set_ functions +***************************************************************/ +template +int +PoissonNestedLogLikelihoodWithGeneralizedPatlakAndDynamicProjectionData:: +set_num_subsets(const int num_subsets) +{ + for(unsigned int frame_num=this->_patlak_plot_sptr->get_starting_frame();frame_num<=this->_patlak_plot_sptr->get_time_frame_definitions().get_num_frames();++frame_num) + { + if(this->_single_frame_obj_funcs.size() != 0) + if(this->_single_frame_obj_funcs[frame_num].set_num_subsets(num_subsets) != num_subsets) + error("set_num_subsets didn't work"); + } + this->num_subsets=num_subsets; + return this->num_subsets; +} + +template +void +PoissonNestedLogLikelihoodWithGeneralizedPatlakAndDynamicProjectionData::set_input_data(const shared_ptr& arg) +{ + this->_dyn_proj_data_sptr = dynamic_pointer_cast(arg); +} + +template +void +PoissonNestedLogLikelihoodWithGeneralizedPatlakAndDynamicProjectionData::set_additive_proj_data_sptr( + const shared_ptr& arg) +{ + this->_additive_dyn_proj_data_sptr = dynamic_pointer_cast(arg); +} + +template +void +PoissonNestedLogLikelihoodWithGeneralizedPatlakAndDynamicProjectionData::set_normalisation_sptr( + const shared_ptr& arg) +{ + // this->normalisation_sptr = arg; + error("Not implemeted yet"); +} + +/*************************************************************** + set_up() +***************************************************************/ +template +Succeeded +PoissonNestedLogLikelihoodWithGeneralizedPatlakAndDynamicProjectionData:: +set_up_before_sensitivity(shared_ptr const& target_sptr) +{ + if (this->_max_segment_num_to_process==-1) + this->_max_segment_num_to_process = + (this->_dyn_proj_data_sptr)->get_proj_data_sptr(1)->get_max_segment_num(); + + if (this->_max_segment_num_to_process > (this->_dyn_proj_data_sptr)->get_proj_data_sptr(1)->get_max_segment_num()) + { + warning("_max_segment_num_to_process (%d) is too large", + this->_max_segment_num_to_process); + return Succeeded::no; + } + + shared_ptr proj_data_info_sptr( + (this->_dyn_proj_data_sptr->get_proj_data_sptr(1))->get_proj_data_info_sptr()->clone()); + proj_data_info_sptr-> + reduce_segment_range(-this->_max_segment_num_to_process, + +this->_max_segment_num_to_process); + + if (is_null_ptr(this->_projector_pair_ptr)) + { + warning("You need to specify a projector pair"); + return Succeeded::no; } + + if (this->num_subsets <= 0) + { + warning("Number of subsets %d should be larger than 0.", + this->num_subsets); + return Succeeded::no; + } + + if (is_null_ptr(this->_normalisation_sptr)) + { + warning("Invalid normalisation object"); + return Succeeded::no; + } + + if (this->_normalisation_sptr->set_up(this->get_dyn_proj_data_sptr()->get_exam_info_sptr(), + proj_data_info_sptr) == Succeeded::no) + return Succeeded::no; + + // this->_patlak_plot_sptr->set_radionuclide(this->get_dyn_proj_data_sptr()->get_exam_info_sptr()->get_radionuclide()); + + if (this->_patlak_plot_sptr->set_up() == Succeeded::no) + { + warning("Generalized Patlak Plot set up did not succeed!"); + return Succeeded::no; + } + + if (this->_patlak_plot_sptr->get_starting_frame()<=0 || this->_patlak_plot_sptr->get_starting_frame()>this->_patlak_plot_sptr->get_time_frame_definitions().get_num_frames()) + { + warning("Starting frame is %d. Generally, it should be a late frame,\nbut in any case it should be less than the number of frames %d\nand at least 1.",this->_patlak_plot_sptr->get_starting_frame(), this->_patlak_plot_sptr->get_time_frame_definitions().get_num_frames()); + return Succeeded::no; + } + + { + const shared_ptr > + density_template_sptr((target_sptr->construct_single_density(1)).get_empty_copy()); + const shared_ptr scanner_sptr(new Scanner(*proj_data_info_sptr->get_scanner_ptr())); + this->_dyn_image_template= + DynamicDiscretisedDensity(this->_patlak_plot_sptr->get_time_frame_definitions(), + this->_dyn_proj_data_sptr->get_start_time_in_secs_since_1970(), + scanner_sptr, + density_template_sptr); + this->_imp_response_image_template= + DynamicDiscretisedDensity(this->_patlak_plot_sptr->get_time_frame_definitions(), + this->_patlak_plot_sptr->get_num_conv_params(), + this->_dyn_proj_data_sptr->get_start_time_in_secs_since_1970(), + scanner_sptr, + density_template_sptr); + + //Initialize an impulse reponse vector according to the GeneralizedPatlak plot model matrix + if(!((this->_patlak_plot_sptr->get_model_matrix()).get_model_array()).get_regular_range(this->model_array_min,this->model_array_max)) + error("Model array has not regular range"); + + this->impulse_response=this->_imp_response_image_template; + this->sensitivity_impulse_response_image=this->_imp_response_image_template; + + for(int conv_param_num = model_array_min[1];conv_param_num<=model_array_max[1] ; ++conv_param_num) + std::fill(this->impulse_response[conv_param_num].begin_all(), + this->impulse_response[conv_param_num].end_all(), + 1.F); + + for(int conv_param_num = model_array_min[1];conv_param_num<=model_array_max[1] ; ++conv_param_num) + std::fill(this->sensitivity_impulse_response_image[conv_param_num].begin_all(), + this->sensitivity_impulse_response_image[conv_param_num].end_all(), + 1.F); + + //Computes model sensitivity image by utilizing GeneralizedPatlak plot model matrix + this->compute_model_sensitivity_impulse_response(this->sensitivity_impulse_response_image); + + //Computes initialization model sensitivity image by utilizing the Generalized Patlak plot model matrix initialization methods + this->compute_initialization_model_sensitivity_image(*target_sptr); + + //this->impulse_response = VectorWithOffset(this->model_array_min[1],this->model_array_max[1]); + //this->sensitivity_impulse_response_vector = VectorWithOffset(this->model_array_min[1],this->model_array_max[1]); + + //By default during set-up we start the first iteration with initialization mode. + //However, if user selects "this->num_initialization_subiterations=0" at the par file, then no initialization mode is activated at all. + this->is_initialization_subiteration=true; + + // construct _single_frame_obj_funcs + this->_single_frame_obj_funcs.resize(this->_patlak_plot_sptr->get_starting_frame(),this->_patlak_plot_sptr->get_time_frame_definitions().get_num_frames()); + + for(unsigned int frame_num=this->_patlak_plot_sptr->get_starting_frame();frame_num<=this->_patlak_plot_sptr->get_time_frame_definitions().get_num_frames();++frame_num) + { + this->_single_frame_obj_funcs[frame_num].set_projector_pair_sptr(this->_projector_pair_ptr); + this->_single_frame_obj_funcs[frame_num].set_proj_data_sptr(this->_dyn_proj_data_sptr->get_proj_data_sptr(frame_num)); + this->_single_frame_obj_funcs[frame_num].set_max_segment_num_to_process(this->_max_segment_num_to_process); + this->_single_frame_obj_funcs[frame_num].set_zero_seg0_end_planes(this->_zero_seg0_end_planes!=0); + if(this->_additive_dyn_proj_data_sptr!=NULL) + this->_single_frame_obj_funcs[frame_num].set_additive_proj_data_sptr(this->_additive_dyn_proj_data_sptr->get_proj_data_sptr(frame_num)); + this->_single_frame_obj_funcs[frame_num].set_num_subsets(this->num_subsets); + this->_single_frame_obj_funcs[frame_num].set_frame_num(frame_num); + this->_single_frame_obj_funcs[frame_num].set_frame_definitions(this->_patlak_plot_sptr->get_time_frame_definitions()); + this->_single_frame_obj_funcs[frame_num].set_normalisation_sptr(this->_normalisation_sptr); + this->_single_frame_obj_funcs[frame_num].set_recompute_sensitivity(this->get_recompute_sensitivity()); + this->_single_frame_obj_funcs[frame_num].set_use_subset_sensitivities(this->get_use_subset_sensitivities()); + if(this->_single_frame_obj_funcs[frame_num].set_up(density_template_sptr) != Succeeded::yes) + error("Single frame objective functions is not set correctly!"); + } + }//_single_frame_obj_funcs[frame_num] + + return Succeeded::yes; +} + +/************************************************************************* + functions that compute the value/gradient of the objective function etc +*************************************************************************/ + +template +void +PoissonNestedLogLikelihoodWithGeneralizedPatlakAndDynamicProjectionData:: +actual_compute_subset_gradient_without_penalty(TargetT& gradient, + const TargetT ¤t_estimate, + const int subset_num, + const bool add_sensitivity) +{ + + if (!add_sensitivity) + error("Not supported for nested Patlak objective functions; use OSMAPOSL/POSMAPOSL-style callers"); + + // Clone the const TargetT& current estimate to a TargetT nested_estimate + shared_ptr current_nested_estimate(current_estimate.get_empty_copy()); + + { + typename TargetT::const_full_iterator current_estimate_iter = current_estimate.begin_all_const(); + const typename TargetT::const_full_iterator end_current_estimate_iter = current_estimate.end_all_const(); + typename TargetT::full_iterator current_nested_estimate_iter = current_nested_estimate->begin_all(); + while (current_estimate_iter!=end_current_estimate_iter) + { + *current_nested_estimate_iter = (*current_estimate_iter); + ++current_nested_estimate_iter; ++current_estimate_iter; + } + } + + this->actual_compute_nested_sub_gradient_without_penalty(gradient, + *current_nested_estimate, + subset_num, add_sensitivity); + + this->last_nested_estimate_sptr = current_nested_estimate; + +} + +template +void +PoissonNestedLogLikelihoodWithGeneralizedPatlakAndDynamicProjectionData:: +actual_compute_nested_sub_gradient_without_penalty(TargetT& gradient, + TargetT ¤t_estimate, + const int subset_num, + const bool add_sensitivity) +{ + assert(subset_num>=0); + assert(subset_numnum_subsets); + + DynamicDiscretisedDensity dyn_gradient=this->_dyn_image_template; + DynamicDiscretisedDensity dyn_image_estimate=this->_dyn_image_template; + DynamicDiscretisedDensity dyn_image_reference_data=this->_dyn_image_template; + DynamicDiscretisedDensity dyn_image_nested_loop_estimate=this->_dyn_image_template; + DynamicDiscretisedDensity dyn_sensitivity=this->_dyn_image_template; + + DynamicDiscretisedDensity impulse_response_gradient=this->_imp_response_image_template; + + for(unsigned int frame_num=this->_patlak_plot_sptr->get_starting_frame();frame_num<=this->_patlak_plot_sptr->get_time_frame_definitions().get_num_frames();++frame_num) + std::fill(dyn_image_estimate[frame_num].begin_all(), + dyn_image_estimate[frame_num].end_all(), + 1.F); + + for(int conv_param_num = model_array_min[1];conv_param_num<=model_array_max[1] ; ++conv_param_num) + std::fill(impulse_response_gradient[conv_param_num].begin_all(), + impulse_response_gradient[conv_param_num].end_all(), + 1.F); + + //The counter below measures all the global subiterations (both the initialization and the regular ones) + this->subiterations_counter++; + + // Only under initialization mode, i.e. when this->is_initialization_subiteration==true, initialize parametric image estimate with standard Patlak + // By default during set-up we start the first iteration with initialization mode and + // continue for as many iterations as determined by user-defined parameter: "this->num_initialization_subiterations". + // However, if user selects "this->num_initialization_subiterations=0" at the par file, then no initialization mode is activated at all. + if (this->is_initialization_subiteration) + { + this->initialization_subiterations_counter++; + + if (this->initialization_subiterations_counter>this->num_initialization_subiterations) + this->is_initialization_subiteration=false; + } + + //Print out the min and max values of the initial parametric estimate after initialization + const float current_min_estimate = + *std::min_element(current_estimate.begin_all(), + current_estimate.end_all()); + const float current_max_estimate = + *std::max_element(current_estimate.begin_all(), + current_estimate.end_all()); + cerr << "Initial parametric image " + << ", (min, max): (" << current_min_estimate << ", " << current_max_estimate << ")" << endl; + + if (this->is_initialization_subiteration) + { + //At initialization mode, standard Patlak model is used and, therefore, dynamic image can be directly obtained from parametric image estimate. + this->_patlak_plot_sptr->get_dynamic_image_from_initialization_parametric_image(dyn_image_estimate, + current_estimate); + } + else + { + + //At regular mode, we can indirectly (through imp response) forward project from parameter space to time space using a parametric image estimate as an input + //Not used any more, since the indirect fwd proj was broken down to two dicrete steps: parametric image->imp response image->dynamic image + //this->_patlak_plot_sptr->get_dynamic_image_from_parametric_image(dyn_image_estimate,current_estimate); + + //At regular mode, synthesize initial impulse response image from previous full iteration parametric image estimate + this->_patlak_plot_sptr->get_impulse_response_from_parametric_image(this->impulse_response,current_estimate); + + //Print out the min and max values of initial impulse response (only for first and last convolution time point) + cerr << " Initial impulse response current value for current full iteration [conv point](min, max):" << endl; + + for(int conv_param_num = model_array_min[1];conv_param_num<=model_array_max[1] ; ++conv_param_num) + { + if ((conv_param_num==model_array_min[1]) || (conv_param_num==model_array_max[1])) + { + const float current_min_initial_impulse_response = + *std::min_element(this->impulse_response[conv_param_num].begin_all(), + this->impulse_response[conv_param_num].end_all()); + const float current_max_initial_impulse_response = + *std::max_element(this->impulse_response[conv_param_num].begin_all(), + this->impulse_response[conv_param_num].end_all()); + + cerr << " [" << conv_param_num << "](" << current_min_initial_impulse_response << ", " << current_max_initial_impulse_response << ") " << endl; + } + } + + //Then convolve the initial impulse response with the input function matrix to get the dynamic images (2nd step of kinetic forward projection) + this->_patlak_plot_sptr->get_dynamic_image_from_impulse_response(dyn_image_estimate,this->impulse_response); + + } + + dyn_image_reference_data = dyn_image_estimate; + + CPUTimer outer_loop_timer; + outer_loop_timer.start(); + + // loop over single_frame and use model_matrix + for(unsigned int frame_num=this->_patlak_plot_sptr->get_starting_frame();frame_num<=this->_patlak_plot_sptr->get_time_frame_definitions().get_num_frames();++frame_num) + { + //Printing out the min and max values of each frame of the forward projected dynamic image (current dyn frame) + const float min_dyn_image_estimate = + *std::min_element(dyn_image_estimate[frame_num].begin_all(), + dyn_image_estimate[frame_num].end_all()); + const float max_dyn_image_estimate = + *std::max_element(dyn_image_estimate[frame_num].begin_all(), + dyn_image_estimate[frame_num].end_all()); + cerr << "Forward projected dynamic image outer loop estimate for frame: " << frame_num << " (min, max): (" + << min_dyn_image_estimate << ", " << max_dyn_image_estimate + << ") " << endl; + + //Get system sensitivity for each dynamic frame + cerr << "Getting system sub-sensitivity image for dynamic frame: " << frame_num << "..." << endl; + dyn_sensitivity[frame_num]=this->_single_frame_obj_funcs[frame_num].get_subset_sensitivity(subset_num); + + //Print out the min and max values of the system dynamic sensitivity image + const float current_min_system_dyn_sensitivity = + *std::min_element(dyn_sensitivity[frame_num].begin_all(), + dyn_sensitivity[frame_num].end_all()); + const float current_max_system_dyn_sensitivity = + *std::max_element(dyn_sensitivity[frame_num].begin_all(), + dyn_sensitivity[frame_num].end_all()); + cerr << "System sensitivity image for dynamic frame: " << frame_num + << ", (min, max): (" << current_min_system_dyn_sensitivity << ", " << current_max_system_dyn_sensitivity << ")" << endl; + + //Compute sub-gradient for each frame + cerr << "Compute sub-gradient (update image) for dynamic frame: " << frame_num << "." << endl; + std::fill(dyn_gradient[frame_num].begin_all(), + dyn_gradient[frame_num].end_all(), + 1.F); + + this->_single_frame_obj_funcs[frame_num]. + actual_compute_subset_gradient_without_penalty(dyn_gradient[frame_num], + dyn_image_estimate[frame_num], + subset_num, add_sensitivity); + + //Print out the min and max values of the sub-gradient for each fynamic frame + const float current_min_outer_loop_gradient = + *std::min_element(dyn_gradient[frame_num].begin_all(), + dyn_gradient[frame_num].end_all()); + const float current_max_outer_loop_gradient = + *std::max_element(dyn_gradient[frame_num].begin_all(), + dyn_gradient[frame_num].end_all()); + cerr << "Outer loop dynamic sub-gradient image (frame " << frame_num + << "), (min, max): (" << current_min_outer_loop_gradient << ", " << current_max_outer_loop_gradient << ")" << endl; + + // Perform projection matrix sensitivity division and update for the single outer loop iteration + + // Devide by system matrix sensitivity + cerr << "Divide sub-gradient (update image) by system sub-sensitivity for dynamic frame " << frame_num << "." << endl; + divide(dyn_gradient[frame_num].begin_all(), + dyn_gradient[frame_num].end_all(), + dyn_sensitivity[frame_num].begin_all(), + small_num); + + //Print out the min and max values of the sub-gradient/sensitivity for each fynamic frame + const float current_min_outer_loop_gradient_over_sensitivity = + *std::min_element(dyn_gradient[frame_num].begin_all(), + dyn_gradient[frame_num].end_all()); + const float current_max_outer_loop_gradient_over_sensitivity = + *std::max_element(dyn_gradient[frame_num].begin_all(), + dyn_gradient[frame_num].end_all()); + cerr << "Outer loop dynamic sub-gradient/sensitivity image (frame " << frame_num + << "), (min, max): (" << current_min_outer_loop_gradient_over_sensitivity << ", " << current_max_outer_loop_gradient_over_sensitivity << ")" << endl; + + // Update outer loop dynamic image estimate + cerr << "Update dynamic image frame estimate " << frame_num << " with the sub-gradient (update image) of dynamic frame " << frame_num << "." << endl; + DiscretisedDensity<3,float>::const_full_iterator dyn_gradient_single_frame_iter = dyn_gradient[frame_num].begin_all_const(); + DiscretisedDensity<3,float>::const_full_iterator end_dyn_gradient_single_frame_iter = dyn_gradient[frame_num].end_all_const(); + DiscretisedDensity<3,float>::full_iterator dyn_image_reference_data_single_frame_iter = dyn_image_reference_data[frame_num].begin_all(); + while (dyn_gradient_single_frame_iter!=end_dyn_gradient_single_frame_iter) + { + *dyn_image_reference_data_single_frame_iter *= (*dyn_gradient_single_frame_iter); + ++dyn_image_reference_data_single_frame_iter; ++dyn_gradient_single_frame_iter; + } + + //Print out the min and max values of the outer loop updated dynamic images for each fynamic frame + const float current_min_outer_loop_updated_image = + *std::min_element(dyn_image_reference_data[frame_num].begin_all(), + dyn_image_reference_data[frame_num].end_all()); + const float current_max_outer_loop_updated_image = + *std::max_element(dyn_image_reference_data[frame_num].begin_all(), + dyn_image_reference_data[frame_num].end_all()); + cerr << "Outer loop updated image (frame): " << frame_num + << ", (min, max): (" << current_min_outer_loop_updated_image << ", " << current_max_outer_loop_updated_image << ")" << endl << endl << endl; + + } + + cerr << "Current outer loop computation time: " << outer_loop_timer.value() << endl << endl; + + + if (this->is_initialization_subiteration) + { + //ITERATIVE NESTED INITIALIZATION MODE + + if (this->initialization_subiterations_counter==1) + { + cerr << endl << endl << "ENTERING 4D RECONSTRUCTION INITIALIZATION MODE." << endl << endl; + cerr << "NOTE regarding kloss initialization:" << endl + << "User has opted for model initialization with standard Patlak model, i.e. kloss is initialized with ZEROS regardless of users initial kloss estimate" << endl + << "If user wishes to initialize kloss with their own NON-ZERO kloss value (not recommended, unless they have a pretty good estimate of true kloss), then they should BOTH: " << endl + << "1) Deactivate initialization mode (not recommended) AND " << endl + << "2) Specify their own initial parametric image estimates (exercise with caution)." << endl + << "In any other case, kloss image will be initialized with ZEROS (recommended), before regular 4D reconstruction mode. " << endl << endl; + + if (this->is_alternating_initialization_model) + cerr << "ALTERNATING INITIALIZATION MODE IS ON." << endl + << "After " << this->num_nested_initialization_subiterations << " initialization standard Patlak nested EM iterations, " + << this->num_nested_subiterations << " initialization generalized Patlak nested EM iterations will follow, within each full initialization iteration." << endl << endl + << "Please NOTE that the number of nested generalized Patlak iterations utilized within each initialization full iteration are always equal to " << endl + << "the number of nested generalized Patlak iterations selected by the user for the regular full iterations" << endl << endl; + else + cerr << "ALTERNATING INITIALIZATION MODE IS OFF." << endl << endl; + } + + //Only at initialization mode perform standard Patlak nested loop updates of the parametric image estimates. + + CPUTimer nested_initialization_loop_timer; + nested_initialization_loop_timer.start(); + + //Entering nested EM initialization loop + + // This method iteratively estimates standard Patlak estimates in a nested initialization EM loop to properly + // initialize the estimates passed to the the next nested loop which performs the regular generalized Patlak reconstruction + this->initialize_nested_loop_parameters_with_initialization_model(gradient, + current_estimate, + dyn_image_estimate, + dyn_image_reference_data, + dyn_image_nested_loop_estimate); + + cerr << "Total computation time for " << this->num_nested_initialization_subiterations + << " initialization standard Patlak nested EM initialization iterations: " << nested_initialization_loop_timer.value() << endl << endl; + + // If alternating initialization is activated, also perform generalized Patlak nested loop updates + // after the standard Patlak updates, at each global initialization iteration. + if (this->is_alternating_initialization_model) + { + CPUTimer nested_loop_timer; + nested_loop_timer.start(); + + cerr << "Switching from standard Patlak initialization to generalized Patlak initialization iterations. " << endl << endl; + + // This is the principal method that iteratively estimates the generalized Patlak estimates in a nested EM loop + // At this section of the code, nested EM generalized Patlak updates are alternating, for initialization purposes, with nested EM standard Patlak updates + this->estimate_nested_loop_parameters_with_model(impulse_response_gradient, + current_estimate, + dyn_image_estimate, + dyn_image_reference_data, + dyn_image_nested_loop_estimate); + + cerr << "Total computation time for " << this->num_nested_subiterations + << " generalized Patlak nested EM initialization iterations: " << nested_loop_timer.value() << endl << endl; + } + + } + else + { + //ITERATIVE NESTED REGULAR RECONSTRUCTION MODE + + if (this->subiterations_counter==this->num_initialization_subiterations+1) + cerr << endl << endl << "ENTERING REGULAR 4D RECONSTRUCTION MODE." << endl << endl + << "Only generalized Patlak nested EM updates are performed onwards." << endl << endl + << "NOTE regarding kloss initialization: " << endl + << "Unless user has opted for: " << endl + << "1) for NO model initialization (not recommended) AND " << endl + << "2) their OWN NON-ZERO kloss initial estimate (exercise caution), then" << endl + << "kloss image is initialized with ZEROS (recommended), by default, before the first nested EM generalized Patlak loop." << endl << endl; + + CPUTimer nested_loop_timer; + nested_loop_timer.start(); + + // This is the principal method that iteratively estimates the generalized Patlak estimates in a nested EM loop + this->estimate_nested_loop_parameters_with_model(impulse_response_gradient, + current_estimate, + dyn_image_estimate, + dyn_image_reference_data, + dyn_image_nested_loop_estimate); + + cerr << "Total computation time for " << this->num_nested_subiterations + << " generalized Patlak nested EM iterations: " << nested_loop_timer.value() << endl << endl; + } +} + +template +void +PoissonNestedLogLikelihoodWithGeneralizedPatlakAndDynamicProjectionData:: +estimate_nested_loop_parameters_with_model(DynamicDiscretisedDensity &impulse_response_gradient, + TargetT ¤t_estimate, + DynamicDiscretisedDensity &dyn_image_estimate, + DynamicDiscretisedDensity &dyn_image_reference_data, + DynamicDiscretisedDensity &dyn_image_nested_loop_estimate) +{ + + // Now synthesize the initial impulse response before the first nested generalized Patlak iteration + // using as initial estimates the last iteration's parametric image estimates (Initialization step of the nested EM loop) + cerr << endl << "Synthesizing the initial impulse response from the previous generalized Patlak parameter estimates ..." << endl << endl; + this->_patlak_plot_sptr->get_impulse_response_from_parametric_image(this->impulse_response,current_estimate); + + //Print out the min and max values of initial impulse response (only for first and last convolution time point) + cerr << "Initialized impulse response value [conv point](min, max):" << endl; + + for(int conv_param_num = model_array_min[1];conv_param_num<=model_array_max[1] ; ++conv_param_num) + { + if ((conv_param_num==model_array_min[1]) || (conv_param_num==model_array_max[1])) + { + const float current_min_nested_initial_impulse_response = + *std::min_element(this->impulse_response[conv_param_num].begin_all(), + this->impulse_response[conv_param_num].end_all()); + const float current_max_nested_initial_impulse_response = + *std::max_element(this->impulse_response[conv_param_num].begin_all(), + this->impulse_response[conv_param_num].end_all()); + + cerr << " [" << conv_param_num << "](" << current_min_nested_initial_impulse_response << ", " << current_max_nested_initial_impulse_response << ") " << endl; + } + } + cerr << endl; + + //nested EM loop + cerr << endl << "Entering nested loop (" << this->num_nested_subiterations << " generalized Patlak EM subiterations)." << endl; + + for(nested_subiterations_num=1;nested_subiterations_num<=this->num_nested_subiterations; nested_subiterations_num++) + { + + //Print out the min and max values of the initial parametric estimate at the beginning of each nested update + const float current_min_estimate_nested = + *std::min_element(current_estimate.begin_all(), + current_estimate.end_all()); + const float current_max_estimate_nested = + *std::max_element(current_estimate.begin_all(), + current_estimate.end_all()); + cerr << "Nested iteration: " << nested_subiterations_num + << " Initial parametric image for the current nested iteration " + << ", (min, max): (" << current_min_estimate_nested << ", " << current_max_estimate_nested << ")" << endl; + + + // This function is used to directly obtain dynamic images from the parametric image estimates. + // Not used anymore, as the process has been broken down to two steps: parametric image->impulse response image->dynamic image + //this->_patlak_plot_sptr->get_dynamic_image_from_parametric_image(dyn_image_nested_loop_estimate,current_estimate) ; + + // First synthesize the initial impulse response at each nested iteration from the current parametric image estimate (1st step of kinetic forward projection) + // Not used anymore, as the impulse response image is directly loaded from previous nested impulse response estimate (to speed-up nested EM update) + //this->_patlak_plot_sptr->get_impulse_response_from_parametric_image(this->impulse_response,current_estimate); + + //Print out the min and max values of initial impulse response (only for first and last convolution time point) + cerr << "Nested iteration: " << nested_subiterations_num << " initial impulse response current value [conv point](min, max):" << endl; + + for(int conv_param_num = model_array_min[1];conv_param_num<=model_array_max[1] ; ++conv_param_num) + { + if ((conv_param_num==model_array_min[1]) || (conv_param_num==model_array_max[1])) + { + const float current_min_nested_initial_impulse_response = + *std::min_element(this->impulse_response[conv_param_num].begin_all(), + this->impulse_response[conv_param_num].end_all()); + const float current_max_nested_initial_impulse_response = + *std::max_element(this->impulse_response[conv_param_num].begin_all(), + this->impulse_response[conv_param_num].end_all()); + + cerr << " [" << conv_param_num << "](" << current_min_nested_initial_impulse_response << ", " << current_max_nested_initial_impulse_response << ") " << endl; + } + } + cerr << endl; + + //Then multiply the initial impulse response with the input function convolution matrix to get the dynamic images (2nd step of kinetic forward projection) + this->_patlak_plot_sptr->get_dynamic_image_from_impulse_response(dyn_image_nested_loop_estimate,this->impulse_response); + + //Print out the min and max values of the forward projected frames of the dynamic image at the beginning of each nested update + for(unsigned int frame_num=this->_patlak_plot_sptr->get_starting_frame();frame_num<=this->_patlak_plot_sptr->get_time_frame_definitions().get_num_frames();++frame_num) + { + const float current_min_nested_dyn_image_estimate = + *std::min_element(dyn_image_nested_loop_estimate[frame_num].begin_all(), + dyn_image_nested_loop_estimate[frame_num].end_all()); + const float current_max_nested_dyn_image_estimate = + *std::max_element(dyn_image_nested_loop_estimate[frame_num].begin_all(), + dyn_image_nested_loop_estimate[frame_num].end_all()); + cerr << "Nested iteration: " << nested_subiterations_num + << " Forward projected dynamic image nested estimate for frame: " << frame_num << " (min, max): (" + << current_min_nested_dyn_image_estimate << ", " << current_max_nested_dyn_image_estimate + << ") " << endl; + } + + //At each nested iteration, always use the dynamic image estimate from the outer loop EM update as reference + dyn_image_estimate = dyn_image_reference_data; + + //Print out the min and max values of the outer loop estimate (operating as reference) of the dynamic image at the beginning of each nested update + for(unsigned int frame_num=this->_patlak_plot_sptr->get_starting_frame();frame_num<=this->_patlak_plot_sptr->get_time_frame_definitions().get_num_frames();++frame_num) + { + const float current_min_nested_ref_dyn_image_estimate = + *std::min_element(dyn_image_estimate[frame_num].begin_all(), + dyn_image_estimate[frame_num].end_all()); + const float current_max_nested_ref_dyn_image_estimate = + *std::max_element(dyn_image_estimate[frame_num].begin_all(), + dyn_image_estimate[frame_num].end_all()); + cerr << "Nested iteration: " << nested_subiterations_num + << " Reference dynamic image estimate for frame: " << frame_num << " (min, max): (" + << current_min_nested_ref_dyn_image_estimate << ", " << current_max_nested_ref_dyn_image_estimate + << ") " << endl; + } + + + // loop over single_frame and use model_matrix and the outer loop dynamic image estimate + for(unsigned int frame_num=this->_patlak_plot_sptr->get_starting_frame();frame_num<=this->_patlak_plot_sptr->get_time_frame_definitions().get_num_frames();++frame_num) + divide(dyn_image_estimate[frame_num].begin_all(), + dyn_image_estimate[frame_num].end_all(), + dyn_image_nested_loop_estimate[frame_num].begin_all(), + small_num); + + //Print out the min and max values of the ratio of the reference and the forward projected dynamic frames at each nested update + for(unsigned int frame_num=this->_patlak_plot_sptr->get_starting_frame();frame_num<=this->_patlak_plot_sptr->get_time_frame_definitions().get_num_frames();++frame_num) + { + const float current_min_nested_dyn_image_ratio_estimate = + *std::min_element(dyn_image_estimate[frame_num].begin_all(), + dyn_image_estimate[frame_num].end_all()); + const float current_max_nested_dyn_image_ratio_estimate = + *std::max_element(dyn_image_estimate[frame_num].begin_all(), + dyn_image_estimate[frame_num].end_all()); + cerr << "Nested iteration: " << nested_subiterations_num + << " Dynamic images ratio for frame: " << frame_num << " (min, max): (" + << current_min_nested_dyn_image_ratio_estimate << ", " << current_max_nested_dyn_image_ratio_estimate + << ") " << endl; + } + + // Then multiply the generated dynamic image ratio factors with the transverse of the input function convolution matrix + // to obtain the non-normalized update factors for the impulse response (kinetic back projection) + this->_patlak_plot_sptr->multiply_dynamic_image_with_model_gradient(impulse_response_gradient, + dyn_image_estimate) ; + + + //Print out the min and max values of impulse response gradient (update factors) before sensitivity division (i.e. non-normalized), (only for first and last convolution time point) + cerr << "Nested iteration: " << nested_subiterations_num + << " sub-gradient (update factors for impulse response) before sensitivity devision current value [conv point](min, max):" << endl; + + for(int conv_param_num = model_array_min[1];conv_param_num<=model_array_max[1] ; ++conv_param_num) + { + if ((conv_param_num==model_array_min[1]) || (conv_param_num==model_array_max[1])) + { + const float current_min_nested_impulse_response_gradient = + *std::min_element(impulse_response_gradient[conv_param_num].begin_all(), + impulse_response_gradient[conv_param_num].end_all()); + const float current_max_nested_impulse_response_gradient = + *std::max_element(impulse_response_gradient[conv_param_num].begin_all(), + impulse_response_gradient[conv_param_num].end_all()); + + cerr << " [" << conv_param_num << "](" << current_min_nested_impulse_response_gradient << ", " << current_max_nested_impulse_response_gradient << ") " << endl; + } + } + cerr << endl; + + // Perform model sensitivity division for each time convolution point + for(int conv_param_num = model_array_min[1];conv_param_num<=model_array_max[1] ; ++conv_param_num) + { + // Devide by model sensitivity + divide(impulse_response_gradient[conv_param_num].begin_all(), + impulse_response_gradient[conv_param_num].end_all(), + this->sensitivity_impulse_response_image[conv_param_num].begin_all(), + small_num); + } + + //Print out the min and max values of impulse response gradient (update factors) after sensitivity division (only for first and last convolution time point) + cerr << "Nested iteration: " << nested_subiterations_num + << " sub-gradient (update factors for impulse response) old value: [conv point](min, max), new value: [conv point](min, max)" << endl; + + for(int conv_param_num = model_array_min[1];conv_param_num<=model_array_max[1] ; ++conv_param_num) + { + + const float new_min_nested_impulse_response = + static_cast(this->minimum_nested_relative_change); + const float new_max_nested_impulse_response = + static_cast(this->maximum_nested_relative_change); + + if ((conv_param_num==model_array_min[1]) || (conv_param_num==model_array_max[1])) + { + + const float current_min_nested_impulse_response_normalized_gradient = + *std::min_element(impulse_response_gradient[conv_param_num].begin_all(), + impulse_response_gradient[conv_param_num].end_all()); + const float current_max_nested_impulse_response_normalized_gradient = + *std::max_element(impulse_response_gradient[conv_param_num].begin_all(), + impulse_response_gradient[conv_param_num].end_all()); + + cerr << " [" << conv_param_num << "](" << current_min_nested_impulse_response_normalized_gradient << ", " + << current_max_nested_impulse_response_normalized_gradient << "), " + << " [" << conv_param_num << "](" << std::max(current_min_nested_impulse_response_normalized_gradient, new_min_nested_impulse_response) << ", " + << std::min(current_max_nested_impulse_response_normalized_gradient, new_max_nested_impulse_response) << ") " << endl; + } + + threshold_upper_lower(impulse_response_gradient[conv_param_num].begin_all(), + impulse_response_gradient[conv_param_num].end_all(), + new_min_nested_impulse_response, new_max_nested_impulse_response); + } + cerr << endl; + + + //Nested updates of impulse response estimates and printing of updated values + + cerr << "Nested iteration: " << nested_subiterations_num + << " Updated impulse response value for each convolution point [conv point](min, max):" << endl; + + for(int conv_param_num = model_array_min[1];conv_param_num<=model_array_max[1] ; ++conv_param_num) + { + DiscretisedDensity<3,float>::const_full_iterator imp_response_gradient_iter = impulse_response_gradient[conv_param_num].begin_all_const(); + DiscretisedDensity<3,float>::const_full_iterator end_imp_response_gradient_iter = impulse_response_gradient[conv_param_num].end_all_const(); + DiscretisedDensity<3,float>::full_iterator imp_response_iter = this->impulse_response[conv_param_num].begin_all(); + //Update mechanism for impulse response image + while (imp_response_gradient_iter!=end_imp_response_gradient_iter) + { + *imp_response_iter *= (*imp_response_gradient_iter); + ++imp_response_iter; ++imp_response_gradient_iter; + } + + //Print out the min and max values of the nested updated impulse response vector for each nested iteration (first and last convolution point only) + if ((conv_param_num==model_array_min[1]) || (conv_param_num==model_array_max[1])) + { + const float current_min_nested_updated_impulse_response = + *std::min_element(this->impulse_response[conv_param_num].begin_all(), + this->impulse_response[conv_param_num].end_all()); + const float current_max_nested_updated_impulse_response = + *std::max_element(this->impulse_response[conv_param_num].begin_all(), + this->impulse_response[conv_param_num].end_all()); + cerr << " [" << conv_param_num << "](" << current_min_nested_updated_impulse_response << ", " << current_max_nested_updated_impulse_response << ") " << endl; + } + } + cerr << endl; + + + // Calculation of nested parametric image updates from the updated impulse response estimates + // To speed-up nested EM updates, estimate Ki, kloss and V parametric images only for the last nested iteration + if (nested_subiterations_num==this->num_nested_subiterations) + { + + cerr << "Nested EM iteration: " << nested_subiterations_num << " is the last generalized Patlak nested EM iteration... " << endl + << "Estimation of nested generalized Patlak parametric image estimates is now conducted... " << endl; + + this->_patlak_plot_sptr->get_generalized_patlak_parameters_from_impulse_response(current_estimate, dyn_image_estimate, this->impulse_response); + + //Print out the min and max values of the nested updated image for each nested iteration + const float current_min_nested_updated_image = + *std::min_element(current_estimate.begin_all(), + current_estimate.end_all()); + const float current_max_nested_updated_image = + *std::max_element(current_estimate.begin_all(), + current_estimate.end_all()); + cerr << "Nested iteration: " << nested_subiterations_num + << " Updated image value (min, max) (" + << current_min_nested_updated_image << ", " << current_max_nested_updated_image << ")" << endl << endl; + } + else + { + cerr << "Nested EM iteration: " << nested_subiterations_num << " is not the last generalized Patlak nested EM iteration (" + << this->num_nested_subiterations << "). Estimation of nested generalized Patlak parametric image estimates is skipped until last nested EM iteration" << endl; + } + + } + + cerr << "End of regular nested reconstruction process of parameter estimates and impulse response (after " + << this->num_nested_subiterations << " nested EM subiterations)" << endl << endl; + +} + +template +void +PoissonNestedLogLikelihoodWithGeneralizedPatlakAndDynamicProjectionData:: +initialize_nested_loop_parameters_with_initialization_model(TargetT ¶metric_gradient, + TargetT ¤t_estimate, + DynamicDiscretisedDensity &dyn_image_estimate, + DynamicDiscretisedDensity &dyn_image_reference_data, + DynamicDiscretisedDensity &dyn_image_nested_loop_estimate) +{ + + //nested EM initialization loop + cerr << endl << "Entering nested initialization loop (" << this->num_nested_initialization_subiterations << " subiterations)." << endl << endl; + + for(nested_initialization_subiterations_num=1; nested_initialization_subiterations_num<=this->num_nested_initialization_subiterations; nested_initialization_subiterations_num++) + { + //equivalent of forward-projection operation for kinetic parameter estimation + this->_patlak_plot_sptr->get_dynamic_image_from_initialization_parametric_image(dyn_image_nested_loop_estimate, + current_estimate); + + dyn_image_estimate = dyn_image_reference_data; + // loop over single_frame and use model_matrix and the outer loop dynamic image estimate + for(unsigned int frame_num=this->_patlak_plot_sptr->get_starting_frame();frame_num<=this->_patlak_plot_sptr->get_time_frame_definitions().get_num_frames();++frame_num) + divide(dyn_image_estimate[frame_num].begin_all(), + dyn_image_estimate[frame_num].end_all(), + dyn_image_nested_loop_estimate[frame_num].begin_all(), + small_num); + + //equivalent of back-projection operation for kinetic parameter estimation + this->_patlak_plot_sptr->multiply_dynamic_image_with_initialization_model_gradient(parametric_gradient, + dyn_image_estimate) ; + + + // Perform model sensitivity division and update for all nested iterations + + // Devide by model sensitivity + divide(parametric_gradient.begin_all(), + parametric_gradient.end_all(), + this->initialization_model_sensitivity_image_sptr->begin_all(), + small_num); + + if (nested_initialization_subiterations_num != 1) + { + const float current_min_nested_gradient = + *std::min_element(parametric_gradient.begin_all(), + parametric_gradient.end_all()); + const float current_max_nested_gradient = + *std::max_element(parametric_gradient.begin_all(), + parametric_gradient.end_all()); + const float new_min_nested_gradient = + static_cast(this->minimum_nested_relative_change); + const float new_max_nested_gradient = + static_cast(this->maximum_nested_relative_change); + cerr << "Nested initialization iteration: " << nested_initialization_subiterations_num + << " sub-gradient(update image) old value (min, max): (" + << current_min_nested_gradient << ", " << current_max_nested_gradient + << "), new value (min, max) (" + << std::max(current_min_nested_gradient, new_min_nested_gradient) << ", " + << std::min(current_max_nested_gradient, new_max_nested_gradient) << ")" << endl; + + threshold_upper_lower(parametric_gradient.begin_all(), + parametric_gradient.end_all(), + new_min_nested_gradient, new_max_nested_gradient); + } + + //Nested updates of image estimates + { + typename TargetT::const_full_iterator parametric_gradient_iter = parametric_gradient.begin_all_const(); + const typename TargetT::const_full_iterator end_parametric_gradient_iter = parametric_gradient.end_all_const(); + typename TargetT::full_iterator current_estimate_iter = current_estimate.begin_all(); + while (parametric_gradient_iter!=end_parametric_gradient_iter) + { + *current_estimate_iter *= (*parametric_gradient_iter); + ++current_estimate_iter; ++parametric_gradient_iter; + } + } + + //Print out the min and max values of the nested updated image for each nested iteration + const float current_min_nested_updated_image = + *std::min_element(current_estimate.begin_all(), + current_estimate.end_all()); + const float current_max_nested_updated_image = + *std::max_element(current_estimate.begin_all(), + current_estimate.end_all()); + cerr << "Nested initialization iteration: " << nested_initialization_subiterations_num + << " Updated image value (min, max) (" + << current_min_nested_updated_image << ", " << current_max_nested_updated_image << ")" << endl << endl; + + } + + cerr << "End of initialization process of parameter estimates and impulse response (after " + << this->num_nested_initialization_subiterations << " nested initialization EM subiterations)" << endl << endl; +} + + +template +double +PoissonNestedLogLikelihoodWithGeneralizedPatlakAndDynamicProjectionData:: +actual_compute_objective_function_without_penalty(const TargetT& current_estimate, + const int subset_num) +{ + assert(subset_num>=0); + assert(subset_numnum_subsets); + + double result = 0.; + DynamicDiscretisedDensity dyn_image_estimate=this->_dyn_image_template; + + // TODO why fill with 1? + for(unsigned int frame_num=this->_patlak_plot_sptr->get_starting_frame();frame_num<=this->_patlak_plot_sptr->get_time_frame_definitions().get_num_frames();++frame_num) + std::fill(dyn_image_estimate[frame_num].begin_all(), + dyn_image_estimate[frame_num].end_all(), + 1.F); + this->_patlak_plot_sptr->get_dynamic_image_from_parametric_image(dyn_image_estimate,current_estimate) ; + + // loop over single_frame + for(unsigned int frame_num=this->_patlak_plot_sptr->get_starting_frame(); + frame_num<=this->_patlak_plot_sptr->get_time_frame_definitions().get_num_frames(); + ++frame_num) + { + result += + this->_single_frame_obj_funcs[frame_num]. + compute_objective_function_without_penalty(dyn_image_estimate[frame_num], + subset_num); + } + return result; +} + +template +void +PoissonNestedLogLikelihoodWithGeneralizedPatlakAndDynamicProjectionData:: +compute_model_sensitivity_impulse_response(DynamicDiscretisedDensity& impulse_response_image) +{ + + //Initialize model sensitivity image + //for(int conv_param_num = model_array_min[1];conv_param_num<=model_array_max[1] ; ++conv_param_num) + // std::fill(impulse_response_image[conv_param_num].begin_all(), + // impulse_response_image[conv_param_num].end_all(), + // 1.F); + + DynamicDiscretisedDensity dyn_image_of_all_ones=this->_dyn_image_template; + + for(unsigned int frame_num=this->_patlak_plot_sptr->get_starting_frame();frame_num<=this->_patlak_plot_sptr->get_time_frame_definitions().get_num_frames();++frame_num) + std::fill(dyn_image_of_all_ones[frame_num].begin_all(), + dyn_image_of_all_ones[frame_num].end_all(), + 1.F); + + cerr << "Computing impulse response sensitivity image..." << endl; + + this->_patlak_plot_sptr->multiply_dynamic_image_with_model_gradient(impulse_response_image, + dyn_image_of_all_ones); + + + //Print out the min and max values of the model sensitivity image + const float current_min_model_sensitivity = + *std::min_element(impulse_response_image.begin_all(), + impulse_response_image.end_all()); + const float current_max_model_sensitivity = + *std::max_element(impulse_response_image.begin_all(), + impulse_response_image.end_all()); + cerr << "Impulse response sensitivity image " + << ", (min, max): (" << current_min_model_sensitivity << ", " << current_max_model_sensitivity << ")" << endl; + + cerr << "Impulse_response sensitivity image has been computed." << endl; +} + + +template +void +PoissonNestedLogLikelihoodWithGeneralizedPatlakAndDynamicProjectionData:: +compute_initialization_model_sensitivity_image(const TargetT& param_image) +{ + + //Initialize the initialization model sensitivity image + shared_ptr param_image_sptr(param_image.get_empty_copy()); + this->initialization_model_sensitivity_image_sptr=param_image_sptr; + + std::fill(initialization_model_sensitivity_image_sptr->begin_all(), + this->initialization_model_sensitivity_image_sptr->end_all(), + 1.F); + + DynamicDiscretisedDensity dyn_image_of_all_ones=this->_dyn_image_template; + + // loop over single_frame and use model_matrix + for(unsigned int frame_num=this->_patlak_plot_sptr->get_starting_frame();frame_num<=this->_patlak_plot_sptr->get_time_frame_definitions().get_num_frames();++frame_num) + std::fill(dyn_image_of_all_ones[frame_num].begin_all(), + dyn_image_of_all_ones[frame_num].end_all(), + 1.F); + + cerr << "Computing initialization model sensitivity image..." << endl; + + this->_patlak_plot_sptr->multiply_dynamic_image_with_initialization_model_gradient(*this->initialization_model_sensitivity_image_sptr, + dyn_image_of_all_ones); + + + //Print out the min and max values of the initialization model sensitivity image + const float current_min_initialization_model_sensitivity = + *std::min_element(this->initialization_model_sensitivity_image_sptr->begin_all(), + this->initialization_model_sensitivity_image_sptr->end_all()); + const float current_max_initialization_model_sensitivity = + *std::max_element(this->initialization_model_sensitivity_image_sptr->begin_all(), + this->initialization_model_sensitivity_image_sptr->end_all()); + cerr << "Initialization Model sensitivity image " + << ", (min, max): (" << current_min_initialization_model_sensitivity << ", " << current_max_initialization_model_sensitivity << ")" << endl; + + cerr << "Initialization Model sensitivity image has been computed." << endl; +} + + + +template +void +PoissonNestedLogLikelihoodWithGeneralizedPatlakAndDynamicProjectionData:: +add_subset_sensitivity(TargetT& sensitivity, const int subset_num) const +{ + DynamicDiscretisedDensity dyn_image_of_all_ones=this->_dyn_image_template; + DynamicDiscretisedDensity sensitivity_impulse_response=this->_imp_response_image_template; + + // loop over single_frame and use model_matrix + for(unsigned int frame_num=this->_patlak_plot_sptr->get_starting_frame();frame_num<=this->_patlak_plot_sptr->get_time_frame_definitions().get_num_frames();++frame_num) + std::fill(dyn_image_of_all_ones[frame_num].begin_all(), + dyn_image_of_all_ones[frame_num].end_all(), + 1.F); + + this->add_subset_impulse_response_sensitivity(sensitivity_impulse_response,subset_num); + + this->_patlak_plot_sptr->get_generalized_patlak_parameters_from_impulse_response(sensitivity, dyn_image_of_all_ones, sensitivity_impulse_response); + +} + +template +void +PoissonNestedLogLikelihoodWithGeneralizedPatlakAndDynamicProjectionData:: +add_subset_initialization_sensitivity(TargetT& initialization_model_sensitivity, const int subset_num) const +{ + DynamicDiscretisedDensity dyn_image_of_all_ones=this->_dyn_image_template; + + // loop over single_frame and use model_matrix + for(unsigned int frame_num=this->_patlak_plot_sptr->get_starting_frame();frame_num<=this->_patlak_plot_sptr->get_time_frame_definitions().get_num_frames();++frame_num) + std::fill(dyn_image_of_all_ones[frame_num].begin_all(), + dyn_image_of_all_ones[frame_num].end_all(), + 1.F); + + this->_patlak_plot_sptr->multiply_dynamic_image_with_initialization_model_gradient_and_add_to_input(initialization_model_sensitivity, + dyn_image_of_all_ones); + + +} + +template +void +PoissonNestedLogLikelihoodWithGeneralizedPatlakAndDynamicProjectionData:: +add_subset_impulse_response_sensitivity(DynamicDiscretisedDensity& impulse_response, const int subset_num) const +{ + DynamicDiscretisedDensity dyn_image_of_all_ones=this->_dyn_image_template; + + // loop over single_frame and use model_matrix + for(unsigned int frame_num=this->_patlak_plot_sptr->get_starting_frame();frame_num<=this->_patlak_plot_sptr->get_time_frame_definitions().get_num_frames();++frame_num) + std::fill(dyn_image_of_all_ones[frame_num].begin_all(), + dyn_image_of_all_ones[frame_num].end_all(), + 1.F); + + + this->_patlak_plot_sptr->multiply_dynamic_image_with_model_gradient_and_add_to_input(impulse_response, + dyn_image_of_all_ones); + + +} + +template +Succeeded +PoissonNestedLogLikelihoodWithGeneralizedPatlakAndDynamicProjectionData:: +actual_add_multiplication_with_approximate_sub_Hessian_without_penalty(TargetT& output, + const TargetT& input, + const int subset_num) const +{ + { + std::string explanation; + if (!input.has_same_characteristics(this->get_sensitivity(), + explanation)) + { + warning("PoissonNestedLogLikelihoodWithGeneralizedPatlakAndDynamicProjectionData:\n" + "sensitivity and input for add_multiplication_with_approximate_Hessian_without_penalty\n" + "should have the same characteristics.\n%s", + explanation.c_str()); + return Succeeded::no; + } + } +#ifndef NDEBUG + std::cerr << "INPUT max: (" << input.construct_single_density(1).find_max() + << " , " << input.construct_single_density(2).find_max() + << ")\n"; +#endif //NDEBUG + DynamicDiscretisedDensity dyn_input=this->_dyn_image_template; + DynamicDiscretisedDensity dyn_output=this->_dyn_image_template; + this->_patlak_plot_sptr->get_dynamic_image_from_parametric_image(dyn_input,input) ; + + VectorWithOffset scale_factor(this->_patlak_plot_sptr->get_starting_frame(),this->_patlak_plot_sptr->get_time_frame_definitions().get_num_frames()); + for(unsigned int frame_num=this->_patlak_plot_sptr->get_starting_frame(); + frame_num<=this->_patlak_plot_sptr->get_time_frame_definitions().get_num_frames(); + ++frame_num) + { + assert(dyn_input[frame_num].find_max()==dyn_input[frame_num].find_min()); + if (dyn_input[frame_num].find_max()==dyn_input[frame_num].find_min() && dyn_input[frame_num].find_min()>0.F) + scale_factor[frame_num]=dyn_input[frame_num].find_max(); + else + error("The input image should be uniform even after multiplying with the Patlak Plot.\n"); + +/*! /note This is used to avoid higher values than these set in the precompute_denominator_of_conditioner_without_penalty() function. +/sa for more information see the recon_array_functions.cxx and the value of the max_quotient (originaly set to 10000.F) +*/ + dyn_input[frame_num]/=scale_factor[frame_num]; +#ifndef NDEBUG + std::cerr << "scale factor[" << frame_num << "] " << scale_factor[frame_num] << "\n"; + std::cerr << "dyn_input[" << frame_num << "] max after scale: " + << dyn_input[frame_num].find_max() << "\n"; +#endif //NDEBUG + this->_single_frame_obj_funcs[frame_num]. + add_multiplication_with_approximate_sub_Hessian_without_penalty(dyn_output[frame_num], + dyn_input[frame_num], + subset_num); +#ifndef NDEBUG + std::cerr << "dyn_output[" << frame_num << "] max before scale: (" + << dyn_output[frame_num].find_max() << "\n"; +#endif //NDEBUG + dyn_output[frame_num]*=scale_factor[frame_num]; +#ifndef NDEBUG + std::cerr << "dyn_output[" << frame_num << "] max after scale: (" + << dyn_output[frame_num].find_max() << "\n"; +#endif //NDEBUG + } // end of loop over frames + shared_ptr unnormalised_temp(output.get_empty_copy()); + DynamicDiscretisedDensity unnormalised_imp_response; + shared_ptr temp(output.get_empty_copy()); + this->_patlak_plot_sptr->multiply_dynamic_image_with_model_gradient(unnormalised_imp_response, + dyn_output) ; + + this->_patlak_plot_sptr->get_generalized_patlak_parameters_from_impulse_response(*unnormalised_temp,dyn_output,unnormalised_imp_response); + // Trick to use a better step size for the two parameters. + (this->_patlak_plot_sptr->get_model_matrix()).normalise_parametric_image_with_model_sum(*temp,*unnormalised_temp,this->_patlak_plot_sptr->_num_conv_params) ; +#ifndef NDEBUG + std::cerr << "TEMP max: (" << temp->construct_single_density(1).find_max() + << " , " << temp->construct_single_density(2).find_max() + << ")\n"; + // Writing images + OutputFileFormat::default_sptr()->write_to_file("all_params_one_input.img", input); + OutputFileFormat::default_sptr()->write_to_file("temp_denominator.img", *temp); + dyn_input.write_to_ecat7("dynamic_input_from_all_params_one.img"); + dyn_output.write_to_ecat7("dynamic_precomputed_denominator.img"); + DynamicProjData temp_projdata = this->get_dyn_proj_data(); + for(unsigned int frame_num=this->_patlak_plot_sptr->get_starting_frame(); + frame_num<=this->_patlak_plot_sptr->get_time_frame_definitions().get_num_frames(); + ++frame_num) + temp_projdata.set_proj_data_sptr(this->_single_frame_obj_funcs[frame_num].get_proj_data_sptr(),frame_num); + + temp_projdata.write_to_ecat7("DynamicProjections.S"); +#endif // NDEBUG + // output += temp + typename TargetT::full_iterator out_iter = output.begin_all(); + typename TargetT::full_iterator out_end = output.end_all(); + typename TargetT::const_full_iterator temp_iter = temp->begin_all_const(); + while (out_iter != out_end) + { + *out_iter += *temp_iter; + ++out_iter; ++temp_iter; + } +#ifndef NDEBUG + std::cerr << "OUTPUT max: (" << output.construct_single_density(1).find_max() + << " , " << output.construct_single_density(2).find_max() + << ")\n"; +#endif // NDEBUG + + + return Succeeded::yes; +} + + +END_NAMESPACE_STIR + diff --git a/src/include/stir/recon_buildblock/PoissonNestedLogLikelihoodWithLinearKineticModelAndDynamicProjectionData.h b/src/include/stir/recon_buildblock/PoissonNestedLogLikelihoodWithLinearKineticModelAndDynamicProjectionData.h new file mode 100644 index 0000000000..2e878dad48 --- /dev/null +++ b/src/include/stir/recon_buildblock/PoissonNestedLogLikelihoodWithLinearKineticModelAndDynamicProjectionData.h @@ -0,0 +1,206 @@ +// +/* + Copyright (C) 2006 - 2011, Hammersmith Imanet Ltd + This file is part of STIR. + + SPDX-License-Identifier: Apache-2.0 + + See STIR/LICENSE.txt for details +*/ +/*! + \file + \ingroup GeneralisedObjectiveFunction + \ingroup modelling + \brief Declaration of class stir::PoissonNestedLogLikelihoodWithLinearKineticModelAndDynamicProjectionData + + \author Nicolas A Karakatsanis + +*/ + +#ifndef __stir_recon_buildblock_PoissonNestedLogLikelihoodWithLinearKineticModelAndDynamicProjectionData_H__ +#define __stir_recon_buildblock_PoissonNestedLogLikelihoodWithLinearKineticModelAndDynamicProjectionData_H__ +#include "stir/RegisteredParsingObject.h" +#include "stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.h" +#include "stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMean.h" +#include "stir/VectorWithOffset.h" +#include "stir/DynamicProjData.h" +#include "stir/DynamicDiscretisedDensity.h" +#include "stir/modelling/ParseAndCreateParametricDiscretisedDensityFrom.h" +#include "stir/modelling/ParametricDiscretisedDensity.h" +#include "stir/modelling/KineticParameters.h" +#include "stir/modelling/PatlakPlot.h" + +START_NAMESPACE_STIR + +/*! + \ingroup GeneralisedObjectiveFunction + \ingroup modelling + \brief a base class for LogLikelihood of independent Poisson variables + where the mean values are linear combinations of the kinetic parameters. + + \par Parameters for parsing + +*/ + +template +class PoissonNestedLogLikelihoodWithLinearKineticModelAndDynamicProjectionData + : public RegisteredParsingObject, + GeneralisedObjectiveFunction, + PoissonLogLikelihoodWithLinearModelForMean> +{ +private: + typedef RegisteredParsingObject, + GeneralisedObjectiveFunction, + PoissonLogLikelihoodWithLinearModelForMean> + base_type; + typedef PoissonLogLikelihoodWithLinearModelForMeanAndProjData> SingleFrameObjFunc; + VectorWithOffset _single_frame_obj_funcs; + +public: + //! Name which will be used when parsing a GeneralisedObjectiveFunction object + static const char* const registered_name; + + PoissonNestedLogLikelihoodWithLinearKineticModelAndDynamicProjectionData(); + + //! Returns a pointer to a newly allocated target object (with 0 data). + /*! Dimensions etc are set from the \a dyn_proj_data_sptr and other information set by parsing, + such as \c zoom, \c output_image_size_z etc. + */ + TargetT* construct_target_ptr() const override; + + // Computes the outer loop gradient after conducting nested iterations + /* At each nested iteration \current_estimate is updated and therefore it + is declared as TargetT and NOT as const TargetT + */ + + void actual_compute_subset_gradient_without_penalty(TargetT& gradient, + const TargetT& current_estimate, + const int subset_num, + const bool add_sensitivity) override; + +protected: + virtual void actual_compute_nested_sub_gradient_without_penalty(TargetT& gradient, + TargetT& current_estimate, + const int subset_num, + const bool add_sensitivity); + + // The principal nested EM reconstruction method that performs the generalized Patlak reconstruction + virtual void estimate_nested_loop_parameters_with_model(TargetT& gradient, + TargetT& current_estimate, + DynamicDiscretisedDensity& dyn_image_estimate, + DynamicDiscretisedDensity& dyn_image_reference_data, + DynamicDiscretisedDensity& dyn_image_nested_loop_estimate); + + double actual_compute_objective_function_without_penalty(const TargetT& current_estimate, const int subset_num) override; + + Succeeded set_up_before_sensitivity(shared_ptr const& target_sptr) override; + + //! Add subset sensitivity to existing data + /*! \todo Current implementation does NOT add to the subset sensitivity, but overwrites + */ + void add_subset_sensitivity(TargetT& model_sensitivity, const int subset_num) const override; + + Succeeded actual_add_multiplication_with_approximate_sub_Hessian_without_penalty(TargetT& output, + const TargetT& input, + const int subset_num) const override; + +public: + /*! \name Functions to get parameters + \warning Be careful with changing shared pointers. If you modify the objects in + one place, all objects that use the shared pointer will be affected. + */ + //@{ + const DynamicProjData& get_dyn_proj_data() const; + const shared_ptr& get_dyn_proj_data_sptr() const; + const int get_max_segment_num_to_process() const; + const bool get_zero_seg0_end_planes() const; + const DynamicProjData& get_input_data() const override; + const DynamicProjData& get_additive_proj_data() const; + const shared_ptr& get_additive_proj_data_sptr() const; + const ProjectorByBinPair& get_projector_pair() const; + const shared_ptr& get_projector_pair_sptr() const; + const BinNormalisation& get_normalisation() const; + const shared_ptr& get_normalisation_sptr() const; + const TargetT& get_model_sensitivity_image() const; + const shared_ptr& get_model_sensitivity_image_sptr() const; + + //@} + + /*! \name Functions to set parameters + This can be used as alternative to the parsing mechanism. + \warning After using any of these, you have to call set_up(). + \warning Be careful with setting shared pointers. If you modify the objects in + one place, all objects that use the shared pointer will be affected. + */ + //@{ + void set_recompute_sensitivity(const bool); + void set_sensitivity_sptr(const shared_ptr&); + int set_num_subsets(const int num_subsets) override; + void set_input_data(const shared_ptr&) override; + void set_additive_proj_data_sptr(const shared_ptr&) override; + void set_normalisation_sptr(const shared_ptr&) override; + //@} +protected: + //! Filename with input projection data + std::string _input_filename; + + //! points to the object for the total input projection data + shared_ptr _dyn_proj_data_sptr; + + //! the maximum absolute ring difference number to use in the reconstruction + /*! convention: if -1, use get_max_segment_num()*/ + int _max_segment_num_to_process; + + /**********************/ + ParseAndCreateFrom target_parameter_parser; + + /**********************/ + //! the current subiteration index + int nested_subiterations_num; + + //! restrict updates (larger nested relative updates will be thresholded) + double maximum_nested_relative_change; + + //! restrict updates (smaller nested relative updates will be thresholded) + double minimum_nested_relative_change; + + /********************************/ + //! name of file in which additive projection data are stored + std::string _additive_dyn_proj_data_filename; + //! points to the additive projection data + /*! the projection data in this file is bin-wise added to forward projection results*/ + shared_ptr _additive_dyn_proj_data_sptr; + /*! the normalisation or/and attenuation data */ + shared_ptr _normalisation_sptr; + //! Stores the projectors that are used for the computations + shared_ptr _projector_pair_ptr; + //! signals whether to zero the data in the end planes of the projection data + bool _zero_seg0_end_planes; + // Patlak Plot Parameters + /*! the patlak plot pointer where all the parameters are stored */ + shared_ptr _patlak_plot_sptr; + //! dynamic image template + DynamicDiscretisedDensity _dyn_image_template; + + // Define a shared pointer for the (linear kinetic) model sensitivity image + shared_ptr model_sensitivity_image_sptr; + + bool actual_subsets_are_approximately_balanced(std::string& warning_message) const override; + + // void compute_model_sensitivity_image(shared_ptr const& target_sptr); + void compute_model_sensitivity_image(const TargetT& param_image); + + //! Sets defaults for parsing + /*! Resets \c sensitivity_filename and \c sensitivity_sptr and + \c recompute_sensitivity to \c false. + */ + void set_defaults() override; + void initialise_keymap() override; + bool post_processing() override; +}; + +END_NAMESPACE_STIR + +//#include "stir/recon_buildblock/PoissonNestedLogLikelihoodWithLinearKineticModelAndDynamicProjectionData.inl" + +#endif \ No newline at end of file diff --git a/src/include/stir/recon_buildblock/PoissonNestedLogLikelihoodWithLinearKineticModelAndDynamicProjectionData.txx b/src/include/stir/recon_buildblock/PoissonNestedLogLikelihoodWithLinearKineticModelAndDynamicProjectionData.txx new file mode 100644 index 0000000000..62194616d6 --- /dev/null +++ b/src/include/stir/recon_buildblock/PoissonNestedLogLikelihoodWithLinearKineticModelAndDynamicProjectionData.txx @@ -0,0 +1,948 @@ +// +/* + Copyright (C) 2006 - 2011, Hammersmith Imanet Ltd + This file is part of STIR. + + SPDX-License-Identifier: Apache-2.0 + + See STIR/LICENSE.txt for details +*/ +/*! + \file + \ingroup GeneralisedObjectiveFunction + \ingroup modelling + \brief Implementation of class stir::PoissonNestedLogLikelihoodWithLinearKineticModelAndDynamicProjectionData + + \author Nicolas A Karakatsanis + +*/ +#include "stir/DiscretisedDensity.h" +#include "stir/DynamicDiscretisedDensity.h" +#include "stir/modelling/ParametricDiscretisedDensity.h" +#include "stir/modelling/KineticParameters.h" + +#include "stir/is_null_ptr.h" +#include "stir/numerics/divide.h" +#include "stir/thresholding.h" +#include "stir/NumericInfo.h" +#include "stir/recon_buildblock/TrivialBinNormalisation.h" +#include "stir/Succeeded.h" +#include "stir/RelatedViewgrams.h" +#include "stir/stream.h" +#include "stir/recon_buildblock/ProjectorByBinPair.h" +#include "stir/CPUTimer.h" +#include "stir/format.h" + +// for get_symmetries_ptr() +#include "stir/DataSymmetriesForViewSegmentNumbers.h" +// include the following to set defaults +#ifndef USE_PMRT +#include "stir/recon_buildblock/ForwardProjectorByBinUsingRayTracing.h" +#include "stir/recon_buildblock/BackProjectorByBinUsingInterpolation.h" +#else +#include "stir/recon_buildblock/ForwardProjectorByBinUsingProjMatrixByBin.h" +#include "stir/recon_buildblock/BackProjectorByBinUsingProjMatrixByBin.h" +#include "stir/recon_buildblock/ProjMatrixByBinUsingRayTracing.h" +#endif +#include "stir/recon_buildblock/ProjectorByBinPairUsingSeparateProjectors.h" + +#include "stir/Succeeded.h" +#include "stir/IO/OutputFileFormat.h" + +#include "stir/Viewgram.h" +#include "stir/recon_array_functions.h" +#include +#include +// For the Patlak Plot Modelling +#include "stir/modelling/ModelMatrix.h" +#include "stir/recon_buildblock/PoissonNestedLogLikelihoodWithLinearKineticModelAndDynamicProjectionData.h" + +#ifndef STIR_NO_NAMESPACES +using std::cerr; +using std::endl; +#endif + +START_NAMESPACE_STIR + +const float small_num = 0.000001F; + +template +const char * const +PoissonNestedLogLikelihoodWithLinearKineticModelAndDynamicProjectionData:: +registered_name = +"PoissonNestedLogLikelihoodWithLinearKineticModelAndDynamicProjectionData"; + +template +void +PoissonNestedLogLikelihoodWithLinearKineticModelAndDynamicProjectionData:: +set_defaults() +{ + base_type::set_defaults(); + + this->_input_filename=""; + this->_max_segment_num_to_process=-1; + //num_views_to_add=1; // KT 20/06/2001 disabled + + this->_dyn_proj_data_sptr.reset(); + this->_zero_seg0_end_planes = 0; + + this->_additive_dyn_proj_data_filename = "0"; + this->_additive_dyn_proj_data_sptr.reset(); + +#ifndef USE_PMRT // set default for _projector_pair_ptr + shared_ptr forward_projector_ptr(new ForwardProjectorByBinUsingRayTracing()); + shared_ptr back_projector_ptr(new BackProjectorByBinUsingInterpolation()); +#else + shared_ptr PM(new ProjMatrixByBinUsingRayTracing()); + shared_ptr forward_projector_ptr(new ForwardProjectorByBinUsingProjMatrixByBin(PM)); + shared_ptr back_projector_ptr(new BackProjectorByBinUsingProjMatrixByBin(PM)); +#endif + + this->_projector_pair_ptr. + reset(new ProjectorByBinPairUsingSeparateProjectors(forward_projector_ptr, back_projector_ptr)); + this->_normalisation_sptr.reset(new TrivialBinNormalisation); + + //Number of nested iterations + this->num_nested_subiterations=1; + + this->maximum_nested_relative_change = NumericInfo().max_value(); + this->minimum_nested_relative_change = 0; + + this->target_parameter_parser.set_defaults(); + + // Modelling Stuff + this->_patlak_plot_sptr.reset(); +} + +template +void +PoissonNestedLogLikelihoodWithLinearKineticModelAndDynamicProjectionData:: +initialise_keymap() +{ + base_type::initialise_keymap(); + this->parser.add_start_key("PoissonNestedLogLikelihoodWithLinearKineticModelAndDynamicProjectionData Parameters"); + this->parser.add_stop_key("End PoissonNestedLogLikelihoodWithLinearKineticModelAndDynamicProjectionData Parameters"); + this->parser.add_key("input file",&this->_input_filename); + + // parser.add_key("mash x views", &num_views_to_add); // KT 20/06/2001 disabled + this->parser.add_key("maximum absolute segment number to process", &this->_max_segment_num_to_process); + this->parser.add_key("zero end planes of segment 0", &this->_zero_seg0_end_planes); + + this->target_parameter_parser.add_to_keymap(this->parser); + this->parser.add_parsing_key("projector pair type", &this->_projector_pair_ptr); + + // Scatter correction + this->parser.add_key("additive sinograms",&this->_additive_dyn_proj_data_filename); + + // normalisation (and attenuation correction) + this->parser.add_parsing_key("Bin Normalisation type", &this->_normalisation_sptr); + + // Modelling Information + this->parser.add_parsing_key("Kinetic Model Type", &this->_patlak_plot_sptr); // Do sth with dynamic_cast to get the PatlakPlot + + // Regularization Information + // this->parser.add_parsing_key("prior type", &this->_prior_sptr); + + // Nested subiterations + this->parser.add_key("number of nested subiterations", &this->num_nested_subiterations); + + //max and min allowed relative change between nested updates + this->parser.add_key("maximum nested relative change", &this->maximum_nested_relative_change); + this->parser.add_key("minimum nested relative change",&this->minimum_nested_relative_change); +} + +template +bool +PoissonNestedLogLikelihoodWithLinearKineticModelAndDynamicProjectionData:: +post_processing() +{ + if (base_type::post_processing() == true) + return true; + if (this->_input_filename.length() == 0) + { + warning("You need to specify an input filename"); + return true; + } + +#if 0 // KT 20/06/2001 disabled as not functional yet + if (num_views_to_add!=1 && (num_views_to_add<=0 || num_views_to_add%2 != 0)) + { warning("The 'mash x views' key has an invalid value (must be 1 or even number)"); return true; } +#endif + + this->_dyn_proj_data_sptr = DynamicProjData::read_from_file(_input_filename); + if (is_null_ptr(this->_dyn_proj_data_sptr)) + { + warning(format("Error reading input file {}", _input_filename)); + return true; + } + + this->target_parameter_parser.check_values(); + + if (this->_additive_dyn_proj_data_filename != "0") + { + info(format("Reading additive projdata data {}", this->_additive_dyn_proj_data_filename)); + this->_additive_dyn_proj_data_sptr = DynamicProjData::read_from_file(this->_additive_dyn_proj_data_filename); + if (is_null_ptr(this->_additive_dyn_proj_data_sptr)) + { + warning(format("Error reading additive input file {}", _additive_dyn_proj_data_filename)); + return true; + } + } + return false; +} + +template +PoissonNestedLogLikelihoodWithLinearKineticModelAndDynamicProjectionData:: +PoissonNestedLogLikelihoodWithLinearKineticModelAndDynamicProjectionData() +{ + this->set_defaults(); +} + +template +TargetT * +PoissonNestedLogLikelihoodWithLinearKineticModelAndDynamicProjectionData:: +construct_target_ptr() const +{ + return this->target_parameter_parser.create(this->get_input_data()); +} +/*************************************************************** + subset balancing +***************************************************************/ + +template +bool +PoissonNestedLogLikelihoodWithLinearKineticModelAndDynamicProjectionData:: +actual_subsets_are_approximately_balanced(std::string& warning_message) const +{ // call actual_subsets_are_approximately_balanced( for first single_frame_obj_func ) + if (this->_patlak_plot_sptr->get_time_frame_definitions().get_num_frames() == 0 || this->_single_frame_obj_funcs.size() == 0) + error("PoissonNestedLogLikelihoodWithLinearKineticModelAndDynamicProjectionData:\n" + "actual_subsets_are_approximately_balanced called but not frames yet.\n"); + else if(this->_single_frame_obj_funcs.size() != 0) + { + bool frames_are_balanced=true; + for(unsigned int frame_num=this->_patlak_plot_sptr->get_starting_frame();frame_num<=this->_patlak_plot_sptr->get_time_frame_definitions().get_num_frames();++frame_num) + frames_are_balanced &= this->_single_frame_obj_funcs[frame_num].subsets_are_approximately_balanced(warning_message); + return frames_are_balanced; + } + else + warning("Something strange happened in PoissonNestedLogLikelihoodWithLinearKineticModelAndDynamicProjectionData:\n" + "actual_subsets_are_approximately_balanced called before setup()?\n"); + return + false; +} + +/*************************************************************** + get_ functions +***************************************************************/ +template +const DynamicProjData& +PoissonNestedLogLikelihoodWithLinearKineticModelAndDynamicProjectionData:: +get_dyn_proj_data() const +{ return *this->_dyn_proj_data_sptr; } + +template +const shared_ptr& +PoissonNestedLogLikelihoodWithLinearKineticModelAndDynamicProjectionData:: +get_dyn_proj_data_sptr() const +{ return this->_dyn_proj_data_sptr; } + +template +const int +PoissonNestedLogLikelihoodWithLinearKineticModelAndDynamicProjectionData:: +get_max_segment_num_to_process() const +{ return this->_max_segment_num_to_process; } + +template +const bool +PoissonNestedLogLikelihoodWithLinearKineticModelAndDynamicProjectionData:: +get_zero_seg0_end_planes() const +{ return this->_zero_seg0_end_planes; } + +template +const DynamicProjData& +PoissonNestedLogLikelihoodWithLinearKineticModelAndDynamicProjectionData:: +get_additive_proj_data() const +{ return *this->_additive_dyn_proj_data_sptr; } + +template +const shared_ptr& +PoissonNestedLogLikelihoodWithLinearKineticModelAndDynamicProjectionData:: +get_additive_proj_data_sptr() const +{ return this->_additive_dyn_proj_data_sptr; } + +template +const ProjectorByBinPair& +PoissonNestedLogLikelihoodWithLinearKineticModelAndDynamicProjectionData:: +get_projector_pair() const +{ return *this->_projector_pair_ptr; } + +template +const shared_ptr& +PoissonNestedLogLikelihoodWithLinearKineticModelAndDynamicProjectionData:: +get_projector_pair_sptr() const +{ return this->_projector_pair_ptr; } + +template +const BinNormalisation& +PoissonNestedLogLikelihoodWithLinearKineticModelAndDynamicProjectionData:: +get_normalisation() const +{ return *this->_normalisation_sptr; } + +template +const shared_ptr& +PoissonNestedLogLikelihoodWithLinearKineticModelAndDynamicProjectionData:: +get_normalisation_sptr() const +{ return this->_normalisation_sptr; } + +template +const shared_ptr& +PoissonNestedLogLikelihoodWithLinearKineticModelAndDynamicProjectionData:: +get_model_sensitivity_image_sptr() const +{ + return this->model_sensitivity_image_sptr; +} + +template +const TargetT& +PoissonNestedLogLikelihoodWithLinearKineticModelAndDynamicProjectionData:: +get_model_sensitivity_image() const +{ + return *this->model_sensitivity_image_sptr; +} + +template +const DynamicProjData& +PoissonNestedLogLikelihoodWithLinearKineticModelAndDynamicProjectionData::get_input_data() const +{ + return *this->_dyn_proj_data_sptr; +} + + +/*************************************************************** + set_ functions +***************************************************************/ +template +int +PoissonNestedLogLikelihoodWithLinearKineticModelAndDynamicProjectionData:: +set_num_subsets(const int num_subsets) +{ + for(unsigned int frame_num=this->_patlak_plot_sptr->get_starting_frame();frame_num<=this->_patlak_plot_sptr->get_time_frame_definitions().get_num_frames();++frame_num) + { + if(this->_single_frame_obj_funcs.size() != 0) + if(this->_single_frame_obj_funcs[frame_num].set_num_subsets(num_subsets) != num_subsets) + error("set_num_subsets didn't work"); + } + this->num_subsets=num_subsets; + return this->num_subsets; +} + +template +void +PoissonNestedLogLikelihoodWithLinearKineticModelAndDynamicProjectionData::set_input_data(const shared_ptr& arg) +{ + this->already_set_up = false; + this->_dyn_proj_data_sptr = dynamic_pointer_cast(arg); +} + +template +void +PoissonNestedLogLikelihoodWithLinearKineticModelAndDynamicProjectionData::set_additive_proj_data_sptr( + const shared_ptr& arg) +{ + this->already_set_up = false; + this->_additive_dyn_proj_data_sptr = dynamic_pointer_cast(arg); +} + +template +void +PoissonNestedLogLikelihoodWithLinearKineticModelAndDynamicProjectionData::set_normalisation_sptr( + const shared_ptr& arg) +{ + this->already_set_up = false; + // this->normalisation_sptr = arg; + error("Not implemeted yet"); +} + +/*************************************************************** + set_up() +***************************************************************/ +template +Succeeded +PoissonNestedLogLikelihoodWithLinearKineticModelAndDynamicProjectionData:: +set_up_before_sensitivity(shared_ptr const& target_sptr) +{ + if (this->_max_segment_num_to_process==-1) + this->_max_segment_num_to_process = + (this->_dyn_proj_data_sptr)->get_proj_data_sptr(1)->get_max_segment_num(); + + if (this->_max_segment_num_to_process > (this->_dyn_proj_data_sptr)->get_proj_data_sptr(1)->get_max_segment_num()) + { + warning("_max_segment_num_to_process (%d) is too large", + this->_max_segment_num_to_process); + return Succeeded::no; + } + + shared_ptr proj_data_info_sptr( + (this->_dyn_proj_data_sptr->get_proj_data_sptr(1))->get_proj_data_info_sptr()->clone()); + proj_data_info_sptr-> + reduce_segment_range(-this->_max_segment_num_to_process, + +this->_max_segment_num_to_process); + + if (is_null_ptr(this->_projector_pair_ptr)) + { warning("You need to specify a projector pair"); return Succeeded::no; } + + if (this->num_subsets <= 0) + { + warning("Number of subsets %d should be larger than 0.", + this->num_subsets); + return Succeeded::no; + } + + if (is_null_ptr(this->_normalisation_sptr)) + { + warning("Invalid normalisation object"); + return Succeeded::no; + } + + if (this->_normalisation_sptr->set_up(this->get_dyn_proj_data_sptr()->get_exam_info_sptr(), + proj_data_info_sptr) == Succeeded::no) + return Succeeded::no; + + // this->_patlak_plot_sptr->set_radionuclide(this->get_dyn_proj_data_sptr()->get_exam_info_sptr()->get_radionuclide()); + + if (this->_patlak_plot_sptr->set_up() == Succeeded::no) + return Succeeded::no; + + if (this->_patlak_plot_sptr->get_starting_frame()<=0 || this->_patlak_plot_sptr->get_starting_frame()>this->_patlak_plot_sptr->get_time_frame_definitions().get_num_frames()) + { + warning("Starting frame is %d. Generally, it should be a late frame,\nbut in any case it should be less than the number of frames %d\nand at least 1.",this->_patlak_plot_sptr->get_starting_frame(), this->_patlak_plot_sptr->get_time_frame_definitions().get_num_frames()); + return Succeeded::no; + } + + { + const shared_ptr > + density_template_sptr((target_sptr->construct_single_density(1)).get_empty_copy()); + const shared_ptr scanner_sptr(new Scanner(*proj_data_info_sptr->get_scanner_ptr())); + this->_dyn_image_template= + DynamicDiscretisedDensity(this->_patlak_plot_sptr->get_time_frame_definitions(), + this->_dyn_proj_data_sptr->get_start_time_in_secs_since_1970(), + scanner_sptr, + density_template_sptr); + + //Computes model sensitivity image by utilizing Patlak plot model matrix + this->compute_model_sensitivity_image(*target_sptr); + + // construct _single_frame_obj_funcs + this->_single_frame_obj_funcs.resize(this->_patlak_plot_sptr->get_starting_frame(),this->_patlak_plot_sptr->get_time_frame_definitions().get_num_frames()); + + for(unsigned int frame_num=this->_patlak_plot_sptr->get_starting_frame();frame_num<=this->_patlak_plot_sptr->get_time_frame_definitions().get_num_frames();++frame_num) + { + this->_single_frame_obj_funcs[frame_num].set_projector_pair_sptr(this->_projector_pair_ptr); + this->_single_frame_obj_funcs[frame_num].set_proj_data_sptr(this->_dyn_proj_data_sptr->get_proj_data_sptr(frame_num)); + this->_single_frame_obj_funcs[frame_num].set_max_segment_num_to_process(this->_max_segment_num_to_process); + this->_single_frame_obj_funcs[frame_num].set_zero_seg0_end_planes(this->_zero_seg0_end_planes!=0); + if(this->_additive_dyn_proj_data_sptr!=NULL) + this->_single_frame_obj_funcs[frame_num].set_additive_proj_data_sptr(this->_additive_dyn_proj_data_sptr->get_proj_data_sptr(frame_num)); + this->_single_frame_obj_funcs[frame_num].set_num_subsets(this->num_subsets); + this->_single_frame_obj_funcs[frame_num].set_frame_num(frame_num); + this->_single_frame_obj_funcs[frame_num].set_frame_definitions(this->_patlak_plot_sptr->get_time_frame_definitions()); + this->_single_frame_obj_funcs[frame_num].set_normalisation_sptr(this->_normalisation_sptr); + this->_single_frame_obj_funcs[frame_num].set_recompute_sensitivity(this->get_recompute_sensitivity()); + this->_single_frame_obj_funcs[frame_num].set_use_subset_sensitivities(this->get_use_subset_sensitivities()); + if(this->_single_frame_obj_funcs[frame_num].set_up(density_template_sptr) != Succeeded::yes) + error("Single frame objective functions is not set correctly!"); + } + }//_single_frame_obj_funcs[frame_num] + + return Succeeded::yes; +} + +/************************************************************************* + functions that compute the value/gradient of the objective function etc +*************************************************************************/ + +template +void +PoissonNestedLogLikelihoodWithLinearKineticModelAndDynamicProjectionData:: +actual_compute_subset_gradient_without_penalty(TargetT& gradient, + const TargetT ¤t_estimate, + const int subset_num, + const bool add_sensitivity) +{ + + if (!add_sensitivity) + error("Not supported for nested Patlak objective functions; use OSMAPOSL/POSMAPOSL-style callers"); + + // Clone the const TargetT& current estimate to a TargetT nested_estimate + shared_ptr current_nested_estimate(current_estimate.get_empty_copy()); + + { + typename TargetT::const_full_iterator current_estimate_iter = current_estimate.begin_all_const(); + const typename TargetT::const_full_iterator end_current_estimate_iter = current_estimate.end_all_const(); + typename TargetT::full_iterator current_nested_estimate_iter = current_nested_estimate->begin_all(); + while (current_estimate_iter!=end_current_estimate_iter) + { + *current_nested_estimate_iter = (*current_estimate_iter); + ++current_nested_estimate_iter; ++current_estimate_iter; + } + } + + this->actual_compute_nested_sub_gradient_without_penalty(gradient, + *current_nested_estimate, + subset_num, + add_sensitivity); + + this->last_nested_estimate_sptr = current_nested_estimate; + +} + +template +void +PoissonNestedLogLikelihoodWithLinearKineticModelAndDynamicProjectionData:: +actual_compute_nested_sub_gradient_without_penalty(TargetT& gradient, + TargetT ¤t_estimate, + const int subset_num, + const bool add_sensitivity) +{ + + DynamicDiscretisedDensity dyn_gradient=this->_dyn_image_template; + DynamicDiscretisedDensity dyn_image_estimate=this->_dyn_image_template; + DynamicDiscretisedDensity dyn_image_reference_data=this->_dyn_image_template; + DynamicDiscretisedDensity dyn_image_nested_loop_estimate=this->_dyn_image_template; + DynamicDiscretisedDensity dyn_sensitivity=this->_dyn_image_template; + + + for(unsigned int frame_num=this->_patlak_plot_sptr->get_starting_frame();frame_num<=this->_patlak_plot_sptr->get_time_frame_definitions().get_num_frames();++frame_num) + std::fill(dyn_image_estimate[frame_num].begin_all(), + dyn_image_estimate[frame_num].end_all(), + 1.F); + + + //Print out the min and max values of the initial parametric estimate + const float current_min_estimate = + *std::min_element(current_estimate.begin_all(), + current_estimate.end_all()); + const float current_max_estimate = + *std::max_element(current_estimate.begin_all(), + current_estimate.end_all()); + #ifndef NDEBUG + info(format("Initial parametric image, (min, max): ({}, {})", current_min_estimate, current_max_estimate)); + #endif + + //Forward project from parameter space to time space using a parametric image estimate as an input + this->_patlak_plot_sptr->get_dynamic_image_from_parametric_image(dyn_image_estimate,current_estimate) ; + dyn_image_reference_data = dyn_image_estimate; + + + CPUTimer outer_loop_timer; + outer_loop_timer.start(); + + // loop over single_frame and use model_matrix + for(unsigned int frame_num=this->_patlak_plot_sptr->get_starting_frame();frame_num<=this->_patlak_plot_sptr->get_time_frame_definitions().get_num_frames();++frame_num) + { +#ifndef NDEBUG + //Get system sensitivity for each dynamic frame + info(format("Getting system sub-sensitivity image for dynamic frame: {} ...", frame_num)); +#endif // NDEBUG + dyn_sensitivity[frame_num]=this->_single_frame_obj_funcs[frame_num].get_subset_sensitivity(subset_num); + + //Print out the min and max values of the system dynamic sensitivity image + const float current_min_system_dyn_sensitivity = + *std::min_element(dyn_sensitivity[frame_num].begin_all(), + dyn_sensitivity[frame_num].end_all()); + const float current_max_system_dyn_sensitivity = + *std::max_element(dyn_sensitivity[frame_num].begin_all(), + dyn_sensitivity[frame_num].end_all()); +#ifndef NDEBUG + info(format("System sensitivity image for dynamic frame: {}, (min, max): ({}, {})", frame_num, + current_min_system_dyn_sensitivity, current_max_system_dyn_sensitivity)); +#endif // NDEBUG + + //Compute sub-gradient for each frame +#ifndef NDEBUG + info(format("Compute sub-gradient (update image) for dynamic frame: {}.", frame_num)); +#endif + std::fill(dyn_gradient[frame_num].begin_all(), + dyn_gradient[frame_num].end_all(), + 1.F); + + this->_single_frame_obj_funcs[frame_num]. + actual_compute_subset_gradient_without_penalty(dyn_gradient[frame_num], + dyn_image_estimate[frame_num], + subset_num, add_sensitivity); + + //Print out the min and max values of the sub-gradient for each fynamic frame + const float current_min_outer_loop_gradient = + *std::min_element(dyn_gradient[frame_num].begin_all(), + dyn_gradient[frame_num].end_all()); + const float current_max_outer_loop_gradient = + *std::max_element(dyn_gradient[frame_num].begin_all(), + dyn_gradient[frame_num].end_all()); +#ifndef NDEBUG + info(format("Outer loop dynamic sub-gradient image (frame): {}, (min, max): ({}, {})", frame_num, + current_min_outer_loop_gradient, current_max_outer_loop_gradient)); +#endif // NDEBUG + + // Perform projection matrix sensitivity division and update for the single outer loop iteration + + // Devide by system matrix sensitivity +#ifndef NDEBUG + cerr << "Divide sub-gradient (update image) by system sub-sensitivity for dynamic frame " << frame_num << "." << endl; +#endif // NDEBUG + divide(dyn_gradient[frame_num].begin_all(), + dyn_gradient[frame_num].end_all(), + dyn_sensitivity[frame_num].begin_all(), + small_num); + + //Print out the min and max values of the sub-gradient/sensitivity for each fynamic frame + const float current_min_outer_loop_gradient_over_sensitivity = + *std::min_element(dyn_gradient[frame_num].begin_all(), + dyn_gradient[frame_num].end_all()); + const float current_max_outer_loop_gradient_over_sensitivity = + *std::max_element(dyn_gradient[frame_num].begin_all(), + dyn_gradient[frame_num].end_all()); + #ifndef NDEBUG + info(format("Outer loop dynamic sub-gradient/sensitivity image (frame) {}, (min, max): ({}, {})", + frame_num, current_min_outer_loop_gradient_over_sensitivity, current_max_outer_loop_gradient_over_sensitivity)); + #endif // NDEBUG + + // Update outer loop dynamic image estimate + DiscretisedDensity<3,float>::const_full_iterator dyn_gradient_single_frame_iter = dyn_gradient[frame_num].begin_all_const(); + DiscretisedDensity<3,float>::const_full_iterator end_dyn_gradient_single_frame_iter = dyn_gradient[frame_num].end_all_const(); + DiscretisedDensity<3,float>::full_iterator dyn_image_reference_data_single_frame_iter = dyn_image_reference_data[frame_num].begin_all(); + while (dyn_gradient_single_frame_iter!=end_dyn_gradient_single_frame_iter) + { + *dyn_image_reference_data_single_frame_iter *= (*dyn_gradient_single_frame_iter); + ++dyn_image_reference_data_single_frame_iter; ++dyn_gradient_single_frame_iter; + } + + //Print out the min and max values of the outer loop updated dynamic images for each fynamic frame + const float current_min_outer_loop_updated_image = + *std::min_element(dyn_image_reference_data[frame_num].begin_all(), + dyn_image_reference_data[frame_num].end_all()); + const float current_max_outer_loop_updated_image = + *std::max_element(dyn_image_reference_data[frame_num].begin_all(), + dyn_image_reference_data[frame_num].end_all()); + #ifndef NDEBUG + info(format("Outer loop updated image (frame): {}, (min, max): ({}, {})", + frame_num, current_min_outer_loop_updated_image, current_max_outer_loop_updated_image)); + #endif // NDEBUG + } + + info(format("Current outer loop computation time: {}", outer_loop_timer.value()), 2); + + CPUTimer nested_loop_timer; + nested_loop_timer.start(); + + #ifndef NDEBUG + //nested EM loop + info(format("Entering nested loop ")); + #endif // NDEBUG + + // This is the principal method that iteratively estimates the generalized Patlak estimates in a nested EM loop + this->estimate_nested_loop_parameters_with_model(gradient, + current_estimate, + dyn_image_estimate, + dyn_image_reference_data, + dyn_image_nested_loop_estimate); + + info(format("Total computation time for {} nested iterations: ", this->num_nested_subiterations, nested_loop_timer.value()), 2); + +} + + + +template +void +PoissonNestedLogLikelihoodWithLinearKineticModelAndDynamicProjectionData:: +estimate_nested_loop_parameters_with_model(TargetT &gradient, + TargetT ¤t_estimate, + DynamicDiscretisedDensity &dyn_image_estimate, + DynamicDiscretisedDensity &dyn_image_reference_data, + DynamicDiscretisedDensity &dyn_image_nested_loop_estimate) +{ + + //nested EM loop + info(format("Entering nested loop ({} standard Patlak EM subiterations).", this->num_nested_subiterations)); + + for(nested_subiterations_num=1; nested_subiterations_num<=this->num_nested_subiterations; nested_subiterations_num++) + { + //equivalent of forward-projection operation for kinetic parameter estimation + this->_patlak_plot_sptr->get_dynamic_image_from_parametric_image(dyn_image_nested_loop_estimate, + current_estimate); + + dyn_image_estimate = dyn_image_reference_data; + // loop over single_frame and use model_matrix and the outer loop dynamic image estimate + for(unsigned int frame_num=this->_patlak_plot_sptr->get_starting_frame();frame_num<=this->_patlak_plot_sptr->get_time_frame_definitions().get_num_frames();++frame_num) + divide(dyn_image_estimate[frame_num].begin_all(), + dyn_image_estimate[frame_num].end_all(), + dyn_image_nested_loop_estimate[frame_num].begin_all(), + small_num); + + //equivalent of back-projection operation for kinetic parameter estimation + this->_patlak_plot_sptr->multiply_dynamic_image_with_model_gradient(gradient, + dyn_image_estimate) ; + + + // Perform model sensitivity division and update for all nested iterations + + // Devide by model sensitivity + divide(gradient.begin_all(), + gradient.end_all(), + this->model_sensitivity_image_sptr->begin_all(), + small_num); + + if (nested_subiterations_num != 1) + { + const float current_min_nested_gradient = + *std::min_element(gradient.begin_all(), + gradient.end_all()); + const float current_max_nested_gradient = + *std::max_element(gradient.begin_all(), + gradient.end_all()); + const float new_min_nested_gradient = + static_cast(this->minimum_nested_relative_change); + const float new_max_nested_gradient = + static_cast(this->maximum_nested_relative_change); + cerr << "Nested iteration: " << nested_subiterations_num + << " sub-gradient(update image) old value (min, max): (" + << current_min_nested_gradient << ", " << current_max_nested_gradient + << "), new value (min, max) (" + << std::max(current_min_nested_gradient, new_min_nested_gradient) << ", " + << std::min(current_max_nested_gradient, new_max_nested_gradient) << ")" << endl; + + threshold_upper_lower(gradient.begin_all(), + gradient.end_all(), + new_min_nested_gradient, new_max_nested_gradient); + } + + //Nested updates of image estimates + { + typename TargetT::const_full_iterator gradient_iter = gradient.begin_all_const(); + const typename TargetT::const_full_iterator end_gradient_iter = gradient.end_all_const(); + typename TargetT::full_iterator current_estimate_iter = current_estimate.begin_all(); + while (gradient_iter!=end_gradient_iter) + { + *current_estimate_iter *= (*gradient_iter); + ++current_estimate_iter; ++gradient_iter; + } + } + + //Print out the min and max values of the nested updated image for each nested iteration + const float current_min_nested_updated_image = + *std::min_element(current_estimate.begin_all(), + current_estimate.end_all()); + const float current_max_nested_updated_image = + *std::max_element(current_estimate.begin_all(), + current_estimate.end_all()); + cerr << "Nested iteration: " << nested_subiterations_num + << " Updated image value (min, max) (" + << current_min_nested_updated_image << ", " << current_max_nested_updated_image << ")" << endl << endl; + + } + + info(format("End of nested reconstruction process of parameter estimates (after {}nested EM subiterations)", this->num_nested_subiterations)); + +} + + +template +double +PoissonNestedLogLikelihoodWithLinearKineticModelAndDynamicProjectionData:: +actual_compute_objective_function_without_penalty(const TargetT& current_estimate, + const int subset_num) +{ + assert(subset_num>=0); + assert(subset_numnum_subsets); + + double result = 0.; + DynamicDiscretisedDensity dyn_image_estimate=this->_dyn_image_template; + + // TODO why fill with 1? + for(unsigned int frame_num=this->_patlak_plot_sptr->get_starting_frame();frame_num<=this->_patlak_plot_sptr->get_time_frame_definitions().get_num_frames();++frame_num) + std::fill(dyn_image_estimate[frame_num].begin_all(), + dyn_image_estimate[frame_num].end_all(), + 1.F); + this->_patlak_plot_sptr->get_dynamic_image_from_parametric_image(dyn_image_estimate,current_estimate) ; + + // loop over single_frame + for(unsigned int frame_num=this->_patlak_plot_sptr->get_starting_frame(); + frame_num<=this->_patlak_plot_sptr->get_time_frame_definitions().get_num_frames(); + ++frame_num) + { + result += + this->_single_frame_obj_funcs[frame_num]. + compute_objective_function_without_penalty(dyn_image_estimate[frame_num], + subset_num); + } + return result; +} + +template +void +PoissonNestedLogLikelihoodWithLinearKineticModelAndDynamicProjectionData:: +compute_model_sensitivity_image(const TargetT& param_image) +{ + + shared_ptr param_image_sptr(param_image.get_empty_copy()); + this->model_sensitivity_image_sptr=param_image_sptr; + + //Initialize model sensitivity image + std::fill(model_sensitivity_image_sptr->begin_all(), + this->model_sensitivity_image_sptr->end_all(), + 1.F); + + DynamicDiscretisedDensity dyn_image_of_all_ones=this->_dyn_image_template; + + // loop over single_frame and use model_matrix + for(unsigned int frame_num=this->_patlak_plot_sptr->get_starting_frame();frame_num<=this->_patlak_plot_sptr->get_time_frame_definitions().get_num_frames();++frame_num) + std::fill(dyn_image_of_all_ones[frame_num].begin_all(), + dyn_image_of_all_ones[frame_num].end_all(), + 1.F); + + cerr << "Computing model sensitivity image..." << endl; + + this->_patlak_plot_sptr->multiply_dynamic_image_with_model_gradient(*this->model_sensitivity_image_sptr, + dyn_image_of_all_ones) ; + + + //Print out the min and max values of the model sensitivity image + const float current_min_model_sensitivity = + *std::min_element(this->model_sensitivity_image_sptr->begin_all(), + this->model_sensitivity_image_sptr->end_all()); + const float current_max_model_sensitivity = + *std::max_element(this->model_sensitivity_image_sptr->begin_all(), + this->model_sensitivity_image_sptr->end_all()); + cerr << "Model sensitivity image " + << ", (min, max): (" << current_min_model_sensitivity << ", " << current_max_model_sensitivity << ")" << endl; + + cerr << "Model sensitivity image has been computed." << endl; +} + + +template +void +PoissonNestedLogLikelihoodWithLinearKineticModelAndDynamicProjectionData:: +add_subset_sensitivity(TargetT& model_sensitivity, const int subset_num) const +{ + DynamicDiscretisedDensity dyn_image_of_all_ones=this->_dyn_image_template; + + // loop over single_frame and use model_matrix + for(unsigned int frame_num=this->_patlak_plot_sptr->get_starting_frame();frame_num<=this->_patlak_plot_sptr->get_time_frame_definitions().get_num_frames();++frame_num) + std::fill(dyn_image_of_all_ones[frame_num].begin_all(), + dyn_image_of_all_ones[frame_num].end_all(), + 1.F); + + this->_patlak_plot_sptr->multiply_dynamic_image_with_model_gradient_and_add_to_input(model_sensitivity, + dyn_image_of_all_ones); + + +} + +template +Succeeded +PoissonNestedLogLikelihoodWithLinearKineticModelAndDynamicProjectionData:: +actual_add_multiplication_with_approximate_sub_Hessian_without_penalty(TargetT& output, + const TargetT& input, + const int subset_num) const +{ + { + std::string explanation; + if (!input.has_same_characteristics(this->get_sensitivity(), + explanation)) + { + warning("PoissonNestedLogLikelihoodWithLinearKineticModelAndDynamicProjectionData:\n" + "sensitivity and input for add_multiplication_with_approximate_Hessian_without_penalty\n" + "should have the same characteristics.\n%s", + explanation.c_str()); + return Succeeded::no; + } + } +#ifndef NDEBUG + std::cerr << "INPUT max: (" << input.construct_single_density(1).find_max() + << " , " << input.construct_single_density(2).find_max() + << ")\n"; +#endif //NDEBUG + DynamicDiscretisedDensity dyn_input=this->_dyn_image_template; + DynamicDiscretisedDensity dyn_output=this->_dyn_image_template; + this->_patlak_plot_sptr->get_dynamic_image_from_parametric_image(dyn_input,input) ; + + VectorWithOffset scale_factor(this->_patlak_plot_sptr->get_starting_frame(),this->_patlak_plot_sptr->get_time_frame_definitions().get_num_frames()); + for(unsigned int frame_num=this->_patlak_plot_sptr->get_starting_frame(); + frame_num<=this->_patlak_plot_sptr->get_time_frame_definitions().get_num_frames(); + ++frame_num) + { + assert(dyn_input[frame_num].find_max()==dyn_input[frame_num].find_min()); + if (dyn_input[frame_num].find_max()==dyn_input[frame_num].find_min() && dyn_input[frame_num].find_min()>0.F) + scale_factor[frame_num]=dyn_input[frame_num].find_max(); + else + error("The input image should be uniform even after multiplying with the Patlak Plot.\n"); + +/*! /note This is used to avoid higher values than these set in the precompute_denominator_of_conditioner_without_penalty() function. +/sa for more information see the recon_array_functions.cxx and the value of the max_quotient (originaly set to 10000.F) +*/ + dyn_input[frame_num]/=scale_factor[frame_num]; +#ifndef NDEBUG + std::cerr << "scale factor[" << frame_num << "] " << scale_factor[frame_num] << "\n"; + std::cerr << "dyn_input[" << frame_num << "] max after scale: " + << dyn_input[frame_num].find_max() << "\n"; +#endif //NDEBUG + this->_single_frame_obj_funcs[frame_num]. + add_multiplication_with_approximate_sub_Hessian_without_penalty(dyn_output[frame_num], + dyn_input[frame_num], + subset_num); +#ifndef NDEBUG + std::cerr << "dyn_output[" << frame_num << "] max before scale: (" + << dyn_output[frame_num].find_max() << "\n"; +#endif //NDEBUG + dyn_output[frame_num]*=scale_factor[frame_num]; +#ifndef NDEBUG + std::cerr << "dyn_output[" << frame_num << "] max after scale: (" + << dyn_output[frame_num].find_max() << "\n"; +#endif //NDEBUG + } // end of loop over frames + shared_ptr unnormalised_temp(output.get_empty_copy()); + shared_ptr temp(output.get_empty_copy()); + this->_patlak_plot_sptr->multiply_dynamic_image_with_model_gradient(*unnormalised_temp, + dyn_output) ; + // Trick to use a better step size for the two parameters. + (this->_patlak_plot_sptr->get_model_matrix()).normalise_parametric_image_with_model_sum(*temp,*unnormalised_temp) ; +#ifndef NDEBUG + std::cerr << "TEMP max: (" << temp->construct_single_density(1).find_max() + << " , " << temp->construct_single_density(2).find_max() + << ")\n"; + // Writing images + OutputFileFormat::default_sptr()->write_to_file("all_params_one_input.img", input); + OutputFileFormat::default_sptr()->write_to_file("temp_denominator.img", *temp); + dyn_input.write_to_ecat7("dynamic_input_from_all_params_one.img"); + dyn_output.write_to_ecat7("dynamic_precomputed_denominator.img"); + DynamicProjData temp_projdata = this->get_dyn_proj_data(); + for(unsigned int frame_num=this->_patlak_plot_sptr->get_starting_frame(); + frame_num<=this->_patlak_plot_sptr->get_time_frame_definitions().get_num_frames(); + ++frame_num) + temp_projdata.set_proj_data_sptr(this->_single_frame_obj_funcs[frame_num].get_proj_data_sptr(),frame_num); + + temp_projdata.write_to_ecat7("DynamicProjections.S"); +#endif // NDEBUG + // output += temp + typename TargetT::full_iterator out_iter = output.begin_all(); + typename TargetT::full_iterator out_end = output.end_all(); + typename TargetT::const_full_iterator temp_iter = temp->begin_all_const(); + while (out_iter != out_end) + { + *out_iter += *temp_iter; + ++out_iter; ++temp_iter; + } +#ifndef NDEBUG + std::cerr << "OUTPUT max: (" << output.construct_single_density(1).find_max() + << " , " << output.construct_single_density(2).find_max() + << ")\n"; +#endif // NDEBUG + + + return Succeeded::yes; +} + + +END_NAMESPACE_STIR + diff --git a/src/include/stir/recon_buildblock/PoissonNestedLogLikelihoodWithLinearModelForMeanAndConvolvedProjDataWithMotion.h b/src/include/stir/recon_buildblock/PoissonNestedLogLikelihoodWithLinearModelForMeanAndConvolvedProjDataWithMotion.h new file mode 100644 index 0000000000..43382216b7 --- /dev/null +++ b/src/include/stir/recon_buildblock/PoissonNestedLogLikelihoodWithLinearModelForMeanAndConvolvedProjDataWithMotion.h @@ -0,0 +1,406 @@ +/* + Copyright (C) 2003 - 2011-02-23, Hammersmith Imanet Ltd + This file is part of STIR. + + SPDX-License-Identifier: Apache-2.0 + + See STIR/LICENSE.txt for details +*/ +/*! + \file + \ingroup GeneralisedObjectiveFunction + \brief Declaration of class stir::PoissonNestedLogLikelihoodWithLinearModelForMeanAndConvolvedProjDataWithMotion + + \author Nicolas A Karakatsanis + +*/ + +#ifndef __stir_recon_buildblock_PoissonNestedLogLikelihoodWithLinearModelForMeanAndConvolvedProjDataWithMotion_H__ +#define __stir_recon_buildblock_PoissonNestedLogLikelihoodWithLinearModelForMeanAndConvolvedProjDataWithMotion_H__ + +#include "stir/RegisteredParsingObject.h" +#include "stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMean.h" +#include "stir/ProjData.h" +#include "stir/recon_buildblock/ProjectorByBinPair.h" +#include "stir/recon_buildblock/BinNormalisation.h" +#include "stir/TimeFrameDefinitions.h" +#include "stir/TimeGateDefinitions.h" +#include "stir/spatial_transformation/GatedSpatialTransformation.h" + +#ifdef STIR_MPI +# include "stir/recon_buildblock/distributable.h" // for RPC_process_related_viewgrams_type +#endif + +START_NAMESPACE_STIR + +class DistributedCachingInformation; + +//#ifdef STIR_MPI_CLASS_DEFINITION +//#define PoissonNestedLogLikelihoodWithLinearModelForMeanAndConvolvedProjDataWithMotion +// PoissonNestedLogLikelihoodWithLinearModelForMeanAndConvolvedProjDataWithMotion_MPI #endif + +/*! + \ingroup GeneralisedObjectiveFunction + \brief An objective function class appropriate for PET emission data + + Measured data is given by a ProjData object, and the linear operations + necessary for computing the gradient of the objective function + are performed via a ProjectorByBinPair object together with + a BinNormalisation object. + + \see PoissonLogLikelihoodWithLinearModelForMean for notation. + + Based on non-nested STIR implementation of objective function: + PoissonLogLikelihoodWithLinearModelForMeanAndProjData + + Often, the probability matrix \f$P\f$ can be written as the product + of a diagonal matrix \f$D\f$ and another matrix \f$F\f$ + \f[ P = D G \f] + The measurement model can then be written as + + \f[ P \lambda + r = D ( F \lambda + a ) \f] + + and backprojection is obviously + + \f[ P' y = F' D y \f] + + This can be generalised by using a different matrix \f$B\f$ to perform + the backprojection. + + The expression for the gradient becomes + + \f[ B D \left[ y / \left( D (F \lambda + a) \right) \right] - B D 1 = + B \left[ y / \left(F \lambda + a \right) \right] - B D 1 + \f] + + where \f$D\f$ dropped from the first term. This was probably noticed + for the first time in the context of MLEM by Hebert and Leahy, but + is in fact a feature of the gradient (and indeed log-likelihood). + This was first brought to Kris Thielemans' attention by + Michael Zibulevsky and Arkady Nemirovsky. + + Note that if \f$F\f$ and \f$B\f$ are not exact transposed operations, the + gradient does no longer correspond to the gradient + of a well-defined function. + + In this class, the operation of multiplying with \f$F\f$ is performed + by the forward projector, and with \f$B\f$ by the back projector. + Multiplying with \f$D\f$ corresponds to the BinNormalisation::undo() + function. Finally, the background term \f$a\f$ is stored in + \c additive_proj_data_sptr (note: this is not \f$r\f$). + + \par Parameters for parsing + + \verbatim + PoissonNestedLogLikelihoodWithLinearModelForMeanAndConvolvedProjDataWithMotion Parameters:= + + ; emission projection data + input file := + + maximum absolute segment number to process := + zero end planes of segment 0 := + + ; see ProjectorByBinPair hierarchy for possible values + Projector pair type := + + ; reserved value: 0 means none + ; see PoissonNestedLogLikelihoodWithLinearModelForMeanAndConvolvedProjDataWithMotion + ; class documentation + additive sinogram := + + ; normalisation (and attenuation correction) + ; time info can be used for dead-time correction + + ; see TimeFrameDefinitions + time frame definition filename := + time frame number := + ; see BinNormalisation hierarchy for possible values + Bin Normalisation type := + + End PoissonNestedLogLikelihoodWithLinearModelForMeanAndConvolvedProjDataWithMotion Parameters := + \endverbatim +*/ +template +class PoissonNestedLogLikelihoodWithLinearModelForMeanAndConvolvedProjDataWithMotion + : public RegisteredParsingObject, + GeneralisedObjectiveFunction, + PoissonLogLikelihoodWithLinearModelForMean> +{ +private: + typedef RegisteredParsingObject, + GeneralisedObjectiveFunction, + PoissonLogLikelihoodWithLinearModelForMean> + base_type; + + TimeGateDefinitions _time_gate_definitions; + +public: + //! Name which will be used when parsing a GeneralisedObjectiveFunction object + static const char* const registered_name; + + // + /*! \name Variables for STIR_MPI + + Only used when STIR_MPI is enabled. + \todo move to protected area + */ + //@{ + //! points to the information object needed to support distributed caching + DistributedCachingInformation* caching_info_ptr; + //#ifdef STIR_MPI + //! enable/disable key for distributed caching + bool distributed_cache_enabled; + bool distributed_tests_enabled; + bool message_timings_enabled; + double message_timings_threshold; + bool rpc_timings_enabled; + //#endif + //@} + + //! Default constructor calls set_defaults() + PoissonNestedLogLikelihoodWithLinearModelForMeanAndConvolvedProjDataWithMotion(); + + //! Destructor + /*! Calls end_distributable_computation() + */ + ~PoissonNestedLogLikelihoodWithLinearModelForMeanAndConvolvedProjDataWithMotion(); + + //! Returns a pointer to a newly allocated target object (with 0 data). + /*! Dimensions etc are set from the \a proj_data_sptr and other information set by parsing, + such as \c zoom, \c output_image_size_z etc. + */ + virtual TargetT* construct_target_ptr() const; + + /*! \name Functions to get parameters + \warning Be careful with changing shared pointers. If you modify the objects in + one place, all objects that use the shared pointer will be affected. + */ + //@{ + const ProjData& get_proj_data() const; + const shared_ptr& get_proj_data_sptr() const; + const int get_max_segment_num_to_process() const; + const bool get_zero_seg0_end_planes() const; + const ProjData& get_additive_proj_data() const; + const shared_ptr& get_additive_proj_data_sptr() const; + const ProjectorByBinPair& get_projector_pair() const; + const shared_ptr& get_projector_pair_sptr() const; + const int get_time_frame_num() const; + const TimeFrameDefinitions& get_time_frame_definitions() const; + const BinNormalisation& get_normalisation() const; + const shared_ptr& get_normalisation_sptr() const; + //@} + /*! \name Functions to set parameters + This can be used as alternative to the parsing mechanism. + \warning After using any of these, you have to call set_up(). + \warning Be careful with changing shared pointers. If you modify the objects in + one place, all objects that use the shared pointer will be affected. + + */ + //@{ + int set_num_subsets(const int num_subsets); + void set_proj_data_sptr(const shared_ptr&); + void set_max_segment_num_to_process(const int); + void set_zero_seg0_end_planes(const bool); + void set_additive_proj_data_sptr(const shared_ptr&); + void set_projector_pair_sptr(const shared_ptr&); + void set_frame_num(const int); + void set_frame_definitions(const TimeFrameDefinitions&); + void set_normalisation_sptr(const shared_ptr&); + //@} + + virtual void + compute_sub_gradient_without_penalty_plus_sensitivity(TargetT& gradient, const TargetT& current_estimate, const int subset_num); + + virtual void compute_nested_sub_gradient_without_penalty_plus_sensitivity(TargetT& gradient, + TargetT& current_estimate, + TargetT& conv_gradient, + TargetT& conv_image_estimate, + TargetT& conv_image_reference_data, + TargetT& conv_image_nested_loop_estimate, + TargetT& conv_sensitivity, + const int subset_num); + +#if 0 + // currently not used + float sum_projection_data() const; +#endif + virtual void add_subset_sensitivity(TargetT& sensitivity, const int subset_num) const; + +protected: + virtual Succeeded set_up_before_sensitivity(shared_ptr const& target_sptr); + + virtual double actual_compute_objective_function_without_penalty(const TargetT& current_estimate, const int subset_num); + + // The principal nested EM reconstruction method that performs the RLMCIR reconstruction + virtual void estimate_nested_loop_parameters_with_model(TargetT& gradient, + TargetT& current_estimate, + TargetT& conv_image_estimate, + TargetT& conv_image_reference_data, + TargetT& conv_image_nested_loop_estimate); + + /*! + The Hessian (without penalty) is approximatelly given by: + \f[ H_{jk} = - \sum_i P_{ij} h_i^{''}(y_i) P_{ik} \f] + where + \f[ h_i(l) = y_i log (l) - l; h_i^{''}(y_i) ~= -1/y_i; \f] + and \f$P_{ij} \f$ is the probability matrix. + Hence + \f[ H_{jk} = \sum_i P_{ij}(1/y_i) P_{ik} \f] + + In the above, we've used the plug-in approximation by replacing + forward projection of the true image by the measured data. However, the + later are noisy and this can create problems. + + \todo Two work-arounds for the noisy estimate of the Hessian are listed below, + but they are currently not implemented. + + One could smooth the data before performing the quotient. This should be done + after normalisation to avoid problems with the high-frequency components in + the normalisation factors: + \f[ H_{jk} = \sum_i G_{ij}{1 \over n_i \mathrm{smooth}( n_i y_i)} G_{ik} \f] + where the probability matrix is factorised in a detection efficiency part (i.e. the + normalisation factors \f$n_i\f$) times a geometric part: + \f[ P_{ij} = {1 \over n_i } G_{ij}\f] + + It has also been suggested to use \f$1 \over y_i+1 \f$ (at least if the data are still Poisson. + */ + virtual Succeeded actual_add_multiplication_with_approximate_sub_Hessian_without_penalty(TargetT& output, + const TargetT& input, + const int subset_num) const; + + void set_time_gate_definitions(const TimeGateDefinitions& time_gate_definitions); + +protected: + //! Filename with input projection data + std::string input_filename; + + std::string _motion_vectors_filename_prefix; + std::string _reverse_motion_vectors_filename_prefix; + std::string _gate_definitions_filename; + + //! points to the object for the total input projection data + shared_ptr proj_data_sptr; + + //! the maximum absolute ring difference number to use in the reconstruction + /*! convention: if -1, use get_max_segment_num()*/ + int max_segment_num_to_process; + + const TimeGateDefinitions& get_time_gate_definitions() const; + + /**********************/ + // image stuff + // TODO to be replaced with single class or so (TargetT obviously) + //! the output image size in x and y direction + /*! convention: if -1, use a size such that the whole FOV is covered + */ + int output_image_size_xy; // KT 10122001 appended _xy + + //! the output image size in z direction + /*! convention: if -1, use default as provided by VoxelsOnCartesianGrid constructor + */ + int output_image_size_z; // KT 10122001 new + + //! the zoom factor + double zoom; + + //! offset in the x-direction + double Xoffset; + + //! offset in the y-direction + double Yoffset; + + // KT 20/06/2001 new + //! offset in the z-direction + double Zoffset; + /********************************/ + + //! the current subiteration index + int nested_subiterations_num; + + //! the current subiteration index + int save_nested_subiterations_interval; + + //! restrict updates (larger nested relative updates will be thresholded) + double maximum_nested_relative_change; + + //! restrict updates (smaller nested relative updates will be thresholded) + double minimum_nested_relative_change; + + //! Stores the projectors that are used for the computations + shared_ptr projector_pair_ptr; + + //! signals whether to zero the data in the end planes of the projection data + bool zero_seg0_end_planes; + + /*! the motion vectors where all information is stored */ + int _motion_correction_type; // This could be set up in a more generic way in order to choose how motion correction will be + // applied// + GatedSpatialTransformation _motion_vectors; + GatedSpatialTransformation _reverse_motion_vectors; + + //! convolved (motion-contaminated) image template (same as deconvolved or motion-corrected image template) + // TargetT _conv_image_template; + + //! Define a shared pointer for the (motion transformation) model sensitivity image + shared_ptr model_sensitivity_image_sptr; + + //! name of file in which additive projection data are stored + std::string additive_projection_data_filename; + //! points to the additive projection data + /*! the projection data in this file is bin-wise added to forward projection results*/ + shared_ptr additive_proj_data_sptr; + + // TODO doc + int frame_num; + std::string frame_definition_filename; + TimeFrameDefinitions frame_defs; + shared_ptr normalisation_sptr; + + // Loglikelihood computation parameters + // TODO rename and move higher up in the hierarchy + //! subiteration interval at which the loglikelihood function is evaluated + int loglikelihood_computation_interval; + + //! indicates whether to evaluate the loglikelihood function for all bins or the current subset + bool compute_total_loglikelihood; + + //! name of file in which loglikelihood measurements are stored + std::string loglikelihood_data_filename; + + //! sets any default values + /*! Has to be called by set_defaults in the leaf-class */ + virtual void set_defaults(); + //! sets keys for parsing + /*! Has to be called by initialise_keymap in the leaf-class */ + virtual void initialise_keymap(); + //! checks values after parsing + /*! Has to be called by post_processing in the leaf-class */ + virtual bool post_processing(); + + //! Checks of the current subset scheme is approximately balanced + /*! For this class, this means that the sub-sensitivities are + approximately the same. The test simply looks at the number + of views etc. It ignores unbalancing caused by normalisation_sptr + (e.g. for instance when using asymmetric attenuation). + */ + bool actual_subsets_are_approximately_balanced(std::string& warning_message) const; + + void compute_model_sensitivity_image(TargetT& motion_corrected_image); + +private: + shared_ptr symmetries_sptr; + + void add_view_seg_to_sensitivity(TargetT& sensitivity, const ViewSegmentNumbers& view_seg_nums) const; +}; + +#ifdef STIR_MPI +// made available to be called from DistributedWorker object +RPC_process_related_viewgrams_type RPC_process_related_viewgrams_gradient; +RPC_process_related_viewgrams_type RPC_process_related_viewgrams_accumulate_loglikelihood; +#endif + +END_NAMESPACE_STIR + +//#include "stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMean.inl" + +#endif diff --git a/src/include/stir/recon_buildblock/PoissonNestedLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion.h b/src/include/stir/recon_buildblock/PoissonNestedLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion.h new file mode 100644 index 0000000000..f5c78cff20 --- /dev/null +++ b/src/include/stir/recon_buildblock/PoissonNestedLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion.h @@ -0,0 +1,226 @@ +/* + Copyright (C) 2006- 2009, Hammersmith Imanet Ltd + Copyright (C) 2010- 2013, King's College London + This file is part of STIR. + + SPDX-License-Identifier: Apache-2.0 + + See STIR/LICENSE.txt for details +*/ +/*! + \file + \ingroup GeneralisedObjectiveFunction + \brief Declaration of class stir::PoissonNestedLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion + + \author Nicolas A Karakatsanis +*/ + +#ifndef __stir_recon_buildblock_PoissonNestedLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion_H__ +#define __stir_recon_buildblock_PoissonNestedLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion_H__ +#include "stir/shared_ptr.h" +#include "stir/RegisteredObject.h" +#include "stir/RegisteredParsingObject.h" +#include "stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.h" +#include "stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMean.h" +#include "stir/VectorWithOffset.h" +#include "stir/GatedProjData.h" +#include "stir/GatedDiscretisedDensity.h" +#include "stir/spatial_transformation/GatedSpatialTransformation.h" + +START_NAMESPACE_STIR + +/*! + \ingroup GeneralisedObjectiveFunction + \brief a base class for NestedLogLikelihood of independent Poisson variables + where the mean values are linear combinations of the gated images. + + \f[ + \begin{array}{lcl} + \Lambda_{\nu}^{(s+1)}&&=\Lambda_{\nu}^{(s)} \frac{1}{ \sum\limits_{b\in S_{l}, g} \sum\limits_{\nu'} \hat{W}^{-1} +_{\nu'g\rightarrow \nu}P_{\nu' b}A_{bg}+\beta \nabla_{\Lambda_{\nu}} E_{\nu}^{(s)}}\\ + &&\times \sum\limits_{b\in S_{l}, g} \sum\limits_{\nu'}\left(\hat{W}^{-1} _{\nu'g\rightarrow \nu}P_{\nu' +b}\frac{Y_{bg}}{\sum\limits_{\tilde{\nu}}P_{b\tilde{\nu}}\sum\limits_{\tilde{\nu}'}\hat{W} _{\tilde{\nu}'\rightarrow +\tilde{\nu}g}\Lambda_{\tilde{\nu}'}^{(s)}+\frac{B_{bg}}{A_{bg}}}\right) \end{array} \f] \par Parameters for parsing + + Based on non-nested MCIR implementation: Tsoumpas et al (2013) Physics in Medicine and Biology + +*/ + +template +class PoissonNestedLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion + : public RegisteredParsingObject, + GeneralisedObjectiveFunction, + PoissonLogLikelihoodWithLinearModelForMean> +{ +private: + typedef RegisteredParsingObject, + GeneralisedObjectiveFunction, + PoissonLogLikelihoodWithLinearModelForMean> + base_type; + typedef PoissonLogLikelihoodWithLinearModelForMeanAndProjData> SingleGateObjFunc; + VectorWithOffset _single_gate_obj_funcs; + + TimeGateDefinitions _time_gate_definitions; + +public: + //! Name which will be used when parsing a GeneralisedObjectiveFunction object + static const char* const registered_name; + + PoissonNestedLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion(); + + //! Returns a pointer to a newly allocated target object (with 0 data). + /*! Dimensions etc are set from the \a gated_proj_data_sptr and other information set by parsing, + such as \c zoom, \c output_image_size_z etc. + */ + virtual TargetT* construct_target_ptr() const; + + virtual void + compute_sub_gradient_without_penalty_plus_sensitivity(TargetT& gradient, const TargetT& current_estimate, const int subset_num); + + virtual void compute_nested_sub_gradient_without_penalty_plus_sensitivity(TargetT& gradient, + TargetT& current_estimate, + const int subset_num); + + // The principal nested EM reconstruction method that performs the motion correction within reconstruction + virtual void estimate_nested_loop_parameters_with_model(TargetT& gradient, + TargetT& current_estimate, + GatedDiscretisedDensity& gated_image_estimate, + GatedDiscretisedDensity& gated_image_reference_data, + GatedDiscretisedDensity& gated_image_nested_loop_estimate); + + virtual double actual_compute_objective_function_without_penalty(const TargetT& current_estimate, const int subset_num); + + virtual Succeeded set_up_before_sensitivity(shared_ptr const& target_sptr); + + //! Add subset sensitivity to existing data + virtual void add_subset_sensitivity(TargetT& model_sensitivity, const int subset_num) const; + + virtual Succeeded actual_add_multiplication_with_approximate_sub_Hessian_without_penalty(TargetT& output, + const TargetT& input, + const int subset_num) const; + + void set_time_gate_definitions(const TimeGateDefinitions& time_gate_definitions); + + /*! \name Functions to get parameters + \warning Be careful with changing shared pointers. If you modify the objects in + one place, all objects that use the shared pointer will be affected. + */ + //@{ + const GatedProjData& get_gated_proj_data() const; + const shared_ptr& get_gated_proj_data_sptr() const; + const int get_max_segment_num_to_process() const; + const bool get_zero_seg0_end_planes() const; + const GatedProjData& get_additive_gated_proj_data() const; + const shared_ptr& get_additive_gated_proj_data_sptr() const; + const GatedProjData& get_normalisation_gated_proj_data() const; + const shared_ptr& get_normalisation_gated_proj_data_sptr() const; + const ProjectorByBinPair& get_projector_pair() const; + const shared_ptr& get_projector_pair_sptr() const; + const TargetT& get_model_sensitivity_image() const; + const shared_ptr& get_model_sensitivity_image_sptr() const; + //@} + + /*! \name Functions to set parameters + This can be used as alternative to the parsing mechanism. + \warning After using any of these, you have to call set_up(). + \warning Be careful with setting shared pointers. If you modify the objects in + one place, all objects that use the shared pointer will be affected. + */ + //@{ + void set_recompute_sensitivity(const bool); + void set_sensitivity_sptr(const shared_ptr&); + virtual int set_num_subsets(const int num_subsets); + //@} +protected: + //! Filename with input projection data + std::string _input_filename; + std::string _motion_vectors_filename_prefix; + std::string _reverse_motion_vectors_filename_prefix; + std::string _gate_definitions_filename; + + //! points to the object for the total input projection data + shared_ptr _gated_proj_data_sptr; + + //! the maximum absolute ring difference number to use in the reconstruction + /*! convention: if -1, use get_max_segment_num()*/ + int _max_segment_num_to_process; + + const TimeGateDefinitions& get_time_gate_definitions() const; + + /**********************/ + // image stuff + // TODO to be replaced with single class or so (TargetT obviously) + //! the output image size in x and y direction + /*! convention: if -1, use a size such that the whole FOV is covered + */ + int _output_image_size_xy; // KT 10122001 appended _xy + + //! the output image size in z direction + /*! convention: if -1, use default as provided by VoxelsOnCartesianGrid constructor + */ + int _output_image_size_z; // KT 10122001 new + + //! the zoom factor + double _zoom; + + //! offset in the x-direction + double _Xoffset; + + //! offset in the y-direction + double _Yoffset; + + // KT 20/06/2001 new + //! offset in the z-direction + double _Zoffset; + + //! the current subiteration index + int nested_subiterations_num; + + //! restrict updates (larger nested relative updates will be thresholded) + double maximum_nested_relative_change; + + //! restrict updates (smaller nested relative updates will be thresholded) + double minimum_nested_relative_change; + + /********************************/ + //! name of file in which additive projection data are stored + std::string _additive_gated_proj_data_filename; + + //! name of file in which normalisation projection data are stored + std::string _normalisation_gated_proj_data_filename; + + //! points to the additive projection data + /*! the projection data in this file is bin-wise added to forward projection results*/ + shared_ptr _additive_gated_proj_data_sptr; + shared_ptr _normalisation_gated_proj_data_sptr; + /*! the normalisation or/and attenuation data */ + std::string _normalisation_filename_prefix; + //! Stores the projectors that are used for the computations + shared_ptr _projector_pair_ptr; + //! signals whether to zero the data in the end planes of the projection data + bool _zero_seg0_end_planes; + /*! the motion vectors where all information is stored */ + int _motion_correction_type; // This could be set up in a more generic way in order to choose how motion correction will be + // applied// + GatedSpatialTransformation _motion_vectors; + GatedSpatialTransformation _reverse_motion_vectors; + + //! gated image template + GatedDiscretisedDensity _gated_image_template; + + //! Define a shared pointer for the (motion transformation) model sensitivity image + shared_ptr model_sensitivity_image_sptr; + + bool actual_subsets_are_approximately_balanced(std::string& warning_message) const; + + void compute_model_sensitivity_image(TargetT& motion_corrected_image); + + //! Sets defaults before parsing + virtual void set_defaults(); + virtual void initialise_keymap(); + virtual bool post_processing(); +}; + +END_NAMESPACE_STIR + +#endif diff --git a/src/include/stir/recon_buildblock/PoissonNestedLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion.txx b/src/include/stir/recon_buildblock/PoissonNestedLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion.txx new file mode 100644 index 0000000000..9f54028d92 --- /dev/null +++ b/src/include/stir/recon_buildblock/PoissonNestedLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion.txx @@ -0,0 +1,863 @@ +/* + Copyright (C) 2006- 2009, Hammersmith Imanet Ltd + Copyright (C) 2011 - 2013, King's College London + This file is part of STIR. + + SPDX-License-Identifier: Apache-2.0 + + See STIR/LICENSE.txt for details + */ +/*! + \file + \ingroup GeneralisedObjectiveFunction + \brief Implementation of class stir::PoissonNestedLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion + + \author Nicolas A Karakatsanis + +*/ +#include "stir/DiscretisedDensity.h" +#include "stir/is_null_ptr.h" +#include "stir/numerics/divide.h" +#include "stir/thresholding.h" +#include "stir/NumericInfo.h" +#include "stir/recon_buildblock/TrivialBinNormalisation.h" +#include "stir/Succeeded.h" +#include "stir/RelatedViewgrams.h" +#include "stir/stream.h" +#include "stir/recon_buildblock/ProjectorByBinPair.h" +#include "stir/CPUTimer.h" + +// include the following to set defaults +#ifndef USE_PMRT +#include "stir/recon_buildblock/ForwardProjectorByBinUsingRayTracing.h" +#include "stir/recon_buildblock/BackProjectorByBinUsingInterpolation.h" +#else +#include "stir/recon_buildblock/ForwardProjectorByBinUsingProjMatrixByBin.h" +#include "stir/recon_buildblock/BackProjectorByBinUsingProjMatrixByBin.h" +#include "stir/recon_buildblock/ProjMatrixByBinUsingRayTracing.h" +#endif +#include "stir/recon_buildblock/ProjectorByBinPairUsingSeparateProjectors.h" + +#include +#include +// For Motion +#include "stir/spatial_transformation/GatedSpatialTransformation.h" +#include "stir/recon_buildblock/PoissonNestedLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion.h" +#include "stir/recon_buildblock/BinNormalisationFromProjData.h" + +#ifndef STIR_NO_NAMESPACES +using std::cerr; +using std::endl; +#endif + +START_NAMESPACE_STIR + +const float small_num = 0.000001F; + +template +const char * const +PoissonNestedLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion:: +registered_name = +"PoissonNestedLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion"; + +template +void +PoissonNestedLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion:: +set_defaults() +{ + base_type::set_defaults(); + + this->_input_filename=""; + this->_max_segment_num_to_process=-1; // use all segments + //num_views_to_add=1; // KT 20/06/2001 disabled + + this->_gated_proj_data_sptr.reset(); + this->_zero_seg0_end_planes = 0; + this->_reverse_motion_vectors_filename_prefix="0"; + this->_normalisation_gated_proj_data_filename="1"; + this->_normalisation_gated_proj_data_sptr.reset(); + // this->_reverse_motion_vectors_sptr=NULL; + this->_motion_vectors_filename_prefix="0"; + // this->_motion_vectors_sptr=NULL; + this->_gate_definitions_filename="0"; + // this->_time_gate_definitions_sptr=NULL; + this->_additive_gated_proj_data_filename = "0"; + this->_additive_gated_proj_data_sptr.reset(); + +#ifndef USE_PMRT // set default for _projector_pair_ptr + shared_ptr forward_projector_ptr + (new ForwardProjectorByBinUsingRayTracing()); + shared_ptr back_projector_ptr + (new BackProjectorByBinUsingInterpolation()); +#else + shared_ptr PM + (new ProjMatrixByBinUsingRayTracing()); + shared_ptr forward_projector_ptr + (new ForwardProjectorByBinUsingProjMatrixByBin(PM)); + shared_ptr back_projector_ptr + (new BackProjectorByBinUsingProjMatrixByBin(PM)); +#endif + + this->_projector_pair_ptr.reset( + new ProjectorByBinPairUsingSeparateProjectors(forward_projector_ptr, back_projector_ptr)); + + // image stuff + this->_output_image_size_xy=-1; + this->_output_image_size_z=-1; + this->_zoom=1.F; + this->_Xoffset=0.F; + this->_Yoffset=0.F; + this->_Zoffset=0.F; // KT 20/06/2001 new + + //Number of nested iterations + this->num_nested_subiterations=1; + + this->maximum_nested_relative_change = NumericInfo().max_value(); + this->minimum_nested_relative_change = 0; + +} + +template +void +PoissonNestedLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion:: +initialise_keymap() +{ + base_type::initialise_keymap(); + this->parser.add_start_key("PoissonNestedLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion Parameters"); + this->parser.add_stop_key("End PoissonNestedLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion Parameters"); + this->parser.add_key("input filename",&this->_input_filename); + + // parser.add_key("mash x views", &num_views_to_add); // KT 20/06/2001 disabled + this->parser.add_key("maximum absolute segment number to process", &this->_max_segment_num_to_process); + this->parser.add_key("zero end planes of segment 0", &this->_zero_seg0_end_planes); + + // image stuff + this->parser.add_key("zoom", &this->_zoom); + this->parser.add_key("XY output image size (in pixels)",&this->_output_image_size_xy); + this->parser.add_key("Z output image size (in pixels)",&this->_output_image_size_z); + + // parser.add_key("X offset (in mm)", &this->Xoffset); // KT 10122001 added spaces + // parser.add_key("Y offset (in mm)", &this->Yoffset); + this->parser.add_key("Z offset (in mm)", &this->_Zoffset); + this->parser.add_parsing_key("Projector pair type", &this->_projector_pair_ptr); + + // Scatter correction + this->parser.add_key("additive sinograms",&this->_additive_gated_proj_data_filename); + + // normalisation (and attenuation correction) + this->parser.add_key("normalisation sinograms", &this->_normalisation_gated_proj_data_filename); + + // Motion Information + this->parser.add_key("Gate Definitions filename", &this->_gate_definitions_filename); + this->parser.add_key("Motion Vectors filename prefix", &this->_motion_vectors_filename_prefix); + this->parser.add_key("Reverse Motion Vectors filename prefix", &this->_reverse_motion_vectors_filename_prefix); + + // Nested subiterations + this->parser.add_key("number of nested subiterations", &this->num_nested_subiterations); + + //max and min allowed relative change between nested updates + this->parser.add_key("maximum nested relative change", &this->maximum_nested_relative_change); + this->parser.add_key("minimum nested relative change",&this->minimum_nested_relative_change); + +} + +template +bool +PoissonNestedLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion:: +post_processing() +{ + if (base_type::post_processing() == true) + return true; + if (this->_input_filename.length() == 0) + { warning("You need to specify an input filename"); return true; } + + this->_gated_proj_data_sptr.reset(GatedProjData::read_from_file(this->_input_filename)); + + // image stuff + if (this->_zoom <= 0) + { warning("zoom should be positive"); return true; } + + if (this->_output_image_size_xy!=-1 && this->_output_image_size_xy<1) // KT 10122001 appended_xy + { warning("output image size xy must be positive (or -1 as default)"); return true; } + if (this->_output_image_size_z!=-1 && this->_output_image_size_z<1) // KT 10122001 new + { warning("output image size z must be positive (or -1 as default)"); return true; } + + if (this->_additive_gated_proj_data_filename != "0") + { + std::cerr << "\nReading additive projdata data " + << this->_additive_gated_proj_data_filename + << std::endl; + this->_additive_gated_proj_data_sptr.reset( + GatedProjData::read_from_file(this->_additive_gated_proj_data_filename)); + } + if (this->_normalisation_gated_proj_data_filename != "1") + { + std::cerr << "\nReading normalisation projdata data " + << this->_normalisation_gated_proj_data_filename + << std::endl; + this->_normalisation_gated_proj_data_sptr.reset( + GatedProjData::read_from_file(this->_normalisation_gated_proj_data_filename)); + } + + this->_time_gate_definitions.read_gdef_file(this->_gate_definitions_filename); + + if (this->_reverse_motion_vectors_filename_prefix != "0") + this->_reverse_motion_vectors.read_from_files(this->_reverse_motion_vectors_filename_prefix); + if (this->_motion_vectors_filename_prefix != "0") + this->_motion_vectors.read_from_files(this->_motion_vectors_filename_prefix); + return false; + +} + +template +PoissonNestedLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion:: +PoissonNestedLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion() +{ + this->set_defaults(); +} + +template +TargetT * +PoissonNestedLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion:: +construct_target_ptr() const +{ + return + new VoxelsOnCartesianGrid (*this->_gated_proj_data_sptr->get_proj_data_info_ptr(), + static_cast(this->_zoom), + CartesianCoordinate3D(static_cast(this->_Zoffset), + static_cast(this->_Yoffset), + static_cast(this->_Xoffset)), + CartesianCoordinate3D(this->_output_image_size_z, + this->_output_image_size_xy, + this->_output_image_size_xy) + ); +} +/*************************************************************** + subset balancing +***************************************************************/ + +template +bool +PoissonNestedLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion:: +actual_subsets_are_approximately_balanced(std::string& warning_message) const +{ // call actual_subsets_are_approximately_balanced() for first single_gate_obj_func + if (this->get_time_gate_definitions().get_num_gates() == 0 || this->_single_gate_obj_funcs.size() == 0) + error("PoissonNestedLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion:\n" + "actual_subsets_are_approximately_balanced called but no gates yet."); + else if(this->_single_gate_obj_funcs.size() != 0) + { + bool gates_are_balanced=true; + for(unsigned int gate_num=1;gate_num<=this->get_time_gate_definitions().get_num_gates();++gate_num) + gates_are_balanced &= this->_single_gate_obj_funcs[gate_num].subsets_are_approximately_balanced(warning_message); + return gates_are_balanced; + } + else + error("Something strange happened in PoissonNestedLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion:\n" + "actual_subsets_are_approximately_balanced called before setup()?"); + return + false; +} + +/*************************************************************** + get_ functions +***************************************************************/ +template +const TimeGateDefinitions & +PoissonNestedLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion:: +get_time_gate_definitions() const +{ return this->_time_gate_definitions; } + +template +const GatedProjData& +PoissonNestedLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion:: +get_gated_proj_data() const +{ return *this->_gated_proj_data_sptr; } + +template +const shared_ptr& +PoissonNestedLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion:: +get_gated_proj_data_sptr() const +{ return this->_gated_proj_data_sptr; } + +template +const int +PoissonNestedLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion:: +get_max_segment_num_to_process() const +{ return this->_max_segment_num_to_process; } + +template +const bool +PoissonNestedLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion:: +get_zero_seg0_end_planes() const +{ return this->_zero_seg0_end_planes; } + +template +const GatedProjData& +PoissonNestedLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion:: +get_additive_gated_proj_data() const +{ return *this->_additive_gated_proj_data_sptr; } + +template +const shared_ptr& +PoissonNestedLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion:: +get_additive_gated_proj_data_sptr() const +{ return this->_additive_gated_proj_data_sptr; } + +template +const GatedProjData& +PoissonNestedLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion:: +get_normalisation_gated_proj_data() const +{ return *this->_normalisation_gated_proj_data_sptr; } + +template +const shared_ptr& +PoissonNestedLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion:: +get_normalisation_gated_proj_data_sptr() const +{ return this->_normalisation_gated_proj_data_sptr; } + +template +const ProjectorByBinPair& +PoissonNestedLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion:: +get_projector_pair() const +{ return *this->_projector_pair_ptr; } + +template +const shared_ptr& +PoissonNestedLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion:: +get_projector_pair_sptr() const +{ return this->_projector_pair_ptr; } + +template +const shared_ptr& +PoissonNestedLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion:: +get_model_sensitivity_image_sptr() const +{ + return this->model_sensitivity_image_sptr; +} + +template +const TargetT& +PoissonNestedLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion:: +get_model_sensitivity_image() const +{ + return *this->model_sensitivity_image_sptr; +} + +/*************************************************************** + set_ functions +***************************************************************/ +template +int +PoissonNestedLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion:: +set_num_subsets(const int num_subsets) +{ + for(unsigned int gate_num=1;gate_num<=this->get_time_gate_definitions().get_num_gates();++gate_num) + { + if(this->_single_gate_obj_funcs.size() != 0) + if(this->_single_gate_obj_funcs[gate_num].set_num_subsets(num_subsets) != num_subsets) + error("set_num_subsets didn't work"); + } + this->num_subsets=num_subsets; + return this->num_subsets; +} + +template +void +PoissonNestedLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion:: +set_time_gate_definitions(const TimeGateDefinitions & time_gate_definitions) +{ this->_time_gate_definitions=time_gate_definitions; } + + +/*************************************************************** + set_up() +***************************************************************/ +template +Succeeded +PoissonNestedLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion:: +set_up_before_sensitivity(shared_ptr const& target_sptr) +{ + /*!todo define in the PoissonLogLikelihoodWithLinearModelForMean class to return Succeeded::yes + if (base_type::set_up_before_sensitivity(target_sptr) != Succeeded::yes) + return Succeeded::no; + */ + if (this->_max_segment_num_to_process==-1) + this->_max_segment_num_to_process = + (this->_gated_proj_data_sptr)->get_proj_data_sptr(1)->get_max_segment_num(); + + if (this->_max_segment_num_to_process > (this->_gated_proj_data_sptr)->get_proj_data_sptr(1)->get_max_segment_num()) + { + warning("_max_segment_num_to_process (%d) is too large", + this->_max_segment_num_to_process); + return Succeeded::no; + } + + shared_ptr proj_data_info_sptr( + (this->_gated_proj_data_sptr->get_proj_data_sptr(1))->get_proj_data_info_ptr()->clone()); + proj_data_info_sptr-> + reduce_segment_range(-this->_max_segment_num_to_process, + +this->_max_segment_num_to_process); + + if (is_null_ptr(this->_projector_pair_ptr)) + { warning("You need to specify a projector pair"); return Succeeded::no; } + + if (this->num_subsets <= 0) + { + warning("Number of subsets %d should be larger than 0.", + this->num_subsets); + return Succeeded::no; + } + { + const shared_ptr > density_template_sptr(target_sptr->get_empty_copy()); // target_sptr appears not to be set up correctly + const shared_ptr scanner_sptr(new Scanner(*proj_data_info_sptr->get_scanner_ptr())); + this->_gated_image_template=GatedDiscretisedDensity(this->get_time_gate_definitions(), density_template_sptr); + + //Computes model sensitivity image by utilizing motion model matrix + this->compute_model_sensitivity_image(*target_sptr); + + // construct _single_gate_obj_funcs + this->_single_gate_obj_funcs.resize(1,this->get_time_gate_definitions().get_num_gates()); + + for(unsigned int gate_num=1;gate_num<=this->get_time_gate_definitions().get_num_gates();++gate_num) + { + std::cerr << "Objective Function for Gate Number: " << gate_num << "\n"; + this->_single_gate_obj_funcs[gate_num].set_projector_pair_sptr(this->_projector_pair_ptr); + this->_single_gate_obj_funcs[gate_num].set_proj_data_sptr(this->_gated_proj_data_sptr->get_proj_data_sptr(gate_num)); + this->_single_gate_obj_funcs[gate_num].set_max_segment_num_to_process(this->_max_segment_num_to_process); + this->_single_gate_obj_funcs[gate_num].set_zero_seg0_end_planes(this->_zero_seg0_end_planes!=0); + if(this->_additive_gated_proj_data_sptr!=NULL) + this->_single_gate_obj_funcs[gate_num].set_additive_proj_data_sptr(this->_additive_gated_proj_data_sptr->get_proj_data_sptr(gate_num)); + this->_single_gate_obj_funcs[gate_num].set_num_subsets(this->num_subsets); + this->_single_gate_obj_funcs[gate_num].set_frame_num(1);//This should be gate... + vector > frame_times(1, pair(0,1)); + this->_single_gate_obj_funcs[gate_num].set_frame_definitions(TimeFrameDefinitions(frame_times)); + + shared_ptr current_gate_norm_factors_sptr; + if (is_null_ptr(this->_normalisation_gated_proj_data_sptr)) + current_gate_norm_factors_sptr.reset(new TrivialBinNormalisation); + else { + shared_ptr norm_data_sptr(this->_normalisation_gated_proj_data_sptr->get_proj_data_sptr(gate_num)); + current_gate_norm_factors_sptr.reset( + new BinNormalisationFromProjData(norm_data_sptr)); + } + this->_single_gate_obj_funcs[gate_num].set_normalisation_sptr(current_gate_norm_factors_sptr); + this->_single_gate_obj_funcs[gate_num].set_recompute_sensitivity(this->get_recompute_sensitivity()); + this->_single_gate_obj_funcs[gate_num].set_use_subset_sensitivities(this->get_use_subset_sensitivities()); + + if(this->_single_gate_obj_funcs[gate_num].set_up(density_template_sptr) != Succeeded::yes) + error("Single gate objective functions is not set correctly!"); + } + }//_single_gate_obj_funcs[gate_num] + return Succeeded::yes; +} + +/************************************************************************* + functions that compute the value/gradient of the objective function etc +*************************************************************************/ + +template +void +PoissonNestedLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion:: +compute_sub_gradient_without_penalty_plus_sensitivity(TargetT& gradient, + const TargetT ¤t_estimate, + const int subset_num) +{ + + // Clone the const TargetT& current estimate to a TargetT nested_estimate + shared_ptr current_nested_estimate(current_estimate.get_empty_copy()); + + { + typename TargetT::const_full_iterator current_estimate_iter = current_estimate.begin_all_const(); + const typename TargetT::const_full_iterator end_current_estimate_iter = current_estimate.end_all_const(); + typename TargetT::full_iterator current_nested_estimate_iter = current_nested_estimate->begin_all(); + while (current_estimate_iter!=end_current_estimate_iter) + { + *current_nested_estimate_iter = (*current_estimate_iter); + ++current_nested_estimate_iter; ++current_estimate_iter; + } + } + + this->compute_nested_sub_gradient_without_penalty_plus_sensitivity(gradient, + *current_nested_estimate, + subset_num); + + this->last_nested_estimate_sptr = current_nested_estimate; + +} + +template +void +PoissonNestedLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion:: +compute_nested_sub_gradient_without_penalty_plus_sensitivity(TargetT& gradient, + TargetT ¤t_estimate, + const int subset_num) +{ + assert(subset_num>=0); + assert(subset_numnum_subsets); + + GatedDiscretisedDensity gated_gradient=this->_gated_image_template; + GatedDiscretisedDensity gated_image_estimate=this->_gated_image_template; + GatedDiscretisedDensity gated_image_reference_data=this->_gated_image_template; + GatedDiscretisedDensity gated_image_nested_loop_estimate=this->_gated_image_template; + GatedDiscretisedDensity gated_sensitivity=this->_gated_image_template; + + + // The following initialization doesn't stabilize reconstruction. + for(unsigned int gate_num=1;gate_num<=this->get_time_gate_definitions().get_num_gates();++gate_num) + std::fill(gated_image_estimate[gate_num].begin_all(), + gated_image_estimate[gate_num].end_all(), + 0.F); + + //Print out the min and max values of the initial motion-corrected estimate + const float current_min_estimate = + *std::min_element(current_estimate.begin_all(), + current_estimate.end_all()); + const float current_max_estimate = + *std::max_element(current_estimate.begin_all(), + current_estimate.end_all()); + cerr << "Initial motion-corrected image estimate " + << ", (min, max): (" << current_min_estimate << ", " << current_max_estimate << ")" << endl; + + // Translate (contaminate-with-motion or equivalent to forward project operation for motion model) + // from motion-less space to motion-gated space using a motion-corrected image estimate as an input + this->_motion_vectors.warp_image(gated_image_estimate,current_estimate); + + //Storage of the current gated images so that to be use as a reference for all nested sub-iterations of the current global sub-iteration + gated_image_reference_data = gated_image_estimate; + + CPUTimer outer_loop_timer; + outer_loop_timer.start(); + + // loop over all motion gates to calculate the sub-gradient of all motion gates (gated gradients) + for(unsigned int gate_num=1;gate_num<=this->get_time_gate_definitions().get_num_gates();++gate_num) + { + + //Get system sensitivity for each motion gate + cerr << "Getting system sub-sensitivity image for gate: " << gate_num << "..." << endl; + gated_sensitivity[gate_num]=this->_single_gate_obj_funcs[gate_num].get_subset_sensitivity(subset_num); + + //Print out the min and max values of the system dynamic sensitivity image + const float current_min_system_gated_sensitivity = + *std::min_element(gated_sensitivity[gate_num].begin_all(), + gated_sensitivity[gate_num].end_all()); + const float current_max_system_gated_sensitivity = + *std::max_element(gated_sensitivity[gate_num].begin_all(), + gated_sensitivity[gate_num].end_all()); + cerr << "System sensitivity image for gate: " << gate_num + << ", (min, max): (" << current_min_system_gated_sensitivity << ", " << current_max_system_gated_sensitivity << ")" << endl; + + //Compute sub-gradient for each frame + cerr << "Compute sub-gradient (update image) for gate: " << gate_num << "." << endl; + std::fill(gated_gradient[gate_num].begin_all(), + gated_gradient[gate_num].end_all(), + 0.F); + + this->_single_gate_obj_funcs[gate_num]. + compute_sub_gradient_without_penalty_plus_sensitivity(gated_gradient[gate_num], + gated_image_estimate[gate_num], + subset_num); + + //Print out the min and max values of the sub-gradient for each motion gate + const float current_min_outer_loop_gradient = + *std::min_element(gated_gradient[gate_num].begin_all(), + gated_gradient[gate_num].end_all()); + const float current_max_outer_loop_gradient = + *std::max_element(gated_gradient[gate_num].begin_all(), + gated_gradient[gate_num].end_all()); + cerr << "Outer loop dynamic sub-gradient image (gate) " << gate_num + << ", (min, max): (" << current_min_outer_loop_gradient << ", " << current_max_outer_loop_gradient << ")" << endl; + + // Perform projection matrix sensitivity division and update for the single outer loop iteration + + // Devide by system matrix sensitivity + cerr << "Divide sub-gradient (update image) by system sub-sensitivity for gate " << gate_num << "." << endl; + divide(gated_gradient[gate_num].begin_all(), + gated_gradient[gate_num].end_all(), + gated_sensitivity[gate_num].begin_all(), + small_num); + + //Print out the min and max values of the sub-gradient/sensitivity for each motion gate + const float current_min_outer_loop_gradient_over_sensitivity = + *std::min_element(gated_gradient[gate_num].begin_all(), + gated_gradient[gate_num].end_all()); + const float current_max_outer_loop_gradient_over_sensitivity = + *std::max_element(gated_gradient[gate_num].begin_all(), + gated_gradient[gate_num].end_all()); + cerr << "Outer loop dynamic sub-gradient/sensitivity image (gate) " << gate_num + << ", (min, max): (" << current_min_outer_loop_gradient_over_sensitivity << ", " << current_max_outer_loop_gradient_over_sensitivity << ")" << endl; + + // Update outer loop dynamic image estimate + cerr << "Update gated estimate " << gate_num << " with the sub-gradient (update image) of gate " << gate_num << "." << endl; + DiscretisedDensity<3,float>::const_full_iterator gated_gradient_single_frame_iter = gated_gradient[gate_num].begin_all_const(); + DiscretisedDensity<3,float>::const_full_iterator end_gated_gradient_single_frame_iter = gated_gradient[gate_num].end_all_const(); + DiscretisedDensity<3,float>::full_iterator gated_image_reference_data_single_frame_iter = gated_image_reference_data[gate_num].begin_all(); + while (gated_gradient_single_frame_iter!=end_gated_gradient_single_frame_iter) + { + *gated_image_reference_data_single_frame_iter *= (*gated_gradient_single_frame_iter); + ++gated_image_reference_data_single_frame_iter; ++gated_gradient_single_frame_iter; + } + + //Print out the min and max values of the outer loop updated gated images for each gate + const float current_min_outer_loop_updated_image = + *std::min_element(gated_image_reference_data[gate_num].begin_all(), + gated_image_reference_data[gate_num].end_all()); + const float current_max_outer_loop_updated_image = + *std::max_element(gated_image_reference_data[gate_num].begin_all(), + gated_image_reference_data[gate_num].end_all()); + cerr << "Outer loop updated image (gate): " << gate_num + << ", (min, max): (" << current_min_outer_loop_updated_image << ", " << current_max_outer_loop_updated_image << ")" << endl; + + } + + cerr << "Current outer loop computation time: " << outer_loop_timer.value() << endl << endl; + + CPUTimer nested_loop_timer; + nested_loop_timer.start(); + + //nested EM loop + cerr << endl << "Entering nested loop " << endl; + + // This is the principal method that iteratively estimates the motion-corrected estimates in a nested EM loop + this->estimate_nested_loop_parameters_with_model(gradient, + current_estimate, + gated_image_estimate, + gated_image_reference_data, + gated_image_nested_loop_estimate); + + cerr << "Total computation time for " << this->num_nested_subiterations << " nested iterations: " << nested_loop_timer.value() << endl << endl; + +} + + +template +void +PoissonNestedLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion:: +estimate_nested_loop_parameters_with_model(TargetT &gradient, + TargetT ¤t_estimate, + GatedDiscretisedDensity &gated_image_estimate, + GatedDiscretisedDensity &gated_image_reference_data, + GatedDiscretisedDensity &gated_image_nested_loop_estimate) +{ + + //nested EM loop + cerr << endl << "Entering nested loop (" << this->num_nested_subiterations << " EM sub-iterations for motion modelling and correction)." << endl; + + + for(nested_subiterations_num=1; nested_subiterations_num<=this->num_nested_subiterations; nested_subiterations_num++) + { + + // Translate (contaminate-with-motion or equivalent to forward project operation for motion model) + // from motion-less space to motion-gated space using a motion-corrected image estimate as an input + this->_motion_vectors.warp_image(gated_image_nested_loop_estimate,current_estimate); + gated_image_estimate = gated_image_reference_data; + + // loop over all motion gates + for(unsigned int gate_num=1;gate_num<=this->get_time_gate_definitions().get_num_gates();++gate_num) + divide(gated_image_estimate[gate_num].begin_all(), + gated_image_estimate[gate_num].end_all(), + gated_image_nested_loop_estimate[gate_num].begin_all(), + small_num); + + // Reversely translate (correct-for-motion or equivalent to back project operation for motion model) + // from motion-gated space to motion-corrected single-gate space using the gated image estimate as an input + this->_reverse_motion_vectors.warp_image(gradient,gated_image_estimate); + + // Perform model sensitivity division and update for all nested iterations + + // Devide by motion model sensitivity + divide(gradient.begin_all(), + gradient.end_all(), + this->model_sensitivity_image_sptr->begin_all(), + small_num); + + + const float current_min_nested_gradient = + *std::min_element(gradient.begin_all(), + gradient.end_all()); + const float current_max_nested_gradient = + *std::max_element(gradient.begin_all(), + gradient.end_all()); + const float new_min_nested_gradient = + static_cast(this->minimum_nested_relative_change); + const float new_max_nested_gradient = + static_cast(this->maximum_nested_relative_change); + cerr << "Nested iteration: " << nested_subiterations_num + << " sub-gradient(update image) old value (min, max): (" + << current_min_nested_gradient << ", " << current_max_nested_gradient + << "), new value (min, max) (" + << max(current_min_nested_gradient, new_min_nested_gradient) << ", " + << min(current_max_nested_gradient, new_max_nested_gradient) << ")" << endl; + + threshold_upper_lower(gradient.begin_all(), + gradient.end_all(), + new_min_nested_gradient, new_max_nested_gradient); + + + //Nested updates of image estimates + { + typename TargetT::const_full_iterator gradient_iter = gradient.begin_all_const(); + const typename TargetT::const_full_iterator end_gradient_iter = gradient.end_all_const(); + typename TargetT::full_iterator current_estimate_iter = current_estimate.begin_all(); + while (gradient_iter!=end_gradient_iter) + { + *current_estimate_iter *= (*gradient_iter); + ++current_estimate_iter; ++gradient_iter; + } + } + + //Print out the min and max values of the nested updated image for each nested iteration + const float current_min_nested_updated_image = + *std::min_element(current_estimate.begin_all(), + current_estimate.end_all()); + const float current_max_nested_updated_image = + *std::max_element(current_estimate.begin_all(), + current_estimate.end_all()); + cerr << "Nested iteration: " << nested_subiterations_num + << " Updated image value (min, max) (" + << current_min_nested_updated_image << ", " << current_max_nested_updated_image << ")" << endl << endl; + + } + + cerr << "End of nested reconstruction process of motion-corrected estimates (after " + << this->num_nested_subiterations << " nested EM subiterations)" << endl << endl; + +} + +template +double +PoissonNestedLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion:: +actual_compute_objective_function_without_penalty(const TargetT& current_estimate, + const int subset_num) +{ + assert(subset_num>=0); + assert(subset_numnum_subsets); + + double result = 0.; + GatedDiscretisedDensity gated_image_estimate=this->_gated_image_template; + // The following initialization doesn't stabilize reconstruction. + for(unsigned int gate_num=1; gate_num<=this->get_time_gate_definitions().get_num_gates(); ++gate_num) + std::fill(gated_image_estimate[gate_num].begin_all(), + gated_image_estimate[gate_num].end_all(), + 0.F); + this->_motion_vectors.warp_image(gated_image_estimate,current_estimate) ; + // loop over single_gate + for(unsigned int gate_num=1 ; + gate_num<=this->get_time_gate_definitions().get_num_gates(); + ++gate_num) + { + result += this->_single_gate_obj_funcs[gate_num]. + compute_objective_function_without_penalty(gated_image_estimate[gate_num], + subset_num); + } + return result; +} + +template +void +PoissonNestedLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion:: +compute_model_sensitivity_image(TargetT& motion_corrected_image) +{ + + shared_ptr motion_corrected_image_sptr(motion_corrected_image.get_empty_copy()); + this->model_sensitivity_image_sptr=motion_corrected_image_sptr; + + //Initialize model sensitivity image + std::fill(this->model_sensitivity_image_sptr->begin_all(), + this->model_sensitivity_image_sptr->end_all(), + 1.F); + + GatedDiscretisedDensity gated_image_of_all_ones=this->_gated_image_template; + + // loop over all motion gates + for(unsigned int gate_num=1;gate_num<=this->get_time_gate_definitions().get_num_gates();++gate_num) + std::fill(gated_image_of_all_ones[gate_num].begin_all(), + gated_image_of_all_ones[gate_num].end_all(), + 1.F); + + cerr << "Computing model sensitivity image..." << endl; + + // To obtain motion model sensitivity image reversely translate (correct-for-motion or equivalent to back project operation for motion model) + // from motion-gated space to motion-corrected single-gate space using a gated image estimate of ALL ONES + this->_reverse_motion_vectors.warp_image(*this->model_sensitivity_image_sptr, + gated_image_of_all_ones); + + //Print out the min and max values of the model sensitivity image + const float current_min_model_sensitivity = + *std::min_element(this->model_sensitivity_image_sptr->begin_all(), + this->model_sensitivity_image_sptr->end_all()); + const float current_max_model_sensitivity = + *std::max_element(this->model_sensitivity_image_sptr->begin_all(), + this->model_sensitivity_image_sptr->end_all()); + cerr << "Model sensitivity image " + << ", (min, max): (" << current_min_model_sensitivity << ", " << current_max_model_sensitivity << ")" << endl; + + cerr << "Model sensitivity image has been computed." << endl; +} + +template +void +PoissonNestedLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion:: +add_subset_sensitivity(TargetT& model_sensitivity, const int subset_num) const +{ + GatedDiscretisedDensity gated_image_of_all_ones=this->_gated_image_template; + + // loop over all motion gates + for(unsigned int gate_num=1;gate_num<=this->get_time_gate_definitions().get_num_gates();++gate_num) + std::fill(gated_image_of_all_ones[gate_num].begin_all(), + gated_image_of_all_ones[gate_num].end_all(), + 1.F); + + // To obtain motion model sensitivity image reversely translate (correct-for-motion or equivalent to back project operation for motion model) + // AND accumulate over previous calls the resulting motion corrected images. + // A gated image estimate of ALL ONES is used as input at every call of the function + this->_reverse_motion_vectors.accumulate_warp_image(model_sensitivity, + gated_image_of_all_ones); + +} + + +//! /todo The PoissonLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion::actual_add_multiplication_with_approximate_sub_Hessian_without_penalty is not validated and at the moment OSSPS does not converge with motion correction. +template +Succeeded +PoissonNestedLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion:: +actual_add_multiplication_with_approximate_sub_Hessian_without_penalty(TargetT& output, + const TargetT& input, + const int subset_num) const +{ + // TODO this does not add but replace + { + string explanation; + if (!input.has_same_characteristics(this->get_subset_sensitivity(0), //////////////////// + explanation)) + { + warning("PoissonNestedLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion:\n" + "sensitivity and input for add_multiplication_with_approximate_Hessian_without_penalty\n" + "should have the same characteristics.\n%s", + explanation.c_str()); + return Succeeded::no; + } + } + GatedDiscretisedDensity gated_input=this->_gated_image_template; + GatedDiscretisedDensity gated_output=this->_gated_image_template; + this->_motion_vectors.warp_image(gated_input,input) ; + + VectorWithOffset scale_factor(1,this->get_time_gate_definitions().get_num_gates()); + for(unsigned int gate_num=1; + gate_num<=this->get_time_gate_definitions().get_num_gates(); + ++gate_num) + { + scale_factor[gate_num]=gated_input[gate_num].find_max(); + /*! /note This is used to avoid higher values than these set in the precompute_denominator_of_conditioner_without_penalty() function. + /sa for more information see the recon_array_functions.cxx and the value of the max_quotient (originaly set to 10000.F) */ + gated_input[gate_num]/=scale_factor[gate_num]; + this->_single_gate_obj_funcs[gate_num]. + add_multiplication_with_approximate_sub_Hessian_without_penalty(gated_output[gate_num], + gated_input[gate_num], + subset_num); + gated_output[gate_num]*=scale_factor[gate_num]; + } // end of loop over gates + this->_reverse_motion_vectors.warp_image(output,gated_output); + output/=this->get_time_gate_definitions().get_num_gates(); //Normalizing to get the average value to test if OSSPS works. + return Succeeded::yes; +} + +END_NAMESPACE_STIR diff --git a/src/include/stir/spatial_transformation/GatedSpatialTransformation.h b/src/include/stir/spatial_transformation/GatedSpatialTransformation.h index 63597972c5..0e40e40d4f 100644 --- a/src/include/stir/spatial_transformation/GatedSpatialTransformation.h +++ b/src/include/stir/spatial_transformation/GatedSpatialTransformation.h @@ -68,6 +68,24 @@ class GatedSpatialTransformation : public RegisteredParsingObject& new_reference_image, const GatedDiscretisedDensity& gated_image) const; void warp_image(GatedDiscretisedDensity& gated_image, const DiscretisedDensity<3, float>& reference_image) const; void accumulate_warp_image(DiscretisedDensity<3, float>& new_reference_image, const GatedDiscretisedDensity& gated_image) const; + + // Nicolas A Karakatsanis: After warping of each gate, averaging (instead of accumulation) takes place + void average_warp_image(DiscretisedDensity<3, float>& new_reference_image, const GatedDiscretisedDensity& gated_image) const; + + // Nicolas A Karakatsanis: After warping of each gate, averaging (instead of accumulation) takes place + // and accumulates over previous calls of the same function + void accumulate_average_warp_image(DiscretisedDensity<3, float>& new_reference_image, + const GatedDiscretisedDensity& gated_image) const; + + //! Nicolas A Karakatsanis: Warping functions from to non-gated images. + // Create a convolved (motion-blurred) image according to the designated motion vectors + // Designed for use within Richardson-Lucy deconvolution EM iterative process for intra-gate/frame motion correction + void average_warp_image(DiscretisedDensity<3, float>& avg_warped_image, + const DiscretisedDensity<3, float>& reference_image) const; + + void accumulate_average_warp_image(DiscretisedDensity<3, float>& avg_warped_image, + const DiscretisedDensity<3, float>& reference_image) const; + void set_defaults() override; Succeeded set_up() override; //!@} @@ -75,6 +93,7 @@ class GatedSpatialTransformation : public RegisteredParsingObject base_type; void initialise_keymap() override; bool post_processing() override; + std::string _transformation_filename_prefix; GatedDiscretisedDensity _spatial_transformation_z; GatedDiscretisedDensity _spatial_transformation_y; diff --git a/src/include/stir/thresholding.h b/src/include/stir/thresholding.h index e62e3d0ebf..64d8ff4f8d 100644 --- a/src/include/stir/thresholding.h +++ b/src/include/stir/thresholding.h @@ -18,6 +18,7 @@ by iterators). \author Kris Thielemans + \author Nicolas A Karakatsanis */ @@ -53,6 +54,21 @@ threshold_upper_lower(forw_iterT begin, forw_iterT end, const elemT new_min, con } } +// Nicolas A Karakatsanis: Zero thresholding +//! Set all values of a sequence exceeding range [low_thresh, upper_thresh] to zero +template +inline void +zero_threshold_upper_lower(forw_iterT begin, forw_iterT end, const elemT new_min, const elemT new_max) +{ + for (forw_iterT iter = begin; iter != end; ++iter) + { + if (*iter > new_max) + *iter = 0; + else if (new_min > *iter) + *iter = 0; + } +} + //! Threshold a sequence from above /*! \see threshold_upper_lower for type requirements */ diff --git a/src/iterative/NESTGPOSMAPOSL/CMakeLists.txt b/src/iterative/NESTGPOSMAPOSL/CMakeLists.txt new file mode 100644 index 0000000000..2f912faa07 --- /dev/null +++ b/src/iterative/NESTGPOSMAPOSL/CMakeLists.txt @@ -0,0 +1,20 @@ + +set(dir iterative_NESTGPOSMAPOSL) + +set (dir_LIB_SOURCES ${dir}_LIB_SOURCES) + +# include(stir_lib_target) + +set(dir_EXE_SOURCES ${dir}_EXE_SOURCES) + +set(${dir_EXE_SOURCES} + NESTGPOSMAPOSL.cxx +) + +include(stir_exe_targets) + +if (STIR_MPI) + SET_PROPERTY(TARGET NESTGPOSMAPOSL PROPERTY LINK_FLAGS ${MPI_CXX_LINK_FLAGS}) +endif() + +# target_link_libraries(iterative_NESTGPOSMAPOSL PUBLIC ${STIR_RECON_BUILDBLOCK_LIB}) \ No newline at end of file diff --git a/src/iterative/NESTGPOSMAPOSL/NESTGPOSMAPOSL.cxx b/src/iterative/NESTGPOSMAPOSL/NESTGPOSMAPOSL.cxx new file mode 100644 index 0000000000..0b6087dec9 --- /dev/null +++ b/src/iterative/NESTGPOSMAPOSL/NESTGPOSMAPOSL.cxx @@ -0,0 +1,117 @@ +/* + Copyright (C) 2009, Hammersmith Imanet Ltd + This file is part of STIR. + + SPDX-License-Identifier: Apache-2.0 + See STIR/LICENSE.txt for details +*/ +/*! + + \file + \ingroup main_programs + \brief main() for stir::OSMAPOSLReconstruction on parametric images + + \author Nicolas A Karakatsanis +*/ +#include "stir/Succeeded.h" +#include "stir/OSMAPOSL/OSMAPOSLReconstruction.h" +#include "stir/modelling/ParametricDiscretisedDensity.h" +#include "stir/recon_buildblock/distributable_main.h" + +#include "stir/is_null_ptr.h" +#include +USING_NAMESPACE_STIR + +//! OSMAPOSL for generalized Patlak, allowing a 2-parameter initial estimate +/*! \ingroup recon_buildblock + The generalized Patlak reconstruction uses a 3-parameter target + (\c [slope, kloss, intercept]), so \c IterativeReconstruction::get_initial_data_ptr() + can only read a 3-parameter initial estimate + + In the reconstruction tests (and in practice, where a standard Patlak result is the + starting point) we want to initialise from a 2-parameter image. This class + overrides \c get_initial_data_ptr() to peek at the number of parameters in the file. + +*/ +class NestedGeneralizedPatlakOSMAPOSL : public OSMAPOSLReconstruction +{ +private: + typedef OSMAPOSLReconstruction base_type; + + int find_num_image_data_types(std::ifstream& input) const + { + const std::string key = "number of image data types"; + std::string line; + while (std::getline(input, line)) + { + const auto key_pos = line.find(key); + if (key_pos == std::string::npos) + continue; + const auto eq_pos = line.find(":=", key_pos); + if (eq_pos == std::string::npos) + continue; + return std::atoi(line.c_str() + eq_pos + 2); + } + return -1; + } + +public: + using base_type::base_type; // inherit constructors, so the argv[1] form still works + + Parametric3VoxelsOnCartesianGrid* get_initial_data_ptr() const override + { + + std::ifstream image_stream(this->initial_data_filename.c_str()); + const int file_num_params = this->initial_data_filename.empty() ? -1 : find_num_image_data_types(image_stream); + + if (file_num_params != 2) + return base_type::get_initial_data_ptr(); // uniform, or a normal 3-parameter file + + info("Initialising generalized Patlak from a 2-parameter Patlak image; kloss initialised to zero."); + + const auto par2 = Parametric2VoxelsOnCartesianGrid::read_from_file(this->initial_data_filename); + + if (is_null_ptr(this->objective_function_sptr)) + error("objective function needs to be set before calling get_initial_data_ptr"); + + const auto slope = par2->construct_single_density(1); + const auto intercept = par2->construct_single_density(2); + auto kloss = slope; + kloss.fill(0.F); + + // this constructor takes geometry, exam info and timing from the single image + auto par3 = new Parametric3VoxelsOnCartesianGrid(slope); + par3->update_parametric_image(slope, 1); + par3->update_parametric_image(kloss, 2); + par3->update_parametric_image(intercept, 3); + return par3; + } +}; + +#ifdef STIR_MPI +int +stir::distributable_main(int argc, char** argv) +#else +int +main(int argc, char** argv) +#endif +{ + + HighResWallClockTimer t; + t.reset(); + t.start(); + + NestedGeneralizedPatlakOSMAPOSL reconstruction_object(argc > 1 ? argv[1] : ""); + + if (reconstruction_object.reconstruct() == Succeeded::yes) + { + t.stop(); + std::cout << "Total Wall clock time: " << t.value() << " seconds" << std::endl; + return EXIT_SUCCESS; + } + else + { + t.stop(); + return EXIT_FAILURE; + } +} diff --git a/src/iterative/NESTPOSMAPOSL/CMakeLists.txt b/src/iterative/NESTPOSMAPOSL/CMakeLists.txt new file mode 100644 index 0000000000..53dfccc5ed --- /dev/null +++ b/src/iterative/NESTPOSMAPOSL/CMakeLists.txt @@ -0,0 +1,21 @@ + +# cmake helper file for building STIR. +set(dir iterative_NESTPOSMAPOSL) + +set (dir_LIB_SOURCES ${dir}_LIB_SOURCES) + +# include(stir_lib_target) + +set (dir_EXE_SOURCES ${dir}_EXE_SOURCES) + +set(${dir_EXE_SOURCES} + NESTPOSMAPOSL.cxx +) + +include(stir_exe_targets) + +if (STIR_MPI) + SET_PROPERTY(TARGET NESTPOSMAPOSL PROPERTY LINK_FLAGS ${MPI_CXX_LINK_FLAGS}) +endif() + +# target_link_libraries(iterative_NESTPOSMAPOSL PUBLIC ${STIR_RECON_BUILDBLOCK_LIB}) diff --git a/src/iterative/NESTPOSMAPOSL/NESTPOSMAPOSL.cxx b/src/iterative/NESTPOSMAPOSL/NESTPOSMAPOSL.cxx new file mode 100644 index 0000000000..bee67efbc8 --- /dev/null +++ b/src/iterative/NESTPOSMAPOSL/NESTPOSMAPOSL.cxx @@ -0,0 +1,48 @@ +/* + Copyright (C) 2009, Hammersmith Imanet Ltd + This file is part of STIR. + + SPDX-License-Identifier: Apache-2.0 + See STIR/LICENSE.txt for details +*/ +/*! + + \file + \ingroup main_programs + \brief main() for stir::OSMAPOSLReconstruction on parametric images + + \author Nicolas A Karakatsanis +*/ +#include "stir/Succeeded.h" +#include "stir/OSMAPOSL/OSMAPOSLReconstruction.h" +#include "stir/modelling/ParametricDiscretisedDensity.h" +#include "stir/recon_buildblock/distributable_main.h" + +#ifdef STIR_MPI +int +stir::distributable_main(int argc, char** argv) +#else +int +main(int argc, char** argv) +#endif +{ + USING_NAMESPACE_STIR + + HighResWallClockTimer t; + t.reset(); + t.start(); + + OSMAPOSLReconstruction reconstruction_object(argc > 1 ? argv[1] : ""); + + if (reconstruction_object.reconstruct() == Succeeded::yes) + { + t.stop(); + std::cout << "Total Wall clock time: " << t.value() << " seconds" << std::endl; + return EXIT_SUCCESS; + } + else + { + t.stop(); + return EXIT_FAILURE; + } +} diff --git a/src/iterative/OSMAPOSL/OSMAPOSLReconstruction.cxx b/src/iterative/OSMAPOSL/OSMAPOSLReconstruction.cxx index 4c67e5bba8..e4bdc0043b 100644 --- a/src/iterative/OSMAPOSL/OSMAPOSLReconstruction.cxx +++ b/src/iterative/OSMAPOSL/OSMAPOSLReconstruction.cxx @@ -22,6 +22,7 @@ \author Matthew Jacobson \author Sanida Mustafovic \author Kris Thielemans + \author Nicolas A Karakatsanis \author Ashley Gillman \author Daniel Deidda \author PARAPET project @@ -385,6 +386,11 @@ OSMAPOSLReconstruction::update_estimate(TargetT& current_image_estimate timerSubset.Start(); #endif // PARALLEL + // Nicolas A Karakatsanis: Pass the name and the subiteration number to the obj function + // as a new output filename prefix for the multiple nested images within the same global iteration + // This can be utilized by nested obj functions for many purposes + this->objective_function().set_nested_output_filename_prefix(this->output_filename_prefix, this->subiteration_num); + const int subset_num = this->get_subset_num(); info(format("Now processing subset #: {}", subset_num)); @@ -392,122 +398,133 @@ OSMAPOSLReconstruction::update_estimate(TargetT& current_image_estimate *multiplicative_update_image_ptr, current_image_estimate, subset_num); // divide by subset sensitivity - { - const TargetT& sensitivity = this->get_subset_sensitivity(subset_num); - int count = 0; + if (this->objective_function_sptr->is_nested()) + { + // If nested EM algorithm has been selected a new estimate is already computed + // from method: compute_sub_gradient_without_penalty_plus_sensitivity + cerr << endl << "Update mechanism is nested" << endl; + current_image_estimate = *this->objective_function_sptr->last_nested_estimate_sptr; + } + else + // divide subset gradient by subset sensitivity to obtain update image + // and multuply previous estimate with update image to obtain new estimate + { + const TargetT& sensitivity = this->get_subset_sensitivity(subset_num); - // std::cerr <MAP_model << std::endl; + int count = 0; - if (this->objective_function_sptr->prior_is_zero()) - { - divide(multiplicative_update_image_ptr->begin_all(), - multiplicative_update_image_ptr->end_all(), - sensitivity.begin_all(), - 0.F); // no need to find a threshold for division by sensitivity - } - else - { - unique_ptr denominator_ptr(current_image_estimate.get_empty_copy()); - - this->objective_function_sptr->get_prior_ptr()->compute_gradient(*denominator_ptr, current_image_estimate); - - typename TargetT::full_iterator denominator_iter = denominator_ptr->begin_all(); - const typename TargetT::full_iterator denominator_end = denominator_ptr->end_all(); - typename TargetT::const_full_iterator sensitivity_iter = sensitivity.begin_all(); - - if (this->MAP_model == "additive") - { - // lambda_new = lambda / (p_v + beta*prior_gradient/ num_subsets) * - // sum_subset backproj(measured/forwproj(lambda)) - // with p_v = sum_{b in subset} p_bv - // actually, we restrict 1 + beta*prior_gradient/num_subsets/p_v between .1 and 10 - while (denominator_iter != denominator_end) - { - *denominator_iter = *denominator_iter / this->get_num_subsets() + (*sensitivity_iter); - // bound denominator between (*sensitivity_iter)/10 and (*sensitivity_iter)*10 - *denominator_iter = std::max(std::min(*denominator_iter, (*sensitivity_iter) * 10), (*sensitivity_iter) / 10); - ++denominator_iter; - ++sensitivity_iter; - } - } - else - { - if (this->MAP_model == "multiplicative") - { - // multiplicative form - // lambda_new = lambda / (p_v*(1 + beta*prior_gradient)) * - // sum_subset backproj(measured/forwproj(lambda)) - // with p_v = sum_{b in subset} p_bv - // actually, we restrict 1 + beta*prior_gradient between .1 and 10 - while (denominator_iter != denominator_end) - { - *denominator_iter += 1; - // bound denominator between 1/10 and 1*10 - // TODO code will fail if *denominator_iter is not a float - *denominator_iter = std::max(std::min(*denominator_iter, 10.F), 1 / 10.F); - *denominator_iter *= (*sensitivity_iter); - ++denominator_iter; - ++sensitivity_iter; - } - } - } - - // do the division - // TODO: The thresholding implied in "divide" potentially fails with parametric images - // as the different parametric images can have very different scales. - // See https://github.com/UCL/STIR/issues/906 - divide(multiplicative_update_image_ptr->begin_all(), - multiplicative_update_image_ptr->end_all(), - denominator_ptr->begin_all(), - small_num); - } + // std::cerr <MAP_model << std::endl; + + if (this->objective_function_sptr->prior_is_zero()) + { + divide(multiplicative_update_image_ptr->begin_all(), + multiplicative_update_image_ptr->end_all(), + sensitivity.begin_all(), + 0.F); // no need to find a threshold for division by sensitivity + } + else + { + unique_ptr denominator_ptr(current_image_estimate.get_empty_copy()); + + this->objective_function_sptr->get_prior_ptr()->compute_gradient(*denominator_ptr, current_image_estimate); + + typename TargetT::full_iterator denominator_iter = denominator_ptr->begin_all(); + const typename TargetT::full_iterator denominator_end = denominator_ptr->end_all(); + typename TargetT::const_full_iterator sensitivity_iter = sensitivity.begin_all(); + + if (this->MAP_model == "additive") + { + // lambda_new = lambda / (p_v + beta*prior_gradient/ num_subsets) * + // sum_subset backproj(measured/forwproj(lambda)) + // with p_v = sum_{b in subset} p_bv + // actually, we restrict 1 + beta*prior_gradient/num_subsets/p_v between .1 and 10 + while (denominator_iter != denominator_end) + { + *denominator_iter = *denominator_iter / this->get_num_subsets() + (*sensitivity_iter); + // bound denominator between (*sensitivity_iter)/10 and (*sensitivity_iter)*10 + *denominator_iter = std::max(std::min(*denominator_iter, (*sensitivity_iter) * 10), (*sensitivity_iter) / 10); + ++denominator_iter; + ++sensitivity_iter; + } + } + else + { + if (this->MAP_model == "multiplicative") + { + // multiplicative form + // lambda_new = lambda / (p_v*(1 + beta*prior_gradient)) * + // sum_subset backproj(measured/forwproj(lambda)) + // with p_v = sum_{b in subset} p_bv + // actually, we restrict 1 + beta*prior_gradient between .1 and 10 + while (denominator_iter != denominator_end) + { + *denominator_iter += 1; + // bound denominator between 1/10 and 1*10 + // TODO code will fail if *denominator_iter is not a float + *denominator_iter = std::max(std::min(*denominator_iter, 10.F), 1 / 10.F); + *denominator_iter *= (*sensitivity_iter); + ++denominator_iter; + ++sensitivity_iter; + } + } + } + + // do the division + // TODO: The thresholding implied in "divide" potentially fails with parametric images + // as the different parametric images can have very different scales. + // See https://github.com/UCL/STIR/issues/906 + divide(multiplicative_update_image_ptr->begin_all(), + multiplicative_update_image_ptr->end_all(), + denominator_ptr->begin_all(), + small_num); + } - info(format("Number of (cancelled) singularities in Sensitivity division: {}", count)); - } + info(format("Number of (cancelled) singularities in Sensitivity division: {}", count)); - if (this->inter_update_filter_interval > 0 && !is_null_ptr(this->inter_update_filter_ptr) - && !(this->subiteration_num % this->inter_update_filter_interval)) - { - info("Applying inter-update filter"); - this->inter_update_filter_ptr->apply(current_image_estimate); - } + if (this->inter_update_filter_interval > 0 && !is_null_ptr(this->inter_update_filter_ptr) + && !(this->subiteration_num % this->inter_update_filter_interval)) + { + info("Applying inter-update filter"); + this->inter_update_filter_ptr->apply(current_image_estimate); + } - // KT 17/08/2000 limit update - // TODO move below thresholding? - if (this->write_update_image && !this->_disable_output) - { - // allocate space for the filename assuming that - // we never have more than 10^49 subiterations ... - const size_t filename_length = this->output_filename_prefix.size() + 60; - char* fname = new char[filename_length]; - snprintf(fname, filename_length, "%s_update_%d", this->output_filename_prefix.c_str(), this->subiteration_num); - - // Write it to file - this->output_file_format_ptr->write_to_file(fname, *multiplicative_update_image_ptr); - delete[] fname; - } + // KT 17/08/2000 limit update + // TODO move below thresholding? + if (this->write_update_image && !this->_disable_output) + { + // allocate space for the filename assuming that + // we never have more than 10^49 subiterations ... + const size_t filename_length = this->output_filename_prefix.size() + 60; + char* fname = new char[filename_length]; + snprintf(fname, filename_length, "%s_update_%d", this->output_filename_prefix.c_str(), this->subiteration_num); + + // Write it to file + this->output_file_format_ptr->write_to_file(fname, *multiplicative_update_image_ptr); + delete[] fname; + } - if (this->subiteration_num != 1) - { - const float current_min - = *std::min_element(multiplicative_update_image_ptr->begin_all(), multiplicative_update_image_ptr->end_all()); - const float current_max - = *std::max_element(multiplicative_update_image_ptr->begin_all(), multiplicative_update_image_ptr->end_all()); - const float new_min = static_cast(this->minimum_relative_change); - const float new_max = static_cast(this->maximum_relative_change); - info(format("Update image old min,max: {}, {}, new min,max {}, {}", - current_min, - current_max, - (min(current_min, new_min)), - (max(current_max, new_max)))); - - threshold_upper_lower( - multiplicative_update_image_ptr->begin_all(), multiplicative_update_image_ptr->end_all(), new_min, new_max); - } + if (this->subiteration_num != 1) + { + const float current_min + = *std::min_element(multiplicative_update_image_ptr->begin_all(), multiplicative_update_image_ptr->end_all()); + const float current_max + = *std::max_element(multiplicative_update_image_ptr->begin_all(), multiplicative_update_image_ptr->end_all()); + const float new_min = static_cast(this->minimum_relative_change); + const float new_max = static_cast(this->maximum_relative_change); + info(format("Update image old min,max: {}, {}, new min,max {}, {}", + current_min, + current_max, + (min(current_min, new_min)), + (max(current_max, new_max)))); + + threshold_upper_lower( + multiplicative_update_image_ptr->begin_all(), multiplicative_update_image_ptr->end_all(), new_min, new_max); + } - // current_image_estimate *= *multiplicative_update_image_ptr; - apply_multiplicative_update(current_image_estimate, *multiplicative_update_image_ptr); + // current_image_estimate *= *multiplicative_update_image_ptr; + apply_multiplicative_update(current_image_estimate, *multiplicative_update_image_ptr); + } #ifdef PARALLEL timerSubset.Stop(); @@ -517,5 +534,6 @@ OSMAPOSLReconstruction::update_estimate(TargetT& current_image_estimate template class OSMAPOSLReconstruction>; template class OSMAPOSLReconstruction; +template class OSMAPOSLReconstruction; END_NAMESPACE_STIR diff --git a/src/modelling_buildblock/CMakeLists.txt b/src/modelling_buildblock/CMakeLists.txt index 1c5f3be07b..d0ced0ec85 100644 --- a/src/modelling_buildblock/CMakeLists.txt +++ b/src/modelling_buildblock/CMakeLists.txt @@ -8,6 +8,7 @@ set (dir_LIB_SOURCES ${dir}_LIB_SOURCES) set(${dir_LIB_SOURCES} KineticModel.cxx PatlakPlot.cxx + GeneralizedPatlakPlot.cxx ParametricDiscretisedDensity.cxx ) diff --git a/src/modelling_buildblock/GeneralizedPatlakPlot.cxx b/src/modelling_buildblock/GeneralizedPatlakPlot.cxx new file mode 100644 index 0000000000..1541d9260f --- /dev/null +++ b/src/modelling_buildblock/GeneralizedPatlakPlot.cxx @@ -0,0 +1,866 @@ +// +/* + Copyright (C) 2006 - 2011, Hammersmith Imanet Ltd + This file is part of STIR. + + SPDX-License-Identifier: Apache-2.0 + + See STIR/LICENSE.txt for details +*/ +/*! + \file + \ingroup modelling + \brief Implementations of inline functions of class stir::PatlakPlot + \author Charalampos Tsoumpas + + \sa GeneralizedPatlakPlot.h, GeneralizedModelMatrix.h and KineticModel.h +*/ + +#include "stir/modelling/GeneralizedPatlakPlot.h" +#include +#include "stir/format.h" + +using namespace std; + +START_NAMESPACE_STIR + +void +GeneralizedPatlakPlot::set_defaults() +{ + base_type::set_defaults(); + this->_conv_sample_interval = 60; + this->_kloss_lb = 0.0000001; + this->_kloss_ub = 2; + this->_kloss_num_samples = 1000000; + with_initialization_loops = true; +} + +const char* const GeneralizedPatlakPlot::registered_name = "Generalized Patlak Plot"; + +//! default constructor +GeneralizedPatlakPlot::GeneralizedPatlakPlot() +{ + this->_matrix_is_stored = false; + this->_initialization_matrix_is_stored = false; + this->set_defaults(); +} + +GeneralizedPatlakPlot::~GeneralizedPatlakPlot() //!< default destructor +{} + +//! Simply get model matrix if it has been already stored +GeneralizedPatlakMatrix<2> +GeneralizedPatlakPlot::get_model_matrix() const +{ + if (_matrix_is_stored == false) + error("It seems that ModelMatrix has not been set, yet. "); + + return _model_matrix; +} + +//! Simply get model matrix if it has been already stored +ModelMatrix<2> +GeneralizedPatlakPlot::get_initialization_model_matrix() const +{ + if (_initialization_matrix_is_stored == false) + error("It seems that initialization ModelMatrix has not been set, yet. "); + + return _initialization_model_matrix; +} + +//! Simply set model matrix +void +GeneralizedPatlakPlot::set_model_matrix(GeneralizedPatlakMatrix<2> model_matrix) +{ + this->_model_matrix = model_matrix; + this->_matrix_is_stored = true; +} + +//! Simply set initialization model matrix +void +GeneralizedPatlakPlot::set_initialization_model_matrix(ModelMatrix<2> initialization_model_matrix) +{ + this->_initialization_model_matrix = initialization_model_matrix; + this->_initialization_matrix_is_stored = true; +} + +//! Simply set Hfunction matrix +void +GeneralizedPatlakPlot::set_Hfunction_matrix(GeneralizedPatlakMatrix<2> Hfunction_matrix) +{ + this->_Hfunction_matrix = Hfunction_matrix; +} + +//! Simply get Hfunction matrix +GeneralizedPatlakMatrix<2> +GeneralizedPatlakPlot::get_Hfunction_matrix() const +{ + return _Hfunction_matrix; +} + +//! Simply set Ki matrix +void +GeneralizedPatlakPlot::set_Ki_matrix(GeneralizedPatlakMatrix<2> Ki_matrix) +{ + this->_Ki_matrix = Ki_matrix; +} + +//! Simply get Ki matrix +GeneralizedPatlakMatrix<2> +GeneralizedPatlakPlot::get_Ki_matrix() const +{ + return _Ki_matrix; +} + +//! Create generalized Patlak model matrix from plasma data (has to be in appropriate frames) +GeneralizedPatlakMatrix<2> +GeneralizedPatlakPlot::get_model_matrix(const PlasmaData& complete_plasma_data, + const PlasmaData& plasma_frame_data, + const TimeFrameDefinitions& time_frame_definitions, + const unsigned int starting_frame) +{ + // assert(starting_frame > 0); + + // if (_matrix_is_stored == false) + // { + // this->_starting_frame = starting_frame; + // BasicCoordinate<2, int> min_range; + // BasicCoordinate<2, int> max_range; + // unsigned int num_frames = plasma_frame_data.size(); + // float last_frame_time + // = floor(0.5 * (time_frame_definitions.get_end_time(num_frames) + time_frame_definitions.get_start_time(num_frames))); + // unsigned int last_frame_mid_time = (unsigned int)floor(last_frame_time + 0.5); + // unsigned int num_conv_params = (unsigned int)floor(((last_frame_mid_time - 1) / this->_conv_sample_interval)) + 2; + + // min_range[1] = 1; + // min_range[2] = this->_starting_frame; + // max_range[1] = num_conv_params; + // max_range[2] = num_frames; + // IndexRange<2> data_range(min_range, max_range); + // Array<2, float> patlak_array(data_range); + // VectorWithOffset time_vector(min_range[2], max_range[2]); + // VectorWithOffset plasma_sample_dec_fact(min_range[1], max_range[1]); + // VectorWithOffset dec_fact(min_range[2], max_range[2]); + // PlasmaData::const_iterator cur_iter = plasma_frame_data.begin() + this->_starting_frame - 1; + // PlasmaData::const_iterator complete_plasma_cur_iter; + + // unsigned int frame_num, conv_sample, actual_time_point; + + // if (plasma_frame_data.get_is_decay_corrected()) + // warning("Uncorrecting previous decay correction, while putting the plasma_data into the model_matrix."); + // else if (!plasma_frame_data.get_is_decay_corrected()) + // error("plasma_data have not been corrected during the process, which will create wrong results!!!"); + + // std::cout << "\nCreating Generalized Model Matrix (Here printed in its transverse format)\n\n" + // << "NOTE1: It contains as many columns as the number of later frames participating in parameter estimation\n" + // << "It contains as many rows as the convolution points of the input function\n" + // << "+ 1 last row consisting of the plasma counts for the corresponding later frame\n" + // << "NOTE2: Last element of each column is the plasma counts for the corresponding later frame\n\n" + // << "First Column: plasma samples for frame 1 ... Last Column: plasma samples for last frame\n"; + + // // Fillling of the Patlak array. + // // First conv_sample columns are filled with plasma samples for each sec, + // for (frame_num = this->_starting_frame; cur_iter != this->_plasma_frame_data.end(); ++frame_num, ++cur_iter) + // { + // float cur_frame_time = 0.5 * (this->_frame_defs.get_end_time(frame_num) + + // this->_frame_defs.get_start_time(frame_num)); unsigned int cur_frame_mid_time = (int)floor(cur_frame_time + 0.5); + // std::cout << "\nFrame Number: " << frame_num << " Current Frame Mid Time (float): " << cur_frame_time + // << " Current Frame Mid Time (int): " << cur_frame_mid_time << "\n"; + // complete_plasma_cur_iter = this->_complete_plasma_data.begin() + cur_frame_mid_time - 1; + + // for (conv_sample = 1, actual_time_point = 1; actual_time_point <= last_frame_mid_time; ++conv_sample) + // { + // actual_time_point = (conv_sample - 1) * this->_conv_sample_interval + 1; + // if (actual_time_point <= cur_frame_mid_time) + // patlak_array[conv_sample][frame_num] + // = complete_plasma_cur_iter->get_plasma_counts_in_kBq() * this->_conv_sample_interval; + // else + // patlak_array[conv_sample][frame_num] = 0; + + // complete_plasma_cur_iter = complete_plasma_cur_iter - this->_conv_sample_interval; + // } + + // // Last column is filled with the plasma activity of the later frames + // patlak_array[num_conv_params][frame_num] = cur_iter->get_plasma_counts_in_kBq(); + + // std::cout << "\n"; + // // Un-correcting for decay, if data are decay corrected + // if (this->_plasma_frame_data.get_is_decay_corrected()) + // { + // cerr << "Timing info (Decay correction factor info)" << endl; + // complete_plasma_cur_iter = this->_complete_plasma_data.begin() + cur_frame_mid_time - 1; + // for (conv_sample = 1, actual_time_point = 1; actual_time_point <= last_frame_mid_time; ++conv_sample) + // { + // actual_time_point = (conv_sample - 1) * this->_conv_sample_interval + 1; + // if (actual_time_point <= cur_frame_mid_time) + // { + // cerr << complete_plasma_cur_iter->get_time_in_s() << " "; + // plasma_sample_dec_fact[conv_sample] = static_cast(decay_correction_factor( + // this->_complete_plasma_data.get_isotope_halflife(), complete_plasma_cur_iter->get_time_in_s())); + // patlak_array[conv_sample][frame_num] /= plasma_sample_dec_fact[conv_sample]; + // } + // else + // { + // cerr << 0 << " "; + // plasma_sample_dec_fact[conv_sample] = 1; + // } + + // cerr << " (" << plasma_sample_dec_fact[conv_sample] << ") "; + + // complete_plasma_cur_iter = complete_plasma_cur_iter - this->_conv_sample_interval; + // } + + // dec_fact[frame_num] = static_cast( + // decay_correction_factor(this->_plasma_frame_data.get_isotope_halflife(), + // this->_plasma_frame_data.get_time_frame_definitions().get_start_time(frame_num), + // this->_plasma_frame_data.get_time_frame_definitions().get_end_time(frame_num))); + + // patlak_array[num_conv_params][frame_num] /= dec_fact[frame_num]; + // time_vector[frame_num] = static_cast( + // 0.5 * (this->_frame_defs.get_end_time(frame_num) + this->_frame_defs.get_start_time(frame_num))); + // } + // } + // std::cout << endl << endl; + // // Print out the model matrix + // for (conv_sample = 1; conv_sample <= num_conv_params; ++conv_sample) + // { + // for (frame_num = this->_starting_frame; frame_num <= num_frames; ++frame_num) + // std::cout << patlak_array[conv_sample][frame_num] << " "; + // std::cout << "\n"; + // } + + // if (this->_plasma_frame_data.get_is_decay_corrected()) + // { + // std::cout << "\n\nFrame Index Start Time End Time Decay correction factor\n"; + // cur_iter = this->_plasma_frame_data.begin() + this->_starting_frame - 1; + // for (frame_num = this->_starting_frame; cur_iter != this->_plasma_frame_data.end(); ++frame_num, ++cur_iter) + // { + // // Print out the frame indices, start and time points and the decay correction factors + // std::cout << frame_num << " " + // << _plasma_frame_data.get_time_frame_definitions().get_start_time(frame_num) << " " + // << _plasma_frame_data.get_time_frame_definitions().get_end_time(frame_num) << " " + // << dec_fact[frame_num] << "\n"; + // } + // } + + // assert(frame_num - 1 == plasma_frame_data.size()); + // this->_model_matrix.set_model_array(patlak_array); + // this->_model_matrix.set_conv_sample_interval(this->_conv_sample_interval); + // this->_model_matrix.set_time_vector(time_vector); + // this->_model_matrix.set_if_in_correct_scale(this->_in_correct_scale); + // this->_model_matrix.threshold_model_array(.0000001F); + // this->_matrix_is_stored = true; + // } + // return _model_matrix; +} + +//! Create initialization (standard Patlak) model matrix from plasma data (has to be in appropriate frames: i.e. +//! plasma_frame_data) +ModelMatrix<2> +GeneralizedPatlakPlot::get_initialization_model_matrix(const PlasmaData& plasma_frame_data, + const TimeFrameDefinitions& time_frame_definitions, + const unsigned int starting_frame) +{ + // assert(starting_frame > 0); + + // if (_matrix_is_stored == false) + // { + // this->_starting_frame = starting_frame; + // BasicCoordinate<2, int> min_range; + // BasicCoordinate<2, int> max_range; + // min_range[1] = 1; + // min_range[2] = starting_frame; + // max_range[1] = 2; + // max_range[2] = plasma_frame_data.size(); + // IndexRange<2> data_range(min_range, max_range); + // Array<2, float> patlak_array(data_range); + // VectorWithOffset time_vector(min_range[2], max_range[2]); + // PlasmaData::const_iterator cur_iter = plasma_frame_data.begin(); + + // double sum_value = 0.; + // unsigned int sample_num; + // // std::cerr << "\n" << cur_iter->get_plasma_counts_in_kBq() << " " << cur_iter->get_time_in_s() << "\n"; + // // std::cerr << + // // "\nFrame-PlasmaStart-TimeFrameFileStart-PlasmaDuration-TimeFrameFileDuration-PlasmaEnd-TimeFrameFileEnd\n" ; + // for (sample_num = 1; sample_num < starting_frame; ++sample_num, ++cur_iter) + // { + // sum_value + // += cur_iter->get_plasma_counts_in_kBq() * + // plasma_frame_data.get_time_frame_definitions().get_duration(sample_num); + // } + + // assert(cur_iter == plasma_frame_data.begin() + starting_frame - 1); + + // for (sample_num = starting_frame; cur_iter != plasma_frame_data.end(); ++sample_num, ++cur_iter) + // { + // double integral_step + // = cur_iter->get_plasma_counts_in_kBq() * plasma_frame_data.get_time_frame_definitions().get_duration(sample_num); + // // Calculation of the plasma integral only up to the mid time of the current plasma frame + // sum_value += 0.5 * integral_step; + // // Fillling of the Patlak array. First column is filled with plasma integral, second column with plasma activity + // patlak_array[1][sample_num] = static_cast(sum_value); + // patlak_array[2][sample_num] = cur_iter->get_plasma_counts_in_kBq(); + // if (plasma_frame_data.get_is_decay_corrected()) + // { + // const float dec_fact = static_cast( + // decay_correction_factor(plasma_frame_data.get_isotope_halflife(), + // plasma_frame_data.get_time_frame_definitions().get_start_time(sample_num), + // plasma_frame_data.get_time_frame_definitions().get_end_time(sample_num))); + // patlak_array[1][sample_num] /= dec_fact; + // patlak_array[2][sample_num] /= dec_fact; + // time_vector[sample_num] = static_cast( + // 0.5 * (time_frame_definitions.get_end_time(sample_num) + time_frame_definitions.get_start_time(sample_num))); + // } + // // Completion of integral calculation before moving to the next plasma frame + // sum_value += 0.5 * integral_step; + // } + // if (plasma_frame_data.get_is_decay_corrected()) + // warning("Uncorrecting previous decay correction, while putting the plasma_frame_data into the model_matrix."); + // else if (!plasma_frame_data.get_is_decay_corrected()) + // warning("plasma_frame_data have not been corrected during the process, which might create wrong results!!!"); + + // assert(sample_num - 1 == plasma_frame_data.size()); + // this->_initialization_model_matrix.set_model_array(patlak_array); + // this->_initialization_model_matrix.set_time_vector(time_vector); + // this->_initialization_model_matrix.set_is_in_correct_scale(this->_in_correct_scale); + // this->_initialization_model_matrix.threshold_model_array(.0000001F); + // this->_initialization_matrix_is_stored = true; + // } + // return _initialization_model_matrix; +} + +//! Create prefeched H matrix from private members +void +GeneralizedPatlakPlot::create_Hfunction_matrix() +{ + BasicCoordinate<2, int> min_range; + BasicCoordinate<2, int> max_range; + min_range[1] = 1; + min_range[2] = 1; + max_range[1] = 2; + max_range[2] = this->_kloss_num_samples; + IndexRange<2> kloss_range(min_range, max_range); + Array<2, float> Hfunction_array(kloss_range); + unsigned int kloss_index, conv_sample, actual_time_point; + float kloss_step = (this->_kloss_ub - this->_kloss_lb) / this->_kloss_num_samples; + float kloss_val = this->_kloss_lb; + + std::cout << "\nPrecalculating H Matrix for the following range of " << this->_kloss_num_samples << " kloss values:\n" + << "[ kloss_start : kloss_end : kloss_step ] -> [ " << this->_kloss_lb << " : " << this->_kloss_ub << " : " + << kloss_step << " ]\n\n"; + + for (kloss_index = 1; kloss_index <= this->_kloss_num_samples; ++kloss_index) + { + float numerator_sum = 0, denominator_sum = 0; + for (conv_sample = 1, actual_time_point = 1; actual_time_point <= this->_last_frame_ref_time; ++conv_sample) + { + actual_time_point = (conv_sample - 1) * this->_conv_sample_interval + 1; + numerator_sum += actual_time_point * exp(-kloss_val * actual_time_point); + denominator_sum += exp(-kloss_val * actual_time_point); + } + Hfunction_array[1][kloss_index] = kloss_val; + + // For linear interpolation, the linear H value is sufficient + Hfunction_array[2][kloss_index] = numerator_sum / denominator_sum; + + // For fast linear-log interpolation, better pre-calculate the log value for H + // Hfunction_array[2][kloss_index]=log(numerator_sum/denominator_sum); + + kloss_val += kloss_step; + } + this->_model_matrix.set_Hfunction_array(Hfunction_array); + this->_model_matrix.set_prefetched_sampling(this->_kloss_lb, this->_kloss_ub, this->_kloss_num_samples); + std::cout << "Precalculation of H Matrix has been completed\n\n"; +} + +//! Create prefeched Ki matrix from private members +void +GeneralizedPatlakPlot::create_Ki_matrix() +{ + BasicCoordinate<2, int> min_range; + BasicCoordinate<2, int> max_range; + min_range[1] = 1; + min_range[2] = 1; + max_range[1] = 2; + max_range[2] = this->_kloss_num_samples; + IndexRange<2> kloss_range(min_range, max_range); + Array<2, float> Ki_array(kloss_range); + unsigned int kloss_index, conv_sample, actual_time_point; + float kloss_step = (this->_kloss_ub - this->_kloss_lb) / this->_kloss_num_samples; + float kloss_val = this->_kloss_lb; + + std::cout << "\nPrecalculating Ki Matrix for the following range of " << this->_kloss_num_samples << " kloss values:\n" + << "[ kloss_start : kloss_end : kloss_step ] -> [ " << this->_kloss_lb << " : " << this->_kloss_ub << " : " + << kloss_step << " ]\n\n"; + + for (kloss_index = 1; kloss_index <= this->_kloss_num_samples; ++kloss_index) + { + float Ki_sum = 0; + for (conv_sample = 1, actual_time_point = 1; actual_time_point <= this->_last_frame_ref_time; ++conv_sample) + { + actual_time_point = (conv_sample - 1) * this->_conv_sample_interval + 1; + Ki_sum += exp(-kloss_val * actual_time_point); + } + + Ki_array[1][kloss_index] = kloss_val; + Ki_array[2][kloss_index] = Ki_sum; + kloss_val += kloss_step; + } + this->_model_matrix.set_Ki_array(Ki_array); + // this->_model_matrix.set_prefetched_sampling(this->_kloss_lb,this->_kloss_ub,this->_kloss_num_samples); + + std::cout << "\nPrecalculation of Ki Matrix has been completed\n\n"; +} + +//! Create model matrix from private members +void +GeneralizedPatlakPlot::create_model_matrix() +{ + if (_matrix_is_stored == false) + { + base_type::create_model_matrix(); + + BasicCoordinate<2, int> min_range; + BasicCoordinate<2, int> max_range; + min_range[1] = 1; + min_range[2] = this->get_starting_frame(); + max_range[1] = this->_num_conv_params; + max_range[2] = this->get_plasma_data().size(); + + IndexRange<2> data_range(min_range, max_range); + Array<2, float> patlak_array(data_range); + VectorWithOffset time_vector(min_range[2], max_range[2]); + VectorWithOffset plasma_sample_dec_fact(min_range[1], max_range[1]); + VectorWithOffset dec_fact(min_range[2], max_range[2]); + PlasmaData::const_iterator cur_iter = this->_plasma_frame_data.begin() + this->get_starting_frame() - 1; + PlasmaData::const_iterator complete_plasma_cur_iter; + + unsigned int frame_num, conv_sample, actual_time_point; + const bool integrate_to_midpoint = this->get_frame_reference_time() == 1; + + // std::cout<< "The total number of frames are: " << this->_num_frames << "\n" + // << "The total number of complete plasma samples are: " << this->_plasma_frame_data.size() << "\n" + // << "The last frame middle time is : " << this->_last_frame_mid_time << "\n" + // << "The time shift in complete plasma samples is: " << this->_plasma_frame_data.get_time_shift() << "\n" + // << "The total number of convolution points + 1(one) more column are: " << this->_num_conv_params << "\n" + // << "First Column: plasma samples for frame 1 ... Last Column: plasma samples for last frame\n"; + + // Fillling of the Patlak array. + // First conv_sample columns are filled with plasma samples for each sec, + for (frame_num = this->get_starting_frame(); cur_iter != this->_plasma_frame_data.end(); ++frame_num, ++cur_iter) + { + const double frame_start = this->_plasma_frame_data.get_time_frame_definitions().get_start_time(frame_num); + const double frame_end = this->_plasma_frame_data.get_time_frame_definitions().get_end_time(frame_num); + + // instant at which this frame's model value is evaluated + const double cur_frame_ref_time_f = integrate_to_midpoint ? 0.5 * (frame_start + frame_end) : frame_end; + + unsigned int cur_frame_ref_time = static_cast(floor(cur_frame_ref_time_f + 0.5)); + info(format("Frame Number: {} Current Frame Mid Time (float): {} Current Frame Mid Time (int): {} ", + frame_num, + cur_frame_ref_time_f, + cur_frame_ref_time)); + + complete_plasma_cur_iter = this->_plasma_frame_data.begin() + cur_frame_ref_time - 1; + + for (conv_sample = 1, actual_time_point = 1; actual_time_point <= this->_last_frame_ref_time; ++conv_sample) + { + actual_time_point = (conv_sample - 1) * this->_conv_sample_interval + 1; + if (actual_time_point <= cur_frame_ref_time) + patlak_array[conv_sample][frame_num] + = complete_plasma_cur_iter->get_plasma_counts_in_kBq() * this->_conv_sample_interval; + else + patlak_array[conv_sample][frame_num] = 0; + + complete_plasma_cur_iter = complete_plasma_cur_iter - this->_conv_sample_interval; + } + + // Last column is filled with the plasma activity of the later frames + patlak_array[this->_num_conv_params][frame_num] = cur_iter->get_plasma_counts_in_kBq(); + + // Un-correcting for decay, if data are decay corrected + if (this->_plasma_frame_data.get_is_decay_corrected()) + { + complete_plasma_cur_iter = this->_plasma_frame_data.begin() + cur_frame_ref_time - 1; + for (conv_sample = 1, actual_time_point = 1; actual_time_point <= this->_last_frame_ref_time; ++conv_sample) + { + actual_time_point = (conv_sample - 1) * this->_conv_sample_interval + 1; + if (actual_time_point <= cur_frame_ref_time) + { + cerr << complete_plasma_cur_iter->get_time_in_s() << " "; + plasma_sample_dec_fact[conv_sample] = static_cast(decay_correction_factor( + this->_plasma_frame_data.get_isotope_halflife(), complete_plasma_cur_iter->get_time_in_s())); + patlak_array[conv_sample][frame_num] /= plasma_sample_dec_fact[conv_sample]; + } + else + { + cerr << 0 << " "; + plasma_sample_dec_fact[conv_sample] = 1; + } + + cerr << " (" << plasma_sample_dec_fact[conv_sample] << ") "; + + complete_plasma_cur_iter = complete_plasma_cur_iter - this->_conv_sample_interval; + } + + dec_fact[frame_num] = static_cast( + decay_correction_factor(this->_plasma_frame_data.get_isotope_halflife(), + this->_plasma_frame_data.get_time_frame_definitions().get_start_time(frame_num), + this->_plasma_frame_data.get_time_frame_definitions().get_end_time(frame_num))); + + patlak_array[this->_num_conv_params][frame_num] /= dec_fact[frame_num]; + time_vector[frame_num] = static_cast(0.5 + * (this->get_time_frame_definitions().get_end_time(frame_num) + + this->get_time_frame_definitions().get_start_time(frame_num))); + } + } + + // Print out the model matrix + for (conv_sample = 1; conv_sample <= this->_num_conv_params; ++conv_sample) + { + for (frame_num = this->get_starting_frame(); frame_num <= this->_num_frames; ++frame_num) + std::cout << patlak_array[conv_sample][frame_num] << " "; + std::cout << "\n"; + } + + if (this->_plasma_frame_data.get_is_decay_corrected()) + { + std::cout << "\n\nFrame Index Start Time End Time Decay correction factor\n"; + cur_iter = this->_plasma_frame_data.begin() + this->get_starting_frame() - 1; + for (frame_num = this->get_starting_frame(); cur_iter != this->_plasma_frame_data.end(); ++frame_num, ++cur_iter) + { + // Print out the frame indices, start and time points and the decay correction factors + std::cout << frame_num << " " + << _plasma_frame_data.get_time_frame_definitions().get_start_time(frame_num) << " " + << _plasma_frame_data.get_time_frame_definitions().get_end_time(frame_num) << " " + << dec_fact[frame_num] << "\n"; + } + } + + assert(frame_num - 1 == this->_plasma_frame_data.size()); + this->_model_matrix.set_model_array(patlak_array); + this->_model_matrix.set_conv_sample_interval(this->_conv_sample_interval); + this->_model_matrix.set_time_vector(time_vector); + // Uncalibrate the ModelMatrix instead of Calibrating all the Dynamic Images. This should make faster the computation. + // Supposes the images are not calibrated. + this->_model_matrix.uncalibrate(this->_cal_factor); + this->_model_matrix.set_matrix_in_total_frame_counts(this->_plasma_in_total_cnt); + if (this->_in_total_cnt) + this->_model_matrix.convert_to_total_frame_counts(this->get_time_frame_definitions()); + this->_model_matrix.set_if_in_correct_scale(this->_in_correct_scale); + this->_model_matrix.threshold_model_array(.000000001F); + this->_matrix_is_stored = true; + } + else + warning("ModelMatrix has been already created"); +} + +Succeeded +GeneralizedPatlakPlot::set_up() +{ + if (base_type::set_up() != Succeeded::yes) + return Succeeded::no; + + info("Preparing to set up the Generalized Patlak Plot..."); + // std::cout << "Set up of Generalized Patlak Plot has been completed." << endl; + this->create_model_matrix(); + + if (with_initialization_loops) + { + info("Preparing to set up the initialization (standard) Patlak Plot..."); + linear_model = std::make_shared(this->get_exam_info_sptr()); + + // linear_model->set_starting_frame(this->_starting_frame); + // linear_model->set_cal_factor(this->_cal_factor); + // linear_model->set_time_frame_definitions(this->_frame_defs); + // linear_model->set_in_total_cnt(this->_in_total_cnt); + // linear_model->set_in_correct_scale(this->_in_correct_scale); + // linear_model->set_frame_reference_time(this->get_frame_reference_time()); + // // reuse the plasma data we already sampled — do not re-read the blood file + // linear_model->set_plasma_data(this->_plasma_frame_data.get_sample_data_in_frames(this->_frame_defs)); + + linear_model->set_up(); + info("Set up of initialization (standard) Patlak Plot has been completed."); + } + + std::cout << "Preparing to construct look up tables..." << endl; + this->create_Hfunction_matrix(); + this->create_Ki_matrix(); + std::cout << "Look up tables construction has been completed." << endl; + + if ((this->_matrix_is_stored == true) && (this->_initialization_matrix_is_stored == true)) + { + std::cout << "Set up of generalized and initialization (standard) Patlak Plot is successful." << endl; + return Succeeded::yes; + } + else if (this->_matrix_is_stored == false) + { + std::cout << "Set up of Generalized Patlak Plot has failed." << endl; + return Succeeded::no; + } + else + { + std::cout << "Set up of initialization (standard) Patlak Plot has failed." << endl; + return Succeeded::no; + } +} + +void +GeneralizedPatlakPlot::multiply_dynamic_image_with_model_gradient(DynamicDiscretisedDensity& impulse_response, + const DynamicDiscretisedDensity& dyn_image) const +{ + if (!this->_in_correct_scale) + { +#ifndef NDEBUG + this->_model_matrix.write_to_file("patlak_matrix_not_in_correct_scale.txt", this->_num_conv_params); +#endif // NDEBUG + const DiscretisedDensityOnCartesianGrid<3, float>* image_cartesian_ptr + = dynamic_cast*>(((dyn_image.get_densities())[0]).get()); + const BasicCoordinate<3, float> this_grid_spacing = image_cartesian_ptr->get_grid_spacing(); + this->_model_matrix.scale_model_matrix(this_grid_spacing[2] / dyn_image.get_scanner_default_bin_size()); +#ifndef NDEBUG + this->_model_matrix.write_to_file("patlak_matrix_in_correct_scale.txt", this->_num_conv_params); +#endif // NDEBUG + } + this->_model_matrix.multiply_dynamic_image_with_model(impulse_response, dyn_image, this->_num_conv_params); +} + +void +GeneralizedPatlakPlot::multiply_dynamic_image_with_model_gradient_and_add_to_input( + DynamicDiscretisedDensity& impulse_response, const DynamicDiscretisedDensity& dyn_image) const +{ + if (!this->_in_correct_scale) + { +#ifndef NDEBUG + this->_model_matrix.write_to_file("patlak_matrix_not_in_correct_scale.txt", this->_num_conv_params); +#endif // NDEBUG + const DiscretisedDensityOnCartesianGrid<3, float>* image_cartesian_ptr + = dynamic_cast*>(((dyn_image.get_densities())[0]).get()); + const BasicCoordinate<3, float> this_grid_spacing = image_cartesian_ptr->get_grid_spacing(); + this->_model_matrix.scale_model_matrix(this_grid_spacing[2] / dyn_image.get_scanner_default_bin_size()); +#ifndef NDEBUG + this->_model_matrix.write_to_file("patlak_matrix_in_correct_scale.txt", this->_num_conv_params); +#endif // NDEBUG + } + this->_model_matrix.multiply_dynamic_image_with_model_and_add_to_input(impulse_response, dyn_image, this->_num_conv_params); +} + +// Should be a virtual function declared in the KineticModels or better to the LinearModels +void +GeneralizedPatlakPlot::get_impulse_response_from_parametric_image(DynamicDiscretisedDensity& impulse_response_image, + const Parametric3VoxelsOnCartesianGrid& par_image) const +{ + + this->_model_matrix.synthesize_impulse_response_from_parametric_image( + impulse_response_image, par_image, this->_num_conv_params); +} + +// Should be a virtual function declared in the KineticModels or better to the LinearModels +void +GeneralizedPatlakPlot::get_dynamic_image_from_impulse_response(DynamicDiscretisedDensity& dyn_image, + const DynamicDiscretisedDensity& impulse_response_image) const +{ + if (!this->_in_correct_scale) + { +#ifndef NDEBUG + this->_model_matrix.write_to_file("patlak_matrix_not_in_correct_scale.txt", this->_num_conv_params); +#endif // NDEBUG + const DiscretisedDensityOnCartesianGrid<3, float>* image_cartesian_ptr + = dynamic_cast*>(((dyn_image.get_densities())[0]).get()); + const BasicCoordinate<3, float> this_grid_spacing = image_cartesian_ptr->get_grid_spacing(); + this->_model_matrix.scale_model_matrix(this_grid_spacing[2] / dyn_image.get_scanner_default_bin_size()); +#ifndef NDEBUG + this->_model_matrix.write_to_file("patlak_matrix_in_correct_scale.txt", this->_num_conv_params); +#endif // NDEBUG + } + + this->_model_matrix.multiply_impulse_response_with_model(dyn_image, impulse_response_image, this->_num_conv_params); +} + +// Should be a virtual function declared in the KineticModels or better to the LinearModels +void +GeneralizedPatlakPlot::get_dynamic_image_from_parametric_image(DynamicDiscretisedDensity& dyn_image, + const Parametric3VoxelsOnCartesianGrid& par_image) const +{ + if (!this->_in_correct_scale) + { +#ifndef NDEBUG + this->_model_matrix.write_to_file("patlak_matrix_not_in_correct_scale.txt", this->_num_conv_params); +#endif // NDEBUG + const DiscretisedDensityOnCartesianGrid<3, float>* image_cartesian_ptr + = dynamic_cast*>(((dyn_image.get_densities())[0]).get()); + const BasicCoordinate<3, float> this_grid_spacing = image_cartesian_ptr->get_grid_spacing(); + this->_model_matrix.scale_model_matrix(this_grid_spacing[2] / dyn_image.get_scanner_default_bin_size()); +#ifndef NDEBUG + this->_model_matrix.write_to_file("patlak_matrix_in_correct_scale.txt", this->_num_conv_params); +#endif // NDEBUG + } + + this->_model_matrix.multiply_parametric_image_with_model(dyn_image, par_image, this->_num_conv_params); +} + +void +GeneralizedPatlakPlot::get_generalized_patlak_parameters_from_impulse_response( + Parametric3VoxelsOnCartesianGrid& par_image, + const DynamicDiscretisedDensity& dyn_image, + const DynamicDiscretisedDensity& impulse_response) const +{ + if (!this->_in_correct_scale) + { +#ifndef NDEBUG + this->_model_matrix.write_to_file("patlak_matrix_not_in_correct_scale.txt", this->_num_conv_params); +#endif // NDEBUG + const DiscretisedDensityOnCartesianGrid<3, float>* image_cartesian_ptr + = dynamic_cast*>(((dyn_image.get_densities())[0]).get()); + const BasicCoordinate<3, float> this_grid_spacing = image_cartesian_ptr->get_grid_spacing(); + this->_model_matrix.scale_model_matrix(this_grid_spacing[2] / dyn_image.get_scanner_default_bin_size()); +#ifndef NDEBUG + this->_model_matrix.write_to_file("patlak_matrix_in_correct_scale.txt", this->_num_conv_params); +#endif // NDEBUG + } + + this->_model_matrix.estimate_generalized_patlak_parameters_with_impulse_response( + par_image, impulse_response, this->_num_conv_params); +} + +void +GeneralizedPatlakPlot::multiply_dynamic_image_with_initialization_model_gradient(Parametric3VoxelsOnCartesianGrid& par_image, + const DynamicDiscretisedDensity& dyn_image) const +{ + if (!this->_in_correct_scale) + { +#ifndef NDEBUG + this->_initialization_model_matrix.write_to_file("initialization_patlak_matrix_not_in_correct_scale.txt"); +#endif // NDEBUG + const DiscretisedDensityOnCartesianGrid<3, float>* image_cartesian_ptr + = dynamic_cast*>(((dyn_image.get_densities())[0]).get()); + const BasicCoordinate<3, float> this_grid_spacing = image_cartesian_ptr->get_grid_spacing(); + this->_initialization_model_matrix.scale_model_matrix(this_grid_spacing[2] / dyn_image.get_scanner_default_bin_size()); +#ifndef NDEBUG + this->_initialization_model_matrix.write_to_file("initialization_patlak_matrix_in_correct_scale.txt"); +#endif // NDEBUG + } + this->_initialization_model_matrix.multiply_dynamic_image_with_initialization_model(par_image, dyn_image); +} + +void +GeneralizedPatlakPlot::multiply_dynamic_image_with_initialization_model_gradient_and_add_to_input( + Parametric3VoxelsOnCartesianGrid& par_image, const DynamicDiscretisedDensity& dyn_image) const +{ + if (!this->_in_correct_scale) + { +#ifndef NDEBUG + this->_initialization_model_matrix.write_to_file("initialization_patlak_matrix_not_in_correct_scale.txt"); +#endif // NDEBUG + const DiscretisedDensityOnCartesianGrid<3, float>* image_cartesian_ptr + = dynamic_cast*>(((dyn_image.get_densities())[0]).get()); + const BasicCoordinate<3, float> this_grid_spacing = image_cartesian_ptr->get_grid_spacing(); + this->_initialization_model_matrix.scale_model_matrix(this_grid_spacing[2] / dyn_image.get_scanner_default_bin_size()); +#ifndef NDEBUG + this->_initialization_model_matrix.write_to_file("initialization_patlak_matrix_in_correct_scale.txt"); +#endif // NDEBUG + } + this->_initialization_model_matrix.multiply_dynamic_image_with_initialization_model_and_add_to_input(par_image, dyn_image); +} + +// Should be a virtual function declared in the KineticModels or better to the LinearModels +void +GeneralizedPatlakPlot::get_dynamic_image_from_initialization_parametric_image( + DynamicDiscretisedDensity& dyn_image, const Parametric3VoxelsOnCartesianGrid& par_image) const +{ + if (!this->_in_correct_scale) + { +#ifndef NDEBUG + this->_initialization_model_matrix.write_to_file("patlak_matrix_not_in_correct_scale.txt"); +#endif // NDEBUG + const DiscretisedDensityOnCartesianGrid<3, float>* image_cartesian_ptr + = dynamic_cast*>(((dyn_image.get_densities())[0]).get()); + const BasicCoordinate<3, float> this_grid_spacing = image_cartesian_ptr->get_grid_spacing(); + this->_initialization_model_matrix.scale_model_matrix(this_grid_spacing[2] / dyn_image.get_scanner_default_bin_size()); +#ifndef NDEBUG + this->_initialization_model_matrix.write_to_file("initialization_patlak_matrix_in_correct_scale.txt"); +#endif // NDEBUG + } + + this->_initialization_model_matrix.multiply_parametric_image_with_initialization_model(dyn_image, par_image); +} + +void +GeneralizedPatlakPlot::estimate_nested_loop_parameters_with_model(Parametric3VoxelsOnCartesianGrid& parametric_image, + DynamicDiscretisedDensity& dynamic_image_nested_loop_estimate, + DynamicDiscretisedDensity& dynamic_image_update_factor, + const DynamicDiscretisedDensity& dynamic_image_reference, + float minimum_nested_relative_change, + float maximum_nested_relative_change, + int num_nested_subiterations) const +{ + if (!this->_in_correct_scale) + { +#ifndef NDEBUG + this->_model_matrix.write_to_file("patlak_matrix_not_in_correct_scale.txt", this->_num_conv_params); +#endif // NDEBUG + const DiscretisedDensityOnCartesianGrid<3, float>* image_cartesian_ptr + = dynamic_cast*>( + ((dynamic_image_nested_loop_estimate.get_densities())[0]).get()); + const BasicCoordinate<3, float> this_grid_spacing = image_cartesian_ptr->get_grid_spacing(); + this->_model_matrix.scale_model_matrix(this_grid_spacing[2] + / dynamic_image_nested_loop_estimate.get_scanner_default_bin_size()); +#ifndef NDEBUG + this->_model_matrix.write_to_file("patlak_matrix_in_correct_scale.txt", this->_num_conv_params); +#endif // NDEBUG + } + this->_model_matrix.estimate_nested_loop_parameters_with_model(parametric_image, + dynamic_image_nested_loop_estimate, + dynamic_image_update_factor, + dynamic_image_reference, + num_nested_subiterations, + minimum_nested_relative_change, + maximum_nested_relative_change, + this->_num_conv_params); +} + +unsigned int +GeneralizedPatlakPlot::get_num_conv_params() const +{ + return this->_num_conv_params; +} + +void +GeneralizedPatlakPlot::initialise_keymap() +{ + base_type::initialise_keymap(); + this->parser.add_start_key("Generalized Patlak Plot Parameters"); + this->parser.add_key("convolution sampling interval", &this->_conv_sample_interval); + this->parser.add_key("kloss lower bound", &this->_kloss_lb); + this->parser.add_key("kloss upper bound", &this->_kloss_ub); + this->parser.add_key("number of kloss samples", &this->_kloss_num_samples); + this->parser.add_stop_key("end Generalized Patlak Plot Parameters"); +} + +/*! \todo This currently hard-wired F-18 decay for the plasma data */ +bool +GeneralizedPatlakPlot::post_processing() +{ + if (base_type::post_processing() == true) + return true; + + this->_plasma_frame_data.read_plasma_data(this->_blood_data_filename); // The implementation assumes three list file. + // TODO have parameter + warning("Assuming F-18 tracer for plasma data!!!"); + this->_plasma_frame_data.set_isotope_halflife(6586.2F); + this->_plasma_frame_data.shift_time(this->_time_shift); + + // this->_plasma_frame_data = this->_complete_plasma_data.get_sample_data_in_frames(this->_frame_defs); + this->_num_frames = this->_plasma_frame_data.size(); + float _last_frame_time = this->get_frame_reference_time() == 1 + ? floor(0.5 + * (this->get_time_frame_definitions().get_end_time(this->_num_frames) + + this->get_time_frame_definitions().get_start_time(this->_num_frames))) + : this->get_time_frame_definitions().get_end_time(this->_num_frames); + this->_last_frame_ref_time = static_cast(floor(_last_frame_time + 0.5)); + this->_num_conv_params = ((this->_last_frame_ref_time - 1) / this->_conv_sample_interval) + 2; + + return false; +} + +END_NAMESPACE_STIR \ No newline at end of file diff --git a/src/modelling_buildblock/KineticModel.cxx b/src/modelling_buildblock/KineticModel.cxx index 72dbd684b0..df9ef58838 100644 --- a/src/modelling_buildblock/KineticModel.cxx +++ b/src/modelling_buildblock/KineticModel.cxx @@ -24,9 +24,77 @@ START_NAMESPACE_STIR const char* const KineticModel::registered_name = "Kinetic Model Type"; -KineticModel::KineticModel() //!< default constructor -{} -KineticModel::~KineticModel() //!< default destructor -{} +void +KineticModel::initialise_keymap() +{ + this->parser.add_key("frame reference time", &this->_frame_reference_time); + this->parser.add_key("Blood Data Filename", &this->_blood_data_filename); + this->parser.add_key("Calibration Factor", &this->_cal_factor); + this->parser.add_key("Starting Frame", &this->_starting_frame); + this->parser.add_key("Time Shift", &this->_time_shift); + this->parser.add_key("In total counts", &this->_in_total_cnt); + this->parser.add_key("In correct scale", &this->_in_correct_scale); + this->parser.add_key("Time Frame Definition Filename", &this->_time_frame_definition_filename); +} + +void +KineticModel::set_defaults() +{ + _blood_data_filename = ""; + _cal_factor = 1.F; + _starting_frame = 0; + _time_shift = 0.; + _in_correct_scale = false; + _in_total_cnt = false; + _plasma_in_total_cnt = false; + _already_setup = false; + _matrix_is_stored = false; + _frame_reference_time = 0; +} + +Succeeded +KineticModel::set_up() +{ + _already_setup = true; + return Succeeded::yes; +} + +bool +KineticModel::post_processing() +{ + // read time frame def + if (this->_time_frame_definition_filename.size() != 0) + _frame_defs = TimeFrameDefinitions(this->_time_frame_definition_filename); + else + { + error("No Time Frames Definitions available!!!"); + return true; + } + + // Reading the input function + if (this->_blood_data_filename == "0") + { + warning("You need to specify a file for the input function."); + return true; + } + else + { + this->_if_cardiac = false; + } + + return false; +} + +void +KineticModel::create_model_matrix() +{ + if (!_already_setup) + error("You need to run set_up first!"); + + if (this->_plasma_frame_data.get_is_decay_corrected()) + warning("Uncorrected previous decay correction, while putting the plasma_data into the model_matrix."); + else + error("plasma_data have not been corrected during the process, which will create wrong results!!!"); +} END_NAMESPACE_STIR diff --git a/src/modelling_buildblock/ParametricDiscretisedDensity.cxx b/src/modelling_buildblock/ParametricDiscretisedDensity.cxx index e9d484bddf..fb76ef1d7e 100644 --- a/src/modelling_buildblock/ParametricDiscretisedDensity.cxx +++ b/src/modelling_buildblock/ParametricDiscretisedDensity.cxx @@ -16,8 +16,8 @@ \brief Declaration of class stir::ParametricDiscretisedDensity \author Kris Thielemans + \author Nicolas A Karakatsanis \author Richard Brown - */ #include "stir/modelling/ParametricDiscretisedDensity.h" @@ -133,7 +133,7 @@ TEMPLATE void ParamDiscDensity::update_parametric_image(const SingleDiscretisedDensityType& single_density, const unsigned int param_num) { - assert(param_num <= this->get_num_params()); + assert(param_num >= 1 && param_num <= this->get_num_params()); assert(single_density.get_index_range() == this->get_index_range()); const unsigned int f = param_num; @@ -283,6 +283,8 @@ construct_single_density(const int index) // instantiations // template class ParametricDiscretisedDensity<3,KineticParameters >; -template class ParametricDiscretisedDensity; +template class ParametricDiscretisedDensity; + +template class ParametricDiscretisedDensity; END_NAMESPACE_STIR diff --git a/src/modelling_buildblock/PatlakPlot.cxx b/src/modelling_buildblock/PatlakPlot.cxx index 59b8b0efcb..926387908f 100644 --- a/src/modelling_buildblock/PatlakPlot.cxx +++ b/src/modelling_buildblock/PatlakPlot.cxx @@ -7,14 +7,14 @@ SPDX-License-Identifier: Apache-2.0 See STIR/LICENSE.txt for details - +*/ +/*! \file \ingroup modelling \brief Implementations of inline functions of class stir::PatlakPlot \author Charalampos Tsoumpas - - \sa PatlakPlot.h, ModelMatrix.h and KineticModel.h - + \author Nicolas A Karakatsanis + \author Nikos Efthimiou */ #include "stir/modelling/PatlakPlot.h" @@ -28,12 +28,13 @@ void PatlakPlot::set_defaults() { base_type::set_defaults(); - this->_blood_data_filename = ""; - this->_cal_factor = 1.F; - this->_starting_frame = 0; - this->_time_shift = 0.; - this->_in_correct_scale = false; - this->_in_total_cnt = false; +} + +PatlakPlot::PatlakPlot(const shared_ptr& exam_info_sptr) +{ + this->_matrix_is_stored = false; + this->set_defaults(); + this->set_exam_info(exam_info_sptr); } const char* const PatlakPlot::registered_name = "Patlak Plot"; @@ -41,8 +42,7 @@ const char* const PatlakPlot::registered_name = "Patlak Plot"; //! default constructor PatlakPlot::PatlakPlot() { - this->_matrix_is_stored = false; - this->set_defaults(); + set_defaults(); } PatlakPlot::~PatlakPlot() //!< default destructor @@ -70,8 +70,9 @@ PatlakPlot::set_model_matrix(ModelMatrix<2> model_matrix) void PatlakPlot::create_model_matrix() { - if (_matrix_is_stored == false) + if (this->_matrix_is_stored == false) { + base_type::create_model_matrix(); // Create empty Model matrix. this is a [2 x frames] matrix, that contains Cp(t) and \int{Ct(t)} for each frame (Cp(t): // radiotracer concentration on plasma) @@ -84,59 +85,73 @@ PatlakPlot::create_model_matrix() BasicCoordinate<2, int> min_range; BasicCoordinate<2, int> max_range; min_range[1] = 1; - min_range[2] = this->_starting_frame; + min_range[2] = this->get_starting_frame(); max_range[1] = 2; - max_range[2] = this->_plasma_frame_data.size(); + max_range[2] = this->get_plasma_data().size(); + IndexRange<2> data_range(min_range, max_range); Array<2, float> patlak_array(data_range); VectorWithOffset time_vector(min_range[2], max_range[2]); PlasmaData::const_iterator cur_iter = this->_plasma_frame_data.begin(); double sum_value = 0.; - unsigned int sample_num; + unsigned int frame_num; + const bool integrate_to_midpoint = this->get_frame_reference_time() == 1; + // Compute the value of the integral of Cp(t) for frames before the one we want to start applying Patlak to. // Remember that this code requires all frames, from t=0 to be included, otherwise this integral will be wrongly computed. // TODO: do not require the dynamic images to exist to do this integral. - for (sample_num = 1; sample_num < this->_starting_frame; ++sample_num, ++cur_iter) + for (frame_num = 1; frame_num < this->get_starting_frame(); ++frame_num, ++cur_iter) sum_value += cur_iter->get_plasma_counts_in_kBq() - * this->_plasma_frame_data.get_time_frame_definitions().get_duration(sample_num); + * this->_plasma_frame_data.get_time_frame_definitions().get_duration(frame_num); + + assert(cur_iter == this->_plasma_frame_data.begin() + this->get_starting_frame() - 1); - assert(cur_iter == this->_plasma_frame_data.begin() + this->_starting_frame - 1); // For each frame that we are interested in, fill the model matrix. - for (sample_num = this->_starting_frame; cur_iter != this->_plasma_frame_data.end(); ++sample_num, ++cur_iter) + for (frame_num = this->get_starting_frame(); cur_iter != this->_plasma_frame_data.end(); ++frame_num, ++cur_iter) { - sum_value += cur_iter->get_plasma_counts_in_kBq() - * this->_plasma_frame_data.get_time_frame_definitions().get_duration(sample_num); + const double integral_step = cur_iter->get_plasma_counts_in_kBq() + * this->_plasma_frame_data.get_time_frame_definitions().get_duration(frame_num); + + // accumulate up to the frame's reference time: half the frame for the midpoint + // convention, the whole frame for the end-of-frame convention + sum_value += integrate_to_midpoint ? 0.5 * integral_step : integral_step; + // integral of Cp(t) - patlak_array[1][sample_num] = static_cast(sum_value); + patlak_array[1][frame_num] = static_cast(sum_value); // Cp(t) - patlak_array[2][sample_num] = cur_iter->get_plasma_counts_in_kBq(); + patlak_array[2][frame_num] = cur_iter->get_plasma_counts_in_kBq(); // As we will do the reconstruction in un-corrected data, if the plasma data is corrected, we need to undo that if (this->_plasma_frame_data.get_is_decay_corrected()) { const float dec_fact = static_cast( decay_correction_factor(this->_plasma_frame_data.get_isotope_halflife(), - this->_plasma_frame_data.get_time_frame_definitions().get_start_time(sample_num), - this->_plasma_frame_data.get_time_frame_definitions().get_end_time(sample_num))); - patlak_array[1][sample_num] /= dec_fact; - patlak_array[2][sample_num] /= dec_fact; - time_vector[sample_num] = static_cast( - 0.5 * (this->_frame_defs.get_end_time(sample_num) + this->_frame_defs.get_start_time(sample_num))); + this->_plasma_frame_data.get_time_frame_definitions().get_start_time(frame_num), + this->_plasma_frame_data.get_time_frame_definitions().get_end_time(frame_num))); + patlak_array[1][frame_num] /= dec_fact; + patlak_array[2][frame_num] /= dec_fact; } + // If we don't undo decay correction time_vector has not been initialized. -- But we force error. + time_vector[frame_num] = static_cast(0.5 + * (this->get_time_frame_definitions().get_end_time(frame_num) + + this->get_time_frame_definitions().get_start_time(frame_num))); + // complete the current frame's contribution before moving on + if (integrate_to_midpoint) + sum_value += 0.5 * integral_step; } - if (this->_plasma_frame_data.get_is_decay_corrected()) - warning("Uncorrecting previous decay correction, while putting the plasma_data into the model_matrix."); - else - error("plasma_data have not been corrected during the process, which will create wrong results!!!"); - assert(sample_num - 1 == this->_plasma_frame_data.size()); + // assert(frame_num - 1 == this->_plasma_frame_data.size()); + this->_model_matrix.set_model_array(patlak_array); this->_model_matrix.set_time_vector(time_vector); // Uncalibrate the ModelMatrix instead of Calibrating all the Dynamic Images. This should make faster the computation. // Supposes the images are not calibrated. this->_model_matrix.uncalibrate(this->_cal_factor); + this->_model_matrix.set_matrix_in_total_frame_counts(this->_plasma_in_total_cnt); + if (this->_in_total_cnt) - this->_model_matrix.convert_to_total_frame_counts(this->_frame_defs); + this->_model_matrix.convert_to_total_frame_counts(this->get_time_frame_definitions()); + this->_model_matrix.set_is_in_correct_scale(this->_in_correct_scale); this->_model_matrix.threshold_model_array(.000000001F); this->_matrix_is_stored = true; @@ -148,8 +163,20 @@ PatlakPlot::create_model_matrix() Succeeded PatlakPlot::set_up() { - // if (base_type::set_up() != Succeeded::yes) - // return Succeeded::no; + if (base_type::set_up() != Succeeded::yes) + return Succeeded::no; + + if (this->_blood_data_filename != "") + { + info("Reading blood data from file..."); + PlasmaData plasma_data_temp; + plasma_data_temp.read_plasma_data(this->_blood_data_filename); // The implementation assumes three list file. + // TODO have parameter + warning("Assuming F-18 tracer for half-life!!!"); + plasma_data_temp.set_isotope_halflife(6586.2F); + plasma_data_temp.shift_time(this->_time_shift); + this->_plasma_frame_data = plasma_data_temp.get_sample_data_in_frames(this->get_time_frame_definitions()); + } this->create_model_matrix(); if (this->_matrix_is_stored == true) @@ -179,9 +206,9 @@ PatlakPlot::apply_linear_regression(ParametricVoxelsOnCartesianGrid& par_image, } // const DynamicDiscretisedDensity & dyn_image=this->_dyn_image; // TODO check consistency of time-frame definitions - const unsigned int num_frames = (this->_frame_defs).get_num_frames(); + const unsigned int num_frames = this->get_time_frame_definitions().get_num_frames(); unsigned int frame_num; - unsigned int starting_frame = this->_starting_frame; + unsigned int starting_frame = this->get_starting_frame(); Array<2, float> patlak_model_array = this->_model_matrix.get_model_array(); VectorWithOffset patlak_x(starting_frame - 1, num_frames - 1); VectorWithOffset patlak_y(starting_frame - 1, num_frames - 1); @@ -314,22 +341,72 @@ PatlakPlot::get_dynamic_image_from_parametric_image(DynamicDiscretisedDensity& d this->_model_matrix.multiply_parametric_image_with_model(dyn_image, par_image); } -unsigned int -PatlakPlot::get_starting_frame() const +// Currently not used but retained for future potential usage. +// The initialization of generalized Patlak nested estimates is performed by GeneralizedPatlakPlot equivalent method +void +PatlakPlot::multiply_dynamic_image_with_initialization_model_gradient(Parametric3VoxelsOnCartesianGrid& par_image, + const DynamicDiscretisedDensity& dyn_image) const { - return this->_starting_frame; + if (!this->_in_correct_scale) + { +#ifndef NDEBUG + this->_model_matrix.write_to_file("patlak_matrix_not_in_correct_scale.txt"); +#endif // NDEBUG + const DiscretisedDensityOnCartesianGrid<3, float>* image_cartesian_ptr + = dynamic_cast*>(((dyn_image.get_densities())[0]).get()); + const BasicCoordinate<3, float> this_grid_spacing = image_cartesian_ptr->get_grid_spacing(); + this->_model_matrix.scale_model_matrix(this_grid_spacing[2] / dyn_image.get_scanner_default_bin_size()); +#ifndef NDEBUG + this->_model_matrix.write_to_file("patlak_matrix_in_correct_scale.txt"); +#endif // NDEBUG + } + this->_model_matrix.multiply_dynamic_image_with_initialization_model(par_image, dyn_image); } -unsigned int -PatlakPlot::get_ending_frame() const +// Currently not used but retained for future potential usage. +// The initialization of generalized Patlak nested estimates is performed by GeneralizedPatlakPlot equivalent method +void +PatlakPlot::multiply_dynamic_image_with_initialization_model_gradient_and_add_to_input( + Parametric3VoxelsOnCartesianGrid& par_image, const DynamicDiscretisedDensity& dyn_image) const { - return this->get_time_frame_definitions().get_num_frames(); + if (!this->_in_correct_scale) + { +#ifndef NDEBUG + this->_model_matrix.write_to_file("patlak_matrix_not_in_correct_scale.txt"); +#endif // NDEBUG + const DiscretisedDensityOnCartesianGrid<3, float>* image_cartesian_ptr + = dynamic_cast*>(((dyn_image.get_densities())[0]).get()); + const BasicCoordinate<3, float> this_grid_spacing = image_cartesian_ptr->get_grid_spacing(); + this->_model_matrix.scale_model_matrix(this_grid_spacing[2] / dyn_image.get_scanner_default_bin_size()); +#ifndef NDEBUG + this->_model_matrix.write_to_file("patlak_matrix_in_correct_scale.txt"); +#endif // NDEBUG + } + this->_model_matrix.multiply_dynamic_image_with_initialization_model_and_add_to_input(par_image, dyn_image); } -TimeFrameDefinitions -PatlakPlot::get_time_frame_definitions() const +// Should be a virtual function declared in the KineticModels or better to the LinearModels +// Currently not used but retained for future potential usage. +// The initialization of generalized Patlak nested estimates is performed by GeneralizedPatlakPlot equivalent method +void +PatlakPlot::get_dynamic_image_from_initialization_parametric_image(DynamicDiscretisedDensity& dyn_image, + const Parametric3VoxelsOnCartesianGrid& par_image) const { - return this->_frame_defs; + if (!this->_in_correct_scale) + { +#ifndef NDEBUG + this->_model_matrix.write_to_file("patlak_matrix_not_in_correct_scale.txt"); +#endif // NDEBUG + const DiscretisedDensityOnCartesianGrid<3, float>* image_cartesian_ptr + = dynamic_cast*>(((dyn_image.get_densities())[0]).get()); + const BasicCoordinate<3, float> this_grid_spacing = image_cartesian_ptr->get_grid_spacing(); + this->_model_matrix.scale_model_matrix(this_grid_spacing[2] / dyn_image.get_scanner_default_bin_size()); +#ifndef NDEBUG + this->_model_matrix.write_to_file("patlak_matrix_in_correct_scale.txt"); +#endif // NDEBUG + } + + this->_model_matrix.multiply_parametric_image_with_initialization_model(dyn_image, par_image); } void @@ -337,13 +414,6 @@ PatlakPlot::initialise_keymap() { base_type::initialise_keymap(); this->parser.add_start_key("Patlak Plot Parameters"); - this->parser.add_key("Blood Data Filename", &this->_blood_data_filename); - this->parser.add_key("Calibration Factor", &this->_cal_factor); - this->parser.add_key("Starting Frame", &this->_starting_frame); - this->parser.add_key("Time Shift", &this->_time_shift); - this->parser.add_key("In total counts", &this->_in_total_cnt); - this->parser.add_key("In correct scale", &this->_in_correct_scale); - this->parser.add_key("Time Frame Definition Filename", &this->_time_frame_definition_filename); this->parser.add_stop_key("end Patlak Plot Parameters"); } @@ -354,31 +424,6 @@ PatlakPlot::post_processing() if (base_type::post_processing() == true) return true; - // read time frame def - if (this->_time_frame_definition_filename.size() != 0) - this->_frame_defs = TimeFrameDefinitions(this->_time_frame_definition_filename); - else - { - error("No Time Frames Definitions available!!!\n "); - return true; - } - // Reading the input function - if (this->_blood_data_filename == "0") - { - warning("You need to specify a file for the input function."); - return true; - } - else - { - this->_if_cardiac = false; - PlasmaData plasma_data_temp; - plasma_data_temp.read_plasma_data(this->_blood_data_filename); // The implementation assumes three list file. - // TODO have parameter - warning("Assuming F-18 tracer for half-life!!!"); - plasma_data_temp.set_isotope_halflife(6586.2F); - plasma_data_temp.shift_time(this->_time_shift); - this->_plasma_frame_data = plasma_data_temp.get_sample_data_in_frames(this->_frame_defs); - } return false; } @@ -417,17 +462,23 @@ get_model_matrix(const BloodFrameData& blood_frame_data, const unsigned int star { const float blood=cur_iter->get_blood_counts_in_kBq(); const float durat=(cur_iter->get_frame_end_time_in_s()-cur_iter->get_frame_start_time_in_s()); - sum_value+=blood*durat + // Calculation of the plasma integral only up to the mid time of the current plasma frame + sum_value+=0.5*blood*durat *decay_correct_factor(this->_plasma_frame_data.get_isotope_halflife(), cur_iter->get_frame_start_time_in_s(), cur_iter->get_frame_end_time_in_s()) ; // Normalize with the decay correct factor now. patlak_array[1][sample_num]=sum_value/decay_correct_factor(this->_plasma_frame_data.get_isotope_halflife(), cur_iter->get_frame_start_time_in_s(), - cur_iter->get_frame_end_time_in_s()) ; - patlak_array[2][sample_num]=blood; - time_vector[sample_num]=0.5*(cur_iter->get_frame_start_time_in_s()+cur_iter->get_frame_end_time_in_s()) ; - } + cur_iter->get_frame_end_time_in_s()); + patlak_array[2][sample_num]=blood/decay_correct_factor(this->_plasma_frame_data.get_isotope_halflife(), + cur_iter->get_frame_start_time_in_s(), + cur_iter->get_frame_end_time_in_s()); + time_vector[sample_num]=0.5*(cur_iter->get_frame_start_time_in_s()+cur_iter->get_frame_end_time_in_s()); + // Completion of integral calculation before moving to the next plasma frame + sum_value+=0.5*blood*durat; + } + assert(sample_num-1==blood_frame_data.size()); this->_model_matrix.set_model_array(patlak_array); diff --git a/src/modelling_buildblock/modelling_registries.cxx b/src/modelling_buildblock/modelling_registries.cxx index f2b022db39..576d1aff26 100644 --- a/src/modelling_buildblock/modelling_registries.cxx +++ b/src/modelling_buildblock/modelling_registries.cxx @@ -14,12 +14,14 @@ \brief File that registers all stir::RegisterObject children in modelling \author Charalampos Tsoumpas - + \author Nicolas A Karakatsanis */ #include "stir/modelling/PatlakPlot.h" +#include "stir/modelling/GeneralizedPatlakPlot.h" START_NAMESPACE_STIR static PatlakPlot::RegisterIt dummy113; +static GeneralizedPatlakPlot::RegisterIt dummysss; END_NAMESPACE_STIR diff --git a/src/modelling_utilities/apply_patlak_to_images.cxx b/src/modelling_utilities/apply_patlak_to_images.cxx index 3be199900d..145b7333f8 100644 --- a/src/modelling_utilities/apply_patlak_to_images.cxx +++ b/src/modelling_utilities/apply_patlak_to_images.cxx @@ -13,6 +13,7 @@ \ingroup utilities \brief Apply the Patlak linear fit using Dynamic Images \author Charalampos Tsoumpas + \author Nicolas A Karakatsanis \par Usage: diff --git a/src/modelling_utilities/extract_single_images_from_parametric_image.cxx b/src/modelling_utilities/extract_single_images_from_parametric_image.cxx index 62e6dcd0a8..415ee06e8c 100644 --- a/src/modelling_utilities/extract_single_images_from_parametric_image.cxx +++ b/src/modelling_utilities/extract_single_images_from_parametric_image.cxx @@ -51,10 +51,66 @@ #include "stir/format.h" #include +USING_NAMESPACE_STIR + +template +Succeeded +actual_extract_parameters(const ParametricDensityT& param_im, + const OutputFileFormat>& output, + const std::string& output_filename_prefix) +{ + + // Loop over each image + for (unsigned i = 1; i <= param_im.get_num_params(); ++i) + { + auto disc = param_im.construct_single_density(i); + { + // Get the time frame definition (from start of first frame to end of last) + ExamInfo exam_info = disc.get_exam_info(); + TimeFrameDefinitions tdefs = exam_info.get_time_frame_definitions(); + const double start = tdefs.get_start_time(1); + const double end = tdefs.get_end_time(tdefs.get_num_frames()); + tdefs.set_num_time_frames(1); + tdefs.set_time_frame(1, start, end); + exam_info.set_time_frame_definitions(tdefs); + disc.set_exam_info(exam_info); + } + + std::string current_filename; + try + { + if (output_filename_prefix.find("%") != std::string::npos) + { + warning("The output_filename pattern is using the boost::format convention ('\%d')." + "It is recommended to use fmt::format/std::format style formatting ('{}')."); + current_filename = boost::str(boost::format(output_filename_prefix) % i); + } + else + { + current_filename = runtime_format(output_filename_prefix, i); + } + } + catch (std::exception& e) + { + error(format("Error using 'output_filename' pattern (which is set to '{}'). " + "Check syntax for fmt::format. Error is:\n{}", + output_filename_prefix, + e.what())); + return Succeeded::no; + } + + // Write to file + const Succeeded success = output.write_to_file(current_filename, disc); + if (success == Succeeded::no) + throw std::runtime_error("Failed writing."); + } + + return Succeeded::yes; +} + int main(int argc, char* argv[]) { - USING_NAMESPACE_STIR if (argc != 3 && argc != 4) { @@ -63,89 +119,53 @@ main(int argc, char* argv[]) return EXIT_FAILURE; } - try - { - - // Read images - auto param_im_sptr(read_from_file(argv[2])); - - // Check - if (is_null_ptr(param_im_sptr)) - throw std::runtime_error("Failed to read dynamic image (" + std::string(argv[2]) + ")."); - - // Set up the output type - shared_ptr>> output_file_format_sptr; - if (argc == 3) - output_file_format_sptr = OutputFileFormat>::default_sptr(); - else - { - KeyParser parser; - parser.add_start_key("OutputFileFormat Parameters"); - parser.add_parsing_key("output file format type", &output_file_format_sptr); - parser.add_stop_key("END"); - std::ifstream in(argv[3]); - if (!parser.parse(in) || is_null_ptr(output_file_format_sptr)) - throw std::runtime_error("Failed to parse output format file (" + std::string(argv[3]) + ")."); - } + std::string output_prefix_str(argv[1]); + std::string error_2param, error_3param; - // Loop over each image - for (unsigned i = 1; i <= param_im_sptr->get_num_params(); ++i) - { + shared_ptr>> output_file_format_sptr; - auto disc = param_im_sptr->construct_single_density(i); - { - // Get the time frame definition (from start of first frame to end of last) - ExamInfo exam_info = disc.get_exam_info(); - TimeFrameDefinitions tdefs = exam_info.get_time_frame_definitions(); - const double start = tdefs.get_start_time(1); - const double end = tdefs.get_end_time(tdefs.get_num_frames()); - tdefs.set_num_time_frames(1); - tdefs.set_time_frame(1, start, end); - exam_info.set_time_frame_definitions(tdefs); - disc.set_exam_info(exam_info); - } - - std::string current_filename; - try - { - if (std::string(argv[1]).find("%") != std::string::npos) - { - warning("The output_filename pattern is using the boost::format convention ('\%d')." - "It is recommended to use fmt::format/std::format style formatting ('{}')."); - current_filename = boost::str(boost::format(argv[1]) % i); - } - else - { - current_filename = runtime_format(argv[1], i); - } - } - catch (std::exception& e) - { - error(format("Error using 'output_filename' pattern (which is set to '{}'). " - "Check syntax for fmt::format. Error is:\n{}", - argv[1], - e.what())); - return EXIT_FAILURE; - } - - // Write to file - const Succeeded success = output_file_format_sptr->write_to_file(current_filename, disc); - if (success == Succeeded::no) - throw std::runtime_error("Failed writing."); - } + if (argc == 3) + output_file_format_sptr = OutputFileFormat>::default_sptr(); + else + { + KeyParser parser; + parser.add_start_key("OutputFileFormat Parameters"); + parser.add_parsing_key("output file format type", &output_file_format_sptr); + parser.add_stop_key("END"); + std::ifstream in(argv[3]); + if (!parser.parse(in) || is_null_ptr(output_file_format_sptr)) + error(format("Failed to parse output format file ({})", std::string(argv[3]))); + } - // If all is good, exit + try + { + const auto param_im_sptr(read_from_file(argv[2])); + if (actual_extract_parameters(*param_im_sptr, *output_file_format_sptr, output_prefix_str) + == Succeeded::no) + return EXIT_FAILURE; return EXIT_SUCCESS; - - // If there was an error } - catch (const std::exception& error) + catch (const std::runtime_error& e) { - std::cerr << "\nHere's the error:\n\t" << error.what() << "\n\n"; - return EXIT_FAILURE; + error_2param = e.what(); } - catch (...) + + try { - return EXIT_FAILURE; + const auto param_im_sptr(read_from_file(argv[2])); + if (actual_extract_parameters(*param_im_sptr, *output_file_format_sptr, output_prefix_str) + == Succeeded::no) + return EXIT_FAILURE; + return EXIT_SUCCESS; } + catch (const std::runtime_error& e) + { + error_3param = e.what(); + } + + // Somehow end up here + error(format("Failed to read parametric image ({}).\n 2-param attempt: {}\n 3-param attempt: {}", + argv[2], + error_2param, + error_3param)); } diff --git a/src/modelling_utilities/make_parametric_image_from_components.cxx b/src/modelling_utilities/make_parametric_image_from_components.cxx index 1acc6f18b7..1da139be4d 100644 --- a/src/modelling_utilities/make_parametric_image_from_components.cxx +++ b/src/modelling_utilities/make_parametric_image_from_components.cxx @@ -80,7 +80,7 @@ main(int argc, char* argv[]) if (params.size() == 2) { // Construct the parametric image - ParametricVoxelsOnCartesianGridBaseType base_type( + Parametric2VoxelsOnCartesianGridBaseType base_type( params[0].get_index_range(), params[0].get_origin(), params[0].get_grid_spacing()); ParametricVoxelsOnCartesianGrid param_im(base_type); diff --git a/src/recon_buildblock/CMakeLists.txt b/src/recon_buildblock/CMakeLists.txt index a3169db0cf..0a06639110 100644 --- a/src/recon_buildblock/CMakeLists.txt +++ b/src/recon_buildblock/CMakeLists.txt @@ -92,6 +92,10 @@ if (NOT MINI_STIR) PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx PoissonLogLikelihoodWithLinearKineticModelAndDynamicProjectionData.cxx PoissonLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion.cxx + PoissonNestedLogLikelihoodWithLinearKineticModelAndDynamicProjectionData.cxx + PoissonNestedLogLikelihoodWithGeneralizedPatlakAndDynamicProjectionData.cxx + # PoissonNestedLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion.cxx + # PoissonNestedLogLikelihoodWithLinearModelForMeanAndConvolvedProjDataWithMotion.cxx SqrtHessianRowSum.cxx ) diff --git a/src/recon_buildblock/GeneralisedObjectiveFunction.cxx b/src/recon_buildblock/GeneralisedObjectiveFunction.cxx index fb76684818..d0d568f53c 100644 --- a/src/recon_buildblock/GeneralisedObjectiveFunction.cxx +++ b/src/recon_buildblock/GeneralisedObjectiveFunction.cxx @@ -19,6 +19,7 @@ \author Kris Thielemans \author Robert Twyman Skelly \author Sanida Mustafovic + \author Nicolas A Karakatsanis */ @@ -31,6 +32,7 @@ #include "stir/info.h" #include "stir/warning.h" #include "stir/error.h" +#include "stir/format.h" using std::string; START_NAMESPACE_STIR @@ -42,6 +44,10 @@ GeneralisedObjectiveFunction::set_defaults() this->prior_sptr.reset(); // note: cannot use set_num_subsets(1) here, as other parameters (such as projectors) are not set-up yet. this->num_subsets = 1; + // Number of nested iterations + this->num_nested_subiterations = 1; + // Number of nested initialization iterations + this->num_nested_initialization_subiterations = 0; } template @@ -106,6 +112,23 @@ GeneralisedObjectiveFunction::prior_is_zero() const return is_null_ptr(this->prior_sptr) || this->prior_sptr->get_penalisation_factor() == 0; } +template +bool +GeneralisedObjectiveFunction::is_nested() const +{ + return !is_null_ptr(this->last_nested_estimate_sptr); +} + +template +void +GeneralisedObjectiveFunction::set_nested_output_filename_prefix(std::string& filename_prefix, + int global_subiteration_num) +{ + std::string num = format("_{}", global_subiteration_num); + + this->nested_output_filename_prefix = filename_prefix + num; +} + template double GeneralisedObjectiveFunction::compute_penalty(const TargetT& current_estimate) @@ -513,5 +536,6 @@ GeneralisedObjectiveFunction::subsets_are_approximately_balanced(std::s template class GeneralisedObjectiveFunction>; template class GeneralisedObjectiveFunction; +template class GeneralisedObjectiveFunction; END_NAMESPACE_STIR diff --git a/src/recon_buildblock/IterativeReconstruction.cxx b/src/recon_buildblock/IterativeReconstruction.cxx index 471cda465f..1bba6a8278 100644 --- a/src/recon_buildblock/IterativeReconstruction.cxx +++ b/src/recon_buildblock/IterativeReconstruction.cxx @@ -645,6 +645,7 @@ IterativeReconstruction::get_subset_num() #endif template class IterativeReconstruction>; -template class IterativeReconstruction; +template class IterativeReconstruction; +template class IterativeReconstruction; END_NAMESPACE_STIR diff --git a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMean.cxx b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMean.cxx index 967c15a812..4bfe46666a 100644 --- a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMean.cxx +++ b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMean.cxx @@ -477,5 +477,6 @@ PoissonLogLikelihoodWithLinearModelForMean::fill_nonidentifiable_target template class PoissonLogLikelihoodWithLinearModelForMean>; template class PoissonLogLikelihoodWithLinearModelForMean; +template class PoissonLogLikelihoodWithLinearModelForMean; END_NAMESPACE_STIR diff --git a/src/recon_buildblock/PoissonNestedLogLikelihoodWithGeneralizedPatlakAndDynamicProjectionData.cxx b/src/recon_buildblock/PoissonNestedLogLikelihoodWithGeneralizedPatlakAndDynamicProjectionData.cxx new file mode 100644 index 0000000000..f969e03f3a --- /dev/null +++ b/src/recon_buildblock/PoissonNestedLogLikelihoodWithGeneralizedPatlakAndDynamicProjectionData.cxx @@ -0,0 +1,30 @@ +// +/* + Copyright (C) 2006- $Date: 2013-07-12 10:34:00 $, Hammersmith Imanet Ltd + This file is part of STIR. + + SPDX-License-Identifier: Apache-2.0 + + See STIR/LICENSE.txt for details +*/ +/*! + \file + \ingroup GeneralisedObjectiveFunction + \brief Instantiations for class stir::PoissonNestedLogLikelihoodWithGeneralizedPatlakAndDynamicProjectionData + + \author Nicolas A Karakatsanis + +*/ + +#include "stir/recon_buildblock/PoissonNestedLogLikelihoodWithGeneralizedPatlakAndDynamicProjectionData.txx" + +START_NAMESPACE_STIR + +#ifdef _MSC_VER +// prevent warning message on instantiation of abstract class +# pragma warning(disable : 4661) +#endif // _MSC_VER + +template class PoissonNestedLogLikelihoodWithGeneralizedPatlakAndDynamicProjectionData; + +END_NAMESPACE_STIR diff --git a/src/recon_buildblock/PoissonNestedLogLikelihoodWithLinearKineticModelAndDynamicProjectionData.cxx b/src/recon_buildblock/PoissonNestedLogLikelihoodWithLinearKineticModelAndDynamicProjectionData.cxx new file mode 100644 index 0000000000..2b931cd38e --- /dev/null +++ b/src/recon_buildblock/PoissonNestedLogLikelihoodWithLinearKineticModelAndDynamicProjectionData.cxx @@ -0,0 +1,30 @@ +// +/* + Copyright (C) 2006- $Date: 2013-07-12 10:34:00 $, Hammersmith Imanet Ltd + This file is part of STIR. + + SPDX-License-Identifier: Apache-2.0 + + See STIR/LICENSE.txt for details +*/ +/*! + \file + \ingroup GeneralisedObjectiveFunction + \brief Instantiations for class stir::PoissonNestedLogLikelihoodWithLinearKineticModelAndDynamicProjectionData + + \author Nicolas A Karakatsanis + +*/ + +#include "stir/recon_buildblock/PoissonNestedLogLikelihoodWithLinearKineticModelAndDynamicProjectionData.txx" + +START_NAMESPACE_STIR + +#ifdef _MSC_VER +// prevent warning message on instantiation of abstract class +# pragma warning(disable : 4661) +#endif // _MSC_VER + +template class PoissonNestedLogLikelihoodWithLinearKineticModelAndDynamicProjectionData; + +END_NAMESPACE_STIR diff --git a/src/recon_buildblock/PoissonNestedLogLikelihoodWithLinearModelForMeanAndConvolvedProjDataWithMotion.cxx b/src/recon_buildblock/PoissonNestedLogLikelihoodWithLinearModelForMeanAndConvolvedProjDataWithMotion.cxx new file mode 100644 index 0000000000..c12690baa4 --- /dev/null +++ b/src/recon_buildblock/PoissonNestedLogLikelihoodWithLinearModelForMeanAndConvolvedProjDataWithMotion.cxx @@ -0,0 +1,1407 @@ +/* + Copyright (C) 2000 PARAPET partners + Copyright (C) 2000-2011, Hammersmith Imanet Ltd + This file is part of STIR. + + This file is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + This file is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + See STIR/LICENSE.txt for details +*/ +/*! + \file + \ingroup GeneralisedObjectiveFunction + \brief Declaration of class stir::PoissonNestedLogLikelihoodWithLinearModelForMeanAndConvolvedProjDataWithMotion + + \author Nicolas A Karakatsanis +*/ + +#include "stir/recon_buildblock/PoissonNestedLogLikelihoodWithLinearModelForMeanAndConvolvedProjDataWithMotion.h" +#include "stir/VoxelsOnCartesianGrid.h" +#include "stir/recon_buildblock/TrivialBinNormalisation.h" +#include "stir/Succeeded.h" +#include "stir/RelatedViewgrams.h" +#include "stir/stream.h" +#include "stir/IO/OutputFileFormat.h" + +#include "stir/recon_buildblock/ProjectorByBinPair.h" + +#include "stir/DiscretisedDensity.h" +#ifdef STIR_MPI +# include "stir/recon_buildblock/DistributedCachingInformation.h" +#endif +#include "stir/recon_buildblock/distributable.h" +// for get_symmetries_ptr() +#include "stir/DataSymmetriesForViewSegmentNumbers.h" +// include the following to set defaults +#ifndef USE_PMRT +# include "stir/recon_buildblock/ForwardProjectorByBinUsingRayTracing.h" +# include "stir/recon_buildblock/BackProjectorByBinUsingInterpolation.h" +#else +# include "stir/recon_buildblock/ForwardProjectorByBinUsingProjMatrixByBin.h" +# include "stir/recon_buildblock/BackProjectorByBinUsingProjMatrixByBin.h" +# include "stir/recon_buildblock/ProjMatrixByBinUsingRayTracing.h" +#endif +#include "stir/recon_buildblock/ProjectorByBinPairUsingSeparateProjectors.h" + +#include "stir/Viewgram.h" +#include "stir/recon_array_functions.h" +#include "stir/is_null_ptr.h" +#include "stir/numerics/divide.h" +#include "stir/thresholding.h" +#include "stir/NumericInfo.h" +#include +#include +#include +#ifdef STIR_MPI +# include "stir/recon_buildblock/distributed_functions.h" +#endif +#include "stir/CPUTimer.h" +#include "stir/info.h" +#include + +// For Motion +#include "stir/spatial_transformation/GatedSpatialTransformation.h" + +#ifndef STIR_NO_NAMESPACES +using std::ends; +using std::cerr; +using std::endl; +using std::max; +#endif + +START_NAMESPACE_STIR + +const int rim_truncation_sino = 0; // TODO get rid of this +const float small_num = 0.000001F; + +template +const char* const PoissonNestedLogLikelihoodWithLinearModelForMeanAndConvolvedProjDataWithMotion::registered_name + = "PoissonNestedLogLikelihoodWithLinearModelForMeanAndConvolvedProjDataWithMotion"; + +template +void +PoissonNestedLogLikelihoodWithLinearModelForMeanAndConvolvedProjDataWithMotion::set_defaults() +{ + base_type::set_defaults(); + + this->input_filename = ""; + this->max_segment_num_to_process = -1; + // KT 20/06/2001 disabled + // num_views_to_add=1; + this->proj_data_sptr.reset(); + this->zero_seg0_end_planes = 0; + + this->_reverse_motion_vectors_filename_prefix = "0"; + // this->_reverse_motion_vectors_sptr=NULL; + this->_motion_vectors_filename_prefix = "0"; + // this->_motion_vectors_sptr=NULL; + this->_gate_definitions_filename = "0"; + // this->_time_gate_definitions_sptr=NULL; + + this->additive_projection_data_filename = "0"; + this->additive_proj_data_sptr.reset(); + + // set default for projector_pair_ptr +#ifndef USE_PMRT + shared_ptr forward_projector_ptr(new ForwardProjectorByBinUsingRayTracing()); + shared_ptr back_projector_ptr(new BackProjectorByBinUsingInterpolation()); +#else + shared_ptr PM(new ProjMatrixByBinUsingRayTracing()); + // PM->set_num_tangential_LORs(5); + shared_ptr forward_projector_ptr(new ForwardProjectorByBinUsingProjMatrixByBin(PM)); + shared_ptr back_projector_ptr(new BackProjectorByBinUsingProjMatrixByBin(PM)); +#endif + + this->projector_pair_ptr.reset(new ProjectorByBinPairUsingSeparateProjectors(forward_projector_ptr, back_projector_ptr)); + + this->normalisation_sptr.reset(new TrivialBinNormalisation); + this->frame_num = 1; + this->frame_definition_filename = ""; + // make a single frame starting from 0 to 1. + vector> frame_times(1, pair(0, 1)); + this->frame_defs = TimeFrameDefinitions(frame_times); + + // image stuff + this->output_image_size_xy = -1; + this->output_image_size_z = -1; + this->zoom = 1.F; + this->Xoffset = 0.F; + this->Yoffset = 0.F; + // KT 20/06/2001 new + this->Zoffset = 0.F; + + // Number of nested iterations + this->num_nested_subiterations = 1; + // We choose that default value instead of 1, to avoid saving nested estimates + // when only 1 nested iteration is selected (default number of nested iterations) + this->save_nested_subiterations_interval = 2; + + this->maximum_nested_relative_change = NumericInfo().max_value(); + this->minimum_nested_relative_change = 0; + +#ifdef STIR_MPI + // distributed stuff + this->distributed_cache_enabled = false; + this->distributed_tests_enabled = false; + this->message_timings_enabled = false; + this->message_timings_threshold = 0.1; + this->rpc_timings_enabled = false; +#endif +} + +template +void +PoissonNestedLogLikelihoodWithLinearModelForMeanAndConvolvedProjDataWithMotion::initialise_keymap() +{ + base_type::initialise_keymap(); + this->parser.add_start_key("PoissonNestedLogLikelihoodWithLinearModelForMeanAndConvolvedProjDataWithMotion Parameters"); + this->parser.add_stop_key("End PoissonNestedLogLikelihoodWithLinearModelForMeanAndConvolvedProjDataWithMotion Parameters"); + this->parser.add_key("input file", &this->input_filename); + // KT 20/06/2001 disabled + // parser.add_key("mash x views", &num_views_to_add); + + this->parser.add_key("maximum absolute segment number to process", &this->max_segment_num_to_process); + this->parser.add_key("zero end planes of segment 0", &this->zero_seg0_end_planes); + + // image stuff + + this->parser.add_key("zoom", &this->zoom); + this->parser.add_key("XY output image size (in pixels)", &this->output_image_size_xy); + this->parser.add_key("Z output image size (in pixels)", &this->output_image_size_z); + // parser.add_key("X offset (in mm)", &this->Xoffset); // KT 10122001 added spaces + // parser.add_key("Y offset (in mm)", &this->Yoffset); + + this->parser.add_key("Z offset (in mm)", &this->Zoffset); + + this->parser.add_parsing_key("Projector pair type", &this->projector_pair_ptr); + this->parser.add_key("additive sinogram", &this->additive_projection_data_filename); + // normalisation (and attenuation correction) + this->parser.add_key("time frame definition filename", &this->frame_definition_filename); + this->parser.add_key("time frame number", &this->frame_num); + this->parser.add_parsing_key("Bin Normalisation type", &this->normalisation_sptr); + + // Motion Information + this->parser.add_key("Gate Definitions filename", &this->_gate_definitions_filename); + this->parser.add_key("Motion Vectors filename prefix", &this->_motion_vectors_filename_prefix); + this->parser.add_key("Reverse Motion Vectors filename prefix", &this->_reverse_motion_vectors_filename_prefix); + + // Nested subiterations + this->parser.add_key("number of nested subiterations", &this->num_nested_subiterations); + this->parser.add_key("save estimates at nested subiterations interval", &this->save_nested_subiterations_interval); + + // max and min allowed relative change between nested updates + this->parser.add_key("maximum nested relative change", &this->maximum_nested_relative_change); + this->parser.add_key("minimum nested relative change", &this->minimum_nested_relative_change); + +#ifdef STIR_MPI + // distributed stuff + this->parser.add_key("enable distributed caching", &distributed_cache_enabled); + this->parser.add_key("enable distributed tests", &distributed_tests_enabled); + this->parser.add_key("enable message timings", &message_timings_enabled); + this->parser.add_key("message timings threshold", &message_timings_threshold); + this->parser.add_key("enable rpc timings", &rpc_timings_enabled); +#endif +} + +template +bool +PoissonNestedLogLikelihoodWithLinearModelForMeanAndConvolvedProjDataWithMotion::post_processing() +{ + if (base_type::post_processing() == true) + return true; + + if (this->input_filename.length() == 0) + { + warning("You need to specify an input file"); + return true; + } + // KT 20/06/2001 disabled as not functional yet +#if 0 + if (num_views_to_add!=1 && (num_views_to_add<=0 || num_views_to_add%2 != 0)) + { warning("The 'mash x views' key has an invalid value (must be 1 or even number)"); return true; } +#endif + + this->proj_data_sptr = ProjData::read_from_file(input_filename); + if (is_null_ptr(this->proj_data_sptr)) + { + warning("Failed to read input file %s", input_filename.c_str()); + return true; + } + + // image stuff + if (this->zoom <= 0) + { + warning("zoom should be positive"); + return true; + } + + if (this->output_image_size_xy != -1 && this->output_image_size_xy < 1) // KT 10122001 appended_xy + { + warning("output image size xy must be positive (or -1 as default)"); + return true; + } + if (this->output_image_size_z != -1 && this->output_image_size_z < 1) // KT 10122001 new + { + warning("output image size z must be positive (or -1 as default)"); + return true; + } + + if (this->additive_projection_data_filename != "0") + { + cerr << "\nReading additive projdata data " << this->additive_projection_data_filename << endl; + this->additive_proj_data_sptr = ProjData::read_from_file(this->additive_projection_data_filename); + }; + + // read time frame def + if (this->frame_definition_filename.size() != 0) + this->frame_defs = TimeFrameDefinitions(this->frame_definition_filename); + else + { + // make a single frame starting from 0 to 1. + vector> frame_times(1, pair(0, 1)); + this->frame_defs = TimeFrameDefinitions(frame_times); + } + + this->_time_gate_definitions.read_gdef_file(this->_gate_definitions_filename); + + if (this->_reverse_motion_vectors_filename_prefix != "0") + this->_reverse_motion_vectors.read_from_files(this->_reverse_motion_vectors_filename_prefix); + if (this->_motion_vectors_filename_prefix != "0") + this->_motion_vectors.read_from_files(this->_motion_vectors_filename_prefix); + +#ifndef STIR_MPI +# if 0 + //check caching enabled value + if (this->distributed_cache_enabled==true) + { + warning("STIR must be compiled with MPI-compiler to use distributed caching.\n\tDistributed Caching support will be disabled!"); + this->distributed_cache_enabled=false; + } + //check tests enabled value + if (this->distributed_tests_enabled==true || rpc_timings_enabled==true || message_timings_enabled==true) + { + warning("STIR must be compiled with MPI-compiler and debug symbols to use distributed testing.\n\tDistributed tests will not be performed!"); + this->distributed_tests_enabled=false; + } +# endif +#else + // check caching enabled value + if (this->distributed_cache_enabled == true) + cerr << "\nWill use distributed caching!" << endl; + else + cerr << "\nDistributed caching is disabled. Will use standard distributed version without forced caching!" << endl; + +# ifndef NDEBUG + // check tests enabled value + if (this->distributed_tests_enabled == true) + { + warning("\nWill perform distributed tests! Beware that this decreases the performance"); + distributed::test = true; + } +# else + // check tests enabled value + if (this->distributed_tests_enabled == true) + { + warning("\nDistributed tests only abvailable in debug mode!"); + distributed::test = false; + } +# endif + + // check timing values + if (this->message_timings_enabled == true) + { + cerr << "\nWill print timings of MPI-Messages! This is used to find bottlenecks!" << endl; + distributed::test_send_receive_times = true; + } + // set timing threshold + distributed::min_threshold = this->message_timings_threshold; + + if (this->rpc_timings_enabled == true) + { + cerr << "\nWill print run-times of processing RPC_process_related_viewgrams_gradient for every slave! This will give an " + "idea of the parallelization effect!" + << endl; + distributed::rpc_time = true; + } + +#endif + + // this->already_setup = false; + return false; +} + +template +PoissonNestedLogLikelihoodWithLinearModelForMeanAndConvolvedProjDataWithMotion< + TargetT>::PoissonNestedLogLikelihoodWithLinearModelForMeanAndConvolvedProjDataWithMotion() +{ + this->set_defaults(); +} + +template +PoissonNestedLogLikelihoodWithLinearModelForMeanAndConvolvedProjDataWithMotion< + TargetT>::~PoissonNestedLogLikelihoodWithLinearModelForMeanAndConvolvedProjDataWithMotion() +{ + end_distributable_computation(); +} + +template +TargetT* +PoissonNestedLogLikelihoodWithLinearModelForMeanAndConvolvedProjDataWithMotion::construct_target_ptr() const +{ + return new VoxelsOnCartesianGrid( + *this->proj_data_sptr->get_proj_data_info_ptr(), + static_cast(this->zoom), + CartesianCoordinate3D( + static_cast(this->Zoffset), static_cast(this->Yoffset), static_cast(this->Xoffset)), + CartesianCoordinate3D(this->output_image_size_z, this->output_image_size_xy, this->output_image_size_xy)); +} + +/*************************************************************** + get_ functions +***************************************************************/ +template +const ProjData& +PoissonNestedLogLikelihoodWithLinearModelForMeanAndConvolvedProjDataWithMotion::get_proj_data() const +{ + return *this->proj_data_sptr; +} + +template +const shared_ptr& +PoissonNestedLogLikelihoodWithLinearModelForMeanAndConvolvedProjDataWithMotion::get_proj_data_sptr() const +{ + return this->proj_data_sptr; +} + +template +const int +PoissonNestedLogLikelihoodWithLinearModelForMeanAndConvolvedProjDataWithMotion::get_max_segment_num_to_process() const +{ + return this->max_segment_num_to_process; +} + +template +const bool +PoissonNestedLogLikelihoodWithLinearModelForMeanAndConvolvedProjDataWithMotion::get_zero_seg0_end_planes() const +{ + return this->zero_seg0_end_planes; +} + +template +const ProjData& +PoissonNestedLogLikelihoodWithLinearModelForMeanAndConvolvedProjDataWithMotion::get_additive_proj_data() const +{ + return *this->additive_proj_data_sptr; +} + +template +const shared_ptr& +PoissonNestedLogLikelihoodWithLinearModelForMeanAndConvolvedProjDataWithMotion::get_additive_proj_data_sptr() const +{ + return this->additive_proj_data_sptr; +} + +template +const ProjectorByBinPair& +PoissonNestedLogLikelihoodWithLinearModelForMeanAndConvolvedProjDataWithMotion::get_projector_pair() const +{ + return *this->projector_pair_ptr; +} + +template +const shared_ptr& +PoissonNestedLogLikelihoodWithLinearModelForMeanAndConvolvedProjDataWithMotion::get_projector_pair_sptr() const +{ + return this->projector_pair_ptr; +} + +template +const int +PoissonNestedLogLikelihoodWithLinearModelForMeanAndConvolvedProjDataWithMotion::get_time_frame_num() const +{ + return this->frame_num; +} + +template +const TimeFrameDefinitions& +PoissonNestedLogLikelihoodWithLinearModelForMeanAndConvolvedProjDataWithMotion::get_time_frame_definitions() const +{ + return this->frame_defs; +} + +template +const BinNormalisation& +PoissonNestedLogLikelihoodWithLinearModelForMeanAndConvolvedProjDataWithMotion::get_normalisation() const +{ + return *this->normalisation_sptr; +} + +template +const shared_ptr& +PoissonNestedLogLikelihoodWithLinearModelForMeanAndConvolvedProjDataWithMotion::get_normalisation_sptr() const +{ + return this->normalisation_sptr; +} + +/*************************************************************** + set_ functions +***************************************************************/ + +template +int +PoissonNestedLogLikelihoodWithLinearModelForMeanAndConvolvedProjDataWithMotion::set_num_subsets( + const int new_num_subsets) +{ + this->num_subsets = std::max(new_num_subsets, 1); + return this->num_subsets; +} + +template +void +PoissonNestedLogLikelihoodWithLinearModelForMeanAndConvolvedProjDataWithMotion::set_proj_data_sptr( + const shared_ptr& arg) +{ + this->proj_data_sptr = arg; +} + +template +void +PoissonNestedLogLikelihoodWithLinearModelForMeanAndConvolvedProjDataWithMotion::set_max_segment_num_to_process( + const int arg) +{ + this->max_segment_num_to_process = arg; +} + +template +void +PoissonNestedLogLikelihoodWithLinearModelForMeanAndConvolvedProjDataWithMotion::set_zero_seg0_end_planes(const bool arg) +{ + this->zero_seg0_end_planes = arg; +} + +template +void +PoissonNestedLogLikelihoodWithLinearModelForMeanAndConvolvedProjDataWithMotion::set_additive_proj_data_sptr( + const shared_ptr& arg) +{ + + this->additive_proj_data_sptr = arg; +} + +template +void +PoissonNestedLogLikelihoodWithLinearModelForMeanAndConvolvedProjDataWithMotion::set_projector_pair_sptr( + const shared_ptr& arg) +{ + this->projector_pair_ptr = arg; +} + +template +void +PoissonNestedLogLikelihoodWithLinearModelForMeanAndConvolvedProjDataWithMotion::set_frame_num(const int arg) +{ + this->frame_num = arg; +} + +template +void +PoissonNestedLogLikelihoodWithLinearModelForMeanAndConvolvedProjDataWithMotion::set_frame_definitions( + const TimeFrameDefinitions& arg) +{ + this->frame_defs = arg; +} + +template +void +PoissonNestedLogLikelihoodWithLinearModelForMeanAndConvolvedProjDataWithMotion::set_normalisation_sptr( + const shared_ptr& arg) +{ + this->normalisation_sptr = arg; +} + +/*************************************************************** + subset balancing + ***************************************************************/ + +template +bool +PoissonNestedLogLikelihoodWithLinearModelForMeanAndConvolvedProjDataWithMotion< + TargetT>::actual_subsets_are_approximately_balanced(std::string& warning_message) const +{ + assert(this->num_subsets > 0); + const DataSymmetriesForViewSegmentNumbers& symmetries + = *this->projector_pair_ptr->get_back_projector_sptr()->get_symmetries_used(); + + Array<1, int> num_vs_in_subset(this->num_subsets); + num_vs_in_subset.fill(0); + for (int subset_num = 0; subset_num < this->num_subsets; ++subset_num) + { + for (int segment_num = -this->max_segment_num_to_process; segment_num <= this->max_segment_num_to_process; ++segment_num) + for (int view_num = this->proj_data_sptr->get_min_view_num() + subset_num; + view_num <= this->proj_data_sptr->get_max_view_num(); + view_num += this->num_subsets) + { + const ViewSegmentNumbers view_segment_num(view_num, segment_num); + if (!symmetries.is_basic(view_segment_num)) + continue; + num_vs_in_subset[subset_num] += symmetries.num_related_view_segment_numbers(view_segment_num); + } + } + for (int subset_num = 1; subset_num < this->num_subsets; ++subset_num) + { + if (num_vs_in_subset[subset_num] != num_vs_in_subset[0]) + { + std::stringstream str(warning_message); + str << "Number of subsets is such that subsets will be very unbalanced.\n" + << "Number of viewgrams in each subset would be:\n" + << num_vs_in_subset + << "\nEither reduce the number of symmetries used by the projector, or\n" + "change the number of subsets. It usually should be a divisor of\n" + << this->proj_data_sptr->get_num_views() << "/4 (or if that's not an integer, a divisor of " + << this->proj_data_sptr->get_num_views() << "/2 or " << this->proj_data_sptr->get_num_views() << ").\n"; + warning_message = str.str(); + return false; + } + } + return true; +} + +/*************************************************************** + set_up() +***************************************************************/ +template +Succeeded +PoissonNestedLogLikelihoodWithLinearModelForMeanAndConvolvedProjDataWithMotion::set_up_before_sensitivity( + shared_ptr const& target_sptr) +{ + if (this->max_segment_num_to_process == -1) + this->max_segment_num_to_process = this->proj_data_sptr->get_max_segment_num(); + + if (this->max_segment_num_to_process > this->proj_data_sptr->get_max_segment_num()) + { + warning("max_segment_num_to_process (%d) is too large", this->max_segment_num_to_process); + return Succeeded::no; + } + + shared_ptr proj_data_info_sptr(this->proj_data_sptr->get_proj_data_info_ptr()->clone()); + + proj_data_info_sptr->reduce_segment_range(-this->max_segment_num_to_process, +this->max_segment_num_to_process); + + if (is_null_ptr(this->projector_pair_ptr)) + { + warning("You need to specify a projector pair"); + return Succeeded::no; + } + + // Computes model sensitivity image by utilizing motion model matrix + // Not needed as the sensitivity image should be ALL ONES and when using it for division has no effect + this->compute_model_sensitivity_image(*target_sptr); + + // set projectors to be used for the calculations + + setup_distributable_computation(this->projector_pair_ptr, + this->proj_data_sptr->get_exam_info_sptr(), + this->proj_data_sptr->get_proj_data_info_ptr(), + target_sptr, + zero_seg0_end_planes, + distributed_cache_enabled); + +#ifdef STIR_MPI + // set up distributed caching object + if (distributed_cache_enabled) + { + this->caching_info_ptr = new DistributedCachingInformation(distributed::num_processors); + } + else + caching_info_ptr = NULL; +#else + // non parallel version + caching_info_ptr = NULL; +#endif + + this->projector_pair_ptr->set_up(proj_data_info_sptr, target_sptr); + + // TODO check compatibility between symmetries for forward and backprojector + this->symmetries_sptr.reset(this->projector_pair_ptr->get_back_projector_sptr()->get_symmetries_used()->clone()); + + if (is_null_ptr(this->normalisation_sptr)) + { + warning("Invalid normalisation object"); + return Succeeded::no; + } + + if (this->normalisation_sptr->set_up(proj_data_info_sptr) == Succeeded::no) + return Succeeded::no; + + if (frame_num <= 0) + { + warning("frame_num should be >= 1"); + return Succeeded::no; + } + + if (static_cast(frame_num) > frame_defs.get_num_frames()) + { + warning("frame_num is %d, but should be less than the number of frames %d.", frame_num, frame_defs.get_num_frames()); + return Succeeded::no; + } + + return Succeeded::yes; +} + +/*************************************************************** + functions that compute the value/gradient of the objective function etc +***************************************************************/ + +template +void +PoissonNestedLogLikelihoodWithLinearModelForMeanAndConvolvedProjDataWithMotion< + TargetT>::compute_sub_gradient_without_penalty_plus_sensitivity(TargetT& gradient, + const TargetT& current_estimate, + const int subset_num) +{ + + // Clone the const TargetT& current estimate to a TargetT nested_estimate + shared_ptr current_nested_estimate(current_estimate.get_empty_copy()); + + shared_ptr convolved_gradient(current_estimate.get_empty_copy()); + shared_ptr convolved_image_estimate(current_estimate.get_empty_copy()); + shared_ptr convolved_image_reference_data(current_estimate.get_empty_copy()); + shared_ptr convolved_image_nested_loop_estimate(current_estimate.get_empty_copy()); + shared_ptr convolved_sensitivity(current_estimate.get_empty_copy()); + + { + typename TargetT::const_full_iterator current_estimate_iter = current_estimate.begin_all_const(); + const typename TargetT::const_full_iterator end_current_estimate_iter = current_estimate.end_all_const(); + typename TargetT::full_iterator current_nested_estimate_iter = current_nested_estimate->begin_all(); + while (current_estimate_iter != end_current_estimate_iter) + { + *current_nested_estimate_iter = (*current_estimate_iter); + ++current_nested_estimate_iter; + ++current_estimate_iter; + } + } + + this->compute_nested_sub_gradient_without_penalty_plus_sensitivity(gradient, + *current_nested_estimate, + *convolved_gradient, + *convolved_image_estimate, + *convolved_image_reference_data, + *convolved_image_nested_loop_estimate, + *convolved_sensitivity, + subset_num); + + this->last_nested_estimate_sptr = current_nested_estimate; +} + +template +void +PoissonNestedLogLikelihoodWithLinearModelForMeanAndConvolvedProjDataWithMotion< + TargetT>::compute_nested_sub_gradient_without_penalty_plus_sensitivity(TargetT& gradient, + TargetT& current_estimate, + TargetT& conv_gradient, + TargetT& conv_image_estimate, + TargetT& conv_image_reference_data, + TargetT& conv_image_nested_loop_estimate, + TargetT& conv_sensitivity, + const int subset_num) +{ + assert(subset_num >= 0); + assert(subset_num < this->num_subsets); + + // The following initialization doesn't stabilize reconstruction. + std::fill(conv_image_estimate.begin_all(), conv_image_estimate.end_all(), 0.F); + + // Print out the min and max values of the initial motion-corrected estimate + const float current_min_estimate = *std::min_element(current_estimate.begin_all(), current_estimate.end_all()); + const float current_max_estimate = *std::max_element(current_estimate.begin_all(), current_estimate.end_all()); + cerr << "Initial motion-corrected image estimate " + << ", (min, max): (" << current_min_estimate << ", " << current_max_estimate << ")" << endl; + + // Translate (contaminate-with-motion or equivalent to forward project operation for motion model) + // from motion-less space to motion-gated space using a motion-corrected image estimate as an input + this->_motion_vectors.average_warp_image(conv_image_estimate, current_estimate); + + // Print out the min and max values of the initial motion-corrected estimate + const float current_conv_min_estimate = *std::min_element(conv_image_estimate.begin_all(), conv_image_estimate.end_all()); + const float current_conv_max_estimate = *std::max_element(conv_image_estimate.begin_all(), conv_image_estimate.end_all()); + cerr << "Initial motion-contaminated (after forward motion convolution) image estimate " + << ", (min, max): (" << current_conv_min_estimate << ", " << current_conv_max_estimate << ")" << endl; + + // Storage of the current estimate of convolved image so that to be used as + // a reference for all nested sub-iterations of the current global sub-iteration + conv_image_reference_data = conv_image_estimate; + + CPUTimer outer_loop_timer; + outer_loop_timer.start(); + + // Get system sensitivity for convolved space + cerr << "Getting system sub-sensitivity image for for convolved (motion-contaminated) space: " << endl; + conv_sensitivity = this->get_subset_sensitivity(subset_num); + + // Print out the min and max values of the system convolved sensitivity image + const float current_min_system_conv_sensitivity = *std::min_element(conv_sensitivity.begin_all(), conv_sensitivity.end_all()); + const float current_max_system_conv_sensitivity = *std::max_element(conv_sensitivity.begin_all(), conv_sensitivity.end_all()); + cerr << "System sensitivity image for convolved (motion-contaminated) space: " + << ", (min, max): (" << current_min_system_conv_sensitivity << ", " << current_max_system_conv_sensitivity << ")" << endl; + + // Compute sub-gradient for convolved space + cerr << "Compute sub-gradient (update image) for convolved (motion-contaminated) space" << endl; + std::fill(conv_gradient.begin_all(), conv_gradient.end_all(), 0.F); + + // Calculation of the outer loop sub-gradient image in the convolved (motion-contaminated) space using the forward-convolved + // estimate + distributable_compute_outer_loop_gradient(this->projector_pair_ptr->get_forward_projector_sptr(), + this->projector_pair_ptr->get_back_projector_sptr(), + this->symmetries_sptr, + conv_gradient, + conv_image_estimate, + this->proj_data_sptr, + subset_num, + this->num_subsets, + -this->max_segment_num_to_process, + this->max_segment_num_to_process, + this->zero_seg0_end_planes != 0, + NULL, + this->additive_proj_data_sptr, + caching_info_ptr); + + // Print out the min and max values of the sub-gradient for the convolved (motion-contaminated) image + const float current_min_outer_loop_gradient = *std::min_element(conv_gradient.begin_all(), conv_gradient.end_all()); + + const float current_max_outer_loop_gradient = *std::max_element(conv_gradient.begin_all(), conv_gradient.end_all()); + + cerr << "Outer loop dynamic sub-gradient image (convolved / motion-contaminated space) " + << ", (min, max): (" << current_min_outer_loop_gradient << ", " << current_max_outer_loop_gradient << ")" << endl; + + // Perform projection matrix sensitivity division and update for the single outer loop iteration + + // Devide by system matrix sensitivity + cerr << "Divide sub-gradient (update image) by system sub-sensitivity for convolved (motion-contaminated) image" << endl; + divide(conv_gradient.begin_all(), conv_gradient.end_all(), conv_sensitivity.begin_all(), small_num); + + // Print out the min and max values of the sub-gradient/sensitivity for convolved space + const float current_min_outer_loop_gradient_over_sensitivity + = *std::min_element(conv_gradient.begin_all(), conv_gradient.end_all()); + const float current_max_outer_loop_gradient_over_sensitivity + = *std::max_element(conv_gradient.begin_all(), conv_gradient.end_all()); + cerr << "Outer loop dynamic sub-gradient/sensitivity image (convolved / motion-contaminated space) " + << ", (min, max): (" << current_min_outer_loop_gradient_over_sensitivity << ", " + << current_max_outer_loop_gradient_over_sensitivity << ")" << endl; + + // Update outer loop dynamic image estimate + cerr << "Update convolved (motion-contaminated) image estimate" << endl; + typename TargetT::const_full_iterator conv_gradient_single_frame_iter = conv_gradient.begin_all_const(); + const typename TargetT::const_full_iterator end_conv_gradient_single_frame_iter = conv_gradient.end_all_const(); + typename TargetT::full_iterator conv_image_reference_data_single_frame_iter = conv_image_reference_data.begin_all(); + while (conv_gradient_single_frame_iter != end_conv_gradient_single_frame_iter) + { + *conv_image_reference_data_single_frame_iter *= (*conv_gradient_single_frame_iter); + ++conv_image_reference_data_single_frame_iter; + ++conv_gradient_single_frame_iter; + } + + // Print out the min and max values of the outer loop updated convolved (motion-contaminated) images + const float current_min_outer_loop_updated_image + = *std::min_element(conv_image_reference_data.begin_all(), conv_image_reference_data.end_all()); + const float current_max_outer_loop_updated_image + = *std::max_element(conv_image_reference_data.begin_all(), conv_image_reference_data.end_all()); + cerr << "Outer loop updated image (convolved / motion-contaminated space): " + << ", (min, max): (" << current_min_outer_loop_updated_image << ", " << current_max_outer_loop_updated_image << ")" + << endl; + + cerr << "Current outer loop computation time: " << outer_loop_timer.value() << endl << endl; + + CPUTimer nested_loop_timer; + nested_loop_timer.start(); + + // nested EM loop + cerr << endl << "Entering nested loop " << endl; + + // This is the principal method that iteratively estimates the motion-corrected estimates in a nested EM loop + this->estimate_nested_loop_parameters_with_model( + gradient, current_estimate, conv_image_estimate, conv_image_reference_data, conv_image_nested_loop_estimate); + + cerr << "Total computation time for " << this->num_nested_subiterations << " nested iterations: " << nested_loop_timer.value() + << endl + << endl; +} + +template +void +PoissonNestedLogLikelihoodWithLinearModelForMeanAndConvolvedProjDataWithMotion< + TargetT>::estimate_nested_loop_parameters_with_model(TargetT& gradient, + TargetT& current_estimate, + TargetT& conv_image_estimate, + TargetT& conv_image_reference_data, + TargetT& conv_image_nested_loop_estimate) +{ + + // Initializations-Declarations + std::stringstream nested_iter_num_str; + + // nested EM loop + cerr << endl + << "Entering nested loop (" << this->num_nested_subiterations + << " Richardson-Lucy EM deconvolution sub-iterations for motion correction)." << endl; + + for (nested_subiterations_num = 1; nested_subiterations_num <= this->num_nested_subiterations; nested_subiterations_num++) + { + + // Print out the min and max values of the initial deconvolved image estimate + const float current_min_estimate = *std::min_element(current_estimate.begin_all(), current_estimate.end_all()); + const float current_max_estimate = *std::max_element(current_estimate.begin_all(), current_estimate.end_all()); + cerr << "Richardson-Lucy nested iteration: " << nested_subiterations_num + << " Initial deconvolved image nested estimate , (min, max): (" << current_min_estimate << ", " << current_max_estimate + << ")" << endl; + + // Translate (contaminate-with-motion or equivalent to convolve or forward project operation for motion model) + // from deconvolved (motion-corrected) image space to convolved (motion-contaminated) image space using a motion-corrected + // image estimate as an input + this->_motion_vectors.average_warp_image(conv_image_nested_loop_estimate, current_estimate); + + // Print out the min and max values of the current convolved image estimate after the forward convolution + const float conv_min_nested_loop_estimate + = *std::min_element(conv_image_nested_loop_estimate.begin_all(), conv_image_nested_loop_estimate.end_all()); + const float conv_max_nested_loop_estimate + = *std::max_element(conv_image_nested_loop_estimate.begin_all(), conv_image_nested_loop_estimate.end_all()); + cerr << "Richardson-Lucy nested iteration: " << nested_subiterations_num + << " Current convolved image nested estimate after forward convolution , (min, max): (" + << conv_min_nested_loop_estimate << ", " << conv_max_nested_loop_estimate << ")" << endl; + + conv_image_estimate = conv_image_reference_data; + + divide( + conv_image_estimate.begin_all(), conv_image_estimate.end_all(), conv_image_nested_loop_estimate.begin_all(), small_num); + + // Print out the min and max values of the convolved image gradient after the division of the reference with the nested + // estimate in the convolved space + const float conv_min_nested_estimate = *std::min_element(conv_image_estimate.begin_all(), conv_image_estimate.end_all()); + const float conv_max_nested_estimate = *std::max_element(conv_image_estimate.begin_all(), conv_image_estimate.end_all()); + cerr << "Richardson-Lucy nested iteration: " << nested_subiterations_num + << " Current convolved image gradient after division of reference with the current nested estimate in the convolved " + "space , (min, max): (" + << conv_min_nested_estimate << ", " << conv_max_nested_estimate << ") , division small number: " << small_num << endl; + + // Reversely translate (correct-for-motion or deconvolve equivalent to back project operation for motion model) + // from convolved (motion-contaminated) image space to deconvolved (motion-corrected) image space using the convolved image + // estimate as an input + this->_reverse_motion_vectors.average_warp_image(gradient, conv_image_estimate); + + // Print out the min and max values of the deconvolved image gradient after the backward convolution of the previous + // gradient from the convolved space + const float unnormalized_gradient_min_estimate = *std::min_element(gradient.begin_all(), gradient.end_all()); + const float unnormalized_gradient_max_estimate = *std::max_element(gradient.begin_all(), gradient.end_all()); + cerr << "Richardson-Lucy nested iteration: " << nested_subiterations_num + << " Current deconvolved image gradient after the backward convolution of the previous image gradient from the " + "convolved space , (min, max): (" + << unnormalized_gradient_min_estimate << ", " << unnormalized_gradient_max_estimate << ")" << endl; + + // Perform model sensitivity division and update for all nested iterations + + // Divide by motion model sensitivity + divide(gradient.begin_all(), gradient.end_all(), this->model_sensitivity_image_sptr->begin_all(), small_num); + + /* Include this for testing purposes + if ( (iterations_num % save_iterations_interval)==0 ) + { + iter_num_str.str(string()); + iter_num_str << "_norm_bwdconv_grad_it" << nested_subiterations_num; + string iter_output_filename = output_filename + iter_num_str.str(); + + OutputFileFormat >:: + default_sptr()-> write_to_file(iter_output_filename, gradient); + } + */ + + // Print out the min and max values of the normalized deconvolved image gradient after the devision to the model sensitivity + // image + const float current_min_nested_gradient = *std::min_element(gradient.begin_all(), gradient.end_all()); + const float current_max_nested_gradient = *std::max_element(gradient.begin_all(), gradient.end_all()); + const float new_min_nested_gradient = static_cast(this->minimum_nested_relative_change); + const float new_max_nested_gradient = static_cast(this->maximum_nested_relative_change); + cerr << "Richardson-Lucy nested iteration: " << nested_subiterations_num + << " Gradient(update image) after sensitivity devision: old value (min, max): (" << current_min_nested_gradient << ", " + << current_max_nested_gradient << "), new value (min, max) (" + << max(current_min_nested_gradient, new_min_nested_gradient) << ", " + << min(current_max_nested_gradient, new_max_nested_gradient) << "), threshold limits (min, max) (" + << new_min_nested_gradient << ", " << new_max_nested_gradient << ")" << endl; + + zero_threshold_upper_lower(gradient.begin_all(), gradient.end_all(), new_min_nested_gradient, new_max_nested_gradient); + + // Nested updates of image estimates + { + typename TargetT::const_full_iterator gradient_iter = gradient.begin_all_const(); + const typename TargetT::const_full_iterator end_gradient_iter = gradient.end_all_const(); + typename TargetT::full_iterator current_estimate_iter = current_estimate.begin_all(); + while (gradient_iter != end_gradient_iter) + { + *current_estimate_iter *= (*gradient_iter); + ++current_estimate_iter; + ++gradient_iter; + } + } + + // Print out the min and max values of the nested updated image for each nested iteration + const float current_min_nested_updated_image = *std::min_element(current_estimate.begin_all(), current_estimate.end_all()); + const float current_max_nested_updated_image = *std::max_element(current_estimate.begin_all(), current_estimate.end_all()); + cerr << "Richardson-Lucy nested iteration: " << nested_subiterations_num + << " Updated deconvolved (motion corrected) image value (min, max) (" << current_min_nested_updated_image << ", " + << current_max_nested_updated_image << ")" << endl + << endl; + + if ((nested_subiterations_num % this->save_nested_subiterations_interval) == 0) + { + nested_iter_num_str.str(string()); + nested_iter_num_str << "_nit" << nested_subiterations_num; + string nested_iter_output_filename = this->nested_output_filename_prefix + nested_iter_num_str.str(); + + Succeeded write_result = OutputFileFormat>::default_sptr()->write_to_file( + nested_iter_output_filename, current_estimate); + + if (write_result == Succeeded::yes) + cerr << "Richardson-Lucy nested iteration: " << nested_subiterations_num + << " Writing of current deconvolved image estimate (header file " << nested_iter_output_filename + << ") was successful." << endl + << endl + << endl; + else + cerr << "Richardson-Lucy nested iteration: " << nested_subiterations_num + << " Writing of current deconvolved image estimate (header file " << nested_iter_output_filename + << ") did not succeed." << endl + << endl + << endl; + } + } + + cerr << "End of nested reconstruction process of motion-corrected estimates (after " << this->num_nested_subiterations + << " nested Richardson-Lucy EM subiterations)" << endl + << endl; +} + +template +double +PoissonNestedLogLikelihoodWithLinearModelForMeanAndConvolvedProjDataWithMotion< + TargetT>::actual_compute_objective_function_without_penalty(const TargetT& current_estimate, const int subset_num) +{ + double accum = 0.; + + distributable_accumulate_nested_loglikelihood(this->projector_pair_ptr->get_forward_projector_sptr(), + this->projector_pair_ptr->get_back_projector_sptr(), + this->symmetries_sptr, + current_estimate, + this->proj_data_sptr, + subset_num, + this->get_num_subsets(), + -this->max_segment_num_to_process, + this->max_segment_num_to_process, + this->zero_seg0_end_planes != 0, + &accum, + this->additive_proj_data_sptr, + this->normalisation_sptr, + this->get_time_frame_definitions().get_start_time(this->get_time_frame_num()), + this->get_time_frame_definitions().get_end_time(this->get_time_frame_num()), + this->caching_info_ptr); + + return accum; +} + +#if 0 +template +float +PoissonNestedLogLikelihoodWithLinearModelForMeanAndConvolvedProjDataWithMotion:: +sum_projection_data() const +{ + + float counts=0.0F; + + for (int segment_num = -max_segment_num_to_process; segment_num <= max_segment_num_to_process; segment_num++) + { + for (int view_num = proj_data_sptr->get_min_view_num(); + view_num <= proj_data_sptr->get_max_view_num(); + ++view_num) + { + + Viewgram viewgram=proj_data_sptr->get_viewgram(view_num,segment_num); + + //first adjust data + + // KT 05/07/2000 made parameters.zero_seg0_end_planes int + if(segment_num==0 && zero_seg0_end_planes!=0) + { + viewgram[viewgram.get_min_axial_pos_num()].fill(0); + viewgram[viewgram.get_max_axial_pos_num()].fill(0); + } + + truncate_rim(viewgram,rim_truncation_sino); + + //now take totals + counts+=viewgram.sum(); + } + } + + return counts; + +} + +#endif + +template +void +PoissonNestedLogLikelihoodWithLinearModelForMeanAndConvolvedProjDataWithMotion::compute_model_sensitivity_image( + TargetT& motion_corrected_image) +{ + shared_ptr motion_corrected_image_sptr(motion_corrected_image.get_empty_copy()); + this->model_sensitivity_image_sptr = motion_corrected_image_sptr; + + // Initialize model sensitivity image + std::fill(this->model_sensitivity_image_sptr->begin_all(), this->model_sensitivity_image_sptr->end_all(), 1.F); + + shared_ptr conv_image_of_all_ones(motion_corrected_image.get_empty_copy()); + + std::fill(conv_image_of_all_ones->begin_all(), conv_image_of_all_ones->end_all(), 1.F); + + cerr << "Computing model sensitivity image..." << endl; + + // To obtain motion model sensitivity image reversely translate (correct-for-motion or equivalent to back project operation for + // motion model) from motion-gated space to motion-corrected single-gate space using a gated image estimate of ALL ONES Nicolas + // K. : We leave sensitivity image to be all of ones as this is the definition of the sensitivity image in the case of motion + // transformations + // this->_reverse_motion_vectors.average_warp_image(*this->model_sensitivity_image_sptr, + // *conv_image_of_all_ones); + + // Print out the min and max values of the model sensitivity image + const float current_min_model_sensitivity + = *std::min_element(this->model_sensitivity_image_sptr->begin_all(), this->model_sensitivity_image_sptr->end_all()); + const float current_max_model_sensitivity + = *std::max_element(this->model_sensitivity_image_sptr->begin_all(), this->model_sensitivity_image_sptr->end_all()); + cerr << "Model sensitivity image " + << ", (min, max): (" << current_min_model_sensitivity << ", " << current_max_model_sensitivity << ")" << endl; + + cerr << "Model sensitivity image has been computed." << endl; +} + +template +void +PoissonNestedLogLikelihoodWithLinearModelForMeanAndConvolvedProjDataWithMotion::add_subset_sensitivity( + TargetT& sensitivity, const int subset_num) const +{ + // FIRST: Calculation of projection space sensitivity image factor of overall sensitivity image + const int min_segment_num = -this->max_segment_num_to_process; + const int max_segment_num = this->max_segment_num_to_process; + + // warning: has to be same as subset scheme used as in distributable_computation + for (int segment_num = min_segment_num; segment_num <= max_segment_num; ++segment_num) + { + // CPUTimer timer; + // timer.start(); + + for (int view = this->proj_data_sptr->get_min_view_num() + subset_num; view <= this->proj_data_sptr->get_max_view_num(); + view += this->num_subsets) + { + const ViewSegmentNumbers view_segment_num(view, segment_num); + + if (!symmetries_sptr->is_basic(view_segment_num)) + continue; + this->add_view_seg_to_sensitivity(sensitivity, view_segment_num); + } + } + + // cerr< +void +PoissonNestedLogLikelihoodWithLinearModelForMeanAndConvolvedProjDataWithMotion::add_view_seg_to_sensitivity( + TargetT& sensitivity, const ViewSegmentNumbers& view_seg_nums) const +{ + RelatedViewgrams viewgrams = this->proj_data_sptr->get_empty_related_viewgrams(view_seg_nums, this->symmetries_sptr); + viewgrams.fill(1.F); + // find efficiencies + { + const double start_frame = this->frame_defs.get_start_time(this->frame_num); + const double end_frame = this->frame_defs.get_end_time(this->frame_num); + this->normalisation_sptr->undo(viewgrams, start_frame, end_frame); + } + // backproject + { + const int range_to_zero = view_seg_nums.segment_num() == 0 && this->zero_seg0_end_planes ? 1 : 0; + const int min_ax_pos_num = viewgrams.get_min_axial_pos_num() + range_to_zero; + const int max_ax_pos_num = viewgrams.get_max_axial_pos_num() - range_to_zero; + + this->projector_pair_ptr->get_back_projector_sptr()->back_project(sensitivity, viewgrams, min_ax_pos_num, max_ax_pos_num); + } +} + +template +Succeeded +PoissonNestedLogLikelihoodWithLinearModelForMeanAndConvolvedProjDataWithMotion< + TargetT>::actual_add_multiplication_with_approximate_sub_Hessian_without_penalty(TargetT& output, + const TargetT& input, + const int subset_num) const +{ + { + string explanation; + if (!input.has_same_characteristics(this->get_sensitivity(), explanation)) + { + warning("PoissonNestedLogLikelihoodWithLinearModelForMeanAndConvolvedProjDataWithMotion:\n" + "sensitivity and input for add_multiplication_with_approximate_Hessian_without_penalty\n" + "should have the same characteristics.\n%s", + explanation.c_str()); + return Succeeded::no; + } + } + + shared_ptr symmetries_sptr(this->get_projector_pair().get_symmetries_used()->clone()); + + const double start_time = this->get_time_frame_definitions().get_start_time(this->get_time_frame_num()); + const double end_time = this->get_time_frame_definitions().get_end_time(this->get_time_frame_num()); + + for (int segment_num = -this->get_max_segment_num_to_process(); segment_num <= this->get_max_segment_num_to_process(); + ++segment_num) + { + for (int view = this->get_proj_data().get_min_view_num() + subset_num; view <= this->get_proj_data().get_max_view_num(); + view += this->num_subsets) + { + const ViewSegmentNumbers view_segment_num(view, segment_num); + + if (!symmetries_sptr->is_basic(view_segment_num)) + continue; + + // first compute data-term: y*norm^2 + RelatedViewgrams viewgrams = this->get_proj_data().get_related_viewgrams(view_segment_num, symmetries_sptr); + // TODO add 1 for 1/(y+1) approximation + + this->get_normalisation().apply(viewgrams, start_time, end_time); + + // smooth TODO + + this->get_normalisation().apply(viewgrams, start_time, end_time); + + RelatedViewgrams tmp_viewgrams; + // set tmp_viewgrams to geometric forward projection of input + { + tmp_viewgrams = this->get_proj_data().get_empty_related_viewgrams(view_segment_num, symmetries_sptr); + this->get_projector_pair().get_forward_projector_sptr()->forward_project(tmp_viewgrams, input); + } + + // now divide by the data term + { + int tmp1 = 0, tmp2 = 0; // ignore counters returned by divide_and_truncate + divide_and_truncate(tmp_viewgrams, viewgrams, 0, tmp1, tmp2); + } + + // back-project + this->get_projector_pair().get_back_projector_sptr()->back_project(output, tmp_viewgrams); + } + + } // end of loop over segments + + return Succeeded::yes; +} + +/*********************** distributable_* ***************************/ +// TODO all this stuff is specific to DiscretisedDensity, so wouldn't work for TargetT + +#ifdef STIR_MPI +// make call-backs public for the moment + +//! Call-back function for compute_gradient +RPC_process_related_viewgrams_type RPC_process_related_viewgrams_gradient; + +//! Call-back function for accumulate_loglikelihood +RPC_process_related_viewgrams_type RPC_process_related_viewgrams_accumulate_loglikelihood; +#else +//! Call-back function for compute_gradient +static RPC_process_related_viewgrams_type RPC_process_related_viewgrams_gradient; + +//! Call-back function for accumulate_loglikelihood +static RPC_process_related_viewgrams_type RPC_process_related_viewgrams_accumulate_loglikelihood; +#endif + +void +distributable_compute_outer_loop_gradient(const shared_ptr& forward_projector_sptr, + const shared_ptr& back_projector_sptr, + const shared_ptr& symmetries_sptr, + DiscretisedDensity<3, float>& output_image, + const DiscretisedDensity<3, float>& input_image, + const shared_ptr& proj_dat, + int subset_num, + int num_subsets, + int min_segment, + int max_segment, + bool zero_seg0_end_planes, + double* log_likelihood_ptr, + shared_ptr const& additive_binwise_correction, + DistributedCachingInformation* caching_info_ptr) +{ + + distributable_computation(forward_projector_sptr, + back_projector_sptr, + symmetries_sptr, + &output_image, + &input_image, + proj_dat, + true, // i.e. do read projection data + subset_num, + num_subsets, + min_segment, + max_segment, + zero_seg0_end_planes, + log_likelihood_ptr, + additive_binwise_correction, + /* normalisation info to be ignored */ shared_ptr(), + 0., + 0., + &RPC_process_related_viewgrams_gradient, + caching_info_ptr); +} + +void +distributable_accumulate_nested_loglikelihood(const shared_ptr& forward_projector_sptr, + const shared_ptr& back_projector_sptr, + const shared_ptr& symmetries_sptr, + const DiscretisedDensity<3, float>& input_image, + const shared_ptr& proj_dat, + int subset_num, + int num_subsets, + int min_segment, + int max_segment, + bool zero_seg0_end_planes, + double* log_likelihood_ptr, + shared_ptr const& additive_binwise_correction, + shared_ptr const& normalisation_sptr, + const double start_time_of_frame, + const double end_time_of_frame, + DistributedCachingInformation* caching_info_ptr) + +{ + distributable_computation(forward_projector_sptr, + back_projector_sptr, + symmetries_sptr, + NULL, + &input_image, + proj_dat, + true, // i.e. do read projection data + subset_num, + num_subsets, + min_segment, + max_segment, + zero_seg0_end_planes, + log_likelihood_ptr, + additive_binwise_correction, + normalisation_sptr, + start_time_of_frame, + end_time_of_frame, + &RPC_process_related_viewgrams_accumulate_loglikelihood, + caching_info_ptr); +} + +//////////// RPC functions + +void +RPC_process_related_viewgrams_gradient(const shared_ptr& forward_projector_sptr, + const shared_ptr& back_projector_sptr, + DiscretisedDensity<3, float>* output_image_ptr, + const DiscretisedDensity<3, float>* input_image_ptr, + RelatedViewgrams* measured_viewgrams_ptr, + int& count, + int& count2, + double* log_likelihood_ptr /* = NULL */, + const RelatedViewgrams* additive_binwise_correction_ptr, + const RelatedViewgrams* mult_viewgrams_ptr) +{ + assert(output_image_ptr != NULL); + assert(input_image_ptr != NULL); + assert(measured_viewgrams_ptr != NULL); + if (!is_null_ptr(mult_viewgrams_ptr)) + error("Internal error: mult_viewgrams_ptr should be zero when computing gradient"); + + RelatedViewgrams estimated_viewgrams = measured_viewgrams_ptr->get_empty_copy(); + + /*if (distributed::first_iteration) + { + stir::RelatedViewgrams::iterator viewgrams_iter = measured_viewgrams_ptr->begin(); + stir::RelatedViewgrams::iterator viewgrams_end = measured_viewgrams_ptr->end(); + while (viewgrams_iter!= viewgrams_end) + { + printf("\nSLAVE VIEWGRAM\n"); + int pos=0; + for ( int tang_pos = -144 ;tang_pos <= 143 ;++tang_pos) + for ( int ax_pos = 0; ax_pos <= 62 ;++ax_pos) + { + if (pos>3616 && pos <3632) printf("%f, ",(*viewgrams_iter)[ax_pos][tang_pos]); + pos++; + } + viewgrams_iter++; + } + } +*/ + forward_projector_sptr->forward_project(estimated_viewgrams, *input_image_ptr); + + if (additive_binwise_correction_ptr != NULL) + { + estimated_viewgrams += (*additive_binwise_correction_ptr); + } + + // for sinogram division + + divide_and_truncate(*measured_viewgrams_ptr, estimated_viewgrams, rim_truncation_sino, count, count2, log_likelihood_ptr); + + back_projector_sptr->back_project(*output_image_ptr, *measured_viewgrams_ptr); +}; + +void +RPC_process_related_viewgrams_accumulate_loglikelihood(const shared_ptr& forward_projector_sptr, + const shared_ptr& back_projector_sptr, + DiscretisedDensity<3, float>* output_image_ptr, + const DiscretisedDensity<3, float>* input_image_ptr, + RelatedViewgrams* measured_viewgrams_ptr, + int& count, + int& count2, + double* log_likelihood_ptr, + const RelatedViewgrams* additive_binwise_correction_ptr, + const RelatedViewgrams* mult_viewgrams_ptr) +{ + + assert(output_image_ptr == NULL); + assert(input_image_ptr != NULL); + assert(measured_viewgrams_ptr != NULL); + assert(log_likelihood_ptr != NULL); + + RelatedViewgrams estimated_viewgrams = measured_viewgrams_ptr->get_empty_copy(); + + forward_projector_sptr->forward_project(estimated_viewgrams, *input_image_ptr); + + if (additive_binwise_correction_ptr != NULL) + { + estimated_viewgrams += (*additive_binwise_correction_ptr); + }; + + if (mult_viewgrams_ptr != NULL) + { + estimated_viewgrams *= (*mult_viewgrams_ptr); + } + + RelatedViewgrams::iterator meas_viewgrams_iter = measured_viewgrams_ptr->begin(); + RelatedViewgrams::const_iterator est_viewgrams_iter = estimated_viewgrams.begin(); + // call function that does the actual work, it sits in recon_array_funtions.cxx (TODO) + for (; meas_viewgrams_iter != measured_viewgrams_ptr->end(); ++meas_viewgrams_iter, ++est_viewgrams_iter) + accumulate_loglikelihood(*meas_viewgrams_iter, *est_viewgrams_iter, rim_truncation_sino, log_likelihood_ptr); +}; + +#ifdef _MSC_VER +// prevent warning message on instantiation of abstract class +# pragma warning(disable : 4661) +#endif + +template class PoissonNestedLogLikelihoodWithLinearModelForMeanAndConvolvedProjDataWithMotion>; + +END_NAMESPACE_STIR diff --git a/src/recon_buildblock/PoissonNestedLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion.cxx b/src/recon_buildblock/PoissonNestedLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion.cxx new file mode 100644 index 0000000000..1312a7f54a --- /dev/null +++ b/src/recon_buildblock/PoissonNestedLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion.cxx @@ -0,0 +1,35 @@ +/* + Copyright (C) 2009- 2013, King's College London + This file is part of STIR. + + This file is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.3 of the License, or + (at your option) any later version. + + This file is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + See STIR/LICENSE.txt for details + */ +/*! + \file + \ingroup GeneralisedObjectiveFunction + \brief Instantiations for class stir::PoissonNestedLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion + \author Nicolas A Karakatsanis + +*/ + +#include "stir/recon_buildblock/PoissonNestedLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion.txx" + +START_NAMESPACE_STIR + +#ifdef _MSC_VER +// prevent warning message on instantiation of abstract class +# pragma warning(disable : 4661) +#endif // _MSC_VER +template class PoissonNestedLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion>; + +END_NAMESPACE_STIR diff --git a/src/recon_buildblock/Reconstruction.cxx b/src/recon_buildblock/Reconstruction.cxx index 76462e7b6a..7f667acb7d 100644 --- a/src/recon_buildblock/Reconstruction.cxx +++ b/src/recon_buildblock/Reconstruction.cxx @@ -194,5 +194,6 @@ Reconstruction::get_target_image() } template class Reconstruction>; -template class Reconstruction; +template class Reconstruction; +template class Reconstruction; END_NAMESPACE_STIR diff --git a/src/recon_buildblock/recon_buildblock_registries.cxx b/src/recon_buildblock/recon_buildblock_registries.cxx index 740d0ca70e..9b307e27fe 100644 --- a/src/recon_buildblock/recon_buildblock_registries.cxx +++ b/src/recon_buildblock/recon_buildblock_registries.cxx @@ -54,10 +54,13 @@ #include "stir/DynamicDiscretisedDensity.h" #include "stir/recon_buildblock/PoissonLogLikelihoodWithLinearKineticModelAndDynamicProjectionData.h" #include "stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion.h" +#include "stir/recon_buildblock/PoissonNestedLogLikelihoodWithLinearKineticModelAndDynamicProjectionData.h" +#include "stir/recon_buildblock/PoissonNestedLogLikelihoodWithGeneralizedPatlakAndDynamicProjectionData.h" +// #include "stir/recon_buildblock/PoissonNestedLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion.h" +// #include "stir/recon_buildblock/PoissonNestedLogLikelihoodWithLinearModelForMeanAndConvolvedProjDataWithMotion.h" #include "stir/analytic/FBP2D/FBP2DReconstruction.h" #include "stir/analytic/FBP3DRP/FBP3DRPReconstruction.h" - #include "stir/OSMAPOSL/OSMAPOSLReconstruction.h" #include "stir/KOSMAPOSL/KOSMAPOSLReconstruction.h" #include "stir/OSSPS/OSSPSReconstruction.h" @@ -163,6 +166,15 @@ static BackProjectorByBinParallelproj::RegisterIt parallelproj_bck; static ProjectorByBinPairUsingParallelproj::RegisterIt parallelproj_pair; #endif +static PoissonNestedLogLikelihoodWithLinearKineticModelAndDynamicProjectionData::RegisterIt + Dummyzzz; +static PoissonNestedLogLikelihoodWithGeneralizedPatlakAndDynamicProjectionData::RegisterIt + Dummykkk; +// static PoissonNestedLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion>::RegisterIt +// Dummyxxxzz1; +// static PoissonNestedLogLikelihoodWithLinearModelForMeanAndConvolvedProjDataWithMotion>::RegisterIt +// Dummyxxxzz2; + #ifdef HAVE_LLN_MATRIX START_NAMESPACE_ECAT START_NAMESPACE_ECAT7 diff --git a/src/spatial_transformation_buildblock/GatedSpatialTransformation.cxx b/src/spatial_transformation_buildblock/GatedSpatialTransformation.cxx index 21e7d6a629..ea9f881d45 100644 --- a/src/spatial_transformation_buildblock/GatedSpatialTransformation.cxx +++ b/src/spatial_transformation_buildblock/GatedSpatialTransformation.cxx @@ -138,7 +138,7 @@ void GatedSpatialTransformation::warp_image(GatedDiscretisedDensity& new_gated_image, const GatedDiscretisedDensity& gated_image) const { std::string explanation; - if (!(gated_image.get_densities()[0])->has_same_characteristics(*(gated_image.get_densities()[0]), explanation)) + if (!(new_gated_image.get_densities()[0])->has_same_characteristics(*(gated_image.get_densities()[0]), explanation)) { error(format("GatedSpatialTransformation::warp_image needs the same sizes for input and output images: {}", explanation)); } @@ -179,6 +179,40 @@ GatedSpatialTransformation::accumulate_warp_image(DiscretisedDensity<3, float>& // new_reference_image /= gated_image.get_time_gate_definitions().get_num_gates(); } +void +GatedSpatialTransformation::average_warp_image(DiscretisedDensity<3, float>& new_reference_image, + const GatedDiscretisedDensity& gated_image) const +{ + new_reference_image.fill(0.F); + this->accumulate_average_warp_image(new_reference_image, gated_image); +} + +void +GatedSpatialTransformation::accumulate_average_warp_image(DiscretisedDensity<3, float>& new_reference_image, + const GatedDiscretisedDensity& gated_image) const +{ + GatedDiscretisedDensity new_gated_image(gated_image); + new_gated_image.fill_with_zero(); + this->warp_image(new_gated_image, gated_image); + //! todo This is not implemented as sum (or should it be the average?) + for (unsigned int gate_num = 1; gate_num <= gated_image.get_time_gate_definitions().get_num_gates(); ++gate_num) + { + float gate_relative_duration = new_gated_image.get_time_gate_definitions().get_gate_relative_duration(gate_num); + + DiscretisedDensity<3, float>::full_iterator new_gated_image_iter = new_gated_image[gate_num].begin_all(); + DiscretisedDensity<3, float>::full_iterator end_new_gated_image_iter = new_gated_image[gate_num].end_all(); + DiscretisedDensity<3, float>::full_iterator new_reference_image_iter = new_reference_image.begin_all(); + + while (new_gated_image_iter != end_new_gated_image_iter) + { + *new_gated_image_iter + *= gate_relative_duration; // Nicolas K. : Scalar multiplication of current gate with the relative duration + *new_reference_image_iter += *new_gated_image_iter; // Nicolas K. : Scaled value accumulated over all motions/gates + ++new_gated_image_iter, ++new_reference_image_iter; + } + } +} + void GatedSpatialTransformation::warp_image(GatedDiscretisedDensity& gated_image, const DiscretisedDensity<3, float>& reference_image) const @@ -214,6 +248,45 @@ GatedSpatialTransformation::warp_image(GatedDiscretisedDensity& gated_image, error("The transformation fields haven't been set properly yet."); } +void +GatedSpatialTransformation::average_warp_image(DiscretisedDensity<3, float>& avg_warped_image, + const DiscretisedDensity<3, float>& reference_image) const +{ + avg_warped_image.fill(0.F); + this->accumulate_average_warp_image(avg_warped_image, reference_image); +} + +void +GatedSpatialTransformation::accumulate_average_warp_image(DiscretisedDensity<3, float>& avg_warped_image, + const DiscretisedDensity<3, float>& reference_image) const +{ + + // Creation of a copy a gated image to temporarily store the motion transformed images of each motion/gate + const shared_ptr> density_template_sptr(reference_image.get_empty_copy()); + GatedDiscretisedDensity gated_image = GatedDiscretisedDensity(this->_gate_defs, density_template_sptr); + + this->warp_image(gated_image, reference_image); + + // The relative durations for each motion/gate when applied as weights in the following weighted sum operation + // effectively produced a time-weighted average warped image + for (unsigned int gate_num = 1; gate_num <= gated_image.get_time_gate_definitions().get_num_gates(); ++gate_num) + { + float gate_relative_duration = gated_image.get_time_gate_definitions().get_gate_relative_duration(gate_num); + + DiscretisedDensity<3, float>::full_iterator gated_image_iter = gated_image[gate_num].begin_all(); + DiscretisedDensity<3, float>::full_iterator end_gated_image_iter = gated_image[gate_num].end_all(); + DiscretisedDensity<3, float>::full_iterator avg_warped_image_iter = avg_warped_image.begin_all(); + + while (gated_image_iter != end_gated_image_iter) + { + *gated_image_iter + *= gate_relative_duration; // Nicolas K. : Scalar multiplication of current gate with the relative duration + *avg_warped_image_iter += *gated_image_iter; // Nicolas K. : Scaled value accumulated over all motions/gates + ++gated_image_iter, ++avg_warped_image_iter; + } + } +} + void GatedSpatialTransformation::set_spatial_transformations(const GatedDiscretisedDensity& transformation_z, const GatedDiscretisedDensity& transformation_y, diff --git a/src/test/IO/test_IO_ParametricDiscretisedDensity.cxx b/src/test/IO/test_IO_ParametricDiscretisedDensity.cxx index cf3e253fb8..91cc4add94 100644 --- a/src/test/IO/test_IO_ParametricDiscretisedDensity.cxx +++ b/src/test/IO/test_IO_ParametricDiscretisedDensity.cxx @@ -103,7 +103,7 @@ IOTests_ParametricDiscretisedDensity::create_image() scanner_sptr, num_axial_pos_per_segment, min_ring_diff, max_ring_diff, num_views, num_tangential_poss); _image_to_write_sptr.reset(new ParametricVoxelsOnCartesianGrid( - ParametricVoxelsOnCartesianGridBaseType(proj_data_info, zoom, dummy_im_sptr->get_grid_spacing(), sizes))); + Parametric2VoxelsOnCartesianGridBaseType(proj_data_info, zoom, dummy_im_sptr->get_grid_spacing(), sizes))); // Fill the first param param_2_sptr->fill(2.F); diff --git a/src/test/modelling/test_ParametricDiscretisedDensity.cxx b/src/test/modelling/test_ParametricDiscretisedDensity.cxx index bd1e880d6a..45bd282423 100644 --- a/src/test/modelling/test_ParametricDiscretisedDensity.cxx +++ b/src/test/modelling/test_ParametricDiscretisedDensity.cxx @@ -91,7 +91,7 @@ ParametricDiscretisedDensityTests::run_tests() const CartesianCoordinate3D sizes(1, 1, 1); const shared_ptr parametric_image_sptr( - new ParametricVoxelsOnCartesianGrid(ParametricVoxelsOnCartesianGridBaseType(proj_data_info, zoom, grid_spacing, sizes))); + new ParametricVoxelsOnCartesianGrid(Parametric2VoxelsOnCartesianGridBaseType(proj_data_info, zoom, grid_spacing, sizes))); ParametricVoxelsOnCartesianGrid& parametric_image = *parametric_image_sptr; parametric_image[0][0][0][1] = 1.F; parametric_image[0][0][0][2] = 2.F; @@ -112,7 +112,7 @@ ParametricDiscretisedDensityTests::run_tests() // ParametricVoxelsOnCartesianGrid & parametric_image = *parametric_image_sptr; ParametricVoxelsOnCartesianGrid parametric_image( - ParametricVoxelsOnCartesianGridBaseType(proj_data_info, zoom, grid_spacing, sizes)); + Parametric2VoxelsOnCartesianGridBaseType(proj_data_info, zoom, grid_spacing, sizes)); for (int k = 0; k < 63; ++k) for (int j = -64; j < 63; ++j) for (int i = -64; i < 63; ++i) diff --git a/src/test/modelling/test_modelling.cxx b/src/test/modelling/test_modelling.cxx index 5bc60b8018..d1de6be014 100644 --- a/src/test/modelling/test_modelling.cxx +++ b/src/test/modelling/test_modelling.cxx @@ -210,10 +210,10 @@ modellingTests::run_tests() std::cerr << "\nTesting the creation of Model Matrix based on Plasma Data..." << std::endl; PatlakPlot patlak_plot; const unsigned int starting_frame = 23; - patlak_plot._plasma_frame_data = sample_plasma_data_in_frames; - patlak_plot._frame_defs = time_frame_def; - patlak_plot._starting_frame = starting_frame; - patlak_plot._cal_factor = 10.0F; + patlak_plot.set_plasma_data(sample_plasma_data_in_frames); + patlak_plot.set_time_frame_definitions(time_frame_def); + patlak_plot.set_starting_frame(starting_frame); + patlak_plot.set_calibration_factor(10.0F); patlak_plot.set_up(); ModelMatrix<2> stir_model_matrix = (patlak_plot.get_model_matrix()); ModelMatrix<2> mathematica_model_matrix; @@ -224,10 +224,10 @@ modellingTests::run_tests() for (unsigned int frame_num = 23; frame_num <= 28; ++frame_num) { - check_if_equal(mathematica_model_array[1][frame_num] / patlak_plot._cal_factor, + check_if_equal(mathematica_model_array[1][frame_num] / patlak_plot.get_calibration_factor(), stir_model_array[1][frame_num], "Check _model_array-1st column in ModelMatrix"); - check_if_equal(mathematica_model_array[2][frame_num] / patlak_plot._cal_factor, + check_if_equal(mathematica_model_array[2][frame_num] / patlak_plot.get_calibration_factor(), stir_model_array[2][frame_num], "Check _model_array-2nd column in ModelMatrix"); } diff --git a/src/utilities/CMakeLists.txt b/src/utilities/CMakeLists.txt index 05faf85500..40291fbe3f 100644 --- a/src/utilities/CMakeLists.txt +++ b/src/utilities/CMakeLists.txt @@ -61,7 +61,9 @@ if (NOT MINI_STIR) stir_list_registries.cxx shift_image_origin.cxx warp_and_accumulate_gated_images.cxx + warp_gated_images.cxx warp_image.cxx + apply_RL_deconvolution.cxx zeropad_planes.cxx apply_normfactors3D.cxx apply_normfactors.cxx diff --git a/src/utilities/apply_RL_deconvolution.cxx b/src/utilities/apply_RL_deconvolution.cxx new file mode 100644 index 0000000000..98c3d82fd0 --- /dev/null +++ b/src/utilities/apply_RL_deconvolution.cxx @@ -0,0 +1,344 @@ +// +/* + Copyright (C) 2009 - 2013, King's College London + This file is part of STIR. + + This file is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.3 of the License, or + (at your option) any later version. + + This file is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + See STIR/LICENSE.txt for details + */ +/*! + \file + \ingroup utilities + \ingroup spatial_transformation + + \brief This program applies motion transformation (warping) and then either accumulates or averages the resulting gated images + \author Nicolas A Karakatsanis + */ +#include "stir/IO/OutputFileFormat.h" +#include "stir/IO/read_from_file.h" +#include "stir/VoxelsOnCartesianGrid.h" +#include "stir/DiscretisedDensity.h" +#include "stir/GatedDiscretisedDensity.h" +#include "stir/spatial_transformation/GatedSpatialTransformation.h" +#include "stir/Succeeded.h" + +#include "stir/Viewgram.h" +#include "stir/RelatedViewgrams.h" +#include "stir/stream.h" +#include "stir/recon_array_functions.h" +#include "stir/is_null_ptr.h" +#include "stir/numerics/divide.h" +#include "stir/thresholding.h" +#include "stir/NumericInfo.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "stir/CPUTimer.h" +#include "stir/info.h" +#include + +#ifndef STIR_NO_NAMESPACES +using std::ends; +using std::cerr; +using std::endl; +using std::max; +using std::string; +#endif + +USING_NAMESPACE_STIR + +using namespace BSpline; + +const float small_num = 0.000001F; + +static void +print_usage_and_exit() +{ + cerr << "\nUsage: apply_RL_deconvolution \n" + << "\t--Applies Richardson-Lucy iterative deconvolution to a motion-contaminated image utilizing the user-specified " + "forward and backward-motion vector fields \n" + << "\t--: Filename prefix indicating the files of the motion vector fields used to " + "introduce motion contamination to the original motion-less image " + << "\t--: Filename prefix indicating the files of the motion vector fields used to reverse " + "the motion contamination (flipped motion kernel) to the motion-contaminated image " + << "\t--: Integer designating the interval of Richardson-Lucy EM iterations at which to save " + "images every time"; + exit(EXIT_FAILURE); +} + +void +RL_deconvolution(DiscretisedDensity<3, float>& gradient, + DiscretisedDensity<3, float>& current_estimate, + DiscretisedDensity<3, float>& model_sensitivity_image, + DiscretisedDensity<3, float>& conv_image_estimate, + DiscretisedDensity<3, float>& conv_image_RL_estimate, + const DiscretisedDensity<3, float>& conv_image_reference, + GatedSpatialTransformation motion_vectors, + GatedSpatialTransformation reverse_motion_vectors, + const unsigned int num_iterations, + const unsigned int save_iterations_interval, + float min_update_factor, + float max_update_factor, + const char* output_filename) +{ + + // Initializations-Declarations + std::stringstream iter_num_str; + + // First, pre-compute motion model sensitivity image for proper Richardson-Lucy EM deconvolution + + // Initialize model sensitivity image and convolved image of ONES + std::fill(model_sensitivity_image.begin_all(), model_sensitivity_image.end_all(), 1.F); + + shared_ptr> conv_image_of_all_ones(conv_image_reference.get_empty_copy()); + std::fill(conv_image_of_all_ones->begin_all(), conv_image_of_all_ones->end_all(), 1.F); + + cerr << "Computing model sensitivity image..." << endl; + + // To obtain motion model sensitivity image reversely translate (correct-for-motion or equivalent to back project operation for + // motion model) from motion-gated space to motion-corrected single-gate space using a gated image estimate of ALL ONES + // reverse_motion_vectors.average_warp_image(model_sensitivity_image, + // *conv_image_of_all_ones); + // + + // Print out the min and max values of the model sensitivity image + const float current_min_model_sensitivity + = *std::min_element(model_sensitivity_image.begin_all(), model_sensitivity_image.end_all()); + const float current_max_model_sensitivity + = *std::max_element(model_sensitivity_image.begin_all(), model_sensitivity_image.end_all()); + cerr << "Model sensitivity image " + << ", (min, max): (" << current_min_model_sensitivity << ", " << current_max_model_sensitivity << ")" << endl; + + cerr << "Model sensitivity image has been computed." << endl; + + // Print out the min and max values of the convolved image reference + const float current_min_reference = *std::min_element(conv_image_reference.begin_all(), conv_image_reference.end_all()); + const float current_max_reference = *std::max_element(conv_image_reference.begin_all(), conv_image_reference.end_all()); + cerr << "Reference convolved image , (min, max): (" << current_min_reference << ", " << current_max_reference << ")" << endl + << endl; + + // Then, enter EM loop of Richardson-Lucy deconvolution of motion contaminated input image (conv_image_reference) utilizing the + // previous sensitivity image + cerr << endl + << "Entering EM update loop (" << num_iterations << " Richardson-Lucy EM deconvolution iterations for motion correction)." + << endl; + + for (unsigned int iterations_num = 1; iterations_num <= num_iterations; iterations_num++) + { + + // Print out the min and max values of the initial deconvolved image estimate + const float current_min_estimate = *std::min_element(current_estimate.begin_all(), current_estimate.end_all()); + const float current_max_estimate = *std::max_element(current_estimate.begin_all(), current_estimate.end_all()); + cerr << "Richardson-Lucy iteration: " << iterations_num << " Initial deconvolved image estimate , (min, max): (" + << current_min_estimate << ", " << current_max_estimate << ")" << endl; + + // Translate (contaminate-with-motion or equivalent to convolve or forward project operation for motion model) + // from deconvolved (motion-corrected) image space to convolved (motion-contaminated) image space using a motion-corrected + // image estimate as an input + motion_vectors.average_warp_image(conv_image_RL_estimate, current_estimate); + + // Print out the min and max values of the current convolved image estimate after the forward convolution + const float conv_min_RL_estimate = *std::min_element(conv_image_RL_estimate.begin_all(), conv_image_RL_estimate.end_all()); + const float conv_max_RL_estimate = *std::max_element(conv_image_RL_estimate.begin_all(), conv_image_RL_estimate.end_all()); + cerr << "Richardson-Lucy iteration: " << iterations_num + << " Current convolved image estimate after forward convolution , (min, max): (" << conv_min_RL_estimate << ", " + << conv_max_RL_estimate << ")" << endl; + + conv_image_estimate = conv_image_reference; + + // Division of the reference with the estimate in the convolved space + divide(conv_image_estimate.begin_all(), conv_image_estimate.end_all(), conv_image_RL_estimate.begin_all(), small_num); + + // Print out the min and max values of the convolved image gradient after the division of the reference with the estimate in + // the convolved space + const float conv_min_estimate = *std::min_element(conv_image_estimate.begin_all(), conv_image_estimate.end_all()); + const float conv_max_estimate = *std::max_element(conv_image_estimate.begin_all(), conv_image_estimate.end_all()); + cerr << "Richardson-Lucy iteration: " << iterations_num + << " Current convolved image gradient after division of reference with the estimate in the convolved space , (min, " + "max): (" + << conv_min_estimate << ", " << conv_max_estimate << ") , division small number: " << small_num << endl; + + // Reversely translate (correct-for-motion or deconvolve equivalent to back project operation for motion model) + // from convolved (motion-contaminated) image space to deconvolved (motion-corrected) image space using the convolved image + // estimate as an input + reverse_motion_vectors.average_warp_image(gradient, conv_image_estimate); + + // Print out the min and max values of the deconvolved image gradient after the backward convolution of the previous + // gradient from the convolved space + const float unnormalized_gradient_min_estimate = *std::min_element(gradient.begin_all(), gradient.end_all()); + const float unnormalized_gradient_max_estimate = *std::max_element(gradient.begin_all(), gradient.end_all()); + cerr << "Richardson-Lucy iteration: " << iterations_num + << " Current deconvolved image gradient after the backward convolution of the previous image gradient from the " + "convolved space , (min, max): (" + << unnormalized_gradient_min_estimate << ", " << unnormalized_gradient_max_estimate << ")" << endl; + + // Divide/Normalize by motion model sensitivity image + // divide(gradient.begin_all(), + // gradient.end_all(), + // model_sensitivity_image.begin_all(), + // small_num); + + /* Include this for testing purposes + if ( (iterations_num % save_iterations_interval)==0 ) + { + iter_num_str.str(string()); + iter_num_str << "_norm_bwdconv_grad_it" << iterations_num; + string iter_output_filename = output_filename + iter_num_str.str(); + + OutputFileFormat >:: + default_sptr()-> write_to_file(iter_output_filename, gradient); + } + */ + + // Print out the min and max values of the normalized deconvolved image gradient after the devision to the model sensitivity + // image + const float current_min_gradient = *std::min_element(gradient.begin_all(), gradient.end_all()); + const float current_max_gradient = *std::max_element(gradient.begin_all(), gradient.end_all()); + cerr << "Richardson-Lucy iteration: " << iterations_num + << " Gradient after sensitivity image division: old value (min, max): (" << current_min_gradient << ", " + << current_max_gradient << "), new value (min, max) (" << std::max(current_min_gradient, min_update_factor) << ", " + << std::min(current_max_gradient, max_update_factor) << ")" + << "), threshold limits (min, max) (" << min_update_factor << ", " << max_update_factor << ")" << endl; + + zero_threshold_upper_lower(gradient.begin_all(), gradient.end_all(), min_update_factor, max_update_factor); + + // EM updates of motion-corrected image estimates + { + DiscretisedDensity<3, float>::const_full_iterator gradient_iter = gradient.begin_all_const(); + const DiscretisedDensity<3, float>::const_full_iterator end_gradient_iter = gradient.end_all_const(); + DiscretisedDensity<3, float>::full_iterator current_estimate_iter = current_estimate.begin_all(); + while (gradient_iter != end_gradient_iter) + { + *current_estimate_iter *= (*gradient_iter); + ++current_estimate_iter; + ++gradient_iter; + } + } + + // Print out the min and max values of the nested updated image for each nested iteration + const float current_min_updated_image = *std::min_element(current_estimate.begin_all(), current_estimate.end_all()); + const float current_max_updated_image = *std::max_element(current_estimate.begin_all(), current_estimate.end_all()); + cerr << "Richardson-Lucy iteration: " << iterations_num + << " Updated deconvolved (motion corrected) image value (min, max) (" << current_min_updated_image << ", " + << current_max_updated_image << ")" << endl + << endl; + + if ((iterations_num % save_iterations_interval) == 0) + { + iter_num_str.str(string()); + iter_num_str << "_it" << iterations_num; + string iter_output_filename = output_filename + iter_num_str.str(); + + Succeeded write_result = OutputFileFormat>::default_sptr()->write_to_file( + iter_output_filename, current_estimate); + + if (write_result == Succeeded::yes) + cerr << "Richardson-Lucy iteration: " << iterations_num + << " Writing of current deconvolved image estimate (header file " << iter_output_filename << ") was successful." + << endl + << endl + << endl; + else + { + cerr << "Richardson-Lucy iteration: " << iterations_num + << " Writing of current deconvolved image estimate (header file " << iter_output_filename + << ") did not succeed." << endl + << endl + << endl; + exit(EXIT_FAILURE); + } + } + } + + cerr << "End of EM deconvolution process to compute motion-corrected estimates (after " << num_iterations + << " Richardson-Lucy iterations)" << endl + << endl; +} + +int +main(int argc, char** argv) +{ + if (argc != 7) + print_usage_and_exit(); + + // get parameters from command line + char const* const output_filename = argv[1]; + char const* const input_filename = argv[2]; + + float min_update_factor = 0; + float max_update_factor = NumericInfo().max_value() - 1; + + cerr << "\nPost-reconstruction application of Richardson-Lucy EM deconvolution algorithm to correct for intra-frame/gate motion" + << endl + << endl; + + const shared_ptr> convolved_density_sptr( + read_from_file>(input_filename)); + + GatedSpatialTransformation motion_vectors; + GatedSpatialTransformation reverse_motion_vectors; + + motion_vectors.read_from_files(argv[3]); + reverse_motion_vectors.read_from_files(argv[4]); + + const unsigned int num_iterations(atoi(argv[5])); + const unsigned int save_iterations_interval(atoi(argv[6])); + + cerr << "\nNumber of Richardson-Lucy deconvolution iterations: " << num_iterations << endl + << "\nSave images every " << save_iterations_interval << " iterations" << endl + << endl + << "\nLow threshold for the update factors: " << min_update_factor << endl + << "Upper threshold for the update factors: " << max_update_factor << endl + << endl; + + shared_ptr> corrected_density_sptr(convolved_density_sptr->get_empty_copy()); + shared_ptr> gradient_sptr(convolved_density_sptr->get_empty_copy()); + shared_ptr> model_sensitivity_image_sptr(convolved_density_sptr->get_empty_copy()); + shared_ptr> convolved_image_estimate_sptr(convolved_density_sptr->get_empty_copy()); + shared_ptr> convolved_image_RL_estimate_sptr(convolved_density_sptr->get_empty_copy()); + + // Initialize the deconvolved estimate with an image of ONES for the first EM iteration + std::fill(corrected_density_sptr->begin_all(), corrected_density_sptr->end_all(), 1.F); + + RL_deconvolution(*gradient_sptr, + *corrected_density_sptr, + *model_sensitivity_image_sptr, + *convolved_image_estimate_sptr, + *convolved_image_RL_estimate_sptr, + *convolved_density_sptr, + motion_vectors, + reverse_motion_vectors, + num_iterations, + save_iterations_interval, + min_update_factor, + max_update_factor, + output_filename); + + string sensitivity_prefix = "sens_"; + string output_ending = "_final"; + string sensitivity_image_filename = sensitivity_prefix + output_filename; + string final_iter_image_filename = output_filename + output_ending; + + OutputFileFormat>::default_sptr()->write_to_file(sensitivity_image_filename, + *model_sensitivity_image_sptr); + const Succeeded res = OutputFileFormat>::default_sptr()->write_to_file(final_iter_image_filename, + *corrected_density_sptr); + + return res == Succeeded::yes ? EXIT_SUCCESS : EXIT_FAILURE; +} \ No newline at end of file diff --git a/src/utilities/warp_gated_images.cxx b/src/utilities/warp_gated_images.cxx new file mode 100644 index 0000000000..2650760746 --- /dev/null +++ b/src/utilities/warp_gated_images.cxx @@ -0,0 +1,92 @@ +// +/* + Copyright (C) 2009 - 2013, King's College London + This file is part of STIR. + + This file is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.3 of the License, or + (at your option) any later version. + + This file is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + See STIR/LICENSE.txt for details + */ +/*! + \file + \ingroup utilities + \ingroup spatial_transformation + + \brief This program applies motion transformation (warping) and then either accumulates or averages the resulting gated images + \author Nicolas A Karakatsanis + \author Charalampos Tsoumpas + */ +#include "stir/IO/OutputFileFormat.h" +#include "stir/DiscretisedDensity.h" +#include "stir/GatedDiscretisedDensity.h" +#include "stir/spatial_transformation/GatedSpatialTransformation.h" +#include "stir/Succeeded.h" +#include +#include +#include + +#ifndef STIR_NO_NAMESPACES +using std::cerr; +#endif + +USING_NAMESPACE_STIR + +using namespace BSpline; + +static void +print_usage_and_exit() +{ + cerr << "\nUsage: warp_gated_images [--accumulation | averaging]\n" + << "\t--accumulation sums up all the warped gates in the end (default option)\n" + << "\t--averaging takes the average (mean) of all the warped gates in the end\n"; + exit(EXIT_FAILURE); +} + +int +main(int argc, char** argv) +{ + // Nicolas K.: Argument 3rd is now compulsory and cannot be omitted + //(not compatible with syntax of older STIR utility: warp_and_accumulate_gated_image) + if (argc < 4 || argc > 5) + print_usage_and_exit(); + + // initialise this option as default to allow backward-compatibility with + // usage of older and still active STIR utility: warp_and_accumulated_gated_images. + // Yet 3rd argument here is now compulsory and cannot be omitted + bool doACCUMULATION = true; + if (argc == 5) + { + if (strcmp(argv[4], "--accumulation") == 0) + doACCUMULATION = true; + else if (strcmp(argv[4], "--averaging") == 0) + doACCUMULATION = false; + else + print_usage_and_exit(); + } + + // GatedDiscretisedDensity tmp; + const GatedDiscretisedDensity gated_density(argv[2]); + GatedSpatialTransformation transformation; + + // Nicolas K.: Argument 3rd is now compulsory and cannot be omitted + //(not compatible with syntax of older STIR utility: warp_and_accumulate_gated_image) + transformation.read_from_files(argv[3]); + shared_ptr> corrected_image_sptr((gated_density[1]).get_empty_copy()); + + if (doACCUMULATION) + transformation.warp_image(*corrected_image_sptr, gated_density); + else + transformation.average_warp_image(*corrected_image_sptr, gated_density); + + const Succeeded res + = OutputFileFormat>::default_sptr()->write_to_file(argv[1], *corrected_image_sptr); + return res == Succeeded::yes ? EXIT_SUCCESS : EXIT_FAILURE; +}