From 72e41775408fa4ea0f47808515f07d2751fcc42e Mon Sep 17 00:00:00 2001 From: Nikos E Date: Mon, 26 Sep 2016 13:27:11 +0100 Subject: [PATCH 001/509] Initial commit. Code from the old work transfered. --- ...oodWithLinearModelForMeanAndListModeData.h | 2 +- ...orMeanAndListModeDataWithProjMatrixByBin.h | 25 ++- ...MeanAndListModeDataWithProjMatrixByBin.cxx | 211 ++++++++++++++++-- 3 files changed, 216 insertions(+), 22 deletions(-) diff --git a/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeData.h b/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeData.h index edaf2c36fe..9e87806828 100644 --- a/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeData.h +++ b/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeData.h @@ -115,7 +115,7 @@ public PoissonLogLikelihoodWithLinearModelForMean //! Listmode pointer shared_ptr list_mode_data_sptr; - int current_frame_num; + unsigned int current_frame_num; //! sets any default values /*! Has to be called by set_defaults in the leaf-class */ diff --git a/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.h b/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.h index f425c4803a..a1c503b17e 100644 --- a/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.h +++ b/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.h @@ -2,7 +2,7 @@ // /* Copyright (C) 2003- 2011, Hammersmith Imanet Ltd - + Copyright (C) 2016, UCL 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 @@ -21,6 +21,7 @@ \brief Declaration of class stir::PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin + \author Nikos Efthimiou \author Kris Thielemans \author Sanida Mustafovic @@ -81,6 +82,8 @@ typedef RegisteredParsingObject&); + protected: virtual double actual_compute_objective_function_without_penalty(const TargetT& current_estimate, @@ -110,6 +113,26 @@ typedef RegisteredParsingObject additive_proj_data_sptr; + + //! + //! \brief normalisation_sptr + //! \author Nikos Efthimiou + //! \details The normalization sinogram. I am going to need it, if I want to be able + //! to run listmode reconstruction without any dependencies on sinogram based functions. + shared_ptr normalisation_sptr; + + //! + //! \brief num_events_to_store + //! \author Nikos Efthimiou + //! \details This is part of some functionality I transfer from lm_to_projdata. + //! The total number of events to be *STORED* not *PROCESSED*. + int num_events_to_store; + + //! + //! \brief do_time_frame + //! \author Nikos Efthimiou + //! \details Reconstruct based on time frames? + bool do_time_frame; std::string additive_projection_data_filename ; //! ProjDataInfo diff --git a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx index 397a1228f3..1ce4ea72a8 100644 --- a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx +++ b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx @@ -1,6 +1,6 @@ /* Copyright (C) 2003- 2011, Hammersmith Imanet Ltd - Copyright (C) 2014, University College London + Copyright (C) 2014, 2016, University College London This file is part of STIR. This file is free software; you can redistribute it and/or modify @@ -20,6 +20,7 @@ \brief Implementation of class stir::PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin + \author Nikos Efthimiou \author Kris Thielemans \author Sanida Mustafovic */ @@ -35,6 +36,17 @@ #include "stir/info.h" #include +#include "stir/recon_buildblock/TrivialBinNormalisation.h" +#include "stir/Viewgram.h" +#include "stir/RelatedViewgrams.h" + +#include "stir/recon_array_functions.h" + +#include +#include +#include +#include "stir/stream.h" + #ifdef STIR_MPI #include "stir/recon_buildblock/distributed_functions.h" #endif @@ -56,6 +68,14 @@ PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin() this->set_defaults(); } +template +void +PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin:: +set_normalisation_sptr(const shared_ptr& arg) +{ + this->normalisation_sptr = arg; +} + template void PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin:: @@ -65,7 +85,10 @@ set_defaults() this->additive_proj_data_sptr.reset(); this->additive_projection_data_filename ="0"; this->max_ring_difference_num_to_process =-1; - this->PM_sptr.reset(new ProjMatrixByBinUsingRayTracing()); + this->PM_sptr.reset(new ProjMatrixByBinUsingRayTracing()); + + this->normalisation_sptr.reset(new TrivialBinNormalisation); + this->do_time_frame = false; } template @@ -80,25 +103,92 @@ initialise_keymap() this->parser.add_parsing_key("Matrix type", &this->PM_sptr); this->parser.add_key("additive sinogram",&this->additive_projection_data_filename); - + this->parser.add_key("num_events_to_store",&this->num_events_to_store); + this->parser.add_parsing_key("Bin Normalisation type", &this->normalisation_sptr); + } template int PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin:: set_num_subsets(const int new_num_subsets) { - if (new_num_subsets!=1) - warning("PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin currently only supports 1 subset"); - this->num_subsets = 1; +// if (new_num_subsets!=1) +// warning("PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin currently only supports 1 subset"); +// this->num_subsets = 1; return this->num_subsets; } template bool PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin:: -actual_subsets_are_approximately_balanced(std::string&) const +actual_subsets_are_approximately_balanced(std::string& warning_message) const { - return true; + assert(this->num_subsets>0); + const DataSymmetriesForBins& symmetries = + *this->PM_sptr->get_symmetries_ptr(); + + Array<1,int> num_bins_in_subset(this->num_subsets); + num_bins_in_subset.fill(0); + + + for (int subset_num=0; subset_numnum_subsets; ++subset_num) + { + for (int segment_num = -this->max_ring_difference_num_to_process; + segment_num <= this->max_ring_difference_num_to_process; ++segment_num) + { + for (int axial_num = this->proj_data_info_cyl_uncompressed_ptr->get_min_axial_pos_num(segment_num); + axial_num < this->proj_data_info_cyl_uncompressed_ptr->get_max_axial_pos_num(segment_num); + axial_num ++) + { + // For debugging. + // std::cout <proj_data_info_cyl_uncompressed_ptr->get_min_tangential_pos_num(); + tang_num < this->proj_data_info_cyl_uncompressed_ptr->get_max_tangential_pos_num(); + tang_num ++ ) + { + for(int view_num = this->proj_data_info_cyl_uncompressed_ptr->get_min_view_num() + subset_num; + view_num <= this->proj_data_info_cyl_uncompressed_ptr->get_max_view_num(); + view_num += this->num_subsets) + { + const Bin tmp_bin(segment_num, + view_num, + axial_num, + tang_num, 1); + + if (!this->PM_sptr->get_symmetries_ptr()->is_basic(tmp_bin) ) + continue; + + num_bins_in_subset[subset_num] += + symmetries.num_related_bins(tmp_bin); + + } + } + } + } + } + + for (int subset_num=1; subset_numnum_subsets; ++subset_num) + { + if(num_bins_in_subset[subset_num] != num_bins_in_subset[0]) + { + std::stringstream str(warning_message); + str <<"Number of subsets is such that subsets will be very unbalanced.\n" + << "Number of Bins in each subset would be:\n" + << num_bins_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_info_cyl_uncompressed_ptr->get_num_views() + << "/4 (or if that's not an integer, a divisor of " + << this->proj_data_info_cyl_uncompressed_ptr->get_num_views() + << "/2 or " + << this->proj_data_info_cyl_uncompressed_ptr->get_num_views() + << ").\n"; + warning_message = str.str(); + return false; + } + } + return true; } template @@ -107,14 +197,38 @@ PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin const& target_sptr) { #ifdef STIR_MPI - //broadcast objective_function (100=PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin) - distributed::send_int_value(100, -1); + //broadcast objective_function (100=PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin) + distributed::send_int_value(100, -1); #endif - - - // set projector to be used for the calculations - this->PM_sptr->set_up(this->proj_data_info_cyl_uncompressed_ptr->create_shared_clone(),target_sptr); - return Succeeded::yes; + + + // set projector to be used for the calculations + this->PM_sptr->set_up(this->proj_data_info_cyl_uncompressed_ptr->create_shared_clone(),target_sptr); + + if (is_null_ptr(this->normalisation_sptr)) + { + warning("Invalid normalisation object"); + return Succeeded::no; + } + + if (this->normalisation_sptr->set_up( + this->proj_data_info_cyl_uncompressed_ptr->create_shared_clone()) == Succeeded::no) + return Succeeded::no; + + if (this->current_frame_num<=0) + { + warning("frame_num should be >= 1"); + return Succeeded::no; + } + + if (this->current_frame_num > this->frame_defs.get_num_frames()) + { + warning("frame_num is %d, but should be less than the number of frames %d.", + this->current_frame_num, this->frame_defs.get_num_frames()); + return Succeeded::no; + } + + return Succeeded::yes; } @@ -201,20 +315,46 @@ compute_sub_gradient_without_penalty_plus_sensitivity(TargetT& gradient, const int subset_num) { + assert(subset_num>=0); + assert(subset_numnum_subsets); + + ProjDataInfoCylindricalNoArcCorr* proj_data_no_arc_ptr = + dynamic_cast ( this->proj_data_info_cyl_uncompressed_ptr.get()); + const double start_time = this->frame_defs.get_start_time(this->current_frame_num); const double end_time = this->frame_defs.get_end_time(this->current_frame_num); + + long num_stored_events = 0; + const float max_quotient = 10000.F; + //go to the beginning of this frame // list_mode_data_sptr->set_get_position(start_time); // TODO implement function that will do this for a random time this->list_mode_data_sptr->reset(); double current_time = 0.; ProjMatrixElemsForOneBin proj_matrix_row; + shared_ptr record_sptr = this->list_mode_data_sptr->get_empty_record_sptr(); CListRecord& record = *record_sptr; // int count_of_events=0; //int in_the_range =0; - while (this->list_mode_data_sptr->get_next_record(record) == Succeeded::yes) + + if (num_events_to_store <= 0 ) + do_time_frame = true; + else + do_time_frame = false; + + int more_events = 0 ; + + more_events = do_time_frame? 1 : (num_events_to_store); + + while (more_events)//this->list_mode_data_sptr->get_next_record(record) == Succeeded::yes) { + if (this->list_mode_data_sptr->get_next_record(record) == Succeeded::no) + { + info("End of file!"); + break; //get out of while loop + } //count_of_events++; if(record.is_time()) { @@ -226,15 +366,31 @@ compute_sub_gradient_without_penalty_plus_sensitivity(TargetT& gradient, } if (current_time < start_time) continue; + if (record.is_event() && record.event().is_prompt()) { Bin measured_bin; + measured_bin.set_bin_value(1.0f); + record.event().get_bin(measured_bin, *proj_data_info_cyl_uncompressed_ptr); - if (measured_bin.get_bin_value() <= 0) - continue; + // If more than 1 subsets, check if the current bin belongs to + // the current. + if (this->num_subsets > 1) + { + Bin basic_bin = measured_bin; + if (!this->PM_sptr->get_symmetries_ptr()->is_basic(measured_bin) ) + this->PM_sptr->get_symmetries_ptr()->find_basic_bin(basic_bin); + + if (subset_num != static_cast(basic_bin.view_num() % this->num_subsets)) + { + continue; + } + } + this->PM_sptr->get_proj_matrix_elems_for_one_bin(proj_matrix_row, measured_bin); //in_the_range++; Bin fwd_bin; + fwd_bin.set_bin_value(0.0f); proj_matrix_row.forward_project(fwd_bin,current_estimate); // additive sinogram if (!is_null_ptr(this->additive_proj_data_sptr)) @@ -243,7 +399,22 @@ compute_sub_gradient_without_penalty_plus_sensitivity(TargetT& gradient, float value= fwd_bin.get_bin_value()+add_value; fwd_bin.set_bin_value(value); } - float measured_div_fwd = measured_bin.get_bin_value()/fwd_bin.get_bin_value(); + float measured_div_fwd = 0.0f; + + if(!do_time_frame) + more_events -=1 ; + + num_stored_events += 1; + + if (num_stored_events%100000L==0) + info( boost::format("Proccessed events: %1% ") % num_stored_events); + + if ( measured_bin.get_bin_value() <= max_quotient *fwd_bin.get_bin_value()) + measured_div_fwd = 1.0f /fwd_bin.get_bin_value(); + else + continue; + +// float measured_div_fwd = measured_bin.get_bin_value()/fwd_bin.get_bin_value(); measured_bin.set_bin_value(measured_div_fwd); proj_matrix_row.back_project(gradient, measured_bin); @@ -251,7 +422,7 @@ compute_sub_gradient_without_penalty_plus_sensitivity(TargetT& gradient, } // cerr << " The number_of_events " << count_of_events << " "; //cerr << " The number of events proecessed " << in_the_range << " "; - + info(boost::format("Number of used events: %1%") % num_stored_events); } # ifdef _MSC_VER From 25a8067b23ca5d2b281ea8324241f7e2f87b0e4f Mon Sep 17 00:00:00 2001 From: Nikos E Date: Mon, 3 Oct 2016 14:52:38 +0100 Subject: [PATCH 002/509] Some functions added in the listmode objective function. --- ...oodWithLinearModelForMeanAndListModeData.h | 13 ++ ...orMeanAndListModeDataWithProjMatrixByBin.h | 33 +--- ...dWithLinearModelForMeanAndListModeData.cxx | 14 +- ...MeanAndListModeDataWithProjMatrixByBin.cxx | 144 +++++++++++++----- 4 files changed, 136 insertions(+), 68 deletions(-) diff --git a/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeData.h b/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeData.h index 9e87806828..69b6e076a3 100644 --- a/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeData.h +++ b/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeData.h @@ -116,6 +116,19 @@ public PoissonLogLikelihoodWithLinearModelForMean shared_ptr list_mode_data_sptr; unsigned int current_frame_num; + + //! + //! \brief num_events_to_store + //! \author Nikos Efthimiou + //! \details This is part of some functionality I transfer from lm_to_projdata. + //! The total number of events to be *STORED* not *PROCESSED*. + int num_events_to_store; + + //! + //! \brief do_time_frame + //! \author Nikos Efthimiou + //! \details Reconstruct based on time frames? + bool do_time_frame; //! sets any default values /*! Has to be called by set_defaults in the leaf-class */ diff --git a/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.h b/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.h index a1c503b17e..16b82a5f90 100644 --- a/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.h +++ b/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.h @@ -82,8 +82,6 @@ typedef RegisteredParsingObject&); - protected: virtual double actual_compute_objective_function_without_penalty(const TargetT& current_estimate, @@ -96,12 +94,8 @@ typedef RegisteredParsingObject const& target_sptr); - // TODO virtual void - add_subset_sensitivity(TargetT& sensitivity, const int subset_num) const - { - error("add_subset_sensitivity not implemented yet"); - } + add_subset_sensitivity(TargetT& sensitivity, const int subset_num) const; //! Maximum ring difference to take into account /*! \todo Might be removed */ @@ -109,30 +103,9 @@ typedef RegisteredParsingObject PM_sptr; - //shared_ptr projector_by_bin_pair; //! points to the additive projection data - shared_ptr additive_proj_data_sptr; - - //! - //! \brief normalisation_sptr - //! \author Nikos Efthimiou - //! \details The normalization sinogram. I am going to need it, if I want to be able - //! to run listmode reconstruction without any dependencies on sinogram based functions. - shared_ptr normalisation_sptr; - - //! - //! \brief num_events_to_store - //! \author Nikos Efthimiou - //! \details This is part of some functionality I transfer from lm_to_projdata. - //! The total number of events to be *STORED* not *PROCESSED*. - int num_events_to_store; - - //! - //! \brief do_time_frame - //! \author Nikos Efthimiou - //! \details Reconstruct based on time frames? - bool do_time_frame; + shared_ptr additive_proj_data_sptr; std::string additive_projection_data_filename ; //! ProjDataInfo @@ -147,6 +120,8 @@ typedef RegisteredParsingObjectlist_mode_filename =""; this->frame_defs_filename =""; this->list_mode_data_sptr.reset(); - this->current_frame_num =0; + this->current_frame_num = 1; this->output_image_size_xy=-1; this->output_image_size_z=-1; @@ -81,7 +81,7 @@ initialise_keymap() this->parser.add_key("time frame definition filename", &this->frame_defs_filename); // SM TODO -- later do not parse this->parser.add_key("time frame number", &this->current_frame_num); - + this->parser.add_parsing_key("Bin Normalisation type", &this->normalisation_sptr); } template @@ -93,6 +93,14 @@ PoissonLogLikelihoodWithLinearModelForMeanAndListModeData::post_process if (this->list_mode_filename.length() == 0) { warning("You need to specify an input file\n"); return true; } + + do_time_frame = num_events_to_store<=0; + + if (do_time_frame && frame_defs_filename.size()==0) + { + warning("Have to specify either 'frame_definition_filename' or 'num_events_to_store'\n"); + return true; + } this->list_mode_data_sptr= read_from_file(this->list_mode_filename); @@ -115,7 +123,7 @@ PoissonLogLikelihoodWithLinearModelForMeanAndListModeData::post_process { warning("output image size z must be positive (or -1 as default)\n"); return true; } - return false; + return false; } template diff --git a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx index 1ce4ea72a8..8a6e60ace0 100644 --- a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx +++ b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx @@ -68,14 +68,6 @@ PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin() this->set_defaults(); } -template -void -PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin:: -set_normalisation_sptr(const shared_ptr& arg) -{ - this->normalisation_sptr = arg; -} - template void PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin:: @@ -104,17 +96,13 @@ initialise_keymap() this->parser.add_key("additive sinogram",&this->additive_projection_data_filename); this->parser.add_key("num_events_to_store",&this->num_events_to_store); - this->parser.add_parsing_key("Bin Normalisation type", &this->normalisation_sptr); - } template int PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin:: set_num_subsets(const int new_num_subsets) { -// if (new_num_subsets!=1) -// warning("PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin currently only supports 1 subset"); -// this->num_subsets = 1; + this->num_subsets = new_num_subsets; return this->num_subsets; } @@ -264,29 +252,109 @@ PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBinget_num_rings()-1; } - - if (this->additive_projection_data_filename != "0") - { + + if (this->additive_projection_data_filename != "0") + { info(boost::format("Reading additive projdata data '%1%'") % additive_projection_data_filename ); - shared_ptr temp_additive_proj_data_sptr = - ProjData::read_from_file(this->additive_projection_data_filename); + shared_ptr temp_additive_proj_data_sptr = + ProjData::read_from_file(this->additive_projection_data_filename); this->additive_proj_data_sptr.reset(new ProjDataInMemory(* temp_additive_proj_data_sptr)); - } - + } + this->proj_data_info_cyl_uncompressed_ptr.reset( dynamic_cast( - ProjDataInfo::ProjDataInfoCTI(scanner_sptr, + ProjDataInfo::ProjDataInfoCTI(scanner_sptr, 1, this->max_ring_difference_num_to_process, scanner_sptr->get_num_detectors_per_ring()/2, - scanner_sptr->get_default_num_arccorrected_bins(), - false))); + scanner_sptr->get_max_num_non_arccorrected_bins(), + false))); + + if ( this->normalisation_sptr->set_up(proj_data_info_cyl_uncompressed_ptr) + != Succeeded::yes) + { +warning("PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin: " + "set-up of pre-normalisation failed\n"); +return true; + } + + + return false; } - +template +void +PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin:: +add_subset_sensitivity(TargetT& sensitivity, const int subset_num) const +{ + std::cout << "!Nere " <max_ring_difference_num_to_process; + const int max_segment_num = this->max_ring_difference_num_to_process; + + std::cout << min_segment_num<< " "<< max_segment_num <proj_data_info_cyl_uncompressed_ptr->get_min_axial_pos_num(segment_num); + axial_num < this->proj_data_info_cyl_uncompressed_ptr->get_max_axial_pos_num(segment_num); + axial_num ++) + { + // For debugging. + std::cout <proj_data_info_cyl_uncompressed_ptr->get_min_tangential_pos_num(); + tang_num < this->proj_data_info_cyl_uncompressed_ptr->get_max_tangential_pos_num(); + tang_num ++ ) + { + + for(int view_num = this->proj_data_info_cyl_uncompressed_ptr->get_min_view_num() + subset_num; + view_num <= this->proj_data_info_cyl_uncompressed_ptr->get_max_view_num(); + view_num += this->num_subsets) + { + Bin tmp_bin(segment_num, + view_num, + axial_num, + tang_num, 1); + + if (!this->PM_sptr->get_symmetries_ptr()->is_basic(tmp_bin) ) + continue; + + this->add_projmatrix_to_sensitivity(sensitivity, tmp_bin); + } + } + } + } +} + +template +void +PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin:: +add_projmatrix_to_sensitivity(TargetT& sensitivity, Bin & this_basic_bin) const +{ + std::vector r_bins; + ProjMatrixElemsForOneBin probabilities; + + this->PM_sptr->get_symmetries_ptr()->get_related_bins(r_bins, this_basic_bin); + + for (unsigned int i = 0; i < r_bins.size(); i++) + r_bins[i].set_bin_value(1.0); + + + // find efficiencies + { + const double start_frame = this->frame_defs.get_start_time(this->current_frame_num); + const double end_frame = this->frame_defs.get_end_time(this->current_frame_num); +// this->normalisation_sptr->undo(r_bins, this->PM_sptr->get_symmetries_ptr(), +// start_frame,end_frame); + } + + this->PM_sptr->get_proj_matrix_elems_for_one_bin(probabilities, this_basic_bin); +// probabilities.back_project(sensitivity, r_bins, this->PM_sptr->get_symmetries_ptr()); +} template TargetT * @@ -336,20 +404,24 @@ compute_sub_gradient_without_penalty_plus_sensitivity(TargetT& gradient, shared_ptr record_sptr = this->list_mode_data_sptr->get_empty_record_sptr(); CListRecord& record = *record_sptr; - // int count_of_events=0; - //int in_the_range =0; - - if (num_events_to_store <= 0 ) - do_time_frame = true; - else - do_time_frame = false; - - int more_events = 0 ; - more_events = do_time_frame? 1 : (num_events_to_store); +long long int more_events = 0 ; + if (this->num_events_to_store <= - 1) + more_events = this->list_mode_data_sptr->get_total_number_of_events(); + else if (this->num_events_to_store == 0 ) + { + this->do_time_frame = true; + more_events = 1; + } + else + { + this->do_time_frame = false; + more_events = this->num_events_to_store; + } while (more_events)//this->list_mode_data_sptr->get_next_record(record) == Succeeded::yes) { + if (this->list_mode_data_sptr->get_next_record(record) == Succeeded::no) { info("End of file!"); @@ -360,7 +432,7 @@ compute_sub_gradient_without_penalty_plus_sensitivity(TargetT& gradient, { current_time = record.time().get_time_in_secs(); } - if (current_time >= end_time) + if (this->do_time_frame && current_time >= end_time) { break; // get out of while loop } @@ -401,7 +473,7 @@ compute_sub_gradient_without_penalty_plus_sensitivity(TargetT& gradient, } float measured_div_fwd = 0.0f; - if(!do_time_frame) + if(!this->do_time_frame) more_events -=1 ; num_stored_events += 1; From fc6b0b8f8769d5c2753c5e0de67632195c7e1e5f Mon Sep 17 00:00:00 2001 From: Nikos E Date: Sun, 9 Oct 2016 02:03:58 +0100 Subject: [PATCH 003/509] LM reconstruction is working and is able to calculate the sensitivity image and I am working to be able to apply the normalisation factors. I had problems with the RelatedBins class, therefore I created several fucntion which take as input a vector and a DataSymmetriesFromBin, which essentially is the same thing ( at least for this application). --- src/buildblock/ProjDataFromStream.cxx | 177 ++++++++++++++++++ src/include/stir/Bin.h | 6 + src/include/stir/Bin.inl | 23 +++ src/include/stir/ProjData.h | 1 + src/include/stir/ProjDataFromStream.h | 36 ++++ .../stir/recon_buildblock/BinNormalisation.h | 13 +- .../BinNormalisationFromECAT7.h | 2 +- ...oodWithLinearModelForMeanAndListModeData.h | 2 +- .../stir/recon_buildblock/ProjMatrixByBin.h | 5 +- .../stir/recon_buildblock/ProjMatrixByBin.inl | 12 +- .../ProjMatrixElemsForOneBin.h | 10 +- src/listmode_buildblock/LmToProjData.cxx | 6 +- src/recon_buildblock/BinNormalisation.cxx | 27 +++ ...dWithLinearModelForMeanAndListModeData.cxx | 14 +- ...MeanAndListModeDataWithProjMatrixByBin.cxx | 42 ++--- src/recon_buildblock/ProjMatrixByBin.cxx | 2 +- .../ProjMatrixByBinFromFile.cxx | 4 +- .../ProjMatrixByBinSPECTUB.cxx | 20 +- .../ProjMatrixByBinUsingInterpolation.cxx | 2 +- .../ProjMatrixByBinUsingRayTracing.cxx | 2 +- .../ProjMatrixElemsForOneBin.cxx | 24 +++ 21 files changed, 369 insertions(+), 61 deletions(-) diff --git a/src/buildblock/ProjDataFromStream.cxx b/src/buildblock/ProjDataFromStream.cxx index 8ada90b67e..8ecbf9f0d5 100644 --- a/src/buildblock/ProjDataFromStream.cxx +++ b/src/buildblock/ProjDataFromStream.cxx @@ -194,6 +194,96 @@ ProjDataFromStream::get_viewgram(const int view_num, const int segment_num, return viewgram; } +float +ProjDataFromStream::get_bin_value(const Bin& this_bin) const +{ + return get_bin_value(this_bin.segment_num(), + this_bin.axial_pos_num(), + this_bin.view_num(), + this_bin.tangential_pos_num()); +} + + +float +ProjDataFromStream::get_bin_value(const int segment_num, + const int axial_pos_num, + const int view_num, + const int tang_pos_num) const +{ + if (sino_stream == 0) + { + error("ProjDataFromStream::get_viewgram: stream ptr is 0\n"); + } + if (! *sino_stream) + { + error("ProjDataFromStream::get_viewgram: error in stream state before reading\n"); + } + + vector offsets = get_offsets_bin(segment_num, axial_pos_num, view_num, tang_pos_num); + + const streamoff total_offset = offsets[0]; + + sino_stream->seekg(0 , ios::beg); // reset file + sino_stream->seekg(total_offset, ios::cur); // start of view within segment + + if (! *sino_stream) + { + error("ProjDataFromStream::get_viewgram: error after seekg\n"); + } + + Array< 1, float> value; + value.resize(1); + float scale = float(1); + + // Now the storage order is not more important. Just read. + if (read_data(*sino_stream, value, on_disk_data_type, scale, on_disk_byte_order) + == Succeeded::no) + error("ProjDataFromStream: error reading data\n"); + if(scale != 1.f) + error("ProjDataFromStream: error reading data: scale factor returned by read_data should be 1\n"); + +// if (get_storage_order() == Segment_AxialPos_View_TangPos) +// { +// for (int ax_pos_num = get_min_axial_pos_num(segment_num); ax_pos_num <= get_max_axial_pos_num(segment_num); ax_pos_num++) +// { + +// if (read_data(*sino_stream, viewgram[ax_pos_num], on_disk_data_type, scale, on_disk_byte_order) +// == Succeeded::no) +// error("ProjDataFromStream: error reading data\n"); +// if(scale != 1) +// error("ProjDataFromStream: error reading data: scale factor returned by read_data should be 1\n"); +// // seek to next line unless it was the last we need to read +// if(ax_pos_num != get_max_axial_pos_num(segment_num)) +// sino_stream->seekg(intra_views_offset, ios::cur); +// } +// } +// else if (get_storage_order() == Segment_View_AxialPos_TangPos) +// { +// if(read_data(*sino_stream, viewgram, on_disk_data_type, scale, on_disk_byte_order) +// == Succeeded::no) +// error("ProjDataFromStream: error reading data\n"); +// if(scale != 1) +// error("ProjDataFromStream: error reading data: scale factor returned by read_data should be 1\n"); +// } + + value *= scale_factor; + + // if (make_num_tangential_poss_odd &&(get_num_tangential_poss()%2==0)) + // { + // const int new_max_tangential_pos = get_max_tangential_pos_num() + 1; + + // viewgram.grow( + // IndexRange2D(get_min_axial_pos_num(segment_num), + // get_max_axial_pos_num(segment_num), + + // get_min_tangential_pos_num(), + // new_max_tangential_pos)); + // } + + return value[0]; +} + + vector ProjDataFromStream::get_offsets(const int view_num, const int segment_num) const @@ -371,6 +461,93 @@ ProjDataFromStream::set_viewgram(const Viewgram& v) } +std::vector +ProjDataFromStream::get_offsets_bin(const int segment_num, + const int ax_pos_num, + const int view_num, + const int tang_pos_num) const +{ + + if (!(segment_num >= get_min_segment_num() && + segment_num <= get_max_segment_num())) + error("ProjDataFromStream::get_offsets: segment_num out of range : %d", segment_num); + + if (!(ax_pos_num >= get_min_axial_pos_num(segment_num) && + ax_pos_num <= get_max_axial_pos_num(segment_num))) + error("ProjDataFromStream::get_offsets: axial_pos_num out of range : %d", ax_pos_num); + + + const int index = + static_cast(FIND(segment_sequence.begin(), segment_sequence.end(), segment_num) - + segment_sequence.begin()); + + streamoff num_axial_pos_offset = 0; + + for (int i=0; i(num_axial_pos_offset* + get_num_tangential_poss() * + get_num_views() * + on_disk_data_type.size_in_bytes()); + + // Now we are just in front of the correct segment + if (get_storage_order() == Segment_AxialPos_View_TangPos) + { + // skip axial positions + const streamoff ax_pos_offset = + (ax_pos_num - get_min_axial_pos_num(segment_num))* + get_num_views() * + get_num_tangential_poss()* + on_disk_data_type.size_in_bytes(); + + // sinogram location + + //find view + const streamoff view_offset = + (view_num - get_min_view_num()) + * get_num_tangential_poss() + * on_disk_data_type.size_in_bytes(); + + // find tang pos + const streamoff tang_offset = + (tang_pos_num - get_min_tangential_pos_num()) * on_disk_data_type.size_in_bytes(); + + vector temp(1); + temp[0] = segment_offset + ax_pos_offset + view_offset +tang_offset; + + return temp; + } + else //if (get_storage_order() == Segment_View_AxialPos_TangPos) + { + + // Skip views + const streamoff view_offset = + (view_num - get_min_view_num())* + get_num_axial_poss(segment_num) * + get_num_tangential_poss()* + on_disk_data_type.size_in_bytes(); + + + // find axial pos + const streamoff ax_pos_offset = + (ax_pos_num - get_min_axial_pos_num(segment_num)) * + get_num_tangential_poss()* + on_disk_data_type.size_in_bytes(); + + // find tang pos + const streamoff tang_offset = + (tang_pos_num - get_min_tangential_pos_num()) * on_disk_data_type.size_in_bytes(); + + vector temp(1); + temp[0] = segment_offset + ax_pos_offset +view_offset + tang_offset; + + return temp; + } +} // get offsets for the sino data diff --git a/src/include/stir/Bin.h b/src/include/stir/Bin.h index d766257696..0233bf18cc 100644 --- a/src/include/stir/Bin.h +++ b/src/include/stir/Bin.h @@ -79,6 +79,12 @@ class Bin //! accumulate voxel's contribution during forward projection inline Bin& operator+=(const float dx); + //! multiply bin values during normalisation -- apply() + inline Bin& operator*=(const float dx); + //! divide bin values during normalisation -- undo() + //! \todo It is zero division proof in a similar way to divide<,,>(), though I am + //! not sure if it should be. + inline Bin& operator/=(const float dx); //! comparison operators inline bool operator==(const Bin&) const; diff --git a/src/include/stir/Bin.inl b/src/include/stir/Bin.inl index c4607bade6..014f08c821 100644 --- a/src/include/stir/Bin.inl +++ b/src/include/stir/Bin.inl @@ -32,6 +32,8 @@ START_NAMESPACE_STIR +const float SMALL_NUM = 0.000001F; + Bin::Bin() {} @@ -120,4 +122,25 @@ Bin::operator!=(const Bin& bin2) const return !(*this==bin2); } +Bin& +Bin::operator*=(const float dx) +{ + bin_value*=dx; + return *this; +} + +Bin& +Bin::operator/=(const float dx) +{ + float small_value= + bin_value * SMALL_NUM; + + if (std::fabs(dx) < small_value) + bin_value = 0.0f; + else + bin_value /= dx; + + return *this; +} + END_NAMESPACE_STIR diff --git a/src/include/stir/ProjData.h b/src/include/stir/ProjData.h index db213ea41d..a0ba177e9d 100644 --- a/src/include/stir/ProjData.h +++ b/src/include/stir/ProjData.h @@ -36,6 +36,7 @@ #include "stir/Succeeded.h" #include "stir/SegmentBySinogram.h" #include "stir/SegmentByView.h" +#include "stir/Bin.h" //#include #include "stir/IO/ExamData.h" diff --git a/src/include/stir/ProjDataFromStream.h b/src/include/stir/ProjDataFromStream.h index 94f7288200..a333521ba3 100644 --- a/src/include/stir/ProjDataFromStream.h +++ b/src/include/stir/ProjDataFromStream.h @@ -138,6 +138,28 @@ class ProjDataFromStream : public ProjData //! Get scale factor float get_scale_factor() const; + //! + //! \brief get_bin_value + //! \param segment_num + //! \param axial_pos_num + //! \param view_num + //! \param tang_pos_num + //! \return + //! \author Nikos Efthimiou + //! \details This function return the value of a single bin stored in a sinogram in the + //! disk. + float get_bin_value(const int segment_num, + const int axial_pos_num, + const int view_num, + const int tang_pos_num) const; + + //! + //! \brief get_bin_value + //! \param this_bin + //! \return + //! \author Nikos Efthimiou + //! \details Overloaded + float get_bin_value(const Bin& this_bin) const; protected: //! the stream with the data @@ -171,6 +193,20 @@ class ProjDataFromStream : public ProjData //! Calculate offsets for sinogram data std::vector get_offsets_sino(const int ax_pos_num, const int segment_num) const; + //! + //! \brief get_offsets_bin + //! \param segment_num + //! \param axial_pos_num + //! \param view_num + //! \param tang_pos_num + //! \return + //! \author Nikos Efthimiou + //! \details I could make use of get_offsets and get_offset_sino to extract the final offset of the + //! bin, but it would be another one burden in an already slow procedure. + std::vector get_offsets_bin(const int segment_num, + const int ax_pos_num, + const int view_num, + const int tang_pos_num) const; }; diff --git a/src/include/stir/recon_buildblock/BinNormalisation.h b/src/include/stir/recon_buildblock/BinNormalisation.h index 2f3b35436a..1e4530aa0f 100644 --- a/src/include/stir/recon_buildblock/BinNormalisation.h +++ b/src/include/stir/recon_buildblock/BinNormalisation.h @@ -33,6 +33,7 @@ #include "stir/RegisteredObject.h" #include "stir/Bin.h" #include "stir/shared_ptr.h" +#include "DataSymmetriesForBins.h" START_NAMESPACE_STIR @@ -125,7 +126,17 @@ class BinNormalisation : public RegisteredObject The default value for the symmetries means that TrivialDataSymmetriesForBins will be used. */ void undo(ProjData&,const double start_time, const double end_time, - shared_ptr = shared_ptr()) const; + shared_ptr = shared_ptr()) const; + + //! This is the a bin-wise overload for the undo function. + //! \todo RelatedBins should be used, instead of a std::vector. + void apply(std::vector& bins, + const double start_time, const double end_time) const; + + //! This is the a bin-wise overload for the apply function. + //! \todo RelatedBins should be used, instead of a std::vector. + void undo(std::vector& bins, + const double start_time, const double end_time) const; }; diff --git a/src/include/stir/recon_buildblock/BinNormalisationFromECAT7.h b/src/include/stir/recon_buildblock/BinNormalisationFromECAT7.h index 32c0ddfd2a..3d1f63477c 100644 --- a/src/include/stir/recon_buildblock/BinNormalisationFromECAT7.h +++ b/src/include/stir/recon_buildblock/BinNormalisationFromECAT7.h @@ -95,7 +95,7 @@ class BinNormalisationFromECAT7 : BinNormalisationFromECAT7(const std::string& filename); virtual Succeeded set_up(const shared_ptr&); - float get_bin_efficiency(const Bin& bin, const double start_time, const double end_time) const; + virtual float get_bin_efficiency(const Bin& bin, const double start_time, const double end_time) const; bool use_detector_efficiencies() const; bool use_dead_time() const; diff --git a/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeData.h b/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeData.h index 69b6e076a3..41a051045c 100644 --- a/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeData.h +++ b/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeData.h @@ -122,7 +122,7 @@ public PoissonLogLikelihoodWithLinearModelForMean //! \author Nikos Efthimiou //! \details This is part of some functionality I transfer from lm_to_projdata. //! The total number of events to be *STORED* not *PROCESSED*. - int num_events_to_store; + unsigned long int num_events_to_store; //! //! \brief do_time_frame diff --git a/src/include/stir/recon_buildblock/ProjMatrixByBin.h b/src/include/stir/recon_buildblock/ProjMatrixByBin.h index c9e0a51efd..684a03dbcc 100644 --- a/src/include/stir/recon_buildblock/ProjMatrixByBin.h +++ b/src/include/stir/recon_buildblock/ProjMatrixByBin.h @@ -108,7 +108,8 @@ class ProjMatrixByBin : //! get a pointer to an object encoding all symmetries that are used by this ProjMatrixByBin inline const DataSymmetriesForBins* get_symmetries_ptr() const; - + //! get a shared_ptr to an object encoding all symmetries that are used by this ProjMatrixByBin + inline const shared_ptr get_symmetries_sptr() const; //! The main method for getting a row of the matrix. /*! @@ -155,7 +156,7 @@ class ProjMatrixByBin : protected: - shared_ptr symmetries_ptr; + shared_ptr symmetries_sptr; //! default ctor (calls set_defaults()) /*! Note that due to the C++ definition (and some good reasons), diff --git a/src/include/stir/recon_buildblock/ProjMatrixByBin.inl b/src/include/stir/recon_buildblock/ProjMatrixByBin.inl index 4540c34467..e288d8a5cf 100644 --- a/src/include/stir/recon_buildblock/ProjMatrixByBin.inl +++ b/src/include/stir/recon_buildblock/ProjMatrixByBin.inl @@ -37,7 +37,13 @@ START_NAMESPACE_STIR const DataSymmetriesForBins* ProjMatrixByBin:: get_symmetries_ptr() const { - return symmetries_ptr.get(); + return symmetries_sptr.get(); +} + +const shared_ptr +ProjMatrixByBin:: get_symmetries_sptr() const +{ + return symmetries_sptr; } inline void @@ -56,7 +62,7 @@ get_proj_matrix_elems_for_one_bin( // find basic bin Bin basic_bin = bin; std::auto_ptr symm_ptr = - symmetries_ptr->find_symmetry_operation_from_basic_bin(basic_bin); + symmetries_sptr->find_symmetry_operation_from_basic_bin(basic_bin); probabilities.set_bin(basic_bin); // check if basic bin is in cache @@ -84,7 +90,7 @@ get_proj_matrix_elems_for_one_bin( // find basic bin Bin basic_bin = bin; std::auto_ptr symm_ptr = - symmetries_ptr->find_symmetry_operation_from_basic_bin(basic_bin); + symmetries_sptr->find_symmetry_operation_from_basic_bin(basic_bin); probabilities.set_bin(basic_bin); // check if basic bin is in cache diff --git a/src/include/stir/recon_buildblock/ProjMatrixElemsForOneBin.h b/src/include/stir/recon_buildblock/ProjMatrixElemsForOneBin.h index a869a966c9..ce59cb8879 100644 --- a/src/include/stir/recon_buildblock/ProjMatrixElemsForOneBin.h +++ b/src/include/stir/recon_buildblock/ProjMatrixElemsForOneBin.h @@ -37,8 +37,10 @@ #include "stir/recon_buildblock/ProjMatrixElemsForOneBinValue.h" +#include "stir/recon_buildblock/DataSymmetriesForBins.h" #include "stir/Bin.h" #include +#include START_NAMESPACE_STIR @@ -197,7 +199,13 @@ class ProjMatrixElemsForOneBin const DiscretisedDensity<3,float>&) const; //! back project related bins void back_project(DiscretisedDensity<3,float>&, - const RelatedBins&) const; + const RelatedBins&) const; + //! Alternative to back project related bins. + //! \warning N.E. I use this function just because I cannot get the + //! RelatedBins class to work for me. + void back_project(DiscretisedDensity<3,float>&, + const std::vector&, + const shared_ptr&); //! forward project related bins void forward_project(RelatedBins&, const DiscretisedDensity<3,float>&) const; diff --git a/src/listmode_buildblock/LmToProjData.cxx b/src/listmode_buildblock/LmToProjData.cxx index d79313cd8a..2113b26737 100644 --- a/src/listmode_buildblock/LmToProjData.cxx +++ b/src/listmode_buildblock/LmToProjData.cxx @@ -587,8 +587,8 @@ process_data() // ('allowed' independent on the fact of we have its segment in memory or not) // When do_time_frame=true, the number of events is irrelevant, so we // just set more_events to 1, and never change it - long more_events = - do_time_frame? 1 : static_cast(num_events_to_store); + unsigned long int more_events = + do_time_frame? 1 : num_events_to_store; if (start_segment_index != proj_data_ptr->get_min_segment_num()) { @@ -666,7 +666,7 @@ process_data() continue; if (!do_time_frame) - more_events-= event_increment; + more_events -= event_increment; // now check if we have its segment in memory if (bin.segment_num() >= start_segment_index && bin.segment_num()<=end_segment_index) diff --git a/src/recon_buildblock/BinNormalisation.cxx b/src/recon_buildblock/BinNormalisation.cxx index 94a974161c..6f2cc590bd 100644 --- a/src/recon_buildblock/BinNormalisation.cxx +++ b/src/recon_buildblock/BinNormalisation.cxx @@ -174,6 +174,33 @@ undo(ProjData& proj_data,const double start_time, const double end_time, } } +void +BinNormalisation:: +apply(std::vector& bins, + const double start_time, const double end_time) const +{ + + for (std::vector::iterator iter = bins.begin(); iter !=bins.end(); ++iter) + *iter *= this->get_bin_efficiency(*iter, start_time, end_time); + + // TODO: DELETE THE FOLLOWING LINES +// std::vector normalization_values = norm_proj_data_ptr->get_related_bin_values(bins, symmetries); +// assert(bins.size() == normalization_values.size()); + +// for (unsigned int i = 0; i < bins.size(); i++) +// { +// bins[i].set_bin_value(bins.at(i).get_bin_value() / normalization_values.at(i)); +// } +} + +void +BinNormalisation:: +undo(std::vector& bins, + const double start_time, const double end_time) const +{ + for (std::vector::iterator iter = bins.begin(); iter !=bins.end(); ++iter) + *iter /= this->get_bin_efficiency(*iter, start_time, end_time); +} END_NAMESPACE_STIR diff --git a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeData.cxx b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeData.cxx index 1b3f4a9aa0..794e21fe18 100644 --- a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeData.cxx +++ b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeData.cxx @@ -54,6 +54,7 @@ set_defaults() this->frame_defs_filename =""; this->list_mode_data_sptr.reset(); this->current_frame_num = 1; + this->num_events_to_store = 0; this->output_image_size_xy=-1; this->output_image_size_z=-1; @@ -94,13 +95,10 @@ PoissonLogLikelihoodWithLinearModelForMeanAndListModeData::post_process if (this->list_mode_filename.length() == 0) { warning("You need to specify an input file\n"); return true; } - do_time_frame = num_events_to_store<=0; - - if (do_time_frame && frame_defs_filename.size()==0) - { - warning("Have to specify either 'frame_definition_filename' or 'num_events_to_store'\n"); - return true; - } + // handle time frame definitions etc + // If num_events_to_store == 0 && frame_definition_filename.size == 0 + if(num_events_to_store==0 && frame_defs_filename.size() == 0) + do_time_frame = true; this->list_mode_data_sptr= read_from_file(this->list_mode_filename); @@ -110,7 +108,7 @@ PoissonLogLikelihoodWithLinearModelForMeanAndListModeData::post_process else { // make a single frame starting from 0. End value will be ignored. - vector > frame_times(1, pair(0,1)); + vector > frame_times(1, pair(0,0)); this->frame_defs = TimeFrameDefinitions(frame_times); } // image stuff diff --git a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx index 8a6e60ace0..16c4d3dcb9 100644 --- a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx +++ b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx @@ -318,7 +318,7 @@ add_subset_sensitivity(TargetT& sensitivity, const int subset_num) const Bin tmp_bin(segment_num, view_num, axial_num, - tang_num, 1); + tang_num, 1.f); if (!this->PM_sptr->get_symmetries_ptr()->is_basic(tmp_bin) ) continue; @@ -336,24 +336,28 @@ PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin r_bins; - ProjMatrixElemsForOneBin probabilities; + ProjMatrixElemsForOneBin elem_row; this->PM_sptr->get_symmetries_ptr()->get_related_bins(r_bins, this_basic_bin); + if (r_bins.size() == 0 ) + error("Something went wrong with the symmetries. Abort."); + for (unsigned int i = 0; i < r_bins.size(); i++) - r_bins[i].set_bin_value(1.0); + r_bins[i].set_bin_value(1.0f); // find efficiencies { const double start_frame = this->frame_defs.get_start_time(this->current_frame_num); const double end_frame = this->frame_defs.get_end_time(this->current_frame_num); -// this->normalisation_sptr->undo(r_bins, this->PM_sptr->get_symmetries_ptr(), -// start_frame,end_frame); + this->normalisation_sptr->undo(r_bins, start_frame,end_frame); } - this->PM_sptr->get_proj_matrix_elems_for_one_bin(probabilities, this_basic_bin); -// probabilities.back_project(sensitivity, r_bins, this->PM_sptr->get_symmetries_ptr()); + this->PM_sptr->get_proj_matrix_elems_for_one_bin(elem_row, this_basic_bin); + //N.E: I had problems with RelatedBins thats why I use a std::vector and + // symmetries. + elem_row.back_project(sensitivity, r_bins, this->PM_sptr->get_symmetries_sptr()); } template @@ -405,19 +409,8 @@ compute_sub_gradient_without_penalty_plus_sensitivity(TargetT& gradient, shared_ptr record_sptr = this->list_mode_data_sptr->get_empty_record_sptr(); CListRecord& record = *record_sptr; -long long int more_events = 0 ; - if (this->num_events_to_store <= - 1) - more_events = this->list_mode_data_sptr->get_total_number_of_events(); - else if (this->num_events_to_store == 0 ) - { - this->do_time_frame = true; - more_events = 1; - } - else - { - this->do_time_frame = false; - more_events = this->num_events_to_store; - } + unsigned long int more_events = + this->do_time_frame? 1 : this->num_events_to_store; while (more_events)//this->list_mode_data_sptr->get_next_record(record) == Succeeded::yes) { @@ -427,8 +420,8 @@ long long int more_events = 0 ; info("End of file!"); break; //get out of while loop } - //count_of_events++; - if(record.is_time()) + + if(record.is_time() && end_time > 0.01) { current_time = record.time().get_time_in_secs(); } @@ -486,14 +479,11 @@ long long int more_events = 0 ; else continue; -// float measured_div_fwd = measured_bin.get_bin_value()/fwd_bin.get_bin_value(); measured_bin.set_bin_value(measured_div_fwd); proj_matrix_row.back_project(gradient, measured_bin); } - } - // cerr << " The number_of_events " << count_of_events << " "; - //cerr << " The number of events proecessed " << in_the_range << " "; + } info(boost::format("Number of used events: %1%") % num_stored_events); } diff --git a/src/recon_buildblock/ProjMatrixByBin.cxx b/src/recon_buildblock/ProjMatrixByBin.cxx index 8ba26c6443..4aa5993fc2 100644 --- a/src/recon_buildblock/ProjMatrixByBin.cxx +++ b/src/recon_buildblock/ProjMatrixByBin.cxx @@ -202,7 +202,7 @@ get_cached_proj_matrix_elems_for_one_bin( { // Check that this is a 'basic' coordinate Bin bin_copy = bin; - assert ( symmetries_ptr->find_basic_bin(bin_copy) == 0); + assert ( symmetries_sptr->find_basic_bin(bin_copy) == 0); } #endif diff --git a/src/recon_buildblock/ProjMatrixByBinFromFile.cxx b/src/recon_buildblock/ProjMatrixByBinFromFile.cxx index f104e13e70..fec274a423 100644 --- a/src/recon_buildblock/ProjMatrixByBinFromFile.cxx +++ b/src/recon_buildblock/ProjMatrixByBinFromFile.cxx @@ -161,7 +161,7 @@ ProjMatrixByBinFromFile::post_processing() if (this->symmetries_type == standardise_interfile_keyword("PET_CartesianGrid")) { - symmetries_ptr.reset( + symmetries_sptr.reset( new DataSymmetriesForBins_PET_CartesianGrid(proj_data_info_ptr, density_info_sptr, do_symmetry_90degrees_min_phi, @@ -172,7 +172,7 @@ ProjMatrixByBinFromFile::post_processing() } else if (this->symmetries_type == "none") { - symmetries_ptr.reset(new TrivialDataSymmetriesForBins(proj_data_info_ptr)); + symmetries_sptr.reset(new TrivialDataSymmetriesForBins(proj_data_info_ptr)); } else { diff --git a/src/recon_buildblock/ProjMatrixByBinSPECTUB.cxx b/src/recon_buildblock/ProjMatrixByBinSPECTUB.cxx index 0f8acdbfa4..1dd5948193 100644 --- a/src/recon_buildblock/ProjMatrixByBinSPECTUB.cxx +++ b/src/recon_buildblock/ProjMatrixByBinSPECTUB.cxx @@ -189,7 +189,7 @@ set_up( } this->proj_data_info_ptr=proj_data_info_ptr_v; - symmetries_ptr.reset( + symmetries_sptr.reset( new TrivialDataSymmetriesForBins(proj_data_info_ptr_v)); this->densel_range = image_info_ptr->get_index_range(); @@ -205,7 +205,7 @@ set_up( //... fill prj structure from projection data info prj.Nbin = this->proj_data_info_ptr->get_num_tangential_poss(); - prj.szcm = this->proj_data_info_ptr->get_scanner_ptr()->get_default_bin_size()/10.; + prj.szcm = this->proj_data_info_ptr->get_scanner_ptr()->get_default_bin_size()/10.f; prj.Nang = this->proj_data_info_ptr->get_num_views(); @@ -213,24 +213,24 @@ set_up( vol.Ncol = image_info_ptr->get_x_size(); // Image: number of columns vol.Nrow = image_info_ptr->get_y_size(); // Image: number of rows vol.Nsli = image_info_ptr->get_z_size(); // Image: and projections: number of slices - vol.szcm = image_info_ptr->get_voxel_size().x()/10.; // Image: voxel size (cm) - vol.thcm = image_info_ptr->get_voxel_size().z()/10.; // Image: slice thickness (cm) + vol.szcm = image_info_ptr->get_voxel_size().x()/10.f; // Image: voxel size (cm) + vol.thcm = image_info_ptr->get_voxel_size().z()/10.f; // Image: slice thickness (cm) //..... geometrical and other derived parameters of the volume structure............... vol.Npix = vol.Ncol * vol.Nrow; vol.Nvox = vol.Npix * vol.Nsli; - vol.Ncold2 = (float)vol.Ncol / (float)2.; // half of the matrix Nvox (integer or semi-integer) - vol.Nrowd2 = (float)vol.Nrow / (float)2.; // half of the matrix NbOS (integer or semi-integer) - vol.Nslid2 = (float)vol.Nsli / (float)2.; // half of the number of slices (integer or semi-integer) + vol.Ncold2 = (float)vol.Ncol / (float)2.f; // half of the matrix Nvox (integer or semi-integer) + vol.Nrowd2 = (float)vol.Nrow / (float)2.f; // half of the matrix NbOS (integer or semi-integer) + vol.Nslid2 = (float)vol.Nsli / (float)2.f; // half of the number of slices (integer or semi-integer) vol.Xcmd2 = vol.Ncold2 * vol.szcm; // Half of the size of the image volume, dimension x (cm); vol.Ycmd2 = vol.Nrowd2 * vol.szcm; // Half of the size of the image volume, dimension y (cm); vol.Zcmd2 = vol.Nslid2 * vol.thcm; // Half of the size of the image volume, dimension z (cm); - vol.x0 = ( (float)0.5 - vol.Ncold2) * vol.szcm; // coordinate x of the first voxel - vol.y0 = ( (float)0.5 - vol.Nrowd2) * vol.szcm; // coordinate y of the first voxel - vol.z0 = ( (float)0.5 - vol.Nslid2) * vol.thcm; // coordinate z of the first voxel + vol.x0 = ( (float)0.5f - vol.Ncold2) * vol.szcm; // coordinate x of the first voxel + vol.y0 = ( (float)0.5f - vol.Nrowd2) * vol.szcm; // coordinate y of the first voxel + vol.z0 = ( (float)0.5f - vol.Nslid2) * vol.thcm; // coordinate z of the first voxel vol.first_sl = 0; // Image: first slice to take into account (no weight bellow) vol.last_sl = vol.Nsli; // Image: last slice to take into account (no weights above) diff --git a/src/recon_buildblock/ProjMatrixByBinUsingInterpolation.cxx b/src/recon_buildblock/ProjMatrixByBinUsingInterpolation.cxx index 95000eab59..fe4bf6e3b6 100644 --- a/src/recon_buildblock/ProjMatrixByBinUsingInterpolation.cxx +++ b/src/recon_buildblock/ProjMatrixByBinUsingInterpolation.cxx @@ -129,7 +129,7 @@ set_up( (densel_range.get_max_index() + densel_range.get_min_index())*voxel_size.z()/2.F; origin.z() -= z_to_middle; - symmetries_ptr.reset( + symmetries_sptr.reset( new DataSymmetriesForBins_PET_CartesianGrid(proj_data_info_ptr, density_info_ptr, do_symmetry_90degrees_min_phi, diff --git a/src/recon_buildblock/ProjMatrixByBinUsingRayTracing.cxx b/src/recon_buildblock/ProjMatrixByBinUsingRayTracing.cxx index 0b6f56c538..8560fc0f42 100644 --- a/src/recon_buildblock/ProjMatrixByBinUsingRayTracing.cxx +++ b/src/recon_buildblock/ProjMatrixByBinUsingRayTracing.cxx @@ -284,7 +284,7 @@ set_up( origin = image_info_ptr->get_origin(); image_info_ptr->get_regular_range(min_index, max_index); - symmetries_ptr.reset( + symmetries_sptr.reset( new DataSymmetriesForBins_PET_CartesianGrid(proj_data_info_ptr, density_info_ptr, do_symmetry_90degrees_min_phi, diff --git a/src/recon_buildblock/ProjMatrixElemsForOneBin.cxx b/src/recon_buildblock/ProjMatrixElemsForOneBin.cxx index 98b5b7a42e..dcb90e063f 100644 --- a/src/recon_buildblock/ProjMatrixElemsForOneBin.cxx +++ b/src/recon_buildblock/ProjMatrixElemsForOneBin.cxx @@ -415,6 +415,30 @@ back_project(DiscretisedDensity<3,float>& density, } } +void +ProjMatrixElemsForOneBin:: +back_project(DiscretisedDensity<3,float>& density, + const std::vector& r_bins, + const shared_ptr& symmetries) +{ + + ProjMatrixElemsForOneBin row_copy; + + for (std::vector::const_iterator r_bins_iterator =r_bins.begin(); + r_bins_iterator != r_bins.end(); ++ r_bins_iterator) + { + row_copy = *this; + + Bin symmetric_bin = *r_bins_iterator; + // KT 21/02/2002 added check on 0 + if (symmetric_bin.get_bin_value() == 0.f) + return; + auto_ptr symm_ptr = + symmetries->find_symmetry_operation_from_basic_bin(symmetric_bin); + symm_ptr->transform_proj_matrix_elems_for_one_bin(row_copy); + row_copy.back_project(density,symmetric_bin); + } +} void ProjMatrixElemsForOneBin:: From 4141403181d1b584cd2ebfaf16c4b67fc614e800 Mon Sep 17 00:00:00 2001 From: Nikos E Date: Mon, 10 Oct 2016 16:05:21 +0100 Subject: [PATCH 004/509] The listmode reconsturction can calculate the sensitiviity image with normalisation Currently I am using only the new bin-wise normalisation. After the tests run I will try the old viewgram-wise method. And report the results. --- src/buildblock/ProjData.cxx | 19 ++++++++++++ src/buildblock/ProjDataFromStream.cxx | 4 +-- src/buildblock/ProjDataInMemory.cxx | 7 +++-- src/include/stir/Bin.inl | 7 +---- src/include/stir/ProjData.h | 8 ++++- src/include/stir/ProjDataGEAdvance.h | 13 +++++++- .../stir/recon_buildblock/BinNormalisation.h | 13 ++++---- .../BinNormalisationFromAttenuationImage.h | 4 +++ .../BinNormalisationFromECAT8.h | 4 +++ .../BinNormalisationFromProjData.h | 3 ++ .../ChainedBinNormalisation.h | 9 ++++++ .../TrivialBinNormalisation.h | 11 +++++++ src/recon_buildblock/BinNormalisation.cxx | 30 ++++++++++--------- .../BinNormalisationFromAttenuationImage.cxx | 4 +++ .../BinNormalisationFromECAT8.cxx | 5 ++++ .../BinNormalisationFromProjData.cxx | 7 +++++ .../ChainedBinNormalisation.cxx | 29 ++++++++++++++++++ 17 files changed, 145 insertions(+), 32 deletions(-) diff --git a/src/buildblock/ProjData.cxx b/src/buildblock/ProjData.cxx index 33b5e706d9..e665b09a6a 100644 --- a/src/buildblock/ProjData.cxx +++ b/src/buildblock/ProjData.cxx @@ -289,6 +289,25 @@ ProjData::get_related_viewgrams(const ViewSegmentNumbers& view_segmnet_num, return RelatedViewgrams(viewgrams, symmetries_used); } +std::vector +ProjData::get_related_bin_values(const std::vector& r_bins) const +{ + + std::vector values; + values.reserve(r_bins.size()); + + for (std::vector ::const_iterator r_bins_iterator = r_bins.begin(); + r_bins_iterator != r_bins.end(); ++r_bins_iterator) + { + values.push_back(this->get_bin_value(*r_bins_iterator->segment_num(), + *r_bins_iterator->axial_pos_num(), + *r_bins_iterator->view_num(), + *r_bins_iterator->tangential_pos_num())); + } + + return values; +} + Succeeded ProjData::set_related_viewgrams( const RelatedViewgrams& viewgrams) diff --git a/src/buildblock/ProjDataFromStream.cxx b/src/buildblock/ProjDataFromStream.cxx index 8ecbf9f0d5..8020a24aac 100644 --- a/src/buildblock/ProjDataFromStream.cxx +++ b/src/buildblock/ProjDataFromStream.cxx @@ -231,8 +231,8 @@ ProjDataFromStream::get_bin_value(const int segment_num, error("ProjDataFromStream::get_viewgram: error after seekg\n"); } - Array< 1, float> value; - value.resize(1); + Array< 1, float> value(1); +// value.resize(1); float scale = float(1); // Now the storage order is not more important. Just read. diff --git a/src/buildblock/ProjDataInMemory.cxx b/src/buildblock/ProjDataInMemory.cxx index 934bde9487..0154f51185 100644 --- a/src/buildblock/ProjDataInMemory.cxx +++ b/src/buildblock/ProjDataInMemory.cxx @@ -160,10 +160,11 @@ write_to_file(const string& output_filename) const float ProjDataInMemory::get_bin_value(Bin& bin) { - Viewgram viewgram = get_viewgram(bin.view_num(),bin.segment_num()); + Viewgram viewgram = get_viewgram(bin.view_num(),bin.segment_num()); - return viewgram[bin.axial_pos_num()][bin.tangential_pos_num()]; - + return viewgram[bin.axial_pos_num()][bin.tangential_pos_num()]; +// return get_bin_value(bin.segment_num(), bin.axial_pos_num(), +// bin.view_num(), bin.tangential_pos_num()); } END_NAMESPACE_STIR diff --git a/src/include/stir/Bin.inl b/src/include/stir/Bin.inl index 014f08c821..7fe9d8ac3b 100644 --- a/src/include/stir/Bin.inl +++ b/src/include/stir/Bin.inl @@ -32,8 +32,6 @@ START_NAMESPACE_STIR -const float SMALL_NUM = 0.000001F; - Bin::Bin() {} @@ -132,10 +130,7 @@ Bin::operator*=(const float dx) Bin& Bin::operator/=(const float dx) { - float small_value= - bin_value * SMALL_NUM; - - if (std::fabs(dx) < small_value) + if (dx == 0.f) bin_value = 0.0f; else bin_value /= dx; diff --git a/src/include/stir/ProjData.h b/src/include/stir/ProjData.h index a0ba177e9d..3701251cb0 100644 --- a/src/include/stir/ProjData.h +++ b/src/include/stir/ProjData.h @@ -152,6 +152,10 @@ class ProjData : public ExamData //! Set sinogram virtual Succeeded set_sinogram(const Sinogram&) = 0; + //! Get Bin value + virtual float get_bin_value(const int view_pos, const int segment_pos, const int axial_pos, const int tang_pos) const = 0; + //! Get Bin value + virtual float get_bin_value(const Bin& this_bin) const = 0; //! Get empty viewgram Viewgram get_empty_viewgram(const int view, const int segment_num, @@ -192,7 +196,9 @@ class ProjData : public ExamData const bool make_num_tangential_poss_odd = false) const; //! Set related viewgrams virtual Succeeded set_related_viewgrams(const RelatedViewgrams& viewgrams); - + //! Get related bin values + //! \todo This function temporaliry has as input a vector instead this should be replaced by RelatedBins. + std::vector get_related_bin_values(const std::vector&) const; //! Get empty related viewgrams, where the symmetries_ptr specifies the symmetries to use RelatedViewgrams diff --git a/src/include/stir/ProjDataGEAdvance.h b/src/include/stir/ProjDataGEAdvance.h index ac4fb0cae8..e6931b40bd 100644 --- a/src/include/stir/ProjDataGEAdvance.h +++ b/src/include/stir/ProjDataGEAdvance.h @@ -73,7 +73,18 @@ class ProjDataGEAdvance : public ProjData Sinogram get_sinogram(const int ax_pos_num, const int sergment_num,const bool make_num_tangential_poss_odd=false) const; Succeeded set_sinogram(const Sinogram& s); - + float get_bin_value(const int segment_num, + const int axial_pos_num, + const int view_num, + const int tang_pos_num) const + { + //Do nothing + } + + float get_bin_value(const Bin& this_bin) const + { + // Do nothing + } private: //the file with the data //This has to be a reference (or pointer) to a stream, diff --git a/src/include/stir/recon_buildblock/BinNormalisation.h b/src/include/stir/recon_buildblock/BinNormalisation.h index 1e4530aa0f..892819e7de 100644 --- a/src/include/stir/recon_buildblock/BinNormalisation.h +++ b/src/include/stir/recon_buildblock/BinNormalisation.h @@ -33,7 +33,6 @@ #include "stir/RegisteredObject.h" #include "stir/Bin.h" #include "stir/shared_ptr.h" -#include "DataSymmetriesForBins.h" START_NAMESPACE_STIR @@ -130,13 +129,17 @@ class BinNormalisation : public RegisteredObject //! This is the a bin-wise overload for the undo function. //! \todo RelatedBins should be used, instead of a std::vector. - void apply(std::vector& bins, - const double start_time, const double end_time) const; + virtual void apply(std::vector&, const double, const double) const; //! This is the a bin-wise overload for the apply function. //! \todo RelatedBins should be used, instead of a std::vector. - void undo(std::vector& bins, - const double start_time, const double end_time) const; + virtual void undo(std::vector&, + const double, const double) const; + +protected: + + virtual + std::vector get_related_bins_values(const std::vector&) const = 0; }; diff --git a/src/include/stir/recon_buildblock/BinNormalisationFromAttenuationImage.h b/src/include/stir/recon_buildblock/BinNormalisationFromAttenuationImage.h index 5f87b986c6..06f4f5f667 100644 --- a/src/include/stir/recon_buildblock/BinNormalisationFromAttenuationImage.h +++ b/src/include/stir/recon_buildblock/BinNormalisationFromAttenuationImage.h @@ -119,6 +119,10 @@ class BinNormalisationFromAttenuationImage : virtual bool post_processing(); std::string attenuation_image_filename; + +protected: + virtual + std::vector get_related_bins_values(const std::vector&) const; }; diff --git a/src/include/stir/recon_buildblock/BinNormalisationFromECAT8.h b/src/include/stir/recon_buildblock/BinNormalisationFromECAT8.h index e3f8bfed34..d45cc30e6f 100644 --- a/src/include/stir/recon_buildblock/BinNormalisationFromECAT8.h +++ b/src/include/stir/recon_buildblock/BinNormalisationFromECAT8.h @@ -135,6 +135,10 @@ class BinNormalisationFromECAT8 : virtual bool post_processing(); string normalisation_ECAT8_filename; + +protected: + virtual + std::vector get_related_bins_values(const std::vector&) const; }; END_NAMESPACE_ECAT diff --git a/src/include/stir/recon_buildblock/BinNormalisationFromProjData.h b/src/include/stir/recon_buildblock/BinNormalisationFromProjData.h index 1f47bbaf42..e28c6289b1 100644 --- a/src/include/stir/recon_buildblock/BinNormalisationFromProjData.h +++ b/src/include/stir/recon_buildblock/BinNormalisationFromProjData.h @@ -107,6 +107,9 @@ class BinNormalisationFromProjData : virtual bool post_processing(); std::string normalisation_projdata_filename; +protected: + virtual + std::vector get_related_bins_values(const std::vector&) const; }; diff --git a/src/include/stir/recon_buildblock/ChainedBinNormalisation.h b/src/include/stir/recon_buildblock/ChainedBinNormalisation.h index 0aae69824d..a8d3c85be2 100644 --- a/src/include/stir/recon_buildblock/ChainedBinNormalisation.h +++ b/src/include/stir/recon_buildblock/ChainedBinNormalisation.h @@ -110,7 +110,16 @@ ChainedBinNormalisation(shared_ptr const& apply_first, virtual void undo(RelatedViewgrams& viewgrams,const double start_time, const double end_time) const; virtual float get_bin_efficiency(const Bin& bin,const double start_time, const double end_time) const; + //! Apply for related bins + virtual void apply(std::vector& bins, + const double start_time, const double end_time) const; + //! Undo for related bins + virtual void undo(std::vector& bins, + const double start_time, const double end_time) const; +protected: +virtual +std::vector get_related_bins_values(const std::vector&) const; private: shared_ptr apply_first; diff --git a/src/include/stir/recon_buildblock/TrivialBinNormalisation.h b/src/include/stir/recon_buildblock/TrivialBinNormalisation.h index 663cc10712..24fded8f6d 100644 --- a/src/include/stir/recon_buildblock/TrivialBinNormalisation.h +++ b/src/include/stir/recon_buildblock/TrivialBinNormalisation.h @@ -48,6 +48,12 @@ class TrivialBinNormalisation : virtual inline void apply(RelatedViewgrams&,const double start_time, const double end_time) const {} virtual inline void undo(RelatedViewgrams&,const double start_time, const double end_time) const {} + + //! This is the a bin-wise overload for the apply function. + virtual void apply(std::vector&, const double, const double) const{} + + //! This is the a bin-wise overload for the undo function. + virtual void undo(std::vector&, const double, const double) const {} virtual inline float get_bin_efficiency(const Bin& bin,const double start_time, const double end_time) const { return 1;} @@ -57,6 +63,11 @@ class TrivialBinNormalisation : virtual inline void set_defaults() {} virtual inline void initialise_keymap() {} +protected: + virtual + std::vector get_related_bins_values(const std::vector& ) const + {} + }; END_NAMESPACE_STIR diff --git a/src/recon_buildblock/BinNormalisation.cxx b/src/recon_buildblock/BinNormalisation.cxx index 6f2cc590bd..9e42426692 100644 --- a/src/recon_buildblock/BinNormalisation.cxx +++ b/src/recon_buildblock/BinNormalisation.cxx @@ -176,30 +176,32 @@ undo(ProjData& proj_data,const double start_time, const double end_time, void BinNormalisation:: -apply(std::vector& bins, +apply(std::vector& r_bins, const double start_time, const double end_time) const { + std::vector normalization_values = this->get_related_bins_values(r_bins); - for (std::vector::iterator iter = bins.begin(); iter !=bins.end(); ++iter) - *iter *= this->get_bin_efficiency(*iter, start_time, end_time); + assert(r_bins.size() == normalization_values.size()); - // TODO: DELETE THE FOLLOWING LINES -// std::vector normalization_values = norm_proj_data_ptr->get_related_bin_values(bins, symmetries); -// assert(bins.size() == normalization_values.size()); - -// for (unsigned int i = 0; i < bins.size(); i++) -// { -// bins[i].set_bin_value(bins.at(i).get_bin_value() / normalization_values.at(i)); -// } + for (unsigned int i = 0; i < r_bins.size(); i++) + { + r_bins[i] *= normalization_values.at(i); + } } void BinNormalisation:: -undo(std::vector& bins, +undo(std::vector& r_bins, const double start_time, const double end_time) const { - for (std::vector::iterator iter = bins.begin(); iter !=bins.end(); ++iter) - *iter /= this->get_bin_efficiency(*iter, start_time, end_time); + std::vector normalization_values = this->get_related_bins_values(r_bins); + + assert(r_bins.size() == normalization_values.size()); + + for (unsigned int i = 0; i < r_bins.size(); i++) + { + r_bins[i] /= normalization_values.at(i); + } } END_NAMESPACE_STIR diff --git a/src/recon_buildblock/BinNormalisationFromAttenuationImage.cxx b/src/recon_buildblock/BinNormalisationFromAttenuationImage.cxx index d842689774..1a75c39c37 100644 --- a/src/recon_buildblock/BinNormalisationFromAttenuationImage.cxx +++ b/src/recon_buildblock/BinNormalisationFromAttenuationImage.cxx @@ -178,6 +178,10 @@ BinNormalisationFromAttenuationImage::get_bin_efficiency(const Bin& bin,const do return 1; } +std::vector BinNormalisationFromAttenuationImage::get_related_bins_values(const std::vector& r_bins) const +{ +error("Not implemented, yet"); +} END_NAMESPACE_STIR diff --git a/src/recon_buildblock/BinNormalisationFromECAT8.cxx b/src/recon_buildblock/BinNormalisationFromECAT8.cxx index fec7de4206..aac73dfe62 100644 --- a/src/recon_buildblock/BinNormalisationFromECAT8.cxx +++ b/src/recon_buildblock/BinNormalisationFromECAT8.cxx @@ -707,6 +707,11 @@ BinNormalisationFromECAT8::get_dead_time_efficiency (const DetectionPosition<>& } +std::vector BinNormalisationFromECAT8::get_related_bins_values(const std::vector& r_bins) const +{ +error("Not implemented, yet"); +} + END_NAMESPACE_ECAT END_NAMESPACE_STIR diff --git a/src/recon_buildblock/BinNormalisationFromProjData.cxx b/src/recon_buildblock/BinNormalisationFromProjData.cxx index 57449e6346..113ceef497 100644 --- a/src/recon_buildblock/BinNormalisationFromProjData.cxx +++ b/src/recon_buildblock/BinNormalisationFromProjData.cxx @@ -171,6 +171,13 @@ BinNormalisationFromProjData::get_bin_efficiency(const Bin& bin,const double sta return 1; } + + +std::vector +BinNormalisationFromProjData::get_related_bins_values(const std::vector& r_bins) const +{ + return this->norm_proj_data_ptr->get_related_bin_values(r_bins); +} END_NAMESPACE_STIR diff --git a/src/recon_buildblock/ChainedBinNormalisation.cxx b/src/recon_buildblock/ChainedBinNormalisation.cxx index 65cb120adb..c73a568a2e 100644 --- a/src/recon_buildblock/ChainedBinNormalisation.cxx +++ b/src/recon_buildblock/ChainedBinNormalisation.cxx @@ -120,6 +120,35 @@ ChainedBinNormalisation:: get_bin_efficiency(const Bin& bin,const double start_t : 1); } +void +ChainedBinNormalisation:: +apply(std::vector& bins, + const double start_time, const double end_time) const +{ + if (!is_null_ptr(apply_first)) + apply_first->apply(bins,start_time,end_time); + if (!is_null_ptr(apply_second)) + apply_second->apply(bins,start_time,end_time); +} + +void +ChainedBinNormalisation:: +undo(std::vector& bins, + const double start_time, const double end_time) const +{ + if (!is_null_ptr(apply_first)) + apply_first->undo(bins,start_time,end_time); + if (!is_null_ptr(apply_second)) + apply_second->undo(bins,start_time,end_time); +} +std::vector +ChainedBinNormalisation:: +get_related_bins_values(const std::vector& r_bins) const +{ + error("Not implemented, yet"); + //Something like BINSnormA * BINSnormB +} + END_NAMESPACE_STIR From 1dfe186ae08931bfff401d86feae5b7920a6f698 Mon Sep 17 00:00:00 2001 From: Nikos E Date: Mon, 10 Oct 2016 16:11:49 +0100 Subject: [PATCH 005/509] Corrected an error. --- src/buildblock/ProjData.cxx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/buildblock/ProjData.cxx b/src/buildblock/ProjData.cxx index e665b09a6a..8d411b68ac 100644 --- a/src/buildblock/ProjData.cxx +++ b/src/buildblock/ProjData.cxx @@ -299,10 +299,10 @@ ProjData::get_related_bin_values(const std::vector& r_bins) const for (std::vector ::const_iterator r_bins_iterator = r_bins.begin(); r_bins_iterator != r_bins.end(); ++r_bins_iterator) { - values.push_back(this->get_bin_value(*r_bins_iterator->segment_num(), - *r_bins_iterator->axial_pos_num(), - *r_bins_iterator->view_num(), - *r_bins_iterator->tangential_pos_num())); + values.push_back(this->get_bin_value((*r_bins_iterator).segment_num(), + (*r_bins_iterator).axial_pos_num(), + (*r_bins_iterator).view_num(), + (*r_bins_iterator).tangential_pos_num())); } return values; From 00d26eb27261d5b9fdc086455c26b1d0521edf63 Mon Sep 17 00:00:00 2001 From: Nikos E Date: Mon, 10 Oct 2016 18:36:34 +0100 Subject: [PATCH 006/509] Added functions to lm objective function to compute the sensitivity image as the sinogram objective function. --- ...orMeanAndListModeDataWithProjMatrixByBin.h | 10 +- ...MeanAndListModeDataWithProjMatrixByBin.cxx | 134 ++++++++++++++---- 2 files changed, 112 insertions(+), 32 deletions(-) diff --git a/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.h b/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.h index 16b82a5f90..6ecbd2ce3f 100644 --- a/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.h +++ b/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.h @@ -34,7 +34,7 @@ #include "stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeData.h" #include "stir/recon_buildblock/ProjMatrixByBin.h" #include "stir/ProjDataInMemory.h" - +#include "stir/recon_buildblock/ProjectorByBinPair.h" #include "stir/ExamInfo.h" START_NAMESPACE_STIR @@ -103,7 +103,10 @@ typedef RegisteredParsingObject PM_sptr; - + + //! Stores the projectors that are used for the computations + shared_ptr projector_pair_ptr; + //! points to the additive projection data shared_ptr additive_proj_data_sptr; @@ -122,6 +125,9 @@ typedef RegisteredParsingObject @@ -47,6 +47,11 @@ #include #include "stir/stream.h" +#include "stir/recon_buildblock/ForwardProjectorByBinUsingProjMatrixByBin.h" +#include "stir/recon_buildblock/BackProjectorByBinUsingProjMatrixByBin.h" +#include "stir/recon_buildblock/ProjMatrixByBinUsingRayTracing.h" +#include "stir/recon_buildblock/ProjectorByBinPairUsingSeparateProjectors.h" + #ifdef STIR_MPI #include "stir/recon_buildblock/distributed_functions.h" #endif @@ -193,6 +198,14 @@ set_up_before_sensitivity(shared_ptr const& target_sptr) // set projector to be used for the calculations this->PM_sptr->set_up(this->proj_data_info_cyl_uncompressed_ptr->create_shared_clone(),target_sptr); + shared_ptr forward_projector_ptr(new ForwardProjectorByBinUsingProjMatrixByBin(this->PM_sptr)); + shared_ptr back_projector_ptr(new BackProjectorByBinUsingProjMatrixByBin(this->PM_sptr)); + + this->projector_pair_ptr->set_up(this->proj_data_info_cyl_uncompressed_ptr->create_shared_clone(),target_sptr); + + this->projector_pair_ptr.reset( + new ProjectorByBinPairUsingSeparateProjectors(forward_projector_ptr, back_projector_ptr)); + if (is_null_ptr(this->normalisation_sptr)) { warning("Invalid normalisation object"); @@ -246,6 +259,7 @@ PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin scanner_sptr(new Scanner(*this->list_mode_data_sptr->get_scanner_ptr())); + if (this->max_ring_difference_num_to_process == -1) { this->max_ring_difference_num_to_process = @@ -290,46 +304,106 @@ void PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin:: add_subset_sensitivity(TargetT& sensitivity, const int subset_num) const { - std::cout << "!Nere " <max_ring_difference_num_to_process; +// const int max_segment_num = this->max_ring_difference_num_to_process; + +// std::cout << min_segment_num<< " "<< max_segment_num <proj_data_info_cyl_uncompressed_ptr->get_min_axial_pos_num(segment_num); +// axial_num < this->proj_data_info_cyl_uncompressed_ptr->get_max_axial_pos_num(segment_num); +// axial_num ++) +// { +// // For debugging. +// std::cout <proj_data_info_cyl_uncompressed_ptr->get_min_tangential_pos_num(); +// tang_num < this->proj_data_info_cyl_uncompressed_ptr->get_max_tangential_pos_num(); +// tang_num ++ ) +// { + +// for(int view_num = this->proj_data_info_cyl_uncompressed_ptr->get_min_view_num() + subset_num; +// view_num <= this->proj_data_info_cyl_uncompressed_ptr->get_max_view_num(); +// view_num += this->num_subsets) +// { +// Bin tmp_bin(segment_num, +// view_num, +// axial_num, +// tang_num, 1.f); + +// if (!this->PM_sptr->get_symmetries_ptr()->is_basic(tmp_bin) ) +// continue; + +// this->add_projmatrix_to_sensitivity(sensitivity, tmp_bin); +// } +// } +// } +// } + + + const int min_segment_num = -this->max_ring_difference_num_to_process; const int max_segment_num = this->max_ring_difference_num_to_process; - std::cout << min_segment_num<< " "<< max_segment_num <proj_data_info_cyl_uncompressed_ptr->get_min_axial_pos_num(segment_num); - axial_num < this->proj_data_info_cyl_uncompressed_ptr->get_max_axial_pos_num(segment_num); - axial_num ++) - { - // For debugging. - std::cout <proj_data_info_cyl_uncompressed_ptr->get_min_tangential_pos_num(); - tang_num < this->proj_data_info_cyl_uncompressed_ptr->get_max_tangential_pos_num(); - tang_num ++ ) - { + for (int view = this->proj_data_info_cyl_uncompressed_ptr->get_min_view_num() + subset_num; + view <= this->proj_data_info_cyl_uncompressed_ptr->get_max_view_num(); + view += this->num_subsets) + { + const ViewSegmentNumbers view_segment_num(view, segment_num); - for(int view_num = this->proj_data_info_cyl_uncompressed_ptr->get_min_view_num() + subset_num; - view_num <= this->proj_data_info_cyl_uncompressed_ptr->get_max_view_num(); - view_num += this->num_subsets) - { - Bin tmp_bin(segment_num, - view_num, - axial_num, - tang_num, 1.f); + if (! this->projector_pair_ptr->get_symmetries_used()->is_basic(view_segment_num)) + continue; + this->add_view_seg_to_sensitivity(sensitivity, view_segment_num); + } + // cerr<PM_sptr->get_symmetries_ptr()->is_basic(tmp_bin) ) - continue; +template +void +PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin:: +add_view_seg_to_sensitivity(TargetT& sensitivity, const ViewSegmentNumbers& view_seg_nums) const +{ + shared_ptr symmetries_used + (this->projector_pair_ptr->get_symmetries_used()->clone()); + + RelatedViewgrams viewgrams = + this->proj_data_info_cyl_uncompressed_ptr->get_empty_related_viewgrams(view_seg_nums,symmetries_used); + + viewgrams.fill(1.F); + // find efficiencies + { + const double start_frame = this->frame_defs.get_start_time(this->current_frame_num); + const double end_frame = this->frame_defs.get_end_time(this->current_frame_num); + this->normalisation_sptr->undo(viewgrams,start_frame,end_frame); + } + // backproject + { + const int range_to_zero = + view_seg_nums.segment_num() == 0 + ? 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); + } - this->add_projmatrix_to_sensitivity(sensitivity, tmp_bin); - } - } - } - } } + template void PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin:: From 5dfa94f302598253f88cd94632b099375d9a5903 Mon Sep 17 00:00:00 2001 From: Nikos E Date: Thu, 13 Oct 2016 10:17:12 +0100 Subject: [PATCH 007/509] Everything is working. But I would like to remove the bin-wise functions. --- src/include/stir/listmode/CListModeDataECAT.h | 7 +++++++ src/include/stir/listmode/CListModeDataECAT8_32bit.h | 2 +- .../recon_buildblock/BinNormalisationFromECAT7.h | 3 +++ src/recon_buildblock/BinNormalisationFromECAT7.cxx | 5 ++++- ...odelForMeanAndListModeDataWithProjMatrixByBin.cxx | 12 +++++++++--- 5 files changed, 24 insertions(+), 5 deletions(-) diff --git a/src/include/stir/listmode/CListModeDataECAT.h b/src/include/stir/listmode/CListModeDataECAT.h index 1990aaf5ea..9eaebd41b6 100644 --- a/src/include/stir/listmode/CListModeDataECAT.h +++ b/src/include/stir/listmode/CListModeDataECAT.h @@ -89,6 +89,13 @@ class CListModeDataECAT : public CListModeData /*! \todo this might depend on the acquisition parameters */ virtual bool has_delayeds() const { return true; } + virtual inline + unsigned long int + get_total_number_of_events() const + { + error("Not implemented yet. Abort."); + } + private: std::string listmode_filename_prefix; mutable unsigned int current_lm_file; diff --git a/src/include/stir/listmode/CListModeDataECAT8_32bit.h b/src/include/stir/listmode/CListModeDataECAT8_32bit.h index 7feae1741d..d36a44bc96 100644 --- a/src/include/stir/listmode/CListModeDataECAT8_32bit.h +++ b/src/include/stir/listmode/CListModeDataECAT8_32bit.h @@ -81,7 +81,7 @@ class CListModeDataECAT8_32bit : public CListModeData get_total_number_of_events() const { error("Not implemented yet. Abort."); - }; + } private: typedef CListRecordECAT8_32bit CListRecordT; diff --git a/src/include/stir/recon_buildblock/BinNormalisationFromECAT7.h b/src/include/stir/recon_buildblock/BinNormalisationFromECAT7.h index 3d1f63477c..8f0cfa5a80 100644 --- a/src/include/stir/recon_buildblock/BinNormalisationFromECAT7.h +++ b/src/include/stir/recon_buildblock/BinNormalisationFromECAT7.h @@ -136,6 +136,9 @@ class BinNormalisationFromECAT7 : virtual bool post_processing(); std::string normalisation_ECAT7_filename; +protected: + virtual + std::vector get_related_bins_values(const std::vector&) const; }; END_NAMESPACE_ECAT7 diff --git a/src/recon_buildblock/BinNormalisationFromECAT7.cxx b/src/recon_buildblock/BinNormalisationFromECAT7.cxx index a08d1b2f14..dc57f94514 100644 --- a/src/recon_buildblock/BinNormalisationFromECAT7.cxx +++ b/src/recon_buildblock/BinNormalisationFromECAT7.cxx @@ -653,7 +653,10 @@ BinNormalisationFromECAT7::get_dead_time_efficiency (const DetectionPosition<>& } - +std::vector BinNormalisationFromECAT7::get_related_bins_values(const std::vector& r_bins) const +{ +error("Not implemented, yet"); +} END_NAMESPACE_ECAT7 END_NAMESPACE_ECAT diff --git a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx index 22d3decaec..1d0b83b1ec 100644 --- a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx +++ b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx @@ -35,7 +35,7 @@ #include "stir/Viewgram.h" #include "stir/info.h" #include - +#include "stir/HighResWallClockTimer.h" #include "stir/recon_buildblock/TrivialBinNormalisation.h" #include "stir/Viewgram.h" #include "stir/RelatedViewgrams.h" @@ -201,11 +201,12 @@ set_up_before_sensitivity(shared_ptr const& target_sptr) shared_ptr forward_projector_ptr(new ForwardProjectorByBinUsingProjMatrixByBin(this->PM_sptr)); shared_ptr back_projector_ptr(new BackProjectorByBinUsingProjMatrixByBin(this->PM_sptr)); - this->projector_pair_ptr->set_up(this->proj_data_info_cyl_uncompressed_ptr->create_shared_clone(),target_sptr); - this->projector_pair_ptr.reset( new ProjectorByBinPairUsingSeparateProjectors(forward_projector_ptr, back_projector_ptr)); + this->projector_pair_ptr->set_up(this->proj_data_info_cyl_uncompressed_ptr->create_shared_clone(),target_sptr); + + if (is_null_ptr(this->normalisation_sptr)) { warning("Invalid normalisation object"); @@ -304,6 +305,9 @@ void PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin:: add_subset_sensitivity(TargetT& sensitivity, const int subset_num) const { + HighResWallClockTimer t; + t.reset(); + t.start(); // std::cout << "!Nere " <max_ring_difference_num_to_process; // const int max_segment_num = this->max_ring_difference_num_to_process; @@ -366,6 +370,8 @@ add_subset_sensitivity(TargetT& sensitivity, const int subset_num) const } // cerr< From f1e60e6d81cac114dd429e8429627caefab26390 Mon Sep 17 00:00:00 2001 From: Nikos E Date: Thu, 13 Oct 2016 10:38:38 +0100 Subject: [PATCH 008/509] Removed all the Bin-wise functions. My tests showed that the results are not exactly the same as in the viewgram-wise calculations. So I retreat it for now. --- .../stir/recon_buildblock/BinNormalisation.h | 16 +--- .../BinNormalisationFromAttenuationImage.h | 4 - .../BinNormalisationFromECAT7.h | 5 +- .../BinNormalisationFromECAT8.h | 4 - .../BinNormalisationFromProjData.h | 3 - .../ChainedBinNormalisation.h | 9 --- ...orMeanAndListModeDataWithProjMatrixByBin.h | 3 +- .../ProjMatrixElemsForOneBin.h | 10 +-- .../TrivialBinNormalisation.h | 11 --- src/recon_buildblock/BinNormalisation.cxx | 29 ------- .../BinNormalisationFromAttenuationImage.cxx | 4 - .../BinNormalisationFromECAT7.cxx | 5 +- .../BinNormalisationFromECAT8.cxx | 5 -- .../BinNormalisationFromProjData.cxx | 7 -- .../ChainedBinNormalisation.cxx | 29 ------- ...MeanAndListModeDataWithProjMatrixByBin.cxx | 79 ------------------- .../ProjMatrixElemsForOneBin.cxx | 24 ------ 17 files changed, 5 insertions(+), 242 deletions(-) diff --git a/src/include/stir/recon_buildblock/BinNormalisation.h b/src/include/stir/recon_buildblock/BinNormalisation.h index 892819e7de..2f3b35436a 100644 --- a/src/include/stir/recon_buildblock/BinNormalisation.h +++ b/src/include/stir/recon_buildblock/BinNormalisation.h @@ -125,21 +125,7 @@ class BinNormalisation : public RegisteredObject The default value for the symmetries means that TrivialDataSymmetriesForBins will be used. */ void undo(ProjData&,const double start_time, const double end_time, - shared_ptr = shared_ptr()) const; - - //! This is the a bin-wise overload for the undo function. - //! \todo RelatedBins should be used, instead of a std::vector. - virtual void apply(std::vector&, const double, const double) const; - - //! This is the a bin-wise overload for the apply function. - //! \todo RelatedBins should be used, instead of a std::vector. - virtual void undo(std::vector&, - const double, const double) const; - -protected: - - virtual - std::vector get_related_bins_values(const std::vector&) const = 0; + shared_ptr = shared_ptr()) const; }; diff --git a/src/include/stir/recon_buildblock/BinNormalisationFromAttenuationImage.h b/src/include/stir/recon_buildblock/BinNormalisationFromAttenuationImage.h index 06f4f5f667..5f87b986c6 100644 --- a/src/include/stir/recon_buildblock/BinNormalisationFromAttenuationImage.h +++ b/src/include/stir/recon_buildblock/BinNormalisationFromAttenuationImage.h @@ -119,10 +119,6 @@ class BinNormalisationFromAttenuationImage : virtual bool post_processing(); std::string attenuation_image_filename; - -protected: - virtual - std::vector get_related_bins_values(const std::vector&) const; }; diff --git a/src/include/stir/recon_buildblock/BinNormalisationFromECAT7.h b/src/include/stir/recon_buildblock/BinNormalisationFromECAT7.h index 8f0cfa5a80..32c0ddfd2a 100644 --- a/src/include/stir/recon_buildblock/BinNormalisationFromECAT7.h +++ b/src/include/stir/recon_buildblock/BinNormalisationFromECAT7.h @@ -95,7 +95,7 @@ class BinNormalisationFromECAT7 : BinNormalisationFromECAT7(const std::string& filename); virtual Succeeded set_up(const shared_ptr&); - virtual float get_bin_efficiency(const Bin& bin, const double start_time, const double end_time) const; + float get_bin_efficiency(const Bin& bin, const double start_time, const double end_time) const; bool use_detector_efficiencies() const; bool use_dead_time() const; @@ -136,9 +136,6 @@ class BinNormalisationFromECAT7 : virtual bool post_processing(); std::string normalisation_ECAT7_filename; -protected: - virtual - std::vector get_related_bins_values(const std::vector&) const; }; END_NAMESPACE_ECAT7 diff --git a/src/include/stir/recon_buildblock/BinNormalisationFromECAT8.h b/src/include/stir/recon_buildblock/BinNormalisationFromECAT8.h index d45cc30e6f..e3f8bfed34 100644 --- a/src/include/stir/recon_buildblock/BinNormalisationFromECAT8.h +++ b/src/include/stir/recon_buildblock/BinNormalisationFromECAT8.h @@ -135,10 +135,6 @@ class BinNormalisationFromECAT8 : virtual bool post_processing(); string normalisation_ECAT8_filename; - -protected: - virtual - std::vector get_related_bins_values(const std::vector&) const; }; END_NAMESPACE_ECAT diff --git a/src/include/stir/recon_buildblock/BinNormalisationFromProjData.h b/src/include/stir/recon_buildblock/BinNormalisationFromProjData.h index e28c6289b1..1f47bbaf42 100644 --- a/src/include/stir/recon_buildblock/BinNormalisationFromProjData.h +++ b/src/include/stir/recon_buildblock/BinNormalisationFromProjData.h @@ -107,9 +107,6 @@ class BinNormalisationFromProjData : virtual bool post_processing(); std::string normalisation_projdata_filename; -protected: - virtual - std::vector get_related_bins_values(const std::vector&) const; }; diff --git a/src/include/stir/recon_buildblock/ChainedBinNormalisation.h b/src/include/stir/recon_buildblock/ChainedBinNormalisation.h index a8d3c85be2..0aae69824d 100644 --- a/src/include/stir/recon_buildblock/ChainedBinNormalisation.h +++ b/src/include/stir/recon_buildblock/ChainedBinNormalisation.h @@ -110,16 +110,7 @@ ChainedBinNormalisation(shared_ptr const& apply_first, virtual void undo(RelatedViewgrams& viewgrams,const double start_time, const double end_time) const; virtual float get_bin_efficiency(const Bin& bin,const double start_time, const double end_time) const; - //! Apply for related bins - virtual void apply(std::vector& bins, - const double start_time, const double end_time) const; - //! Undo for related bins - virtual void undo(std::vector& bins, - const double start_time, const double end_time) const; -protected: -virtual -std::vector get_related_bins_values(const std::vector&) const; private: shared_ptr apply_first; diff --git a/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.h b/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.h index 6ecbd2ce3f..91174db804 100644 --- a/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.h +++ b/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.h @@ -2,6 +2,7 @@ // /* Copyright (C) 2003- 2011, Hammersmith Imanet Ltd + Copyright (C) 2015, Univ. of Leeds Copyright (C) 2016, UCL 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 @@ -124,8 +125,6 @@ typedef RegisteredParsingObject -#include START_NAMESPACE_STIR @@ -199,13 +197,7 @@ class ProjMatrixElemsForOneBin const DiscretisedDensity<3,float>&) const; //! back project related bins void back_project(DiscretisedDensity<3,float>&, - const RelatedBins&) const; - //! Alternative to back project related bins. - //! \warning N.E. I use this function just because I cannot get the - //! RelatedBins class to work for me. - void back_project(DiscretisedDensity<3,float>&, - const std::vector&, - const shared_ptr&); + const RelatedBins&) const; //! forward project related bins void forward_project(RelatedBins&, const DiscretisedDensity<3,float>&) const; diff --git a/src/include/stir/recon_buildblock/TrivialBinNormalisation.h b/src/include/stir/recon_buildblock/TrivialBinNormalisation.h index 24fded8f6d..663cc10712 100644 --- a/src/include/stir/recon_buildblock/TrivialBinNormalisation.h +++ b/src/include/stir/recon_buildblock/TrivialBinNormalisation.h @@ -48,12 +48,6 @@ class TrivialBinNormalisation : virtual inline void apply(RelatedViewgrams&,const double start_time, const double end_time) const {} virtual inline void undo(RelatedViewgrams&,const double start_time, const double end_time) const {} - - //! This is the a bin-wise overload for the apply function. - virtual void apply(std::vector&, const double, const double) const{} - - //! This is the a bin-wise overload for the undo function. - virtual void undo(std::vector&, const double, const double) const {} virtual inline float get_bin_efficiency(const Bin& bin,const double start_time, const double end_time) const { return 1;} @@ -63,11 +57,6 @@ class TrivialBinNormalisation : virtual inline void set_defaults() {} virtual inline void initialise_keymap() {} -protected: - virtual - std::vector get_related_bins_values(const std::vector& ) const - {} - }; END_NAMESPACE_STIR diff --git a/src/recon_buildblock/BinNormalisation.cxx b/src/recon_buildblock/BinNormalisation.cxx index 9e42426692..94a974161c 100644 --- a/src/recon_buildblock/BinNormalisation.cxx +++ b/src/recon_buildblock/BinNormalisation.cxx @@ -174,35 +174,6 @@ undo(ProjData& proj_data,const double start_time, const double end_time, } } -void -BinNormalisation:: -apply(std::vector& r_bins, - const double start_time, const double end_time) const -{ - std::vector normalization_values = this->get_related_bins_values(r_bins); - - assert(r_bins.size() == normalization_values.size()); - - for (unsigned int i = 0; i < r_bins.size(); i++) - { - r_bins[i] *= normalization_values.at(i); - } -} - -void -BinNormalisation:: -undo(std::vector& r_bins, - const double start_time, const double end_time) const -{ - std::vector normalization_values = this->get_related_bins_values(r_bins); - - assert(r_bins.size() == normalization_values.size()); - - for (unsigned int i = 0; i < r_bins.size(); i++) - { - r_bins[i] /= normalization_values.at(i); - } -} END_NAMESPACE_STIR diff --git a/src/recon_buildblock/BinNormalisationFromAttenuationImage.cxx b/src/recon_buildblock/BinNormalisationFromAttenuationImage.cxx index 1a75c39c37..d842689774 100644 --- a/src/recon_buildblock/BinNormalisationFromAttenuationImage.cxx +++ b/src/recon_buildblock/BinNormalisationFromAttenuationImage.cxx @@ -178,10 +178,6 @@ BinNormalisationFromAttenuationImage::get_bin_efficiency(const Bin& bin,const do return 1; } -std::vector BinNormalisationFromAttenuationImage::get_related_bins_values(const std::vector& r_bins) const -{ -error("Not implemented, yet"); -} END_NAMESPACE_STIR diff --git a/src/recon_buildblock/BinNormalisationFromECAT7.cxx b/src/recon_buildblock/BinNormalisationFromECAT7.cxx index dc57f94514..a08d1b2f14 100644 --- a/src/recon_buildblock/BinNormalisationFromECAT7.cxx +++ b/src/recon_buildblock/BinNormalisationFromECAT7.cxx @@ -653,10 +653,7 @@ BinNormalisationFromECAT7::get_dead_time_efficiency (const DetectionPosition<>& } -std::vector BinNormalisationFromECAT7::get_related_bins_values(const std::vector& r_bins) const -{ -error("Not implemented, yet"); -} + END_NAMESPACE_ECAT7 END_NAMESPACE_ECAT diff --git a/src/recon_buildblock/BinNormalisationFromECAT8.cxx b/src/recon_buildblock/BinNormalisationFromECAT8.cxx index aac73dfe62..fec7de4206 100644 --- a/src/recon_buildblock/BinNormalisationFromECAT8.cxx +++ b/src/recon_buildblock/BinNormalisationFromECAT8.cxx @@ -707,11 +707,6 @@ BinNormalisationFromECAT8::get_dead_time_efficiency (const DetectionPosition<>& } -std::vector BinNormalisationFromECAT8::get_related_bins_values(const std::vector& r_bins) const -{ -error("Not implemented, yet"); -} - END_NAMESPACE_ECAT END_NAMESPACE_STIR diff --git a/src/recon_buildblock/BinNormalisationFromProjData.cxx b/src/recon_buildblock/BinNormalisationFromProjData.cxx index 113ceef497..57449e6346 100644 --- a/src/recon_buildblock/BinNormalisationFromProjData.cxx +++ b/src/recon_buildblock/BinNormalisationFromProjData.cxx @@ -171,13 +171,6 @@ BinNormalisationFromProjData::get_bin_efficiency(const Bin& bin,const double sta return 1; } - - -std::vector -BinNormalisationFromProjData::get_related_bins_values(const std::vector& r_bins) const -{ - return this->norm_proj_data_ptr->get_related_bin_values(r_bins); -} END_NAMESPACE_STIR diff --git a/src/recon_buildblock/ChainedBinNormalisation.cxx b/src/recon_buildblock/ChainedBinNormalisation.cxx index c73a568a2e..65cb120adb 100644 --- a/src/recon_buildblock/ChainedBinNormalisation.cxx +++ b/src/recon_buildblock/ChainedBinNormalisation.cxx @@ -120,35 +120,6 @@ ChainedBinNormalisation:: get_bin_efficiency(const Bin& bin,const double start_t : 1); } -void -ChainedBinNormalisation:: -apply(std::vector& bins, - const double start_time, const double end_time) const -{ - if (!is_null_ptr(apply_first)) - apply_first->apply(bins,start_time,end_time); - if (!is_null_ptr(apply_second)) - apply_second->apply(bins,start_time,end_time); -} - -void -ChainedBinNormalisation:: -undo(std::vector& bins, - const double start_time, const double end_time) const -{ - if (!is_null_ptr(apply_first)) - apply_first->undo(bins,start_time,end_time); - if (!is_null_ptr(apply_second)) - apply_second->undo(bins,start_time,end_time); -} -std::vector -ChainedBinNormalisation:: -get_related_bins_values(const std::vector& r_bins) const -{ - error("Not implemented, yet"); - //Something like BINSnormA * BINSnormB -} - END_NAMESPACE_STIR diff --git a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx index 1d0b83b1ec..f938408c7f 100644 --- a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx +++ b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx @@ -305,49 +305,6 @@ void PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin:: add_subset_sensitivity(TargetT& sensitivity, const int subset_num) const { - HighResWallClockTimer t; - t.reset(); - t.start(); -// std::cout << "!Nere " <max_ring_difference_num_to_process; -// const int max_segment_num = this->max_ring_difference_num_to_process; - -// std::cout << min_segment_num<< " "<< max_segment_num <proj_data_info_cyl_uncompressed_ptr->get_min_axial_pos_num(segment_num); -// axial_num < this->proj_data_info_cyl_uncompressed_ptr->get_max_axial_pos_num(segment_num); -// axial_num ++) -// { -// // For debugging. -// std::cout <proj_data_info_cyl_uncompressed_ptr->get_min_tangential_pos_num(); -// tang_num < this->proj_data_info_cyl_uncompressed_ptr->get_max_tangential_pos_num(); -// tang_num ++ ) -// { - -// for(int view_num = this->proj_data_info_cyl_uncompressed_ptr->get_min_view_num() + subset_num; -// view_num <= this->proj_data_info_cyl_uncompressed_ptr->get_max_view_num(); -// view_num += this->num_subsets) -// { -// Bin tmp_bin(segment_num, -// view_num, -// axial_num, -// tang_num, 1.f); - -// if (!this->PM_sptr->get_symmetries_ptr()->is_basic(tmp_bin) ) -// continue; - -// this->add_projmatrix_to_sensitivity(sensitivity, tmp_bin); -// } -// } -// } -// } - - const int min_segment_num = -this->max_ring_difference_num_to_process; const int max_segment_num = this->max_ring_difference_num_to_process; @@ -355,9 +312,6 @@ add_subset_sensitivity(TargetT& sensitivity, const int subset_num) const // 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_info_cyl_uncompressed_ptr->get_min_view_num() + subset_num; view <= this->proj_data_info_cyl_uncompressed_ptr->get_max_view_num(); view += this->num_subsets) @@ -370,8 +324,6 @@ add_subset_sensitivity(TargetT& sensitivity, const int subset_num) const } // cerr< @@ -409,37 +361,6 @@ add_view_seg_to_sensitivity(TargetT& sensitivity, const ViewSegmentNumbers& view } - -template -void -PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin:: -add_projmatrix_to_sensitivity(TargetT& sensitivity, Bin & this_basic_bin) const -{ - std::vector r_bins; - ProjMatrixElemsForOneBin elem_row; - - this->PM_sptr->get_symmetries_ptr()->get_related_bins(r_bins, this_basic_bin); - - if (r_bins.size() == 0 ) - error("Something went wrong with the symmetries. Abort."); - - for (unsigned int i = 0; i < r_bins.size(); i++) - r_bins[i].set_bin_value(1.0f); - - - // find efficiencies - { - const double start_frame = this->frame_defs.get_start_time(this->current_frame_num); - const double end_frame = this->frame_defs.get_end_time(this->current_frame_num); - this->normalisation_sptr->undo(r_bins, start_frame,end_frame); - } - - this->PM_sptr->get_proj_matrix_elems_for_one_bin(elem_row, this_basic_bin); - //N.E: I had problems with RelatedBins thats why I use a std::vector and - // symmetries. - elem_row.back_project(sensitivity, r_bins, this->PM_sptr->get_symmetries_sptr()); -} - template TargetT * PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin:: diff --git a/src/recon_buildblock/ProjMatrixElemsForOneBin.cxx b/src/recon_buildblock/ProjMatrixElemsForOneBin.cxx index dcb90e063f..98b5b7a42e 100644 --- a/src/recon_buildblock/ProjMatrixElemsForOneBin.cxx +++ b/src/recon_buildblock/ProjMatrixElemsForOneBin.cxx @@ -415,30 +415,6 @@ back_project(DiscretisedDensity<3,float>& density, } } -void -ProjMatrixElemsForOneBin:: -back_project(DiscretisedDensity<3,float>& density, - const std::vector& r_bins, - const shared_ptr& symmetries) -{ - - ProjMatrixElemsForOneBin row_copy; - - for (std::vector::const_iterator r_bins_iterator =r_bins.begin(); - r_bins_iterator != r_bins.end(); ++ r_bins_iterator) - { - row_copy = *this; - - Bin symmetric_bin = *r_bins_iterator; - // KT 21/02/2002 added check on 0 - if (symmetric_bin.get_bin_value() == 0.f) - return; - auto_ptr symm_ptr = - symmetries->find_symmetry_operation_from_basic_bin(symmetric_bin); - symm_ptr->transform_proj_matrix_elems_for_one_bin(row_copy); - row_copy.back_project(density,symmetric_bin); - } -} void ProjMatrixElemsForOneBin:: From 80982364b812e87968986227c7fb2cacbd06cb42 Mon Sep 17 00:00:00 2001 From: Nikos E Date: Thu, 13 Oct 2016 10:43:32 +0100 Subject: [PATCH 009/509] I removed few lines which would conflict with the branch which addresses issue#54. --- src/include/stir/listmode/CListModeDataECAT.h | 7 ------- src/include/stir/listmode/CListModeDataECAT8_32bit.h | 12 ++++++------ 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/src/include/stir/listmode/CListModeDataECAT.h b/src/include/stir/listmode/CListModeDataECAT.h index 9eaebd41b6..1990aaf5ea 100644 --- a/src/include/stir/listmode/CListModeDataECAT.h +++ b/src/include/stir/listmode/CListModeDataECAT.h @@ -89,13 +89,6 @@ class CListModeDataECAT : public CListModeData /*! \todo this might depend on the acquisition parameters */ virtual bool has_delayeds() const { return true; } - virtual inline - unsigned long int - get_total_number_of_events() const - { - error("Not implemented yet. Abort."); - } - private: std::string listmode_filename_prefix; mutable unsigned int current_lm_file; diff --git a/src/include/stir/listmode/CListModeDataECAT8_32bit.h b/src/include/stir/listmode/CListModeDataECAT8_32bit.h index d36a44bc96..6a6fa9e805 100644 --- a/src/include/stir/listmode/CListModeDataECAT8_32bit.h +++ b/src/include/stir/listmode/CListModeDataECAT8_32bit.h @@ -18,7 +18,7 @@ \file \ingroup listmode \brief Declaration of class stir::ecat::CListModeDataECAT8_32bit - + \author Kris Thielemans */ @@ -40,7 +40,7 @@ namespace ecat { //! A class that reads the listmode data for Siemens scanners /*! \ingroup listmode - This file format is currently used by the Siemens Biograph PET/CT and mMR scanners. + This file format is currently used by the Siemens Biograph PET/CT and mMR scanners. There's an Interfile-like header and a binary file with the actual list mode data. The name of the binary file is given by the value of the "name of data file" keyword in the header. @@ -57,13 +57,13 @@ class CListModeDataECAT8_32bit : public CListModeData virtual std::string get_name() const; - virtual + virtual shared_ptr get_empty_record_sptr() const; - virtual + virtual Succeeded get_next_record(CListRecord& record) const; - virtual + virtual Succeeded reset(); virtual @@ -81,7 +81,7 @@ class CListModeDataECAT8_32bit : public CListModeData get_total_number_of_events() const { error("Not implemented yet. Abort."); - } + }; private: typedef CListRecordECAT8_32bit CListRecordT; From f10b815b11522b10c3cfe878739aad4656fffb02 Mon Sep 17 00:00:00 2001 From: Nikos E Date: Thu, 13 Oct 2016 17:15:04 +0100 Subject: [PATCH 010/509] Added tests for lm reconstruction and a couple of fixes [x]. Crash when segment of measured bin was larger than the max segment in par file. FIXED [x]. Zero stored counts if either total_number_of_events or time_frames were not specified. --- .travis.yml | 1 + recon_test_pack/OSMAPOSL_test_lmf.par | 36 ++++++++++ recon_test_pack/OSMAPOSL_test_proj.par | 35 +++++++++ recon_test_pack/PET_ACQ_small.l.hdr.STIR | 67 ++++++++++++++++++ recon_test_pack/Siemens_mMR_seg2.hs | 47 ++++++++++++ recon_test_pack/Siemens_mMR_seg2.s | 0 ...jdata_from_ROOT.par => lm_to_projdata.par} | 4 +- recon_test_pack/run_root_GATE.sh | 3 +- recon_test_pack/small_listmode_file.l | Bin 0 -> 1019264 bytes recon_test_pack/total_mult.hs | 47 ++++++++++++ ...dWithLinearModelForMeanAndListModeData.cxx | 2 +- ...MeanAndListModeDataWithProjMatrixByBin.cxx | 12 ++-- 12 files changed, 244 insertions(+), 10 deletions(-) create mode 100755 recon_test_pack/OSMAPOSL_test_lmf.par create mode 100755 recon_test_pack/OSMAPOSL_test_proj.par create mode 100644 recon_test_pack/PET_ACQ_small.l.hdr.STIR create mode 100644 recon_test_pack/Siemens_mMR_seg2.hs create mode 100644 recon_test_pack/Siemens_mMR_seg2.s rename recon_test_pack/{lm_to_projdata_from_ROOT.par => lm_to_projdata.par} (94%) create mode 100755 recon_test_pack/small_listmode_file.l create mode 100644 recon_test_pack/total_mult.hs diff --git a/.travis.yml b/.travis.yml index 82b524b94c..de4b9c84c8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -50,6 +50,7 @@ script: - export PATH=$PATH:$TRAVIS_BUILD_DIR/install/usr/local/bin - cd $TRAVIS_BUILD_DIR/recon_test_pack - ./run_test_simulate_and_recon.sh + - ./run_test_listmode_recon.sh - ./run_test_simulate_and_recon_with_motion.sh - ./run_scatter_tests.sh - ./run_tests.sh --nointbp diff --git a/recon_test_pack/OSMAPOSL_test_lmf.par b/recon_test_pack/OSMAPOSL_test_lmf.par new file mode 100755 index 0000000000..725b4a44fc --- /dev/null +++ b/recon_test_pack/OSMAPOSL_test_lmf.par @@ -0,0 +1,36 @@ +OSMAPOSLParameters := +objective function type:= PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin +PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin Parameters:= + list mode filename := PET_ACQ_small.l.hdr.STIR + max ring difference num to process := 2 + projector pair type := Matrix + Projector Pair Using Matrix Parameters := + Matrix type := Ray Tracing + Ray tracing matrix parameters := + ; use multiple (almost) parallel LORs for every bin in the sinogram + ; to avoid discretisation artefacts + number of rays in tangential direction to trace for each bin:= 3 + ; you could disable some symmetries if you have enough memory + ; this would for instance allow you to increase the number of subsets + ; do_symmetry_90degrees_min_phi:=0 + End Ray tracing matrix parameters := + End Projector Pair Using Matrix Parameters := + + Bin Normalisation type := From ProjData + Bin Normalisation From ProjData := + normalisation projdata filename:= total_mult.hs + End Bin Normalisation From ProjData:= + + ;num_events_to_store := 100 + recompute sensitivity :=1 + use subset sensitivities:= 0 + sensitivity filename:= my_sens_t_lm_pr_seg2.hv + zoom := 1 + +end PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin Parameters:= +enforce initial positivity condition:= 1 +number of subsets:= 1 +number of subiterations:= 2 +save estimates at subiteration intervals:= 1 +output filename prefix := my_ouput_t_lm_pr_seg2 +END := diff --git a/recon_test_pack/OSMAPOSL_test_proj.par b/recon_test_pack/OSMAPOSL_test_proj.par new file mode 100755 index 0000000000..a30596a108 --- /dev/null +++ b/recon_test_pack/OSMAPOSL_test_proj.par @@ -0,0 +1,35 @@ +OSMAPOSLParameters := +objective function type:= PoissonLogLikelihoodWithLinearModelForMeanAndProjData +PoissonLogLikelihoodWithLinearModelForMeanAndProjData Parameters:= + input file := my_sinogram_f1g1d0b0.hs + maximum absolute segment number to process := 2 + projector pair type := Matrix + Projector Pair Using Matrix Parameters := + Matrix type := Ray Tracing + Ray tracing matrix parameters := + ; use multiple (almost) parallel LORs for every bin in the sinogram + ; to avoid discretisation artefacts + number of rays in tangential direction to trace for each bin:= 3 + ; you could disable some symmetries if you have enough memory + ; this would for instance allow you to increase the number of subsets + ; do_symmetry_90degrees_min_phi:=0 + End Ray tracing matrix parameters := + End Projector Pair Using Matrix Parameters := + +Bin Normalisation type := From ProjData + Bin Normalisation From ProjData := + normalisation projdata filename:= total_mult.hs + End Bin Normalisation From ProjData:= + + recompute sensitivity := 1 + use subset sensitivities:= 0 + sensitivity filename:= my_sens_t_proj_seg2.hv + zoom := 1 + +end PoissonLogLikelihoodWithLinearModelForMeanAndProjData Parameters:= +enforce initial positivity condition:= 1 +number of subsets:= 1 +number of subiterations:= 2 +save estimates at subiteration intervals:= 1 +output filename prefix := my_output_t_proj_seg2 +END := diff --git a/recon_test_pack/PET_ACQ_small.l.hdr.STIR b/recon_test_pack/PET_ACQ_small.l.hdr.STIR new file mode 100644 index 0000000000..b1b711f626 --- /dev/null +++ b/recon_test_pack/PET_ACQ_small.l.hdr.STIR @@ -0,0 +1,67 @@ +!INTERFILE := +!originating system :=2008 +%SMS-MI header name space :=PETLINK bin address +%SMS-MI version number :=3.4 +!GENERAL DATA := +!data offset in bytes :=0 +name of data file := small_listmode_file.l +!GENERAL IMAGE DATA := +!type of data := PET +number of bytes per pixel := 4 +!type of data :=PET +%study date (yyyy:mm:dd) :=2014:06:24 +%study time (hh:mm:ss GMT+00:00):=15:45:07 +isotope name :=F-18 +isotope gamma halflife (sec) :=6586.2 +isotope branching factor :=0.97 +radiopharmaceutical :=Fluorodeoxyglucose +relative time of tracer injection (sec) :=0 +tracer activity at time of injection (Bq) :=6e+007 +injected volume (ml):=0 +%tracer injection date (yyyy:mm:dd):=2014:06:24 +%tracer injection time (hh:mm:ss GMT+00:00):=15:14:00 +%patient orientation :=HFS +PET data type :=Emission +data format :=CoincidenceList +horizontal bed translation :=stepped +start horizontal bed position (mm) :=0 +end horizontal bed position (mm) :=0 +start vertical bed position (mm) :=0 +%bed zero offset (mm) :=0 +number of energy windows :=1 +%energy window lower level (keV) [1]:=430 +%energy window upper level (keV) [1]:=610 +!PET STUDY (Emission data) := +PET scanner type :=cylindrical +transaxial FOV diameter (cm) :=59.6 +number of rings :=64 +distance between rings (cm) :=0.40625 +gantry tilt angle (degrees) :=0 +gantry crystal radius (cm) :=32.8 +bin size (cm) :=0.20445 +septa state :=none +%number of TOF time bins:=1 +%TOF mashing factor:=1 +!IMAGE DATA DESCRIPTION := +%preset type :=time +%preset value :=3600 +%preset unit:=seconds +number of time frames:=1 +!image duration (sec)[1] :=3600 +%total listmode word counts :=1210129934 +%COINCIDENCE LIST DATA := +%LM event and tag words format (bits) :=32 +%timing tagwords interval (msec) :=1 +%singles polling method :=instantaneous +%singles polling interval (sec) :=2 +%singles scale factor :=8 +%total number of singles blocks :=224 +%axial compression :=1 +%maximum ring difference :=60 +%number of projections :=344 +%number of views :=252 +%number of segments :=11 +%segment table :={127,115, 115, 93, 93 ,71, 71, 49, 49, 27, 27} +%time_sync :=24123339 +%comment:=PET/CT gantry offset during PET acquisition was x=0.000000mm, y=0.000000mm, z=0.000000mm +!END OF INTERFILE := diff --git a/recon_test_pack/Siemens_mMR_seg2.hs b/recon_test_pack/Siemens_mMR_seg2.hs new file mode 100644 index 0000000000..f419f744ec --- /dev/null +++ b/recon_test_pack/Siemens_mMR_seg2.hs @@ -0,0 +1,47 @@ +!INTERFILE := +!imaging modality := PT +name of data file := Siemens_mMR_seg2.s +originating system := Siemens mMR +!version of keys := STIR3.0 +!GENERAL DATA := +!GENERAL IMAGE DATA := +!type of data := PET +imagedata byte order := LITTLEENDIAN +!PET STUDY (General) := +!PET data type := Emission +applied corrections := {None} +!number format := float +!number of bytes per pixel := 4 +number of dimensions := 4 +matrix axis label [4] := segment +!matrix size [4] := 5 +matrix axis label [3] := view +!matrix size [3] := 252 +matrix axis label [2] := axial coordinate +!matrix size [2] := { 62,63,64,63,62} +matrix axis label [1] := tangential coordinate +!matrix size [1] := 344 +minimum ring difference per segment := { -2,-1,0,1,2} +maximum ring difference per segment := { -2,-1,0,1,2} +Scanner parameters:= +Scanner type := Siemens mMR +Number of rings := 64 +Number of detectors per ring := 504 +Inner ring diameter (cm) := 65.6 +Average depth of interaction (cm) := 0.7 +Distance between rings (cm) := 0.40625 +Default bin size (cm) := 0.208626 +View offset (degrees) := 0 +Maximum number of non-arc-corrected bins := 344 +Default number of arc-corrected bins := 344 +Number of blocks per bucket in transaxial direction := 1 +Number of blocks per bucket in axial direction := 2 +Number of crystals per block in axial direction := 8 +Number of crystals per block in transaxial direction := 9 +Number of detector layers := 1 +Number of crystals per singles unit in axial direction := 16 +Number of crystals per singles unit in transaxial direction := 9 +end scanner parameters:= +effective central bin size (cm) := 0.208815 +number of time frames := 1 +!END OF INTERFILE := diff --git a/recon_test_pack/Siemens_mMR_seg2.s b/recon_test_pack/Siemens_mMR_seg2.s new file mode 100644 index 0000000000..e69de29bb2 diff --git a/recon_test_pack/lm_to_projdata_from_ROOT.par b/recon_test_pack/lm_to_projdata.par similarity index 94% rename from recon_test_pack/lm_to_projdata_from_ROOT.par rename to recon_test_pack/lm_to_projdata.par index 792cb38be2..d55fd474ae 100755 --- a/recon_test_pack/lm_to_projdata_from_ROOT.par +++ b/recon_test_pack/lm_to_projdata.par @@ -1,11 +1,11 @@ lm_to_projdata Parameters:= - input file := root_header.hroot + input file := ${INPUT} output filename prefix := ${OUT_PROJDATA_FILE} ; parameters that determine the sizes etc of the output - template_projdata := template_for_ROOT_scanner.hs + template_projdata := ${TEMPLATE} ; the next can be used to use a smaller number of segments than given ; in the template maximum absolute segment number to process := -1 diff --git a/recon_test_pack/run_root_GATE.sh b/recon_test_pack/run_root_GATE.sh index 5ffc0f2320..2c0cf8a7c1 100755 --- a/recon_test_pack/run_root_GATE.sh +++ b/recon_test_pack/run_root_GATE.sh @@ -72,7 +72,8 @@ rm -f my_*v my_*s my_*S INSTALL_DIR=$1 ThereWereErrors=0 export INPUT_ROOT_FILE=test_PET_GATE.root - +export INPUT=root_header.hroot +export TEMPLATE=template_for_ROOT_scanner.hs echo ------------- Converting ROOT files to ProjData file ------------- echo Making ProjData for all events diff --git a/recon_test_pack/small_listmode_file.l b/recon_test_pack/small_listmode_file.l new file mode 100755 index 0000000000000000000000000000000000000000..a0ad2449e5dc2221a4037e14be452364c40ad1e2 GIT binary patch literal 1019264 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
sQjbz1fB8?>Qjsy1Z!ShK(Dy@ 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

}4~6YLV2M5~;S*5m0y{gl=GJ z$!Lcce7f8<%}c#S5Uw9D`M#UP@@J1r*7SUWX8pl5a~VtI12hDe)Q$oA;j=`4|GZ&6 zn~n+0LzRj8W&Yg9Nfy71znaJ`egyQno-FoXmPImCKVZ2(l8|8gA@cag?~>UP5u|-; zg4!zDM|>pQga(t0Jw2zz>{OH4xJr;J;#?&~avL8gech-fF;&?w`aQ3LD07LB+iEt! zbPh21lUc3MR6;={`PmNJdM4n%a#8Ws4*(o_vY9s#Tce}YAlVbw$aCt7Kvc^;tUC9X zXkKhFrzd$x-4E@fbQ8wG9*h6LGxQjh^=zt=cdw#4dj8!(W8<`Fbrw z;cwnpektQE(hwzF(?X)Ghe@rTPX5F^C(?g%*O553PK3P<0M>Kmp#JJaw5r>VS9%0V zt|{MV?ti~Vgxy~QjGtkp8^qTT*Sn~s-@pMpebXO(bXrJN*qns=cc}`MGeGfossDrz zCFd1tK3Z!1-V&^4TE2thdwXcNH?xFoA{6NQB@{+noX4Dhc!nTq<2W1N=ZXO)p3F^+ zi`@3sb2`cSal~!99BV%=0OtOABIoNe7b)Gt;ld~jRB_-gIGUo&h^BY}YL5*$C$dMY zxMCOjO>Zhw|1uw*nD_;}tegD(&tfI#CIeCW>Kh`5g`cQ}_UouOlgw(j{rhQ)<#P1A z>Tk>znOUMa8l7k^b&&H8*A=Bh&3Fvq%`cdz#7iw|L(Fp#!T1dW(XEaEu+DxAHQZ>7 z-|lUu*~jy^QRnBZ?J_{{X0ZqK&O{<9P$z)SEG5l}T3lj$UY9Mppbr7p3}vI(YH)c3 zgRMq6+T?l*^o98@-Qrcu*V)^WPnz4Po=Yawa*m|OU-vOfe#HHMcQ` z)TfM$-f8}1og;rBQiYvu)-E0z^g%>3F{Wu{l~7=;!A*9m*n+L!xyWZ{=tS2?xU3$H zHD|ObL|WC!sCgubW9mP1Y9;AhRpmJZNS^_IzrUG#>eDJPeDj2DeW2#tX6(d89~24_ zUu>sbezrolcn#edrS?oBK3z~oj)+QhlK|%8JmgMIjGDDbnbd6#o!k~qLWlbtHQseC zlyA}ul$g5zrA}G_;?uEt77mG1{r%4z^{^8RitP#7lSOBN>)&g$g;VXv5DgkkZ+;vNq>M znlUZ0^zSPN>AMv(^fu>o@``dZG^)3*AZ)Dy2#>}3Y*WN8xxa_j%I^g;VMUYUxLW^+ z=x}H+A`>VBJpQ3bdZiAq87&8~%%3*2nJJ`r^6L!h>wm6FJ=Ml^(eHai4H+TXI_oET zvHU!fwysJjw@X2>`t}Io)8(Z(>&rQI-e3(^1?S@x(~pW*>}BY%>>R@Q+9^rqLNhdO z#RjsLd`6TsJO$kqENP=q2RU5Rgq@hSRxZZSldxI)61aIyAnve^=WMzRpeUVRSoEqi zP;ys?=8nNY#$-P$Wp$ih>5WWsbG$zj*cCurT)i6DP=*U+-+$zHnGW)96&YO9>}`^# zDQk32WPgMAth~g;Up|4C2@c3PZ`2Z{y^G_NdX8XA!iBV3wkJR9xGFtek5FW=18msv z9`XA_8)Z|K^y%N!1>%u7RbbHa175!OH1uH78#`?vnL_gR2xb3th&SkHapjRAqC8z5 zR-AnQuhi0}R=-Unq-S1+maOa*h`(f0wGYmMKaW=JS)CS05qZeXk~*Co&AaG+EGL3Z5SjA zerBW2k8WY};(Ny=z_=`F}|{4f)AbrsX)s-))8*dR*$a$8)tbPGDX_c0JRc$Bhr zJVk#v>;uB%g=%u(Ab8e&H6!V07S=wvEHm%uW;Xq!Be8e+N8a|>Ir@ra5S^gaLAh8( za^F>yRkL3o5%~BjfM7bT(Nc=_9Q7S9=190qjsGtwq zETh_0NUwY9tZ`H~jXU-Z0Z;B*MEovpQwJ_x=fa|JV0uL(5#ib?K1UtJ)+BZa%3Mal z^$!n`KB7QrkLY4a_Wc&fde>TZ<@R*$U0a&Q;~z8F)rE2JuO)fR!hPrAS1rqhvfo1h z?U=XXEVPUN*%?DDnsA}k%r_IXN;|V}My_KH2RPIvtxjCKkx)w#Tm^S*y~4uG3z62g zd_*JN8a2OgkI%Y?lNrW-gl*6^B5aj2=inle)UG+q4yxv|*X_qyfRHA|yRNecPo7XxIM9*|pkWEx^& z{zX2|L`K`-;ZBjS?{`S%^EX!dzx%317Ox>!4NtCrqnYr+i#j@ItwiJDg2$}N+EM1H z-gM#14I?yuFNiApX)d9Gii*JSC$Lw z7jNg8VQEcUQcLe!sh>FLBJB`oMvCtSDdl|&;CH@OW*7Eaf^$x$6Y)*cQGMkj?C`Hw zL~-tQ@a+~Cseb`C^t?`0^WQF)6W)7ekcNxT*o(K?c-7YHq7xpEgteabVnMY5n>AF; zE%iLd2$Xc0PfR$`-gy%KxT%FV|LDfuR2Hh;+w@sj9NR3(n*8wmV*Z@krdkaAmDdAS zT{w&@7Rb`y4V6%3+f4Rnts1{h=`_X^c|)#kX2Rz;dA5YT#3iNLAV$Hq?Dm>1$fll) z?4u<~TzbbFLGYS+!nvQ95g#3V$dV)f$U9HZ02}?3>3IA;lHpF`124LyTKO(g#rrwA z?aLAF&repFHmTA)=sg!1ZAgVmH+CZ?c@MCge*wuEkH5H+$pJ)hyK3cmN5f07>*@e;BRc{d|Ir3HSFc7471yE5d#!L< zW~WHG%9k;XCX@s_?_;6H=iyJTS8z_*olQ6{BzoRB;ogs}xWUjvOzN5noxitl=}o(_ zLD&~bsA6M(l(zB51%Wp*PqQ62dd(S^&;XGBKw#XGoETU+)Qh{{;LuKMb^)KW@YaWJfwi8qqb&4Is z>bUki2EDY~3B1?PB6il7rmQmU$fD(Ts<>i4`^SE>yxoy6V(;v1e9amuYG7g^F&210 zagMt$e&d)v=4L$(|9tJp)E`|AZ1Y0U{;p->me;1R!MjhWLZi2=Iky@7Y^J5Wba$O7 z_t6XW=mKppoRlY_joR$8cj~z1oD`};A&}_K?-eY{E~cwQ7I2g(TsRRn!Ff_Se6jBn zEYaVZeSiN6h@82?Pq>>yH5%rk^_ySw=YC9)ReE_|kh|aqrlgXkd|zNEKhaR8ykW~a zRh?&I=y>-kpk*mek9ikkwYsLl9XE<(l8@{n%CYzCt$tTrNBapcI5&;HF#isAc*0WH zGWRFCT7#9ci_6C6ZhgR6)^1|n(o=}`kF88vMLd(dyiND`(NxyyZW(<+H(Ix+vW

    jBz#)jEYLD~D7yAbN9#j?8*{Y4mbI;H5x555ga`lg=6A^72isaTiR;6s zA#D#9lCPeP_vw3cUERZc(&_=I#NeQe!Oa3?e1(+Ge3?4lRs59FJ911iL#IuF4wevo zv44qOo{y=JN#b<+IVIl~24>v$q)bmAMb^|zkoSGZ z*|QoNyvyE7>fne0_QBm8+&yGV<{KZC_zkYmNWRqvt-s)|GB|GwJoQI|P?&TDZCzeX zky5Q{LAPEJ(Y@=mBTx8)by{^)5U_-G%Sa{13kRS--MiUMFP_70Y5h#G28z%2J&YXN zvxS+SZULV0UrUBU(>S+RKgsCnC88_qcM8geJ>dNfn8e5THX?`vWpVa9@YG|YXnLA4 zfzEOu&PeU09=>jpum4g_T(C+cKSGo{ z{3JFttfm)6zu=E`BkV9_Eo#{eNus3y$lsw=dVb1&YUhe}B&@MUFs0-WGk10ewf5^r zL~H&`ab8>#H+^a$ye#UW*55sA!6Tm!sW<-HM~#%5Gh!?ge>JcadeLZzoNJO3KX`2@ zRQGJeeLg)Vap`tSZss#K=I8?0^TiWf;k#H2j!$K9Z5w7v^eVxG?{3iPOFRDX&N0c8 zu8r)uGiN!)SZng(f=htRo6{Qm?be~;BR^H9NMP1!Tm9sj-FEJ}=2d7A?2v3|k7J^0 zlhK}pA8OPq705aIC$?YiHuKywUr%PvDXM$V1x&%pUwF*wp5A{*Jn#=16ZVvvA?ibK zph-R`Hx#Xp=H#y7@6{Odt|iN8#6Oq>%uA5bDycJy;n$QrE5~MW%8n)>`|c5D@06Jex@wOXGl&r_v>by znjvVsKq7TtF5^*|j_}SUxbVm^CqeJ4k7((%Hh7}>359+x2FT`{JQTMY& zyo|7sO<1VRKGa-`FI~8jir1yMp%rtr$ytU(Q7F(;)YkQ#qH%c6@uEp<3RlGr;02 zGVEwP1S(JFR)Q5E5v7>34V@>EW54V{MiZlUc9;l(d`B)NM8dw0nN6x`jnSGOx$+15 zmI&g$Y{pi~8bL|NizY!PRlvRG>^eXjmv)=kcAcySf!uzPJxG zG0+#t55DBKoKq&Z7}YZ9q?c`t&TB}lOAt!2xI|`+x%7&92azt;L_7)Upes-IAfbzX zQ(sL{!O*cL{%yq)jT$|9{{E^ce#f*@X)IfVxtHJ#Pk*u$l=54}U%FL+VlVvw4S^-x zSguIyoqH4X=q;pwp3Y)(tGtNb#X^SOeV>+KE6I{uZ^?op2+e+KBA;BfSP}cZhBNmb z7ahp%fo?m}GW!xWoUok+f4|}^`yT?yP_)c!HCvrMp0Cjj4g2ZC%6V`IgRu$F zGvKdI9A3S16yK$x!-qSa!q*Sqr&=~&K-+dqaD%aPSzG-^U|afj#NqQzGQdAxXuP6R zNd05ki{o#o%FSCjhYSr#(Axb(T2sDUR(gxvOZoxpQ9M9J_dS-Vh(iTCXT|b^Un7Od z+n%Vc&%J?s9CKq;*98EL;Z{T@6hDF9aVgm#V*;w@vm29tY)K9^md; z#jsb^^`xf`8KLb`eI%!FigXRL1&1Pyk*tKvR7|u2lW)xfQ^JRcuPKVGi?@~PP|01) z?%p6!G3qV4IrEI%G<%mojVYo}AKq%EhhTfg6zTaa3 zK9v`;O|uHr>ceao-3~fGk;Dp9}_Ro?JDJjPnH$vz!{U5wIgM8DGMa@pcfLX z91A}v_(7c7*NA_%y9IsOJRMYhBP9FMmE=f&8_?x!8R3-0GG!lDsU7b#Bd3?nhi{2p zfVgC9jJux1bSnR&vMbhe{~Y!Ui#r46jJaAZm*c1{x_PAyF5v=1 z{@62dueq1_nchO-_`X+Y^ty#aPuMcl30VvA1-*(@imQo77U2wc&PlymW?W~Q&m4MJ zD97XupAg!A8bLzo$As>m2efI|5~lt76aHlAF^S4#R=n5S2(Mo`to$lpmdwdsF56(e zQ&DQL9J{hJmd7mK>$KYhvXe7rDrL?x+Q-m^TRi(dn0IUCWH)yTHswevQPiLYr>@$^ ztejQKFZiYhM!)4*50?-fwpxerpslRlM= zrgM&##(wY@``P{&<{Vg&fs0IBjN*FV@PnLGkP>#dXnWNVHK;UiyL(# zu*AAbm4!}8{NBw1>gl%#)>{4-@px_(>7VJs$;&%(wW(%Q$(QT+&D0DqsvA5 z{>UTh?do3SQLzlysv3cvv=8Gk<@0hP-D=6V&$oDHZ?{-;-%Bb|KT*;95Dx85UX3s4 zb3|NIOSsoz?abct5rQ*a1)pi@LR)gm=?2q$%~k7N@mlGp68~R2wULJv_{q{!_@+lg zbmYRF@QL;|o*a?LhZ}#K#Po+^d4{R1jYF+Uy3@-g<)VtP(uzu0HW6yV(6aJV<+Ci!6fP0mAhi0+$o z^v#SeWzw8kXu5v4-f^1~%57FJnJtqkEew6sfKWNQ{AC>aVt|484ft`Tfi#)QJSXlRl6wuJ^IL^@C^>rAqn6pH!cl?T)rq;QYYC z*OK8lHOYrF(}n)K-zXXTnDZ~_3jlC5ig&wQgh^>=0iULu(rSVnX!e7@cyv}5uCZTD zX?*h=x!0?B`HmZbbcWtpEbAQuozmKn)xe*jGs|2|bj1%{zjZAy;4y= z?Io9`VTiPTm0(@)BGw)8kl9~zN$c|H8p?Q*$=#dOpy#YLj=64e&@0V+uHlPHJMZc8 zrpuLD(Nixte!}w|rSbg$_o4X~p!T08=2WFBPDsty#u+83#{Dz3@rf05)$kT{XCj%R z6#v5v<5x)b&syZ5knJA<3snW860BeCULQ=C_h>=YX)KGQya z=m+*~i!z(9lc0j>ZcsJRD^_)yz6!Y%zY2>uSLF}W!Wd(m}LaA0~L{W0hWE6BVA`K>)9*>G^5AS>)Y zQJm{6No~wt_G;@N>hnrR{&#C6yu31n$(#H?O26F3X2upXA?y=vmwX?g%J^KAI(mub znzpj@5?p}luewmgrYxpgwMra`XX9bAYU)b;KU6*bJE)Dnz6|5~5HvG%x?t#Qg6>Yw zvw{fqZ_LlkUGl@4FTukboCF>h&8f(OekiSZ5%c5tf0+HBJWa(Rd&coYBl_<=!U)-XG(4X7a z6JyIe(I?YIG7xTqI;zh=AJ&avimvZ?T3H)Zj=hL#`w&b_%6sC_p0jLC;Uaq1DO&Qf zCqS3|7DE2p*Uid>^&yr~^3Lm}objLH8QNt5vHXHXElg}=5ExpW%q^p?Qdpq`4@oV6 zXXK>OvwXg(xX+pkf1oOuJda@VNLZjk)0e^S@=M> zT}w&hfEZ`;-r9nCkNU817gyjgcUD_DG#HYdUBDW;Me_p{pRj~(f>eNvh%8aJEOc`t zC@MNajt=c(hsGLU^i~Uzc48PhWZcd?<~)I-w;Fs+>QRC1;acRk5)M4L*++~Ugt00g zV}&Rpq-wrDkTEn^ui}#%FQ`m@kKPSKRmYNt0aEpAX(sQ z3Vy9w1}*0wi@U~2)aGLbGVWjm50+hK)7z`@S5KORDJx$pnD?G1I&99$JC?LD)7KbN z4-clHGv8C%+w1R>E=R|8A6`5xXHhr;JBVp4_W3_i!MiJvg4t@VRxdfaQt6JURPU7J z^46uyi`_2-eRWw%aJnP%=x-Lf$JPUo-DM%_4E{~5nCxy=>^P6N{F;Xk#mH;PEfEqW z;rHa9U8*Dd#{Pl%pbER-zMjw(DPm-Om4Vki}8~OZ_T^F4Y&6W$)+0!|NwRR*LqjC*CfAld5+U z_fCnSw|JQ9@_A8$lmjLdRdj}KGq|W`vEhxx)WRN#-)N5O>w6K0POb&M*&C}@ZyI3W z_O0ZMl>>5X=jB0LzoNpkioV=Lc|Gy&=mz51XZTYgLGWJY0J*Rpj5Hogz z8|?W`j0ZZCkLGJCrcL5m;@L~YMJST85V^6*uI|v6Yhz4*U>Mes_f=AvM?i1iB|}as zcaf;*9}<_$d2GFfKP@-vB$@p!4XTWvQgBMnJ?)*W$t6%V0V0g`BQ3rTyTKU_yjDbsttG9-7S- zq`%dJyNnhw^VKxTmFqv!x|Tw8tGgUJ-7=V8vNVBF^NQo%zDc2Ohd$-x;&R{@hJ&n$ zP9+yI(1AP#Re?hJB<9568Zs6Fgd5GF_V-+&2Av;Z@3jXtA{Aa!TYAGV!?jvMm4g~gRYMw6^Gb$1fca4~Eat)U z9t|Kn$IXBZvnz!Ot-lzm?I{y0$fPrM)PXjK$)dpvfqdu{5mWd1D!3%;ByzDoj`%Ls zr1&?h8*NqE!OT}ZrvB`~Mk4$9AJ*?21_a#gB)(+NXZnoZz*FZ(lWUBlAVR5>@|-;i z+Nowh2}Wh2yW)4ew6hDL|2$oHzvs)ztFP01_zD}m$=wa;7JVm^8?@nF^fZF&@sdc}m_I35ExqI7_Xh^tNX=o4#HLqlK#$SjV zay~J~<;@XT%S_Vj+b6zBJp#%%TZ3&TW6ZjOGgSZnK;~M4iNdZQyRigQ1?c|oK(Ky5 zUgq1988Ce}7iaI zevS%s;HsEzg;b<}@$=|l(RX@$a~fV0@Q+H!t!F>KH-v*$+Q!)T&AFtq(YxW9tP0d9^ zZ~Xvkzp2dMCrtt!;d8NL>jBB8ZO&|mmq>EANtM5+I8|}q^mbOPE-S0IV3dJT1z_n+ zgb{x+5n^Au1luf@3wBKM$Ij?a0MNeoh{21)P+-6@$;6-%ZE_?;WVyEjt_`^c%=Aqs zKW|TFZriz_K$AH#XKxg5dfo-ypL>n__?@6nmJ|zXOfAt&vxDOLrh7!-)mh-ytNV2A z75(9fuk*xB$^X%}-c^cPRyq>1*C|f{ziNm-&uHO}Q?cR;b)G!qCm}av%>m2U#n|Hu z)1c30;nbhlc*)|o=K$v&5=>3U3!bE|vgd*wNUxA|aq^ox%-B&?%s9`HDK#GfMIW{z z<0i^@!1L!=9<2&MPd1Z1rSiyVND^C)9+zJMPH1U-jH5714cU;EE%4s31LQHIUwCp& zBK#2;r2WRPf|>RMs=rsjbZyxN@P4Y3L|=Y2cI+mj?k$+>r6Hw+2*30F z{SWu!y6)?Jy{_jI91&fhHmAi9vWMRTIfMaw(&3bhzu|j5w(Gdi$UYa)$UsomX%shHszyhp9mL}ymEsYE67P-@voZTUMd9fS zSnBCso{1S>t$OMMeq_ZSw!c*c|7Fk$b-bV;lZ&q@S0h;ta7VOZWRCKj$buUs)ilPgb-6Q?H?=rPTW?9_ucFeTFi`L^K<@2d&R`{{jz z%gUI|^*K4Qw&-poFaZWNSN&mr*j__oH=5}@i~K{zv`%8JXFP@X4dzQVBcE6UA0uYW zCxmgyJjvz^cGHfL3;8vjlx*UEu$sO?p|E~rKcVR!soH%zg1q6qmOK_AN1fSqO|XZW zC8{r~!hRJxsg({J!+NFZ$Z-BNq}OgHS!-8DEGoFA+*FvZv$AgqX-ubrqwT%QZ~tru zHdMSUE-;KJW7N7KL>>eEJ5+j%oIH9XDz>fz#Mi%=kq>)TuE;{se(9}EyI@D zRI5Gq`^Oj*TYqhY_~9UdOBXVT~^qQTHDz%|<| zi>erSlMDS`qK-Jp>c<%g~+j`)C3>g=>{K^itZVFX?*}hR)HzQNjxNjP=_INEX z`Po(6)nG#OYF>#(WJ)r(r{E>xJm)Ut{L-GyZ(Jo5%`M~3+gz{^J%eCW*C3xDTcm= z#si+nnICpd`uhKRKAnJh{IaxLI~m!cd$~sf(T? z(JKN3^RFhu%kOPfBu%mfd8$vDam{UDN!S8=MU{LA$yQdQ1 zbU7Qpzy%o?N~P3nS-8*LTNJn@TI}@tADQR4lE8nh!0bH@1bOcasHJB=(@OCTgn02G zvC@s-glw9C{WK$r{3aaY8)Z8IRw??({n2#6E{kb+1!^ajiI~9KG$7)sawf4?}L%#||pRgp& z#Dku~FqJluwNw=q7}dz6Yks0z@4889_ixU){vvJ>Xo1bLPoxYA?5Oc=e&V&RA6bk2 zjfAWAbXaBYKcFCf3f-!kN=$DEMVmfLZpdfNB+I#vI*}E}sklo4{3TP;z}Q%rus4!V zHazcz-FdG`BSU?lyJ(C!pYDzYo19aB5Oot#b5bHs8vo$@K2+iL<}j)DQjbPG9#Nl{ z9bw}xjlnrTWE8qLU|LZDq3pkJOW5@TDcldOTzvhvJu(B&7}Ur!i(KDdEXa=jh+oyv z)G(X6tSGp#5Ka%j2wT~#$2xy^f*;OHlAM(@5%-o#GS=FTcwWnwI09Fprnldc$C7rb z-Ol&}^!Kf%!2nt6-JUE#;pt2&NA(i>H$8`QbM#~!^mV~R?;4(8!%tw?alY!4r}={8 zZyO0Y^FZWD`U%=+%^Utfu$uN&YC@_LPGj1SMp3(y>JA%zr?FBmzM$UtJ;7Y7b@a^R zA@tjYuK%C0_|s35C6dhcvbJqJvEwpWb-bM}ERi@vT@1-G_wB%m7ZE^j;0JWnW(BlN z!xEZZb4+E;%%kGT-9w_zH8Z7t|MpdW6>>@a+pa^xPxZUS|32Bl=}Y?P85sccsF)I$ zsfdZ+0n=F1KwHAq;U{>Tlm@i2&3Wy|zjC3=8}xF<4M0HQBOESUNvV?4q|XdX9QLl? zn0*7=_1@(#p^sfK6TBGx#MiStsxUM?18x+&2WL$`$z30vTVmA z;X6w{=cDo#O7_;p`;7Z}4cVa3^rHsS4!O&pt}SIUl0sPf=$qWmts)@1RS%DSyIuQV z^JD6XNgnIGeK9e5SeZB_$VOh&j_Dasj1yzOm*}}4xY`tS{#ynrE~7yBo~n{FP``pE+>6HTX1F>k3M;H8UIMPCV3CL3CH@Sz%H^<)Qwp? z(4^40=&yYe9ARapSVONAc$!#8`P%vu2{y1eM`tDf*6%?sLiQ1RLU)qTpIyQIF(@K3 zg*OFHC+EVJ4>Oq|?-Xdau{P>_A_1~Lvw}$rHHC6~mlJz~m6bfOqx9g5c^cliBG2Tj8T ziBS0WqddD0o4KNuS%?%G%W7E7Ms@cg@Z3BfDCou`NnR-ixxP`Bb3J+o-8er13EN+$ zF7Liy*I>pAwGDTclHL+#p5^v~@U6R#nIFlR=`j=Ncrn0@cX8B3jMGJ z??=Wz%&|sE9HQ8c$k`^-!iDQ4oS7J;HzNrVEj1NB+mZ_!^<%<%%d@I8>LSQz;cG>U z7k>~s6qA(i0H1bMa-#MeV1#P|6LHh_Civ*h08xvlBQuix6Q#1x!6LVK!o4X@?U(Nq zBfTvX&QXhnGLH>WP6|t5o7&@QhZ-x92L1*b^%17C?&hiX`j;#1*` z59pJ9FL-~OWJQeOcP7+u9PXL64XZk9K!00Mjy>$Y&9qJu)E4g<*zlSg^yV5jQmUK- z=Ewa-+Q3MjNnxH=?B#dNp>zxNKZ%P)dsb`&C%T-FcZYEDjt0aekCTFt?6>%By<^g$ z%yFLE#(M@gy&|(~^}>$*4Z`JTMu5K`i&@oU|FkzI-$2OI&zYRP zk7$FP0J&{@CnKBS1TNQgXRo+jCX4NlC_T2YVr~W+&@!I+gx`<%$k)<9Y>18$j-vn} z6Py6mX0#zcawM#VAG-;cuYvq+bDr{5a0yFZ;R{TBUqSdK{t?W%pFm#>6DjWMk>{qO z29Ua9Iohf212M)wB|f?Bgcu2R7HLTOgfmsCey(e=cPN^CxC&l1i*m zR+0*}x-NYEITYYh%b=ykle)92C0wZQ7kLva?cr6&j?s^L#fmDH--*BsTXDy-NwO*B znAH8@8btN#G!AtAD>L?HCa?c$BK<~cJ*-yVMbOD#$@|xKa|fdKCpDnwo z*Rr#<#|j9}w@nYVk@yhr2i!+AYD3o~w!Sc5l%bM_Z~WcFJ7_g7zuzhd8XA{Ic~Ajy8gxRpMZKWx`}QHn zN1CwwxwiOu!X6trO?e5tq^4~lg!A|+hF;hR%*r1 zt(02JO?spxi2rlBJ8kXqi+jD|xVXPn5$lM4!#hwb`6o8ag@P713O_?re5=Ra0<-nE z!dOWv|$G@J%zUna)UGv?+RJgV<16!|i z#syE&;EM(7tJn=``r*4}c=`EQ}T>4|d@@APu*H$O^<&&!`n-`W=h zHI>{Gc3s0`l$v5bRja-=38q6?OQv%(~ZRGFyaa)E0J^lD`!6i9O>71X__-wC|dtZ1?F8 z!e6yI_)(uZ`0@6S{9%=q5WP}fI;MS}&a&QKq-5VFRqu84urVJ6%_)^3#o{m1^dApr z;B$T`;-AWlWuES|lrneA(D+i5K?v^m5w4s6VoD3^sI`8fwy41I*+ty5ycH@UN*7_Xe+iq#%S|0w!ywrw(b+bCuJTCu|B8%iOHj_o% zCv$bignj7zCK0$Lx?UKong(>7TSnT{^&vOiyrnK&uM&;$Gg!%CJkKYCCp3i5sQisw zMKphQM=k~#2xZFdP(zK8ME2?ZbeaAj9r>e;I<|NpMylF~YXZNEbjzCAi+i-V`Nb~S zCs|X%E%GX)az;)%`rIk>OX)gz%FB-`shNwJ=S=~5o2>=66t;ufW$}RT4}!R;M@wzk z+>XhLjVby1Y1qUUebDvAbU0e|Iikxx43a8-YM{*dZN#nbO( zxrqBLrEp}r$S=s343%B0EYti*Jbc2L`656?hnn6hDLm0--St7`>Yrbt2|;z6U?6 zc2jQYF$FD^SBaoTmnyzERgzcPYol`h$TmEO6#-n~BBXoRm~6=l0%OoQAcqHHU$O&{ z8|J;jpTFLc)mu{#tI`itNl%$T≷N{?9^I?&^A6Q&QVAbA#E5Dm&c=f|WWy3O{l2 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

    Mx#dIL0^dNEI9h`y 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 ? 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�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 z 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%0A&o$=C7VSf$=ZTcxe2~FHq>c3!=+}y6 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::post_process // handle time frame definitions etc // If num_events_to_store == 0 && frame_definition_filename.size == 0 - if(num_events_to_store==0 && frame_defs_filename.size() == 0) + if(this->num_events_to_store==0 && this->frame_defs_filename.size() == 0) do_time_frame = true; this->list_mode_data_sptr= diff --git a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx index f938408c7f..01cab96b11 100644 --- a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx +++ b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx @@ -425,13 +425,11 @@ compute_sub_gradient_without_penalty_plus_sensitivity(TargetT& gradient, if(record.is_time() && end_time > 0.01) { current_time = record.time().get_time_in_secs(); + if (this->do_time_frame && current_time >= end_time) + break; // get out of while loop + if (current_time < start_time) + continue; } - if (this->do_time_frame && current_time >= end_time) - { - break; // get out of while loop - } - if (current_time < start_time) - continue; if (record.is_event() && record.event().is_prompt()) { @@ -439,6 +437,8 @@ compute_sub_gradient_without_penalty_plus_sensitivity(TargetT& gradient, measured_bin.set_bin_value(1.0f); record.event().get_bin(measured_bin, *proj_data_info_cyl_uncompressed_ptr); + if (std::abs(measured_bin.segment_num()) > this->max_ring_difference_num_to_process ) + continue; // If more than 1 subsets, check if the current bin belongs to // the current. if (this->num_subsets > 1) From 16f72e7195614089a0a89eece14e20e548e66678 Mon Sep 17 00:00:00 2001 From: Nikos E Date: Fri, 14 Oct 2016 11:38:46 +0100 Subject: [PATCH 011/509] Minor improvements and code clean out. --- recon_test_pack/OSMAPOSL_test_lmf.par | 4 +-- recon_test_pack/lm_to_projdata.par | 2 +- ...MeanAndListModeDataWithProjMatrixByBin.cxx | 31 ++++++++++++------- 3 files changed, 23 insertions(+), 14 deletions(-) diff --git a/recon_test_pack/OSMAPOSL_test_lmf.par b/recon_test_pack/OSMAPOSL_test_lmf.par index 725b4a44fc..423b5a6c6c 100755 --- a/recon_test_pack/OSMAPOSL_test_lmf.par +++ b/recon_test_pack/OSMAPOSL_test_lmf.par @@ -30,7 +30,7 @@ PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin Par end PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin Parameters:= enforce initial positivity condition:= 1 number of subsets:= 1 -number of subiterations:= 2 +number of subiterations:= 1 save estimates at subiteration intervals:= 1 -output filename prefix := my_ouput_t_lm_pr_seg2 +output filename prefix := my_output_t_lm_pr_seg2 END := diff --git a/recon_test_pack/lm_to_projdata.par b/recon_test_pack/lm_to_projdata.par index d55fd474ae..1fd25c064d 100755 --- a/recon_test_pack/lm_to_projdata.par +++ b/recon_test_pack/lm_to_projdata.par @@ -18,7 +18,7 @@ output filename prefix := ${OUT_PROJDATA_FILE} ; note that this normally counts the total of prompts-delayeds (see below) ; If you don't define a time frame definition file nor the num_events_to_store ; then the total number of events in the listmode file is used. -;num_events_to_store := 200 +; num_events_to_store := 10 ; parameters relating to prompts and delayeds diff --git a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx index 01cab96b11..479afacb25 100644 --- a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx +++ b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx @@ -286,16 +286,16 @@ PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBinget_max_num_non_arccorrected_bins(), false))); + // If Additive is smaller : Error + if ( this->normalisation_sptr->set_up(proj_data_info_cyl_uncompressed_ptr) - != Succeeded::yes) + == Succeeded::no) { warning("PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin: " "set-up of pre-normalisation failed\n"); return true; } - - return false; } @@ -306,8 +306,8 @@ PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBinmax_ring_difference_num_to_process; - const int max_segment_num = this->max_ring_difference_num_to_process; + const int min_segment_num = this->proj_data_info_cyl_uncompressed_ptr->get_min_segment_num(); + const int max_segment_num = this->proj_data_info_cyl_uncompressed_ptr->get_max_segment_num(); // 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) @@ -322,7 +322,6 @@ add_subset_sensitivity(TargetT& sensitivity, const int subset_num) const continue; this->add_view_seg_to_sensitivity(sensitivity, view_segment_num); } - // cerr< this->max_ring_difference_num_to_process ) + record.event().get_bin(measured_bin, *this->proj_data_info_cyl_uncompressed_ptr); + + if (measured_bin.get_bin_value() != 1.0f + || measured_bin.segment_num() < this->proj_data_info_cyl_uncompressed_ptr->get_min_segment_num() + || measured_bin.segment_num() > this->proj_data_info_cyl_uncompressed_ptr->get_max_segment_num() + || measured_bin.tangential_pos_num() < this->proj_data_info_cyl_uncompressed_ptr->get_min_tangential_pos_num() + || measured_bin.tangential_pos_num() > this->proj_data_info_cyl_uncompressed_ptr->get_max_tangential_pos_num() + || measured_bin.axial_pos_num() < this->proj_data_info_cyl_uncompressed_ptr->get_min_axial_pos_num(measured_bin.segment_num()) + || measured_bin.axial_pos_num() > this->proj_data_info_cyl_uncompressed_ptr->get_max_axial_pos_num(measured_bin.segment_num())) + { continue; + } + + measured_bin.set_bin_value(1.0f); // If more than 1 subsets, check if the current bin belongs to // the current. if (this->num_subsets > 1) @@ -472,8 +481,8 @@ compute_sub_gradient_without_penalty_plus_sensitivity(TargetT& gradient, num_stored_events += 1; - if (num_stored_events%100000L==0) - info( boost::format("Proccessed events: %1% ") % num_stored_events); + if (num_stored_events%200000L==0) + info( boost::format("Stored Events: %1% ") % num_stored_events); if ( measured_bin.get_bin_value() <= max_quotient *fwd_bin.get_bin_value()) measured_div_fwd = 1.0f /fwd_bin.get_bin_value(); From 1aa85c18e0abf5d9663091c988f37462ee804de8 Mon Sep 17 00:00:00 2001 From: Nikos E Date: Fri, 14 Oct 2016 11:54:25 +0100 Subject: [PATCH 012/509] Corrected bug in the first and last plane. --- recon_test_pack/OSMAPOSL_test_proj.par | 2 +- ...inearModelForMeanAndListModeDataWithProjMatrixByBin.cxx | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/recon_test_pack/OSMAPOSL_test_proj.par b/recon_test_pack/OSMAPOSL_test_proj.par index a30596a108..358cc04a18 100755 --- a/recon_test_pack/OSMAPOSL_test_proj.par +++ b/recon_test_pack/OSMAPOSL_test_proj.par @@ -16,7 +16,7 @@ PoissonLogLikelihoodWithLinearModelForMeanAndProjData Parameters:= End Ray tracing matrix parameters := End Projector Pair Using Matrix Parameters := -Bin Normalisation type := From ProjData + Bin Normalisation type := From ProjData Bin Normalisation From ProjData := normalisation projdata filename:= total_mult.hs End Bin Normalisation From ProjData:= diff --git a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx index 479afacb25..d9292a3987 100644 --- a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx +++ b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx @@ -345,13 +345,10 @@ add_view_seg_to_sensitivity(TargetT& sensitivity, const ViewSegmentNumbers& view } // backproject { - const int range_to_zero = - view_seg_nums.segment_num() == 0 - ? 1 : 0; const int min_ax_pos_num = - viewgrams.get_min_axial_pos_num() + range_to_zero; + viewgrams.get_min_axial_pos_num(); const int max_ax_pos_num = - viewgrams.get_max_axial_pos_num() - range_to_zero; + viewgrams.get_max_axial_pos_num(); this->projector_pair_ptr->get_back_projector_sptr()-> back_project(sensitivity, viewgrams, From fd5ef3bd7bccd1babe15907d2fa17e563b5e119a Mon Sep 17 00:00:00 2001 From: Nikos E Date: Fri, 14 Oct 2016 12:58:18 +0100 Subject: [PATCH 013/509] Finished the test for listmode reconstruction. :) --- recon_test_pack/run_test_listmode_recon.sh | 112 +++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100755 recon_test_pack/run_test_listmode_recon.sh diff --git a/recon_test_pack/run_test_listmode_recon.sh b/recon_test_pack/run_test_listmode_recon.sh new file mode 100755 index 0000000000..7466b5abce --- /dev/null +++ b/recon_test_pack/run_test_listmode_recon.sh @@ -0,0 +1,112 @@ +#! /bin/sh +# A script to check to see if reconstruction of simulated data gives the expected result. +# +# Copyright (C) 2011 - 2011-01-14, Hammersmith Imanet Ltd +# Copyright (C) 2011-07-01 - 2011, Kris Thielemans +# Copyright (C) 2014, University 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.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 +# +# Author Nikos Efthimiou +# + +# Scripts should exit with error code when a test fails: +if [ -n "$TRAVIS" ]; then + # The code runs inside Travis + set -e +fi + +echo This script should work with STIR version ">"3.0. If you have +echo a later version, you might have to update your test pack. +echo Please check the web site. +echo + +# +# Options +# +MPIRUN="" + +# +# Parse option arguments (--) +# Note that the -- is required to suppress interpretation of $1 as options +# to expr +# +while test `expr -- "$1" : "--.*"` -gt 0 +do + + if test "$1" = "--mpicmd" + then + MPIRUN="$2" + shift 1 + elif test "$1" = "--help" + then + echo "Usage: `basename $0` [--mpicmd somecmd] [install_dir]" + echo "(where [] means that an argument is optional)" + echo "See README.txt for more info." + exit 1 + else + echo Warning: Unknown option "$1" + echo rerun with --help for more info. + exit 1 + fi + + shift 1 + +done + +if [ $# -eq 1 ]; then + echo "Prepending $1 to your PATH for the duration of this script." + PATH=$1:$PATH +fi + +# first delete any files remaining from a previous run +rm -f my_*v my_*s my_*S + +echo "=== reconstruct listmode data" +OSMAPOSL OSMAPOSL_test_lmf.par +echo "=== " +# create sinograms +echo "=== unlist listmode data (for comparison)" +INPUT=PET_ACQ_small.l.hdr.STIR TEMPLATE=Siemens_mMR_seg2.hs OUT_PROJDATA_FILE=my_sinogram lm_to_projdata lm_to_projdata.par +echo "=== reconstruct projection data for comparison" +OSMAPOSL OSMAPOSL_test_proj.par +echo "=== compare sensitivity images" +if ${INSTALL_DIR}compare_image my_sens_t_proj_seg2.hv my_sens_t_lm_pr_seg2.hv 2>my_sens_comparison_stderr.log; +then +echo ---- This test seems to be ok !; +else +echo There were problems here!; +ThereWereErrors=1; +fi + +echo "=== compare reconstructed images" +if ${INSTALL_DIR}compare_image my_output_t_proj_seg2_1.hv my_output_t_lm_pr_seg2_1.hv 2>my_output_comparison_stderr.log; +then +echo ---- This test seems to be ok !; +else +echo There were problems here!; +ThereWereErrors=1; +fi + +echo +echo '--------------- End of tests -------------' +echo +if test ${ThereWereErrors} = 1 ; +then +echo "Check what went wrong. The *.log files might help you." +else +echo "Everything seems to be fine !" +echo 'You could remove all generated files using "rm -f my_* *.log"' +fi + From cde4c01619c71d9f85b666e77e306c4d67a563c1 Mon Sep 17 00:00:00 2001 From: Nikos E Date: Fri, 14 Oct 2016 14:34:42 +0100 Subject: [PATCH 014/509] Removed large file. From 9aa012737ed8d1311bb706eab5e58b04325782cb Mon Sep 17 00:00:00 2001 From: Nikos E Date: Fri, 14 Oct 2016 15:52:29 +0100 Subject: [PATCH 015/509] Applied Kris's comments: [x]. ProjDataFromStream: Only get_bin_value(Bin) left. [x]. ProjData.h get_bin_value was removed [x]. ProjDataGEAdvance.h get_bin_value() was removed [x]. CListModeDataECAT8_32.h reverted. [x]. ProjMatrixByBinSPECTUB reverted. [x]. ProjData.cxx : get_related_bin_values commented out [x]. --- documentation/release_3.1.htm | 1 + recon_test_pack/OSMAPOSL_test_lmf.par | 9 ++-- recon_test_pack/OSMAPOSL_test_proj.par | 9 ++-- recon_test_pack/total_mult.hs | 47 ------------------- src/buildblock/ProjData.cxx | 27 +++++------ src/buildblock/ProjDataFromStream.cxx | 26 +++++----- src/include/stir/Bin.h | 4 +- src/include/stir/ProjData.h | 14 +++--- src/include/stir/ProjDataFromStream.h | 16 +------ src/include/stir/ProjDataGEAdvance.h | 16 ++----- .../stir/listmode/CListModeDataECAT8_32bit.h | 10 ++-- .../ProjMatrixByBinSPECTUB.cxx | 18 +++---- 12 files changed, 62 insertions(+), 135 deletions(-) delete mode 100644 recon_test_pack/total_mult.hs diff --git a/documentation/release_3.1.htm b/documentation/release_3.1.htm index b2eefdcb2e..ae2633c4fb 100644 --- a/documentation/release_3.1.htm +++ b/documentation/release_3.1.htm @@ -147,6 +147,7 @@

    Known problems

    Minor bug fixes

    • fix HighResWallClockTimer on Linux and Win32 which caused reporting wrong timings in certain situations
    • +
    • In ProjMatrixByBin.h the name of DataSymmetriesForBins is corrected to symmetries_sptr.

    Documentation changes

    diff --git a/recon_test_pack/OSMAPOSL_test_lmf.par b/recon_test_pack/OSMAPOSL_test_lmf.par index 423b5a6c6c..a9d7b103f9 100755 --- a/recon_test_pack/OSMAPOSL_test_lmf.par +++ b/recon_test_pack/OSMAPOSL_test_lmf.par @@ -16,10 +16,11 @@ PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin Par End Ray tracing matrix parameters := End Projector Pair Using Matrix Parameters := - Bin Normalisation type := From ProjData - Bin Normalisation From ProjData := - normalisation projdata filename:= total_mult.hs - End Bin Normalisation From ProjData:= + ; You may download this file separately, from STIR website + ;Bin Normalisation type := From ProjData + ; Bin Normalisation From ProjData := + ; normalisation projdata filename:= total_mult.hs + ;End Bin Normalisation From ProjData:= ;num_events_to_store := 100 recompute sensitivity :=1 diff --git a/recon_test_pack/OSMAPOSL_test_proj.par b/recon_test_pack/OSMAPOSL_test_proj.par index 358cc04a18..1fe49b0c9b 100755 --- a/recon_test_pack/OSMAPOSL_test_proj.par +++ b/recon_test_pack/OSMAPOSL_test_proj.par @@ -16,10 +16,11 @@ PoissonLogLikelihoodWithLinearModelForMeanAndProjData Parameters:= End Ray tracing matrix parameters := End Projector Pair Using Matrix Parameters := - Bin Normalisation type := From ProjData - Bin Normalisation From ProjData := - normalisation projdata filename:= total_mult.hs - End Bin Normalisation From ProjData:= +; You may download this file separately, from STIR website + ;Bin Normalisation type := From ProjData + ; Bin Normalisation From ProjData := + ; normalisation projdata filename:= total_mult.hs + ;End Bin Normalisation From ProjData:= recompute sensitivity := 1 use subset sensitivities:= 0 diff --git a/recon_test_pack/total_mult.hs b/recon_test_pack/total_mult.hs deleted file mode 100644 index 6c03e62dac..0000000000 --- a/recon_test_pack/total_mult.hs +++ /dev/null @@ -1,47 +0,0 @@ -!INTERFILE := -!imaging modality := PT -name of data file := total_mult.s -originating system := Siemens mMR -!version of keys := STIR3.0 -!GENERAL DATA := -!GENERAL IMAGE DATA := -!type of data := PET -imagedata byte order := LITTLEENDIAN -!PET STUDY (General) := -!PET data type := Emission -applied corrections := {None} -!number format := float -!number of bytes per pixel := 4 -number of dimensions := 4 -matrix axis label [4] := segment -!matrix size [4] := 5 -matrix axis label [3] := view -!matrix size [3] := 252 -matrix axis label [2] := axial coordinate -!matrix size [2] := { 62,63,64,63,62} -matrix axis label [1] := tangential coordinate -!matrix size [1] := 344 -minimum ring difference per segment := { -2,-1,0,1,2} -maximum ring difference per segment := { -2,-1,0,1,2} -Scanner parameters:= -Scanner type := Siemens mMR -Number of rings := 64 -Number of detectors per ring := 504 -Inner ring diameter (cm) := 65.6 -Average depth of interaction (cm) := 0.7 -Distance between rings (cm) := 0.40625 -Default bin size (cm) := 0.208626 -View offset (degrees) := 0 -Maximum number of non-arc-corrected bins := 344 -Default number of arc-corrected bins := 344 -Number of blocks per bucket in transaxial direction := 1 -Number of blocks per bucket in axial direction := 2 -Number of crystals per block in axial direction := 8 -Number of crystals per block in transaxial direction := 9 -Number of detector layers := 1 -Number of crystals per singles unit in axial direction := 16 -Number of crystals per singles unit in transaxial direction := 9 -end scanner parameters:= -effective central bin size (cm) := 0.208815 -number of time frames := 1 -!END OF INTERFILE := diff --git a/src/buildblock/ProjData.cxx b/src/buildblock/ProjData.cxx index 8d411b68ac..1eea479fa7 100644 --- a/src/buildblock/ProjData.cxx +++ b/src/buildblock/ProjData.cxx @@ -289,24 +289,21 @@ ProjData::get_related_viewgrams(const ViewSegmentNumbers& view_segmnet_num, return RelatedViewgrams(viewgrams, symmetries_used); } -std::vector -ProjData::get_related_bin_values(const std::vector& r_bins) const -{ +//std::vector +//ProjData::get_related_bin_values(const std::vector& r_bins) const +//{ - std::vector values; - values.reserve(r_bins.size()); +// std::vector values; +// values.reserve(r_bins.size()); - for (std::vector ::const_iterator r_bins_iterator = r_bins.begin(); - r_bins_iterator != r_bins.end(); ++r_bins_iterator) - { - values.push_back(this->get_bin_value((*r_bins_iterator).segment_num(), - (*r_bins_iterator).axial_pos_num(), - (*r_bins_iterator).view_num(), - (*r_bins_iterator).tangential_pos_num())); - } +// for (std::vector ::const_iterator r_bins_iterator = r_bins.begin(); +// r_bins_iterator != r_bins.end(); ++r_bins_iterator) +// { +// values.push_back(this->get_bin_value(*r_bins_iterator)); +// } - return values; -} +// return values; +//} Succeeded diff --git a/src/buildblock/ProjDataFromStream.cxx b/src/buildblock/ProjDataFromStream.cxx index 8020a24aac..c42d34806f 100644 --- a/src/buildblock/ProjDataFromStream.cxx +++ b/src/buildblock/ProjDataFromStream.cxx @@ -194,21 +194,18 @@ ProjDataFromStream::get_viewgram(const int view_num, const int segment_num, return viewgram; } -float -ProjDataFromStream::get_bin_value(const Bin& this_bin) const -{ - return get_bin_value(this_bin.segment_num(), - this_bin.axial_pos_num(), - this_bin.view_num(), - this_bin.tangential_pos_num()); -} +//float +//ProjDataFromStream::get_bin_value() const +//{ +// return get_bin_value(this_bin.segment_num(), +// this_bin.axial_pos_num(), +// this_bin.view_num(), +// this_bin.tangential_pos_num()); +//} float -ProjDataFromStream::get_bin_value(const int segment_num, - const int axial_pos_num, - const int view_num, - const int tang_pos_num) const +ProjDataFromStream::get_bin_value(const Bin& this_bin) const { if (sino_stream == 0) { @@ -219,7 +216,8 @@ ProjDataFromStream::get_bin_value(const int segment_num, error("ProjDataFromStream::get_viewgram: error in stream state before reading\n"); } - vector offsets = get_offsets_bin(segment_num, axial_pos_num, view_num, tang_pos_num); + vector offsets = get_offsets_bin(this_bin.segment_num(), this_bin.axial_pos_num(), + this_bin.view_num(), this_bin.tangential_pos_num()); const streamoff total_offset = offsets[0]; @@ -228,7 +226,7 @@ ProjDataFromStream::get_bin_value(const int segment_num, if (! *sino_stream) { - error("ProjDataFromStream::get_viewgram: error after seekg\n"); + error("ProjDataFromStream::get_bin_value: error after seekg."); } Array< 1, float> value(1); diff --git a/src/include/stir/Bin.h b/src/include/stir/Bin.h index 0233bf18cc..b9e445653e 100644 --- a/src/include/stir/Bin.h +++ b/src/include/stir/Bin.h @@ -79,9 +79,9 @@ class Bin //! accumulate voxel's contribution during forward projection inline Bin& operator+=(const float dx); - //! multiply bin values during normalisation -- apply() + //! multiply bin values inline Bin& operator*=(const float dx); - //! divide bin values during normalisation -- undo() + //! divide bin values //! \todo It is zero division proof in a similar way to divide<,,>(), though I am //! not sure if it should be. inline Bin& operator/=(const float dx); diff --git a/src/include/stir/ProjData.h b/src/include/stir/ProjData.h index 3701251cb0..5f61c0cb4f 100644 --- a/src/include/stir/ProjData.h +++ b/src/include/stir/ProjData.h @@ -36,7 +36,7 @@ #include "stir/Succeeded.h" #include "stir/SegmentBySinogram.h" #include "stir/SegmentByView.h" -#include "stir/Bin.h" + //#include #include "stir/IO/ExamData.h" @@ -152,10 +152,8 @@ class ProjData : public ExamData //! Set sinogram virtual Succeeded set_sinogram(const Sinogram&) = 0; - //! Get Bin value - virtual float get_bin_value(const int view_pos, const int segment_pos, const int axial_pos, const int tang_pos) const = 0; - //! Get Bin value - virtual float get_bin_value(const Bin& this_bin) const = 0; + // //! Get Bin value + //virtual float get_bin_value(const Bin& this_bin) const = 0; //! Get empty viewgram Viewgram get_empty_viewgram(const int view, const int segment_num, @@ -196,9 +194,9 @@ class ProjData : public ExamData const bool make_num_tangential_poss_odd = false) const; //! Set related viewgrams virtual Succeeded set_related_viewgrams(const RelatedViewgrams& viewgrams); - //! Get related bin values - //! \todo This function temporaliry has as input a vector instead this should be replaced by RelatedBins. - std::vector get_related_bin_values(const std::vector&) const; +// //! Get related bin values +// //! \todo This function temporaliry has as input a vector instead this should be replaced by RelatedBins. +// std::vector get_related_bin_values(const std::vector&) const; //! Get empty related viewgrams, where the symmetries_ptr specifies the symmetries to use RelatedViewgrams diff --git a/src/include/stir/ProjDataFromStream.h b/src/include/stir/ProjDataFromStream.h index a333521ba3..d3a4cbf97c 100644 --- a/src/include/stir/ProjDataFromStream.h +++ b/src/include/stir/ProjDataFromStream.h @@ -37,7 +37,7 @@ #include "stir/NumericType.h" #include "stir/ByteOrder.h" #include "stir/shared_ptr.h" - +#include "stir/Bin.h" #include #include @@ -138,20 +138,6 @@ class ProjDataFromStream : public ProjData //! Get scale factor float get_scale_factor() const; - //! - //! \brief get_bin_value - //! \param segment_num - //! \param axial_pos_num - //! \param view_num - //! \param tang_pos_num - //! \return - //! \author Nikos Efthimiou - //! \details This function return the value of a single bin stored in a sinogram in the - //! disk. - float get_bin_value(const int segment_num, - const int axial_pos_num, - const int view_num, - const int tang_pos_num) const; //! //! \brief get_bin_value diff --git a/src/include/stir/ProjDataGEAdvance.h b/src/include/stir/ProjDataGEAdvance.h index e6931b40bd..6901db3806 100644 --- a/src/include/stir/ProjDataGEAdvance.h +++ b/src/include/stir/ProjDataGEAdvance.h @@ -73,18 +73,10 @@ class ProjDataGEAdvance : public ProjData Sinogram get_sinogram(const int ax_pos_num, const int sergment_num,const bool make_num_tangential_poss_odd=false) const; Succeeded set_sinogram(const Sinogram& s); - float get_bin_value(const int segment_num, - const int axial_pos_num, - const int view_num, - const int tang_pos_num) const - { - //Do nothing - } - - float get_bin_value(const Bin& this_bin) const - { - // Do nothing - } +// float get_bin_value(const Bin& this_bin) const +// { +// // Do nothing +// } private: //the file with the data //This has to be a reference (or pointer) to a stream, diff --git a/src/include/stir/listmode/CListModeDataECAT8_32bit.h b/src/include/stir/listmode/CListModeDataECAT8_32bit.h index 6a6fa9e805..7feae1741d 100644 --- a/src/include/stir/listmode/CListModeDataECAT8_32bit.h +++ b/src/include/stir/listmode/CListModeDataECAT8_32bit.h @@ -18,7 +18,7 @@ \file \ingroup listmode \brief Declaration of class stir::ecat::CListModeDataECAT8_32bit - + \author Kris Thielemans */ @@ -40,7 +40,7 @@ namespace ecat { //! A class that reads the listmode data for Siemens scanners /*! \ingroup listmode - This file format is currently used by the Siemens Biograph PET/CT and mMR scanners. + This file format is currently used by the Siemens Biograph PET/CT and mMR scanners. There's an Interfile-like header and a binary file with the actual list mode data. The name of the binary file is given by the value of the "name of data file" keyword in the header. @@ -57,13 +57,13 @@ class CListModeDataECAT8_32bit : public CListModeData virtual std::string get_name() const; - virtual + virtual shared_ptr get_empty_record_sptr() const; - virtual + virtual Succeeded get_next_record(CListRecord& record) const; - virtual + virtual Succeeded reset(); virtual diff --git a/src/recon_buildblock/ProjMatrixByBinSPECTUB.cxx b/src/recon_buildblock/ProjMatrixByBinSPECTUB.cxx index 1dd5948193..833f2b89a5 100644 --- a/src/recon_buildblock/ProjMatrixByBinSPECTUB.cxx +++ b/src/recon_buildblock/ProjMatrixByBinSPECTUB.cxx @@ -205,7 +205,7 @@ set_up( //... fill prj structure from projection data info prj.Nbin = this->proj_data_info_ptr->get_num_tangential_poss(); - prj.szcm = this->proj_data_info_ptr->get_scanner_ptr()->get_default_bin_size()/10.f; + prj.szcm = this->proj_data_info_ptr->get_scanner_ptr()->get_default_bin_size()/10.; prj.Nang = this->proj_data_info_ptr->get_num_views(); @@ -213,24 +213,24 @@ set_up( vol.Ncol = image_info_ptr->get_x_size(); // Image: number of columns vol.Nrow = image_info_ptr->get_y_size(); // Image: number of rows vol.Nsli = image_info_ptr->get_z_size(); // Image: and projections: number of slices - vol.szcm = image_info_ptr->get_voxel_size().x()/10.f; // Image: voxel size (cm) - vol.thcm = image_info_ptr->get_voxel_size().z()/10.f; // Image: slice thickness (cm) + vol.szcm = image_info_ptr->get_voxel_size().x()/10.; // Image: voxel size (cm) + vol.thcm = image_info_ptr->get_voxel_size().z()/10.; // Image: slice thickness (cm) //..... geometrical and other derived parameters of the volume structure............... vol.Npix = vol.Ncol * vol.Nrow; vol.Nvox = vol.Npix * vol.Nsli; - vol.Ncold2 = (float)vol.Ncol / (float)2.f; // half of the matrix Nvox (integer or semi-integer) - vol.Nrowd2 = (float)vol.Nrow / (float)2.f; // half of the matrix NbOS (integer or semi-integer) - vol.Nslid2 = (float)vol.Nsli / (float)2.f; // half of the number of slices (integer or semi-integer) + vol.Ncold2 = (float)vol.Ncol / (float)2.; // half of the matrix Nvox (integer or semi-integer) + vol.Nrowd2 = (float)vol.Nrow / (float)2.; // half of the matrix NbOS (integer or semi-integer) + vol.Nslid2 = (float)vol.Nsli / (float)2.; // half of the number of slices (integer or semi-integer) vol.Xcmd2 = vol.Ncold2 * vol.szcm; // Half of the size of the image volume, dimension x (cm); vol.Ycmd2 = vol.Nrowd2 * vol.szcm; // Half of the size of the image volume, dimension y (cm); vol.Zcmd2 = vol.Nslid2 * vol.thcm; // Half of the size of the image volume, dimension z (cm); - vol.x0 = ( (float)0.5f - vol.Ncold2) * vol.szcm; // coordinate x of the first voxel - vol.y0 = ( (float)0.5f - vol.Nrowd2) * vol.szcm; // coordinate y of the first voxel - vol.z0 = ( (float)0.5f - vol.Nslid2) * vol.thcm; // coordinate z of the first voxel + vol.x0 = ( (float)0.5 - vol.Ncold2) * vol.szcm; // coordinate x of the first voxel + vol.y0 = ( (float)0.5 - vol.Nrowd2) * vol.szcm; // coordinate y of the first voxel + vol.z0 = ( (float)0.5 - vol.Nslid2) * vol.thcm; // coordinate z of the first voxel vol.first_sl = 0; // Image: first slice to take into account (no weight bellow) vol.last_sl = vol.Nsli; // Image: last slice to take into account (no weights above) From fe2cae090e6a9c1eec05bec05ce8b2f512e3c1e1 Mon Sep 17 00:00:00 2001 From: Nikos E Date: Fri, 14 Oct 2016 16:22:41 +0100 Subject: [PATCH 016/509] run_test_listmode would perform two iterations. Not really usefull. --- recon_test_pack/OSMAPOSL_test_proj.par | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/recon_test_pack/OSMAPOSL_test_proj.par b/recon_test_pack/OSMAPOSL_test_proj.par index 1fe49b0c9b..9e49ad1c38 100755 --- a/recon_test_pack/OSMAPOSL_test_proj.par +++ b/recon_test_pack/OSMAPOSL_test_proj.par @@ -30,7 +30,7 @@ PoissonLogLikelihoodWithLinearModelForMeanAndProjData Parameters:= end PoissonLogLikelihoodWithLinearModelForMeanAndProjData Parameters:= enforce initial positivity condition:= 1 number of subsets:= 1 -number of subiterations:= 2 +number of subiterations:= 1 save estimates at subiteration intervals:= 1 output filename prefix := my_output_t_proj_seg2 END := From f6a904518f8ebd30bfe72dca5c3cd6b93cfd925e Mon Sep 17 00:00:00 2001 From: Nikos E Date: Fri, 14 Oct 2016 23:20:44 +0100 Subject: [PATCH 017/509] additive sinogram added in the tests. The test script computes a background sinogram. The lm-objective function checks if the additive projdata are compatible. --- recon_test_pack/OSMAPOSL_test_lmf.par | 2 ++ recon_test_pack/OSMAPOSL_test_proj.par | 1 + recon_test_pack/frame_single.fdef | 2 ++ recon_test_pack/lm_fansums_delayed.par | 10 ++++++ recon_test_pack/run_test_listmode_recon.sh | 16 +++++++++ ...MeanAndListModeDataWithProjMatrixByBin.cxx | 35 +++++++++++++++++-- 6 files changed, 64 insertions(+), 2 deletions(-) create mode 100755 recon_test_pack/frame_single.fdef create mode 100644 recon_test_pack/lm_fansums_delayed.par diff --git a/recon_test_pack/OSMAPOSL_test_lmf.par b/recon_test_pack/OSMAPOSL_test_lmf.par index a9d7b103f9..8c014efddf 100755 --- a/recon_test_pack/OSMAPOSL_test_lmf.par +++ b/recon_test_pack/OSMAPOSL_test_lmf.par @@ -28,6 +28,8 @@ PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin Par sensitivity filename:= my_sens_t_lm_pr_seg2.hv zoom := 1 +additive sinogram := my_MLrandoms_f1.hs + end PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin Parameters:= enforce initial positivity condition:= 1 number of subsets:= 1 diff --git a/recon_test_pack/OSMAPOSL_test_proj.par b/recon_test_pack/OSMAPOSL_test_proj.par index 9e49ad1c38..8c0bb0f774 100755 --- a/recon_test_pack/OSMAPOSL_test_proj.par +++ b/recon_test_pack/OSMAPOSL_test_proj.par @@ -27,6 +27,7 @@ PoissonLogLikelihoodWithLinearModelForMeanAndProjData Parameters:= sensitivity filename:= my_sens_t_proj_seg2.hv zoom := 1 +additive sinogram := my_MLrandoms_f1.hs end PoissonLogLikelihoodWithLinearModelForMeanAndProjData Parameters:= enforce initial positivity condition:= 1 number of subsets:= 1 diff --git a/recon_test_pack/frame_single.fdef b/recon_test_pack/frame_single.fdef new file mode 100755 index 0000000000..bb04dd5a98 --- /dev/null +++ b/recon_test_pack/frame_single.fdef @@ -0,0 +1,2 @@ +0 0 +1 1 diff --git a/recon_test_pack/lm_fansums_delayed.par b/recon_test_pack/lm_fansums_delayed.par new file mode 100644 index 0000000000..5743960dff --- /dev/null +++ b/recon_test_pack/lm_fansums_delayed.par @@ -0,0 +1,10 @@ +lm_fansums Parameters:= +input file := ${INPUT} +frame definition file := frame_single.fdef +output filename prefix := ${OUTPUT} +tangential fan size := -1 +maximum absolute segment number to process := +store 'prompts' := 0 +increment to use for 'delayeds' := 1 +list event coordinates := 0 +end:= diff --git a/recon_test_pack/run_test_listmode_recon.sh b/recon_test_pack/run_test_listmode_recon.sh index 7466b5abce..d9fefac834 100755 --- a/recon_test_pack/run_test_listmode_recon.sh +++ b/recon_test_pack/run_test_listmode_recon.sh @@ -73,6 +73,22 @@ fi # first delete any files remaining from a previous run rm -f my_*v my_*s my_*S +echo "=== calculate backround data" +echo "===== randoms" + +echo "====== create delayed fansums" +INPUT=PET_ACQ_small.l.hdr.STIR OUTPUT=my_fansums_delayed ${INSTALL_DIR}lm_fansums lm_fansums_delayed.par 2>my_fansums.log + +echo "====== estimate singles from fansums" +niters=10 +# Note: the last 2 numbers are specific to the mMR +${INSTALL_DIR}find_ML_singles_from_delayed -f my_MLsingles_f1 my_fansums_delayed_f1.dat $niters 2 343 my_construct_randoms_from_singles.log + +echo "=== simulate normalisation data" + echo "=== reconstruct listmode data" OSMAPOSL OSMAPOSL_test_lmf.par echo "=== " diff --git a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx index d9292a3987..01e1c5cd85 100644 --- a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx +++ b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx @@ -286,9 +286,40 @@ PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBinget_max_num_non_arccorrected_bins(), false))); - // If Additive is smaller : Error + if (*(this->additive_proj_data_sptr->get_proj_data_info_sptr()) != *proj_data_info_cyl_uncompressed_ptr) + { + const ProjDataInfo& add_proj = *(this->additive_proj_data_sptr->get_proj_data_info_sptr()); + const ProjDataInfo& proj = *this->proj_data_info_cyl_uncompressed_ptr; + bool ok = + typeid(add_proj) == typeid(proj) && + *add_proj.get_scanner_ptr()== *(proj.get_scanner_ptr()) && + (add_proj.get_min_view_num()==proj.get_min_view_num()) && + (add_proj.get_max_view_num()==proj.get_max_view_num()) && + (add_proj.get_min_tangential_pos_num() ==proj.get_min_tangential_pos_num())&& + (add_proj.get_max_tangential_pos_num() ==proj.get_max_tangential_pos_num()) && + add_proj.get_min_segment_num() <= proj.get_min_segment_num() && + add_proj.get_max_segment_num() >= proj.get_max_segment_num(); + + for (int segment_num=proj.get_min_segment_num(); + ok && segment_num<=proj.get_max_segment_num(); + ++segment_num) + { + ok = + add_proj.get_min_axial_pos_num(segment_num) == proj.get_min_axial_pos_num(segment_num) && + add_proj.get_max_axial_pos_num(segment_num) == proj.get_max_axial_pos_num(segment_num); + } + if (ok) + return false; + else + { + warning(boost::format("Incompatible additive projection data:\nAdditive projdata info:\n%s\nEmission projdata info:\n%s\n--- (end of incompatible projection data info)---\n") + % add_proj.parameter_info() + % proj.parameter_info()); + return true; + } + } - if ( this->normalisation_sptr->set_up(proj_data_info_cyl_uncompressed_ptr) + if( this->normalisation_sptr->set_up(proj_data_info_cyl_uncompressed_ptr) == Succeeded::no) { warning("PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin: " From 01c8aa7cf3e9aadc1c58f9c50d4dccf20c32211c Mon Sep 17 00:00:00 2001 From: Nikos E Date: Fri, 14 Oct 2016 23:24:06 +0100 Subject: [PATCH 018/509] Corrected one small bug --- ...inearModelForMeanAndListModeDataWithProjMatrixByBin.cxx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx index 01e1c5cd85..381e5f6152 100644 --- a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx +++ b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx @@ -308,11 +308,10 @@ PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin Date: Sun, 16 Oct 2016 17:25:11 +0100 Subject: [PATCH 019/509] Tests for listmode reconstruction finished. I added fake normalisation and additive components. In addition, In the run_root_GATE test I changed a bit the way the script detects GATE support, after some complains by users. I hope that is better now. I cleaned up the code in the ProjData FromStream but didn't edit the way get_offsets_bin() works because I see no improvement if it depend or other offset function, or if another offset function depends on that. FInally, I uncomment the use of get_bin_value() in the ProjDataInMemory. --- recon_test_pack/OSMAPOSL_test_lmf.par | 9 ++-- recon_test_pack/OSMAPOSL_test_proj.par | 10 ++-- .../lm_generate_uniform_cylinder.par | 23 +++++++++ recon_test_pack/run_root_GATE.sh | 11 +++-- recon_test_pack/run_test_listmode_recon.sh | 13 +++++ src/buildblock/ProjDataFromStream.cxx | 47 ------------------- src/buildblock/ProjDataInMemory.cxx | 10 ++-- src/include/stir/ProjDataFromStream.h | 20 ++------ src/include/stir/ProjDataInMemory.h | 2 + src/listmode_utilities/lm_to_projdata.cxx | 4 +- 10 files changed, 63 insertions(+), 86 deletions(-) create mode 100644 recon_test_pack/lm_generate_uniform_cylinder.par diff --git a/recon_test_pack/OSMAPOSL_test_lmf.par b/recon_test_pack/OSMAPOSL_test_lmf.par index 8c014efddf..775ae5d047 100755 --- a/recon_test_pack/OSMAPOSL_test_lmf.par +++ b/recon_test_pack/OSMAPOSL_test_lmf.par @@ -16,11 +16,10 @@ PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin Par End Ray tracing matrix parameters := End Projector Pair Using Matrix Parameters := - ; You may download this file separately, from STIR website - ;Bin Normalisation type := From ProjData - ; Bin Normalisation From ProjData := - ; normalisation projdata filename:= total_mult.hs - ;End Bin Normalisation From ProjData:= + Bin Normalisation type := From ProjData + Bin Normalisation From ProjData := + normalisation projdata filename:= my_acfs.hs + End Bin Normalisation From ProjData:= ;num_events_to_store := 100 recompute sensitivity :=1 diff --git a/recon_test_pack/OSMAPOSL_test_proj.par b/recon_test_pack/OSMAPOSL_test_proj.par index 8c0bb0f774..320642abaf 100755 --- a/recon_test_pack/OSMAPOSL_test_proj.par +++ b/recon_test_pack/OSMAPOSL_test_proj.par @@ -16,11 +16,11 @@ PoissonLogLikelihoodWithLinearModelForMeanAndProjData Parameters:= End Ray tracing matrix parameters := End Projector Pair Using Matrix Parameters := -; You may download this file separately, from STIR website - ;Bin Normalisation type := From ProjData - ; Bin Normalisation From ProjData := - ; normalisation projdata filename:= total_mult.hs - ;End Bin Normalisation From ProjData:= + + Bin Normalisation type := From ProjData + Bin Normalisation From ProjData := + normalisation projdata filename:= my_acfs.hs + End Bin Normalisation From ProjData:= recompute sensitivity := 1 use subset sensitivities:= 0 diff --git a/recon_test_pack/lm_generate_uniform_cylinder.par b/recon_test_pack/lm_generate_uniform_cylinder.par new file mode 100644 index 0000000000..25377d9452 --- /dev/null +++ b/recon_test_pack/lm_generate_uniform_cylinder.par @@ -0,0 +1,23 @@ +generate_image Parameters := +output filename:=my_uniform_cylinder +X output image size (in pixels):=111 +Y output image size (in pixels):=111 +Z output image size (in pixels):=65 +X voxel size (in mm):= 3 +Y voxel size (in mm):= 3 +Z voxel size (in mm) := 0.40625 + + Z number of samples to take per voxel := 1 + Y number of samples to take per voxel := 1 + X number of samples to take per voxel := 1 + +shape type:= ellipsoidal cylinder +Ellipsoidal Cylinder Parameters:= + radius-x (in mm):=100 + radius-y (in mm):=100 + length-z (in mm):=110 + origin (in mm):={70,10,20} + END:= +value :=1 + +END:= diff --git a/recon_test_pack/run_root_GATE.sh b/recon_test_pack/run_root_GATE.sh index 2c0cf8a7c1..8d70cb2b8a 100755 --- a/recon_test_pack/run_root_GATE.sh +++ b/recon_test_pack/run_root_GATE.sh @@ -53,13 +53,17 @@ if test "$1" = "--help" done -if ! ${INSTALL_DIR}lm_to_projdata --input-formats 2>&1 | grep ROOT; then +${INSTALL_DIR}lm_to_projdata --input-formats > my_lm_supported_inputs.log 2>&1 + +if ! grep "ROOT" my_lm_supported_inputs.log > /dev/null +then echo GATE support has not been installed in this system. Aborting. exit 1; fi + echo Executing tests on ROOT files generated by GATE simulations, with cylindrical PET scanners @@ -134,7 +138,4 @@ echo "Check what went wrong. The *.log files might help you." else echo "Everything seems to be fine !" echo 'You could remove all generated files using "rm -f my_* *.log"' -fi - - - +#fi diff --git a/recon_test_pack/run_test_listmode_recon.sh b/recon_test_pack/run_test_listmode_recon.sh index d9fefac834..c5a12564f7 100755 --- a/recon_test_pack/run_test_listmode_recon.sh +++ b/recon_test_pack/run_test_listmode_recon.sh @@ -88,6 +88,19 @@ echo " ===== estimate randoms from singles" ${INSTALL_DIR}construct_randoms_from_singles my_MLrandoms_f1 my_MLsingles_f1 Siemens_mMR_seg2.hs $niters 2>my_construct_randoms_from_singles.log echo "=== simulate normalisation data" +# For normalisation data we are going to use a cylinder in the center, +# with water attenuation values + +echo "=== make fake emission image" +${INSTALL_DIR}generate_image lm_generate_uniform_cylinder.par +echo "=== use that as template for fake attenuation" +${INSTALL_DIR}stir_math --including-first --times-scalar .096 my_atten_image.hv my_uniform_cylinder.hv + +echo "=== create ACFs" +${INSTALL_DIR}calculate_attenuation_coefficients --ACF my_acfs.hs my_atten_image.hv Siemens_mMR_seg2.hs > my_create_acfs.log 2>&1 +if [ $? -ne 0 ]; then +echo "ERROR running calculate_attenuation_coefficients. Check my_create_acfs.log"; exit 1; +fi echo "=== reconstruct listmode data" OSMAPOSL OSMAPOSL_test_lmf.par diff --git a/src/buildblock/ProjDataFromStream.cxx b/src/buildblock/ProjDataFromStream.cxx index c42d34806f..0e19dee214 100644 --- a/src/buildblock/ProjDataFromStream.cxx +++ b/src/buildblock/ProjDataFromStream.cxx @@ -194,16 +194,6 @@ ProjDataFromStream::get_viewgram(const int view_num, const int segment_num, return viewgram; } -//float -//ProjDataFromStream::get_bin_value() const -//{ -// return get_bin_value(this_bin.segment_num(), -// this_bin.axial_pos_num(), -// this_bin.view_num(), -// this_bin.tangential_pos_num()); -//} - - float ProjDataFromStream::get_bin_value(const Bin& this_bin) const { @@ -230,7 +220,6 @@ ProjDataFromStream::get_bin_value(const Bin& this_bin) const } Array< 1, float> value(1); -// value.resize(1); float scale = float(1); // Now the storage order is not more important. Just read. @@ -240,44 +229,8 @@ ProjDataFromStream::get_bin_value(const Bin& this_bin) const if(scale != 1.f) error("ProjDataFromStream: error reading data: scale factor returned by read_data should be 1\n"); -// if (get_storage_order() == Segment_AxialPos_View_TangPos) -// { -// for (int ax_pos_num = get_min_axial_pos_num(segment_num); ax_pos_num <= get_max_axial_pos_num(segment_num); ax_pos_num++) -// { - -// if (read_data(*sino_stream, viewgram[ax_pos_num], on_disk_data_type, scale, on_disk_byte_order) -// == Succeeded::no) -// error("ProjDataFromStream: error reading data\n"); -// if(scale != 1) -// error("ProjDataFromStream: error reading data: scale factor returned by read_data should be 1\n"); -// // seek to next line unless it was the last we need to read -// if(ax_pos_num != get_max_axial_pos_num(segment_num)) -// sino_stream->seekg(intra_views_offset, ios::cur); -// } -// } -// else if (get_storage_order() == Segment_View_AxialPos_TangPos) -// { -// if(read_data(*sino_stream, viewgram, on_disk_data_type, scale, on_disk_byte_order) -// == Succeeded::no) -// error("ProjDataFromStream: error reading data\n"); -// if(scale != 1) -// error("ProjDataFromStream: error reading data: scale factor returned by read_data should be 1\n"); -// } - value *= scale_factor; - // if (make_num_tangential_poss_odd &&(get_num_tangential_poss()%2==0)) - // { - // const int new_max_tangential_pos = get_max_tangential_pos_num() + 1; - - // viewgram.grow( - // IndexRange2D(get_min_axial_pos_num(segment_num), - // get_max_axial_pos_num(segment_num), - - // get_min_tangential_pos_num(), - // new_max_tangential_pos)); - // } - return value[0]; } diff --git a/src/buildblock/ProjDataInMemory.cxx b/src/buildblock/ProjDataInMemory.cxx index 0154f51185..358b7e3c2f 100644 --- a/src/buildblock/ProjDataInMemory.cxx +++ b/src/buildblock/ProjDataInMemory.cxx @@ -4,9 +4,11 @@ \ingroup projdata \brief Implementations for non-inline functions of class stir::ProjDataInMemory + \author Nikos Efthimiou \author Kris Thielemans */ /* + * Copyright (C) 2016, UCL Copyright (C) 2002 - 2011-02-23, Hammersmith Imanet Ltd Copyright (C) 2011, Kris Thielemans This file is part of STIR. @@ -160,11 +162,9 @@ write_to_file(const string& output_filename) const float ProjDataInMemory::get_bin_value(Bin& bin) { - Viewgram viewgram = get_viewgram(bin.view_num(),bin.segment_num()); - - return viewgram[bin.axial_pos_num()][bin.tangential_pos_num()]; -// return get_bin_value(bin.segment_num(), bin.axial_pos_num(), -// bin.view_num(), bin.tangential_pos_num()); +// Viewgram viewgram = get_viewgram(bin.view_num(),bin.segment_num()); +// return viewgram[bin.axial_pos_num()][bin.tangential_pos_num()]; + return ProjDataFromStream::get_bin_value(bin); } END_NAMESPACE_STIR diff --git a/src/include/stir/ProjDataFromStream.h b/src/include/stir/ProjDataFromStream.h index d3a4cbf97c..1e374194ba 100644 --- a/src/include/stir/ProjDataFromStream.h +++ b/src/include/stir/ProjDataFromStream.h @@ -6,6 +6,7 @@ \ingroup projdata \brief Declaration of class stir::ProjDataFromStream + \author Nikos Efthimiou \author Sanida Mustafovic \author Kris Thielemans \author Claire Labbe @@ -138,13 +139,7 @@ class ProjDataFromStream : public ProjData //! Get scale factor float get_scale_factor() const; - - //! - //! \brief get_bin_value - //! \param this_bin - //! \return - //! \author Nikos Efthimiou - //! \details Overloaded + //! Get the value of bin. float get_bin_value(const Bin& this_bin) const; protected: @@ -179,16 +174,7 @@ class ProjDataFromStream : public ProjData //! Calculate offsets for sinogram data std::vector get_offsets_sino(const int ax_pos_num, const int segment_num) const; - //! - //! \brief get_offsets_bin - //! \param segment_num - //! \param axial_pos_num - //! \param view_num - //! \param tang_pos_num - //! \return - //! \author Nikos Efthimiou - //! \details I could make use of get_offsets and get_offset_sino to extract the final offset of the - //! bin, but it would be another one burden in an already slow procedure. + //! Calculate the offsets for specific bins. std::vector get_offsets_bin(const int segment_num, const int ax_pos_num, const int view_num, diff --git a/src/include/stir/ProjDataInMemory.h b/src/include/stir/ProjDataInMemory.h index 72fe461d87..408e1ac2a5 100644 --- a/src/include/stir/ProjDataInMemory.h +++ b/src/include/stir/ProjDataInMemory.h @@ -1,4 +1,5 @@ /* + * Copyright (C) 2016, UCL Copyright (C) 2002 - 2011-02-23, Hammersmith Imanet Ltd This file is part of STIR. @@ -19,6 +20,7 @@ \ingroup projdata \brief Declaration of class stir::ProjDataInMemory + \author Nikos Efthimiou \author Kris Thielemans */ diff --git a/src/listmode_utilities/lm_to_projdata.cxx b/src/listmode_utilities/lm_to_projdata.cxx index dc741b4642..6c81789320 100644 --- a/src/listmode_utilities/lm_to_projdata.cxx +++ b/src/listmode_utilities/lm_to_projdata.cxx @@ -54,10 +54,10 @@ int main(int argc, char * argv[]) // which listmode files are supported if (strcmp(argv[1], "--input-formats")==0) { - cerr<::default_sptr()-> list_registered_names(cerr); - exit(EXIT_FAILURE); + exit(EXIT_SUCCESS); } LmToProjData application(argc==2 ? argv[1] : 0); application.process_data(); From 23045f32de6c322313159eb3d3b71b293ae39a6f Mon Sep 17 00:00:00 2001 From: Nikos E Date: Sun, 16 Oct 2016 18:33:24 +0100 Subject: [PATCH 020/509] Corrected a small error. Found that the last fi was commented and the test was failling. --- recon_test_pack/run_root_GATE.sh | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/recon_test_pack/run_root_GATE.sh b/recon_test_pack/run_root_GATE.sh index 8d70cb2b8a..aead914642 100755 --- a/recon_test_pack/run_root_GATE.sh +++ b/recon_test_pack/run_root_GATE.sh @@ -59,17 +59,12 @@ if ! grep "ROOT" my_lm_supported_inputs.log > /dev/null then echo GATE support has not been installed in this system. Aborting. exit 1; +else +echo "GATE support detected!" fi - - - echo Executing tests on ROOT files generated by GATE simulations, with cylindrical PET scanners - - - - # first delete any files remaining from a previous run rm -f my_*v my_*s my_*S @@ -138,4 +133,4 @@ echo "Check what went wrong. The *.log files might help you." else echo "Everything seems to be fine !" echo 'You could remove all generated files using "rm -f my_* *.log"' -#fi +fi From 7645e91869bd33895525798b265d7252de01c139 Mon Sep 17 00:00:00 2001 From: Nikos E Date: Sun, 16 Oct 2016 19:24:40 +0100 Subject: [PATCH 021/509] Added explicit exit status. Trying to narrow the error from Travis. --- recon_test_pack/run_root_GATE.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/recon_test_pack/run_root_GATE.sh b/recon_test_pack/run_root_GATE.sh index aead914642..7a87b581c8 100755 --- a/recon_test_pack/run_root_GATE.sh +++ b/recon_test_pack/run_root_GATE.sh @@ -130,7 +130,9 @@ echo if test ${ThereWereErrors} = 1 ; then echo "Check what went wrong. The *.log files might help you." +exit 1 else echo "Everything seems to be fine !" echo 'You could remove all generated files using "rm -f my_* *.log"' +exit 0 fi From 39fa774d6459b44e547d2d0b0f44981130ffc850 Mon Sep 17 00:00:00 2001 From: Nikos E Date: Sun, 16 Oct 2016 20:40:19 +0100 Subject: [PATCH 022/509] minor corrections --- recon_test_pack/run_root_GATE.sh | 33 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/recon_test_pack/run_root_GATE.sh b/recon_test_pack/run_root_GATE.sh index 7a87b581c8..361f19fac9 100755 --- a/recon_test_pack/run_root_GATE.sh +++ b/recon_test_pack/run_root_GATE.sh @@ -63,8 +63,8 @@ else echo "GATE support detected!" fi -echo Executing tests on ROOT files generated by GATE simulations, with cylindrical PET scanners - +echo "Executing tests on ROOT files generated by GATE simulations, with cylindrical PET scanners" +echo "" # first delete any files remaining from a previous run rm -f my_*v my_*s my_*S @@ -74,18 +74,17 @@ export INPUT_ROOT_FILE=test_PET_GATE.root export INPUT=root_header.hroot export TEMPLATE=template_for_ROOT_scanner.hs -echo ------------- Converting ROOT files to ProjData file ------------- -echo Making ProjData for all events -echo Running ${INSTALL_DIR}lm_to_projdata for all events - +echo "---- Converting ROOT file to ProjData file -----" +echo ">> Running lm_to_projdata for all events" +echo "" export OUT_PROJDATA_FILE=my_proj_from_lm_all_events export EXCLUDE_SCATTERED=0 export EXCLUDE_RANDOM=0 -all_events=$(${INSTALL_DIR}lm_to_projdata ./lm_to_projdata_from_ROOT.par 2>&1 | grep "Number of prompts stored in this time period" | grep -o -E '[0-9]+') +all_events=$(${INSTALL_DIR}lm_to_projdata ./lm_to_projdata.par 2>&1 | grep "Number of prompts stored in this time period" | grep -o -E '[0-9]+') + +echo "Number of prompts stored in this time period:" ${all_events} -echo Number of prompts stored in this time period: ${all_events} -echo echo Reading all values from ROOT file all_root_num=$(root -l ${INPUT_ROOT_FILE} << EOF | grep "Number of prompts stored in this time period" | grep -o -E '[0-9]+' @@ -94,31 +93,31 @@ Int_t N = eventlist->GetN(); cout<> Running lm_to_projdata *ONLY* for true events" +echo "" export OUT_PROJDATA_FILE=my_proj_from_lm_true_events export EXCLUDE_SCATTERED=1 export EXCLUDE_RANDOM=1 -true_events=$(${INSTALL_DIR}lm_to_projdata ./lm_to_projdata_from_ROOT.par 2>&1 | grep "Number of prompts stored in this time period" | grep -o -E '[0-9]+') +true_events=$(${INSTALL_DIR}lm_to_projdata ./lm_to_projdata.par 2>&1 | grep "Number of prompts stored in this time period" | grep -o -E '[0-9]+') echo Number of prompts stored in this time period: ${true_events} echo echo Reading true values from ROOT file ... -true_root_num=$(root -l ${INPUT_ROOT_FILE} << EOF | grep "Number of trues stored in this time period" | grep -o -E '[0-9]+' +true_root_num=$(root -l ${INPUT_ROOT_FILE} << EOF | grep "Number" | grep -o -E '[0-9]+' Coincidences->Draw(">>eventlist","eventID1 == eventID2 && comptonPhantom1 == 0 && comptonPhantom2 == 0","goff"); Int_t N = eventlist->GetN(); cout< Date: Wed, 19 Oct 2016 17:20:09 +0100 Subject: [PATCH 023/509] BUG fix: If the additive data are not set then don't perform any tests. --- ...MeanAndListModeDataWithProjMatrixByBin.cxx | 59 ++++++++++--------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx index 381e5f6152..1c6bd8d8a3 100644 --- a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx +++ b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx @@ -286,37 +286,38 @@ PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBinget_max_num_non_arccorrected_bins(), false))); - if (*(this->additive_proj_data_sptr->get_proj_data_info_sptr()) != *proj_data_info_cyl_uncompressed_ptr) - { - const ProjDataInfo& add_proj = *(this->additive_proj_data_sptr->get_proj_data_info_sptr()); - const ProjDataInfo& proj = *this->proj_data_info_cyl_uncompressed_ptr; - bool ok = - typeid(add_proj) == typeid(proj) && - *add_proj.get_scanner_ptr()== *(proj.get_scanner_ptr()) && - (add_proj.get_min_view_num()==proj.get_min_view_num()) && - (add_proj.get_max_view_num()==proj.get_max_view_num()) && - (add_proj.get_min_tangential_pos_num() ==proj.get_min_tangential_pos_num())&& - (add_proj.get_max_tangential_pos_num() ==proj.get_max_tangential_pos_num()) && - add_proj.get_min_segment_num() <= proj.get_min_segment_num() && - add_proj.get_max_segment_num() >= proj.get_max_segment_num(); - - for (int segment_num=proj.get_min_segment_num(); - ok && segment_num<=proj.get_max_segment_num(); - ++segment_num) - { - ok = - add_proj.get_min_axial_pos_num(segment_num) == proj.get_min_axial_pos_num(segment_num) && - add_proj.get_max_axial_pos_num(segment_num) == proj.get_max_axial_pos_num(segment_num); - } - if (!ok) + if(!is_null_ptr(this->additive_proj_data_sptr->get_proj_data_info_sptr())) + if (*(this->additive_proj_data_sptr->get_proj_data_info_sptr()) != *proj_data_info_cyl_uncompressed_ptr) { - warning(boost::format("Incompatible additive projection data:\nAdditive projdata info:\n%s\nEmission projdata info:\n%s\n" - "--- (end of incompatible projection data info)---\n") - % add_proj.parameter_info() - % proj.parameter_info()); - return true; + const ProjDataInfo& add_proj = *(this->additive_proj_data_sptr->get_proj_data_info_sptr()); + const ProjDataInfo& proj = *this->proj_data_info_cyl_uncompressed_ptr; + bool ok = + typeid(add_proj) == typeid(proj) && + *add_proj.get_scanner_ptr()== *(proj.get_scanner_ptr()) && + (add_proj.get_min_view_num()==proj.get_min_view_num()) && + (add_proj.get_max_view_num()==proj.get_max_view_num()) && + (add_proj.get_min_tangential_pos_num() ==proj.get_min_tangential_pos_num())&& + (add_proj.get_max_tangential_pos_num() ==proj.get_max_tangential_pos_num()) && + add_proj.get_min_segment_num() <= proj.get_min_segment_num() && + add_proj.get_max_segment_num() >= proj.get_max_segment_num(); + + for (int segment_num=proj.get_min_segment_num(); + ok && segment_num<=proj.get_max_segment_num(); + ++segment_num) + { + ok = + add_proj.get_min_axial_pos_num(segment_num) == proj.get_min_axial_pos_num(segment_num) && + add_proj.get_max_axial_pos_num(segment_num) == proj.get_max_axial_pos_num(segment_num); + } + if (!ok) + { + warning(boost::format("Incompatible additive projection data:\nAdditive projdata info:\n%s\nEmission projdata info:\n%s\n" + "--- (end of incompatible projection data info)---\n") + % add_proj.parameter_info() + % proj.parameter_info()); + return true; + } } - } if( this->normalisation_sptr->set_up(proj_data_info_cyl_uncompressed_ptr) == Succeeded::no) From 971eb5768a1d4b25437c813231ee26733dc08ff9 Mon Sep 17 00:00:00 2001 From: Nikos E Date: Fri, 21 Oct 2016 19:24:10 +0100 Subject: [PATCH 024/509] Listmode data can handle compression (span). The lm objective function takes the pro_data_info from CListModeData. New keywords for CListrModeROOT to set the sizes of the proj_data_info. Better tests on ranges. --- recon_test_pack/root_header.hroot | 6 ++ recon_test_pack/run_test_listmode_recon.sh | 2 +- src/include/stir/listmode/CListModeData.h | 8 +- .../stir/listmode/CListModeDataECAT8_32bit.h | 3 +- src/include/stir/listmode/CListModeDataROOT.h | 7 ++ ...orMeanAndListModeDataWithProjMatrixByBin.h | 2 +- src/listmode_buildblock/CListModeData.cxx | 8 ++ src/listmode_buildblock/CListModeDataROOT.cxx | 22 +++++ ...MeanAndListModeDataWithProjMatrixByBin.cxx | 85 ++++++++++--------- 9 files changed, 97 insertions(+), 46 deletions(-) diff --git a/recon_test_pack/root_header.hroot b/recon_test_pack/root_header.hroot index 8936f059d4..a566b779bd 100644 --- a/recon_test_pack/root_header.hroot +++ b/recon_test_pack/root_header.hroot @@ -9,6 +9,12 @@ Distance between rings (cm) := 0.40625 Default bin size (cm) := 0.208626 Maximum number of non-arc-corrected bins := 344 +; Acquisition specific params +%axial_compression := +%maximum_ring_difference := +%number_of_projections := +%number_of_views := +%number_of_segments := GATE scanner type := GATE_Cylindrical_PET diff --git a/recon_test_pack/run_test_listmode_recon.sh b/recon_test_pack/run_test_listmode_recon.sh index c5a12564f7..23322c6a55 100755 --- a/recon_test_pack/run_test_listmode_recon.sh +++ b/recon_test_pack/run_test_listmode_recon.sh @@ -73,7 +73,7 @@ fi # first delete any files remaining from a previous run rm -f my_*v my_*s my_*S -echo "=== calculate backround data" +echo "=== calculate background data" echo "===== randoms" echo "====== create delayed fansums" diff --git a/src/include/stir/listmode/CListModeData.h b/src/include/stir/listmode/CListModeData.h index 75bad2f7e8..98ccbd9646 100644 --- a/src/include/stir/listmode/CListModeData.h +++ b/src/include/stir/listmode/CListModeData.h @@ -30,7 +30,7 @@ #include "stir/shared_ptr.h" #include #include - +#include "stir/ProjDataInfo.h" #include "stir/IO/ExamData.h" #include "stir/RegisteredParsingObject.h" @@ -226,11 +226,15 @@ class CListModeData : public ExamData //! Returns the total number of events in the listmode file virtual inline unsigned long int get_total_number_of_events() const = 0; + shared_ptr get_proj_data_info_sptr() const; + protected: - //! Has to be set by the derived class + //! Has to be initialised by the derived class shared_ptr scanner_sptr; //! Has to be set by the derived class // shared_ptr exam_info_sptr; + + shared_ptr proj_data_info_sptr; }; END_NAMESPACE_STIR diff --git a/src/include/stir/listmode/CListModeDataECAT8_32bit.h b/src/include/stir/listmode/CListModeDataECAT8_32bit.h index 7feae1741d..3ca76eaec6 100644 --- a/src/include/stir/listmode/CListModeDataECAT8_32bit.h +++ b/src/include/stir/listmode/CListModeDataECAT8_32bit.h @@ -87,7 +87,8 @@ class CListModeDataECAT8_32bit : public CListModeData typedef CListRecordECAT8_32bit CListRecordT; std::string listmode_filename; shared_ptr > current_lm_data_ptr; - shared_ptr proj_data_info_sptr; + //moved at CListModeData +// shared_ptr proj_data_info_sptr; InterfileHeader interfile_parser; // members to store info from the interfile header. // These tell us something about how the listmode is stored. diff --git a/src/include/stir/listmode/CListModeDataROOT.h b/src/include/stir/listmode/CListModeDataROOT.h index 8bc13b34d8..c6dc3ae695 100644 --- a/src/include/stir/listmode/CListModeDataROOT.h +++ b/src/include/stir/listmode/CListModeDataROOT.h @@ -88,11 +88,18 @@ class CListModeDataROOT : public CListModeData float average_depth_of_interaction; float ring_spacing; float bin_size; + // These tell us something about how the listmode is stored. + int axial_compression; + int maximum_ring_difference; + int number_of_projections; + int number_of_views; + int number_of_segments; KeyParser parser; //! Name of input chain which is going to be used. Succeeded open_lm_file(); + }; diff --git a/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.h b/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.h index 91174db804..a596588603 100644 --- a/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.h +++ b/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.h @@ -113,7 +113,7 @@ typedef RegisteredParsingObject proj_data_info_cyl_uncompressed_ptr; + shared_ptr proj_data_info_cyl_sptr; //! sets any default values /*! Has to be called by set_defaults in the leaf-class */ diff --git a/src/listmode_buildblock/CListModeData.cxx b/src/listmode_buildblock/CListModeData.cxx index 2cb1266c77..94c9991910 100644 --- a/src/listmode_buildblock/CListModeData.cxx +++ b/src/listmode_buildblock/CListModeData.cxx @@ -59,6 +59,14 @@ get_scanner_ptr() const return this->scanner_sptr.get(); } +shared_ptr +CListModeData:: +get_proj_data_info_sptr() const +{ + assert(!is_null_ptr(proj_data_info_sptr)); + return proj_data_info_sptr; +} + #if 0 std::time_t CListModeData:: diff --git a/src/listmode_buildblock/CListModeDataROOT.cxx b/src/listmode_buildblock/CListModeDataROOT.cxx index 8a19eea7d3..4803bb31a5 100644 --- a/src/listmode_buildblock/CListModeDataROOT.cxx +++ b/src/listmode_buildblock/CListModeDataROOT.cxx @@ -43,6 +43,13 @@ CListModeDataROOT(const std::string& listmode_filename) this->parser.add_start_key("ROOT header"); this->parser.add_stop_key("End ROOT header"); + // Set some defaults, in order to know if this parameters have been initialised + axial_compression = -1; + maximum_ring_difference = -1; + number_of_projections = -1; + number_of_views = -1; + number_of_segments = -1; + // Scanner related & Physical dimentions. this->parser.add_key("originating system", &this->originating_system); this->parser.add_key("Number of rings", &this->num_rings); @@ -54,6 +61,14 @@ CListModeDataROOT(const std::string& listmode_filename) this->parser.add_key("Maximum number of non-arc-corrected bins", &this->max_num_non_arccorrected_bins); // end Scanner and physical dimentions. + // Acquisition related + this->parser.add_key("%axial_compression", &axial_compression); + this->parser.add_key("%maximum_ring_difference", &maximum_ring_difference); + this->parser.add_key("%number_of_projections", &number_of_projections); + this->parser.add_key("%number_of_views", &number_of_views); + this->parser.add_key("%number_of_segments", &number_of_segments); + // + // ROOT related this->parser.add_parsing_key("GATE scanner type", &this->current_lm_data_ptr); this->parser.parse(listmode_filename.c_str(), false /* no warnings about unrecognised keywords */); @@ -116,6 +131,13 @@ CListModeDataROOT(const std::string& listmode_filename) if (this->open_lm_file() == Succeeded::no) error("CListModeDataROOT: error opening the first listmode file for filename %s\n", listmode_filename.c_str()); + + this->proj_data_info_sptr.reset(ProjDataInfo::ProjDataInfoCTI(this->scanner_sptr, + std::max(axial_compression, 1), + std::max(maximum_ring_difference, num_rings), + std::max(number_of_views, num_detectors_per_ring/2), + std::max(number_of_projections, max_num_non_arccorrected_bins), + /* arc_correction*/false)); } std::string diff --git a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx index 1c6bd8d8a3..f5dd6bc1b8 100644 --- a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx +++ b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx @@ -129,19 +129,19 @@ actual_subsets_are_approximately_balanced(std::string& warning_message) const for (int segment_num = -this->max_ring_difference_num_to_process; segment_num <= this->max_ring_difference_num_to_process; ++segment_num) { - for (int axial_num = this->proj_data_info_cyl_uncompressed_ptr->get_min_axial_pos_num(segment_num); - axial_num < this->proj_data_info_cyl_uncompressed_ptr->get_max_axial_pos_num(segment_num); + for (int axial_num = proj_data_info_cyl_sptr->get_min_axial_pos_num(segment_num); + axial_num < proj_data_info_cyl_sptr->get_max_axial_pos_num(segment_num); axial_num ++) { // For debugging. // std::cout <proj_data_info_cyl_uncompressed_ptr->get_min_tangential_pos_num(); - tang_num < this->proj_data_info_cyl_uncompressed_ptr->get_max_tangential_pos_num(); + for (int tang_num= proj_data_info_cyl_sptr->get_min_tangential_pos_num(); + tang_num < proj_data_info_cyl_sptr->get_max_tangential_pos_num(); tang_num ++ ) { - for(int view_num = this->proj_data_info_cyl_uncompressed_ptr->get_min_view_num() + subset_num; - view_num <= this->proj_data_info_cyl_uncompressed_ptr->get_max_view_num(); + for(int view_num = proj_data_info_cyl_sptr->get_min_view_num() + subset_num; + view_num <= proj_data_info_cyl_sptr->get_max_view_num(); view_num += this->num_subsets) { const Bin tmp_bin(segment_num, @@ -171,11 +171,11 @@ actual_subsets_are_approximately_balanced(std::string& warning_message) const << num_bins_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_info_cyl_uncompressed_ptr->get_num_views() + << proj_data_info_cyl_sptr->get_num_views() << "/4 (or if that's not an integer, a divisor of " - << this->proj_data_info_cyl_uncompressed_ptr->get_num_views() + << proj_data_info_cyl_sptr->get_num_views() << "/2 or " - << this->proj_data_info_cyl_uncompressed_ptr->get_num_views() + << proj_data_info_cyl_sptr->get_num_views() << ").\n"; warning_message = str.str(); return false; @@ -196,7 +196,7 @@ set_up_before_sensitivity(shared_ptr const& target_sptr) // set projector to be used for the calculations - this->PM_sptr->set_up(this->proj_data_info_cyl_uncompressed_ptr->create_shared_clone(),target_sptr); + this->PM_sptr->set_up(proj_data_info_cyl_sptr->create_shared_clone(),target_sptr); shared_ptr forward_projector_ptr(new ForwardProjectorByBinUsingProjMatrixByBin(this->PM_sptr)); shared_ptr back_projector_ptr(new BackProjectorByBinUsingProjMatrixByBin(this->PM_sptr)); @@ -204,7 +204,7 @@ set_up_before_sensitivity(shared_ptr const& target_sptr) this->projector_pair_ptr.reset( new ProjectorByBinPairUsingSeparateProjectors(forward_projector_ptr, back_projector_ptr)); - this->projector_pair_ptr->set_up(this->proj_data_info_cyl_uncompressed_ptr->create_shared_clone(),target_sptr); + this->projector_pair_ptr->set_up(proj_data_info_cyl_sptr->create_shared_clone(),target_sptr); if (is_null_ptr(this->normalisation_sptr)) @@ -214,7 +214,7 @@ set_up_before_sensitivity(shared_ptr const& target_sptr) } if (this->normalisation_sptr->set_up( - this->proj_data_info_cyl_uncompressed_ptr->create_shared_clone()) == Succeeded::no) + proj_data_info_cyl_sptr->create_shared_clone()) == Succeeded::no) return Succeeded::no; if (this->current_frame_num<=0) @@ -260,14 +260,12 @@ PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin scanner_sptr(new Scanner(*this->list_mode_data_sptr->get_scanner_ptr())); - if (this->max_ring_difference_num_to_process == -1) { this->max_ring_difference_num_to_process = scanner_sptr->get_num_rings()-1; } - if (this->additive_projection_data_filename != "0") { info(boost::format("Reading additive projdata data '%1%'") @@ -277,20 +275,25 @@ PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBinadditive_proj_data_sptr.reset(new ProjDataInMemory(* temp_additive_proj_data_sptr)); } + proj_data_info_cyl_sptr = this->list_mode_data_sptr->get_proj_data_info_sptr()->create_shared_clone(); - this->proj_data_info_cyl_uncompressed_ptr.reset( - dynamic_cast( - ProjDataInfo::ProjDataInfoCTI(scanner_sptr, - 1, this->max_ring_difference_num_to_process, - scanner_sptr->get_num_detectors_per_ring()/2, - scanner_sptr->get_max_num_non_arccorrected_bins(), - false))); + if (max_ring_difference_num_to_process > proj_data_info_cyl_sptr->get_max_segment_num()) + { + warning("In the parameter file, the 'maximum ring difference' is larger than the number of segments" + "in the emission header. Abort."); + return true; + } + else if (max_ring_difference_num_to_process < proj_data_info_cyl_sptr->get_max_segment_num()) + { + proj_data_info_cyl_sptr->reduce_segment_range(-max_ring_difference_num_to_process, + max_ring_difference_num_to_process); + } if(!is_null_ptr(this->additive_proj_data_sptr->get_proj_data_info_sptr())) - if (*(this->additive_proj_data_sptr->get_proj_data_info_sptr()) != *proj_data_info_cyl_uncompressed_ptr) + if (*(this->additive_proj_data_sptr->get_proj_data_info_sptr()) != *proj_data_info_cyl_sptr) { const ProjDataInfo& add_proj = *(this->additive_proj_data_sptr->get_proj_data_info_sptr()); - const ProjDataInfo& proj = *this->proj_data_info_cyl_uncompressed_ptr; + const ProjDataInfo& proj = *this->proj_data_info_cyl_sptr; bool ok = typeid(add_proj) == typeid(proj) && *add_proj.get_scanner_ptr()== *(proj.get_scanner_ptr()) && @@ -298,7 +301,7 @@ PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin= proj.get_max_segment_num(); for (int segment_num=proj.get_min_segment_num(); @@ -306,8 +309,8 @@ PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin= proj.get_max_axial_pos_num(segment_num); } if (!ok) { @@ -319,7 +322,7 @@ PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBinnormalisation_sptr->set_up(proj_data_info_cyl_uncompressed_ptr) + if( this->normalisation_sptr->set_up(proj_data_info_cyl_sptr) == Succeeded::no) { warning("PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin: " @@ -337,14 +340,14 @@ PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBinproj_data_info_cyl_uncompressed_ptr->get_min_segment_num(); - const int max_segment_num = this->proj_data_info_cyl_uncompressed_ptr->get_max_segment_num(); + const int min_segment_num = proj_data_info_cyl_sptr->get_min_segment_num(); + const int max_segment_num = proj_data_info_cyl_sptr->get_max_segment_num(); // 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) { - for (int view = this->proj_data_info_cyl_uncompressed_ptr->get_min_view_num() + subset_num; - view <= this->proj_data_info_cyl_uncompressed_ptr->get_max_view_num(); + for (int view = proj_data_info_cyl_sptr->get_min_view_num() + subset_num; + view <= proj_data_info_cyl_sptr->get_max_view_num(); view += this->num_subsets) { const ViewSegmentNumbers view_segment_num(view, segment_num); @@ -365,7 +368,7 @@ add_view_seg_to_sensitivity(TargetT& sensitivity, const ViewSegmentNumbers& view (this->projector_pair_ptr->get_symmetries_used()->clone()); RelatedViewgrams viewgrams = - this->proj_data_info_cyl_uncompressed_ptr->get_empty_related_viewgrams(view_seg_nums,symmetries_used); + proj_data_info_cyl_sptr->get_empty_related_viewgrams(view_seg_nums,symmetries_used); viewgrams.fill(1.F); // find efficiencies @@ -395,7 +398,7 @@ construct_target_ptr() const { return - new VoxelsOnCartesianGrid (*this->proj_data_info_cyl_uncompressed_ptr, + new VoxelsOnCartesianGrid (*proj_data_info_cyl_sptr, static_cast(this->zoom), CartesianCoordinate3D(static_cast(this->Zoffset), static_cast(this->Yoffset), @@ -419,7 +422,7 @@ compute_sub_gradient_without_penalty_plus_sensitivity(TargetT& gradient, assert(subset_numnum_subsets); ProjDataInfoCylindricalNoArcCorr* proj_data_no_arc_ptr = - dynamic_cast ( this->proj_data_info_cyl_uncompressed_ptr.get()); + dynamic_cast (proj_data_info_cyl_sptr.get()); const double start_time = this->frame_defs.get_start_time(this->current_frame_num); const double end_time = this->frame_defs.get_end_time(this->current_frame_num); @@ -462,15 +465,15 @@ compute_sub_gradient_without_penalty_plus_sensitivity(TargetT& gradient, { Bin measured_bin; measured_bin.set_bin_value(1.0f); - record.event().get_bin(measured_bin, *this->proj_data_info_cyl_uncompressed_ptr); + record.event().get_bin(measured_bin, *proj_data_info_cyl_sptr); if (measured_bin.get_bin_value() != 1.0f - || measured_bin.segment_num() < this->proj_data_info_cyl_uncompressed_ptr->get_min_segment_num() - || measured_bin.segment_num() > this->proj_data_info_cyl_uncompressed_ptr->get_max_segment_num() - || measured_bin.tangential_pos_num() < this->proj_data_info_cyl_uncompressed_ptr->get_min_tangential_pos_num() - || measured_bin.tangential_pos_num() > this->proj_data_info_cyl_uncompressed_ptr->get_max_tangential_pos_num() - || measured_bin.axial_pos_num() < this->proj_data_info_cyl_uncompressed_ptr->get_min_axial_pos_num(measured_bin.segment_num()) - || measured_bin.axial_pos_num() > this->proj_data_info_cyl_uncompressed_ptr->get_max_axial_pos_num(measured_bin.segment_num())) + || measured_bin.segment_num() < proj_data_info_cyl_sptr->get_min_segment_num() + || measured_bin.segment_num() > proj_data_info_cyl_sptr->get_max_segment_num() + || measured_bin.tangential_pos_num() < proj_data_info_cyl_sptr->get_min_tangential_pos_num() + || measured_bin.tangential_pos_num() > proj_data_info_cyl_sptr->get_max_tangential_pos_num() + || measured_bin.axial_pos_num() < proj_data_info_cyl_sptr->get_min_axial_pos_num(measured_bin.segment_num()) + || measured_bin.axial_pos_num() > proj_data_info_cyl_sptr->get_max_axial_pos_num(measured_bin.segment_num())) { continue; } From 77ff2c1b76bc1f27e7661ddf44c4a9dd8b01c6dd Mon Sep 17 00:00:00 2001 From: Nikos E Date: Sun, 23 Oct 2016 19:39:07 +0100 Subject: [PATCH 025/509] Minor bug corrections and cosmetics. After some more complains about the grep in the run_root_GATE.sh, I changed to an awk. --- recon_test_pack/run_root_GATE.sh | 51 ++++++++----------- src/IO/InputStreamFromROOTFile.cxx | 4 +- ...putStreamFromROOTFileForCylindricalPET.cxx | 20 ++++---- src/IO/InputStreamFromROOTFileForECATPET.cxx | 12 ++--- src/include/stir/IO/InputStreamFromROOTFile.h | 2 +- src/listmode_buildblock/CListModeDataROOT.cxx | 25 +++------ 6 files changed, 47 insertions(+), 67 deletions(-) diff --git a/recon_test_pack/run_root_GATE.sh b/recon_test_pack/run_root_GATE.sh index 361f19fac9..0f487bdbd9 100755 --- a/recon_test_pack/run_root_GATE.sh +++ b/recon_test_pack/run_root_GATE.sh @@ -80,37 +80,36 @@ echo "" export OUT_PROJDATA_FILE=my_proj_from_lm_all_events export EXCLUDE_SCATTERED=0 export EXCLUDE_RANDOM=0 - -all_events=$(${INSTALL_DIR}lm_to_projdata ./lm_to_projdata.par 2>&1 | grep "Number of prompts stored in this time period" | grep -o -E '[0-9]+') - +${INSTALL_DIR}lm_to_projdata ./lm_to_projdata.par 2>"./my_root_log_all.log" +all_events=$(awk -F ":" '/Number of prompts/ {print $2}' my_root_log_all.log) echo "Number of prompts stored in this time period:" ${all_events} -echo Reading all values from ROOT file +echo "" +echo ">> Running lm_to_projdata *ONLY* for true events" +echo "" +export OUT_PROJDATA_FILE=my_proj_from_lm_true_events +export EXCLUDE_SCATTERED=1 +export EXCLUDE_RANDOM=1 +${INSTALL_DIR}lm_to_projdata ./lm_to_projdata.par 2>"./my_root_log_trues.log" +true_events=$(awk -F ":" '/Number of prompts/ {print $2}' my_root_log_trues.log) +echo "Number of prompts stored in this time period:" ${true_events} + +echo "" +echo "Counting all values from ROOT file ..." all_root_num=$(root -l ${INPUT_ROOT_FILE} << EOF | grep "Number of prompts stored in this time period" | grep -o -E '[0-9]+' Coincidences->Draw(">>eventlist","","goff"); Int_t N = eventlist->GetN(); cout<> Running lm_to_projdata *ONLY* for true events" -echo "" -export OUT_PROJDATA_FILE=my_proj_from_lm_true_events -export EXCLUDE_SCATTERED=1 -export EXCLUDE_RANDOM=1 - -true_events=$(${INSTALL_DIR}lm_to_projdata ./lm_to_projdata.par 2>&1 | grep "Number of prompts stored in this time period" | grep -o -E '[0-9]+') - -echo Number of prompts stored in this time period: ${true_events} -echo -echo Reading true values from ROOT file ... +echo "" +echo "Counting true values from ROOT file ..." true_root_num=$(root -l ${INPUT_ROOT_FILE} << EOF | grep "Number" | grep -o -E '[0-9]+' Coincidences->Draw(">>eventlist","eventID1 == eventID2 && comptonPhantom1 == 0 && comptonPhantom2 == 0","goff"); Int_t N = eventlist->GetN(); @@ -118,20 +117,14 @@ cout<SetBranchAddress("time1", &time1); stream_ptr->SetBranchAddress("time2", &time2); - stream_ptr->SetBranchAddress("eventID1",&eventID1); - stream_ptr->SetBranchAddress("eventID2",&eventID2); + stream_ptr->SetBranchAddress("eventID1",&event1); + stream_ptr->SetBranchAddress("eventID2",&event2); stream_ptr->SetBranchAddress("energy1", &energy1); stream_ptr->SetBranchAddress("energy2", &energy2); stream_ptr->SetBranchAddress("comptonPhantom1", &comptonphantom1); diff --git a/src/IO/InputStreamFromROOTFileForCylindricalPET.cxx b/src/IO/InputStreamFromROOTFileForCylindricalPET.cxx index ba7855dff6..bf23f85833 100644 --- a/src/IO/InputStreamFromROOTFileForCylindricalPET.cxx +++ b/src/IO/InputStreamFromROOTFileForCylindricalPET.cxx @@ -67,22 +67,22 @@ get_next_record(CListRecordROOT& record) return Succeeded::no; - if (stream_ptr->GetEntry(current_position) == 0 ) + if (stream_ptr->GetEntry(static_cast(current_position)) == 0 ) return Succeeded::no; current_position ++ ; - if ( (comptonphantom1 > 0 || comptonphantom2>0) && exclude_scattered ) + if ( (this->comptonphantom1 > 0 || this->comptonphantom2 > 0) && this->exclude_scattered ) continue; - else if ( (eventID1 != eventID2) && exclude_randoms ) + if ( this->event1 != this->event2 && this->exclude_randoms) continue; - else if (energy1 < low_energy_window || - energy1 > up_energy_window || - energy2 < low_energy_window || - energy2 > up_energy_window) + if (this->energy1 < this->low_energy_window || + this->energy1 > this->up_energy_window || + this->energy2 < this->low_energy_window || + this->energy2 > this->up_energy_window) continue; - else - break; + + break; } int ring1 = static_cast(crystalID1/crystal_repeater_y) @@ -116,7 +116,7 @@ get_next_record(CListRecordROOT& record) record.init_from_data(ring1, ring2, crystal1, crystal2, time1, time2, - eventID1, eventID2); + event1, event2); } std::string diff --git a/src/IO/InputStreamFromROOTFileForECATPET.cxx b/src/IO/InputStreamFromROOTFileForECATPET.cxx index 35dc0ee7ba..87efb85d8c 100644 --- a/src/IO/InputStreamFromROOTFileForECATPET.cxx +++ b/src/IO/InputStreamFromROOTFileForECATPET.cxx @@ -68,17 +68,17 @@ get_next_record(CListRecordROOT& record) current_position ++ ; - if ( (comptonphantom1 > 0 && comptonphantom2>0) && exclude_scattered ) + if ( (comptonphantom1 > 0 || comptonphantom2 > 0) && exclude_scattered ) continue; - else if ( (eventID1 != eventID2) && exclude_randoms ) + if ( event1 != event2 && exclude_randoms ) continue; - else if (energy1 < low_energy_window || + if (energy1 < low_energy_window || energy1 > up_energy_window || energy2 < low_energy_window || energy2 > up_energy_window) continue; - else - break; + + break; } int ring1 = static_cast(crystalID1/crystal_repeater_z) @@ -106,7 +106,7 @@ get_next_record(CListRecordROOT& record) record.init_from_data(ring1, ring2, crystal1, crystal2, time1, time2, - eventID1, eventID2); + event1, event2); } std::string diff --git a/src/include/stir/IO/InputStreamFromROOTFile.h b/src/include/stir/IO/InputStreamFromROOTFile.h index 7da8255674..0ff6b63a4b 100644 --- a/src/include/stir/IO/InputStreamFromROOTFile.h +++ b/src/include/stir/IO/InputStreamFromROOTFile.h @@ -145,7 +145,7 @@ class InputStreamFromROOTFile : public RegisteredObject< InputStreamFromROOTFile // Variables to store root information std::string chain_name; - Int_t eventID1, eventID2; + Int_t event1, event2; Double_t time1, time2; Float_t energy1, energy2; Int_t comptonphantom1, comptonphantom2; diff --git a/src/listmode_buildblock/CListModeDataROOT.cxx b/src/listmode_buildblock/CListModeDataROOT.cxx index 4803bb31a5..bfc899fb04 100644 --- a/src/listmode_buildblock/CListModeDataROOT.cxx +++ b/src/listmode_buildblock/CListModeDataROOT.cxx @@ -134,7 +134,7 @@ CListModeDataROOT(const std::string& listmode_filename) this->proj_data_info_sptr.reset(ProjDataInfo::ProjDataInfoCTI(this->scanner_sptr, std::max(axial_compression, 1), - std::max(maximum_ring_difference, num_rings), + std::max(maximum_ring_difference, num_rings-1), std::max(number_of_views, num_detectors_per_ring/2), std::max(number_of_projections, max_num_non_arccorrected_bins), /* arc_correction*/false)); @@ -162,40 +162,27 @@ open_lm_file() info(boost::format("CListModeDataROOT: opening ROOT file %s") % this->current_lm_data_ptr->get_ROOT_filename()); - // Read the 4 bytes to check whether this is a ROOT file, indeed. - // I could rewrite it in a more sofisticated way ... + // Read the 4 bytes to check whether this is a ROOT file std::stringstream ss; - char mem[4]="\0"; + std::string mem(4,' '); std::string sig= "root"; std::ifstream t; - t.open(this->current_lm_data_ptr->get_ROOT_filename().c_str(), std::ios::in |std::ios::binary); + t.open(this->current_lm_data_ptr->get_ROOT_filename().c_str(), std::ios::in); if (t.is_open()) { t.seekg(0, std::ios::beg); - t.read(mem,4); - ss << mem; + t.read(&mem[0],4); - if ( !sig.compare(ss.str()) ) + if (sig.compare(mem) ) { warning("CListModeDataROOT: File '%s is not a ROOT file!!'", this->current_lm_data_ptr->get_ROOT_filename().c_str()); return Succeeded::no; } -// current_lm_data_ptr.reset( -// new InputStreamFromROOTFile(this->input_data_filename, -// this->name_of_input_tchain, -// this->number_of_crystals_x, this->number_of_crystals_y, this->number_of_crystals_z, -// this->number_of_submodules_x, this->number_of_submodules_y, this->number_of_submodules_z, -// this->number_of_modules_x, this->number_of_modules_y, this->number_of_modules_z, -// this->number_of_rsectors, -// this->exclude_scattered, this->exclude_randoms, -// static_cast(this->low_energy_window*0.001f), -// static_cast(this->up_energy_window*0.001f), -// this->offset_dets)); t.close(); return Succeeded::yes; From adb10256a01d6a4288b446bd5c29ee18a2a5d529 Mon Sep 17 00:00:00 2001 From: Nikos E Date: Sun, 23 Oct 2016 22:09:45 +0100 Subject: [PATCH 026/509] Removed a redundant variable. --- src/listmode_buildblock/CListModeDataROOT.cxx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/listmode_buildblock/CListModeDataROOT.cxx b/src/listmode_buildblock/CListModeDataROOT.cxx index bfc899fb04..c60a8639b0 100644 --- a/src/listmode_buildblock/CListModeDataROOT.cxx +++ b/src/listmode_buildblock/CListModeDataROOT.cxx @@ -163,7 +163,6 @@ open_lm_file() this->current_lm_data_ptr->get_ROOT_filename()); // Read the 4 bytes to check whether this is a ROOT file - std::stringstream ss; std::string mem(4,' '); std::string sig= "root"; From b1386232e9c16f5a8664558a7a7a727987eef95f Mon Sep 17 00:00:00 2001 From: Nikos E Date: Sun, 23 Oct 2016 23:23:08 +0100 Subject: [PATCH 027/509] Axial compression for CListmodeDataROOT has been commented out. Use of backquotes in the test script. Other minor imrpovements. --- recon_test_pack/root_header.hroot | 8 --- recon_test_pack/run_root_GATE.sh | 12 ++--- ...putStreamFromROOTFileForCylindricalPET.cxx | 2 +- src/include/stir/listmode/CListModeDataROOT.h | 12 ++--- src/listmode_buildblock/CListModeData.cxx | 2 - src/listmode_buildblock/CListModeDataROOT.cxx | 50 +++++++++++-------- 6 files changed, 42 insertions(+), 44 deletions(-) diff --git a/recon_test_pack/root_header.hroot b/recon_test_pack/root_header.hroot index a566b779bd..3bada70ad8 100644 --- a/recon_test_pack/root_header.hroot +++ b/recon_test_pack/root_header.hroot @@ -9,14 +9,6 @@ Distance between rings (cm) := 0.40625 Default bin size (cm) := 0.208626 Maximum number of non-arc-corrected bins := 344 -; Acquisition specific params -%axial_compression := -%maximum_ring_difference := -%number_of_projections := -%number_of_views := -%number_of_segments := - - GATE scanner type := GATE_Cylindrical_PET GATE_Cylindrical_PET Parameters := diff --git a/recon_test_pack/run_root_GATE.sh b/recon_test_pack/run_root_GATE.sh index 0f487bdbd9..4a7d05cbd2 100755 --- a/recon_test_pack/run_root_GATE.sh +++ b/recon_test_pack/run_root_GATE.sh @@ -81,7 +81,7 @@ export OUT_PROJDATA_FILE=my_proj_from_lm_all_events export EXCLUDE_SCATTERED=0 export EXCLUDE_RANDOM=0 ${INSTALL_DIR}lm_to_projdata ./lm_to_projdata.par 2>"./my_root_log_all.log" -all_events=$(awk -F ":" '/Number of prompts/ {print $2}' my_root_log_all.log) +all_events=`awk -F ":" '/Number of prompts/ {print $2}' my_root_log_all.log` echo "Number of prompts stored in this time period:" ${all_events} echo "" @@ -92,16 +92,16 @@ export OUT_PROJDATA_FILE=my_proj_from_lm_true_events export EXCLUDE_SCATTERED=1 export EXCLUDE_RANDOM=1 ${INSTALL_DIR}lm_to_projdata ./lm_to_projdata.par 2>"./my_root_log_trues.log" -true_events=$(awk -F ":" '/Number of prompts/ {print $2}' my_root_log_trues.log) +true_events=`awk -F ":" '/Number of prompts/ {print $2}' my_root_log_trues.log` echo "Number of prompts stored in this time period:" ${true_events} echo "" echo "Counting all values from ROOT file ..." -all_root_num=$(root -l ${INPUT_ROOT_FILE} << EOF | grep "Number of prompts stored in this time period" | grep -o -E '[0-9]+' +all_root_num=`root -l ${INPUT_ROOT_FILE} << EOF | grep "Number of prompts stored in this time period" | grep -o -E '[0-9]+' Coincidences->Draw(">>eventlist","","goff"); Int_t N = eventlist->GetN(); cout<Draw(">>eventlist","eventID1 == eventID2 && comptonPhantom1 == 0 && comptonPhantom2 == 0","goff"); Int_t N = eventlist->GetN(); cout<GetEntry(static_cast(current_position)) == 0 ) + if (stream_ptr->GetEntry(static_cast(current_position)) == 0 ) return Succeeded::no; current_position ++ ; diff --git a/src/include/stir/listmode/CListModeDataROOT.h b/src/include/stir/listmode/CListModeDataROOT.h index c6dc3ae695..7f82670574 100644 --- a/src/include/stir/listmode/CListModeDataROOT.h +++ b/src/include/stir/listmode/CListModeDataROOT.h @@ -88,12 +88,12 @@ class CListModeDataROOT : public CListModeData float average_depth_of_interaction; float ring_spacing; float bin_size; - // These tell us something about how the listmode is stored. - int axial_compression; - int maximum_ring_difference; - int number_of_projections; - int number_of_views; - int number_of_segments; + // Axial compresstion has been commented out, until further testing is done. + // int axial_compression; + // int maximum_ring_difference; + // int number_of_projections; + // int number_of_views; + // int number_of_segments; KeyParser parser; //! Name of input chain which is going to be used. diff --git a/src/listmode_buildblock/CListModeData.cxx b/src/listmode_buildblock/CListModeData.cxx index 94c9991910..184d4f909f 100644 --- a/src/listmode_buildblock/CListModeData.cxx +++ b/src/listmode_buildblock/CListModeData.cxx @@ -55,7 +55,6 @@ const Scanner* CListModeData:: get_scanner_ptr() const { - assert(!is_null_ptr(scanner_sptr)); return this->scanner_sptr.get(); } @@ -63,7 +62,6 @@ shared_ptr CListModeData:: get_proj_data_info_sptr() const { - assert(!is_null_ptr(proj_data_info_sptr)); return proj_data_info_sptr; } diff --git a/src/listmode_buildblock/CListModeDataROOT.cxx b/src/listmode_buildblock/CListModeDataROOT.cxx index c60a8639b0..67d0c9daa4 100644 --- a/src/listmode_buildblock/CListModeDataROOT.cxx +++ b/src/listmode_buildblock/CListModeDataROOT.cxx @@ -32,7 +32,7 @@ #include "stir/error.h" #include #include -#include +#include START_NAMESPACE_STIR @@ -43,12 +43,12 @@ CListModeDataROOT(const std::string& listmode_filename) this->parser.add_start_key("ROOT header"); this->parser.add_stop_key("End ROOT header"); - // Set some defaults, in order to know if this parameters have been initialised - axial_compression = -1; - maximum_ring_difference = -1; - number_of_projections = -1; - number_of_views = -1; - number_of_segments = -1; + // N.E.: Compression on ROOT listmode data is commented out until further testing is done. + // axial_compression = -1; + // maximum_ring_difference = -1; + // number_of_projections = -1; + // number_of_views = -1; + // number_of_segments = -1; // Scanner related & Physical dimentions. this->parser.add_key("originating system", &this->originating_system); @@ -62,18 +62,18 @@ CListModeDataROOT(const std::string& listmode_filename) // end Scanner and physical dimentions. // Acquisition related - this->parser.add_key("%axial_compression", &axial_compression); - this->parser.add_key("%maximum_ring_difference", &maximum_ring_difference); - this->parser.add_key("%number_of_projections", &number_of_projections); - this->parser.add_key("%number_of_views", &number_of_views); - this->parser.add_key("%number_of_segments", &number_of_segments); + // N.E.: Compression on ROOT listmode data has been commented out until further testing is done. + // this->parser.add_key("%axial_compression", &axial_compression); + // this->parser.add_key("%maximum_ring_difference", &maximum_ring_difference); + // this->parser.add_key("%number_of_projections", &number_of_projections); + // this->parser.add_key("%number_of_views", &number_of_views); + // this->parser.add_key("%number_of_segments", &number_of_segments); // // ROOT related this->parser.add_parsing_key("GATE scanner type", &this->current_lm_data_ptr); this->parser.parse(listmode_filename.c_str(), false /* no warnings about unrecognised keywords */); -// this->current_lm_data_ptr->set_up(); // ExamInfo initialisation this->exam_info_sptr.reset(new ExamInfo); @@ -132,11 +132,19 @@ CListModeDataROOT(const std::string& listmode_filename) error("CListModeDataROOT: error opening the first listmode file for filename %s\n", listmode_filename.c_str()); + // N.E.: Compression on ROOT listmode data has been commented out until further testing is done. + // this->proj_data_info_sptr.reset(ProjDataInfo::ProjDataInfoCTI(this->scanner_sptr, + // std::max(axial_compression, 1), + // std::max(maximum_ring_difference, num_rings-1), + // std::max(number_of_views, num_detectors_per_ring/2), + // std::max(number_of_projections, max_num_non_arccorrected_bins), + // /* arc_correction*/false)); + this->proj_data_info_sptr.reset(ProjDataInfo::ProjDataInfoCTI(this->scanner_sptr, - std::max(axial_compression, 1), - std::max(maximum_ring_difference, num_rings-1), - std::max(number_of_views, num_detectors_per_ring/2), - std::max(number_of_projections, max_num_non_arccorrected_bins), + 1, + num_rings-1, + num_detectors_per_ring/2, + max_num_non_arccorrected_bins, /* arc_correction*/false)); } @@ -163,8 +171,8 @@ open_lm_file() this->current_lm_data_ptr->get_ROOT_filename()); // Read the 4 bytes to check whether this is a ROOT file - std::string mem(4,' '); - std::string sig= "root"; + char mem[5]="\0"; + char sig[5]="root"; std::ifstream t; t.open(this->current_lm_data_ptr->get_ROOT_filename().c_str(), std::ios::in); @@ -173,9 +181,9 @@ open_lm_file() { t.seekg(0, std::ios::beg); - t.read(&mem[0],4); + t.read(mem,4); - if (sig.compare(mem) ) + if (strncmp(sig, mem, 4)) { warning("CListModeDataROOT: File '%s is not a ROOT file!!'", this->current_lm_data_ptr->get_ROOT_filename().c_str()); From 3fc9612e97dada0ff2d2c2369fb497be3f92e13a Mon Sep 17 00:00:00 2001 From: Nikos E Date: Mon, 24 Oct 2016 01:00:15 +0100 Subject: [PATCH 028/509] CListModeData::get_proj_data_info_sptr() became pure virtual --- recon_test_pack/run_root_GATE.sh | 15 +++++++++------ src/include/stir/listmode/CListModeData.h | 4 +++- src/include/stir/listmode/CListModeDataECAT.h | 3 +++ .../stir/listmode/CListModeDataECAT8_32bit.h | 3 +++ src/include/stir/listmode/CListModeDataROOT.h | 4 +++- src/listmode_buildblock/CListModeData.cxx | 8 +------- src/listmode_buildblock/CListModeDataECAT.cxx | 8 ++++++++ .../CListModeDataECAT8_32bit.cxx | 8 ++++++++ src/listmode_buildblock/CListModeDataROOT.cxx | 8 ++++++++ 9 files changed, 46 insertions(+), 15 deletions(-) diff --git a/recon_test_pack/run_root_GATE.sh b/recon_test_pack/run_root_GATE.sh index 4a7d05cbd2..76fd022891 100755 --- a/recon_test_pack/run_root_GATE.sh +++ b/recon_test_pack/run_root_GATE.sh @@ -97,11 +97,13 @@ echo "Number of prompts stored in this time period:" ${true_events} echo "" echo "Counting all values from ROOT file ..." -all_root_num=`root -l ${INPUT_ROOT_FILE} << EOF | grep "Number of prompts stored in this time period" | grep -o -E '[0-9]+' +cat>my_root.input<Draw(">>eventlist","","goff"); Int_t N = eventlist->GetN(); -cout<my_root.input<Draw(">>eventlist","eventID1 == eventID2 && comptonPhantom1 == 0 && comptonPhantom2 == 0","goff"); Int_t N = eventlist->GetN(); -cout< get_proj_data_info_sptr() const; + virtual + shared_ptr get_proj_data_info_sptr() const = 0; protected: //! Has to be initialised by the derived class diff --git a/src/include/stir/listmode/CListModeDataECAT.h b/src/include/stir/listmode/CListModeDataECAT.h index 1990aaf5ea..781ea8c2b5 100644 --- a/src/include/stir/listmode/CListModeDataECAT.h +++ b/src/include/stir/listmode/CListModeDataECAT.h @@ -89,6 +89,9 @@ class CListModeDataECAT : public CListModeData /*! \todo this might depend on the acquisition parameters */ virtual bool has_delayeds() const { return true; } + virtual + shared_ptr get_proj_data_info_sptr() const; + private: std::string listmode_filename_prefix; mutable unsigned int current_lm_file; diff --git a/src/include/stir/listmode/CListModeDataECAT8_32bit.h b/src/include/stir/listmode/CListModeDataECAT8_32bit.h index 3ca76eaec6..22394d67d0 100644 --- a/src/include/stir/listmode/CListModeDataECAT8_32bit.h +++ b/src/include/stir/listmode/CListModeDataECAT8_32bit.h @@ -83,6 +83,9 @@ class CListModeDataECAT8_32bit : public CListModeData error("Not implemented yet. Abort."); }; + virtual + shared_ptr get_proj_data_info_sptr() const = 0; + private: typedef CListRecordECAT8_32bit CListRecordT; std::string listmode_filename; diff --git a/src/include/stir/listmode/CListModeDataROOT.h b/src/include/stir/listmode/CListModeDataROOT.h index 7f82670574..104ff24290 100644 --- a/src/include/stir/listmode/CListModeDataROOT.h +++ b/src/include/stir/listmode/CListModeDataROOT.h @@ -70,6 +70,9 @@ class CListModeDataROOT : public CListModeData unsigned long int get_total_number_of_events() const ; + virtual + shared_ptr get_proj_data_info_sptr() const; + private: // typedef CListRecordROOT CListRecordT; @@ -99,7 +102,6 @@ class CListModeDataROOT : public CListModeData //! Name of input chain which is going to be used. Succeeded open_lm_file(); - }; diff --git a/src/listmode_buildblock/CListModeData.cxx b/src/listmode_buildblock/CListModeData.cxx index 184d4f909f..2cb1266c77 100644 --- a/src/listmode_buildblock/CListModeData.cxx +++ b/src/listmode_buildblock/CListModeData.cxx @@ -55,16 +55,10 @@ const Scanner* CListModeData:: get_scanner_ptr() const { + assert(!is_null_ptr(scanner_sptr)); return this->scanner_sptr.get(); } -shared_ptr -CListModeData:: -get_proj_data_info_sptr() const -{ - return proj_data_info_sptr; -} - #if 0 std::time_t CListModeData:: diff --git a/src/listmode_buildblock/CListModeDataECAT.cxx b/src/listmode_buildblock/CListModeDataECAT.cxx index 9ad6fdf837..bf4d101749 100644 --- a/src/listmode_buildblock/CListModeDataECAT.cxx +++ b/src/listmode_buildblock/CListModeDataECAT.cxx @@ -329,6 +329,14 @@ get_num_records() const #endif +template +shared_ptr +CListModeDataECAT:: +get_proj_data_info_sptr() const +{ + assert(!is_null_ptr(proj_data_info_sptr)); + return proj_data_info_sptr; +} // instantiations template class CListModeDataECAT; diff --git a/src/listmode_buildblock/CListModeDataECAT8_32bit.cxx b/src/listmode_buildblock/CListModeDataECAT8_32bit.cxx index 2360926562..d707b76b21 100644 --- a/src/listmode_buildblock/CListModeDataECAT8_32bit.cxx +++ b/src/listmode_buildblock/CListModeDataECAT8_32bit.cxx @@ -164,5 +164,13 @@ set_get_position(const CListModeDataECAT8_32bit::SavedPosition& pos) current_lm_data_ptr->set_get_position(pos); } +shared_ptr +CListModeDataECAT8_32bit:: +get_proj_data_info_sptr() const +{ + assert(!is_null_ptr(proj_data_info_sptr)); + return proj_data_info_sptr; +} + } // namespace ecat END_NAMESPACE_STIR diff --git a/src/listmode_buildblock/CListModeDataROOT.cxx b/src/listmode_buildblock/CListModeDataROOT.cxx index 67d0c9daa4..73945cdecf 100644 --- a/src/listmode_buildblock/CListModeDataROOT.cxx +++ b/src/listmode_buildblock/CListModeDataROOT.cxx @@ -238,5 +238,13 @@ set_get_position(const CListModeDataROOT::SavedPosition& pos) return current_lm_data_ptr->set_get_position(pos); } +shared_ptr +CListModeDataROOT:: +get_proj_data_info_sptr() const +{ + assert(!is_null_ptr(proj_data_info_sptr)); + return proj_data_info_sptr; +} + END_NAMESPACE_STIR From d955723612ee6a53789f25e9359a86d2f348547b Mon Sep 17 00:00:00 2001 From: Nikos E Date: Mon, 24 Oct 2016 08:08:51 +0100 Subject: [PATCH 029/509] minor correction --- src/include/stir/listmode/CListModeDataECAT8_32bit.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/include/stir/listmode/CListModeDataECAT8_32bit.h b/src/include/stir/listmode/CListModeDataECAT8_32bit.h index 22394d67d0..af4d69ece7 100644 --- a/src/include/stir/listmode/CListModeDataECAT8_32bit.h +++ b/src/include/stir/listmode/CListModeDataECAT8_32bit.h @@ -84,7 +84,7 @@ class CListModeDataECAT8_32bit : public CListModeData }; virtual - shared_ptr get_proj_data_info_sptr() const = 0; + shared_ptr get_proj_data_info_sptr() const; private: typedef CListRecordECAT8_32bit CListRecordT; From dcbdf36ac04329cc3152825783f1a9d3bfa6f9c0 Mon Sep 17 00:00:00 2001 From: Nikos E Date: Mon, 24 Oct 2016 11:41:25 +0100 Subject: [PATCH 030/509] One more minor bug found --- ...WithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx index f5dd6bc1b8..f79fb6c043 100644 --- a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx +++ b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx @@ -289,7 +289,7 @@ PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBinadditive_proj_data_sptr->get_proj_data_info_sptr())) + if(!is_null_ptr(this->additive_proj_data_sptr)) if (*(this->additive_proj_data_sptr->get_proj_data_info_sptr()) != *proj_data_info_cyl_sptr) { const ProjDataInfo& add_proj = *(this->additive_proj_data_sptr->get_proj_data_info_sptr()); From f409f8cb7e09bbf858c3fbd79a5711a94ab9f778 Mon Sep 17 00:00:00 2001 From: NikEfth Date: Wed, 21 Dec 2016 18:31:28 +0000 Subject: [PATCH 031/509] First commit with code for TOF-lm reconstruction. This code is transfered from other branches and is cleaned up and commented. --- src/buildblock/ProjDataInfo.cxx | 125 ++++++++++++++++++ src/buildblock/ProjDataInfoCylindrical.cxx | 74 ++++++++++- .../ProjDataInfoCylindricalNoArcCorr.cxx | 37 ++++++ src/include/stir/Bin.h | 30 ++++- src/include/stir/Bin.inl | 37 +++++- src/include/stir/IO/InputStreamFromROOTFile.h | 5 + src/include/stir/ProjData.h | 10 ++ src/include/stir/ProjData.inl | 14 ++ src/include/stir/ProjDataInfo.h | 46 +++++++ 9 files changed, 369 insertions(+), 9 deletions(-) diff --git a/src/buildblock/ProjDataInfo.cxx b/src/buildblock/ProjDataInfo.cxx index ef7cc292f3..4e0b0eb1a2 100644 --- a/src/buildblock/ProjDataInfo.cxx +++ b/src/buildblock/ProjDataInfo.cxx @@ -4,6 +4,7 @@ Copyright (C) 2000 PARAPET partners Copyright (C) 2000 - 2009-05-13, Hammersmith Imanet Ltd Copyright (C) 2011-07-01 - 2011, Kris Thielemans + Copyright (C) 2016, University of Hull This file is part of STIR. This file is free software; you can redistribute it and/or modify @@ -23,6 +24,7 @@ \brief Implementation of non-inline functions of class stir::ProjDataInfo + \author Nikos Efthimiou \author Sanida Mustafovic \author Kris Thielemans \author PARAPET project @@ -56,6 +58,8 @@ #include #endif +#include "stir/info.h" +#include "boost/foreach.hpp" #ifndef STIR_NO_NAMESPACES using std::string; @@ -68,6 +72,24 @@ using std::equal; START_NAMESPACE_STIR +float +ProjDataInfo::get_k(const Bin& bin) const +{ + if (!get_num_timing_poss()%2) + return bin.timing_pos_num() * timing_increament_in_mm; + else + return (bin.timing_pos_num() * timing_increament_in_mm) - timing_increament_in_mm/2.f; +} + +float +ProjDataInfo::get_sampling_in_k(const Bin& bin) const +{ + return (get_k(Bin(bin.segment_num(), bin.view_num(), bin.axial_pos_num(),bin.tangential_pos_num(), + bin.timing_pos_num()+1)) - + get_k(Bin(bin.segment_num(), bin.view_num(), bin.axial_pos_num(),bin.tangential_pos_num(), + bin.timing_pos_num()-1)) + )/2.f; +} float ProjDataInfo::get_sampling_in_t(const Bin& bin) const @@ -157,6 +179,89 @@ ProjDataInfo::set_max_tangential_pos_num(const int max_tang_poss) max_tangential_pos_num = max_tang_poss; } +//! \todo N.E: This function is very ugly and unnessesary complicated. Could be much better. +void +ProjDataInfo::set_tof_mash_factor(const int new_num) +{ + if (scanner_ptr->is_tof_ready()) + { + if(tof_mash_factor < 0 || tof_mash_factor > scanner_ptr->get_num_max_of_timing_bins()) + error("ProjDataInfo: TOF mashing factor must be positive and smaller or equal than" + "the scanner's number of max timing bins. Abort."); + tof_mash_factor = new_num; + + timing_increament_in_mm = tof_mash_factor * scanner_ptr->get_size_of_timing_bin() * 0.299792458f; + + // limit to the size of the diameter. + + Bin b; + + float lowest_t = get_min_tangential_pos_num(); + float highest_t = get_max_tangential_pos_num(); + + b.tangential_pos_num() = lowest_t; + float k = get_sampling_in_t(b); + b.tangential_pos_num() = highest_t; + float l = get_sampling_in_t(b); + + float lowest_boundary = lowest_t * k; + float highest_boundary = highest_t * l; + + int num_tof_positions_in_FOV = static_cast(((highest_boundary - lowest_boundary) / (2.f * timing_increament_in_mm))+0.5); + + min_timing_pos_num = -num_tof_positions_in_FOV/2; + max_timing_pos_num = min_timing_pos_num + num_tof_positions_in_FOV; + + // Upper and lower boundaries of the timing poss; + timing_bin_boundaries.grow(min_timing_pos_num, max_timing_pos_num); + + for (int i = min_timing_pos_num; i <= max_timing_pos_num; ++i ) + { + Bin bin; + bin.timing_pos_num() = i; + + float cur_low = get_k(bin); + float cur_high = get_k(bin) + get_sampling_in_k(bin); + + if (cur_low< 0 || cur_high < 0 ) + { + if (cur_low < lowest_boundary && cur_high > lowest_boundary) + { + timing_bin_boundaries[i].low_lim = lowest_boundary; + timing_bin_boundaries[i].high_lim = cur_high; + } + else if (cur_low >= lowest_boundary && cur_high >= lowest_boundary) + { + timing_bin_boundaries[i].low_lim = cur_low; + timing_bin_boundaries[i].high_lim = cur_high; + } + } + else + { + if (cur_high > highest_boundary && cur_low < highest_boundary) + { + timing_bin_boundaries[i].low_lim = cur_low; + timing_bin_boundaries[i].high_lim = highest_boundary; + } + else if (cur_low <= highest_boundary && cur_high <= highest_boundary) + { + timing_bin_boundaries[i].low_lim = cur_low; + timing_bin_boundaries[i].high_lim = cur_high; + } + } + float lowt = (timing_bin_boundaries[i].low_lim / 0.299792458f ) ; + float hight = ( timing_bin_boundaries[i].high_lim/ 0.299792458f); + info(boost::format("Tbin %1%: %2% - %3% mm (%4% - %5% ps) = %6%") %i % timing_bin_boundaries[i].low_lim % timing_bin_boundaries[i].high_lim + % lowt% hight % get_sampling_in_k(bin)); + // Go to natural coordinates - opted out. + // timing_bin_boundaries[i].low_lim *= r_sqrtdPI_gauss_sigma; + // timing_bin_boundaries[i].high_lim *= r_sqrtdPI_gauss_sigma; + } + } + else + error("Not TOF compatible scanner template. Abort."); +} + ProjDataInfo::ProjDataInfo() {} @@ -174,6 +279,26 @@ ProjDataInfo::ProjDataInfo(const shared_ptr& scanner_ptr_v, set_num_views(num_views_v); set_num_tangential_poss(num_tangential_poss_v); set_num_axial_poss_per_segment(num_axial_pos_per_segment_v); + // Initialise the TOF elements to non-used. + min_timing_pos_num = 0; + max_timing_pos_num = 0; + timing_increament_in_mm = 0.f; + tof_mash_factor = 1; // zero? +} + +// TOF version. +ProjDataInfo::ProjDataInfo(const shared_ptr& scanner_ptr_v, + const VectorWithOffset& num_axial_pos_per_segment_v, + const int num_views_v, + const int num_tangential_poss_v, + const int tof_mash_factor_v) + :scanner_ptr(scanner_ptr_v) + +{ + set_tof_mash_factor(tof_mash_factor_v); + set_num_views(num_views_v); + set_num_tangential_poss(num_tangential_poss_v); + set_num_axial_poss_per_segment(num_axial_pos_per_segment_v); } string diff --git a/src/buildblock/ProjDataInfoCylindrical.cxx b/src/buildblock/ProjDataInfoCylindrical.cxx index f7ed2e159e..808e861b34 100644 --- a/src/buildblock/ProjDataInfoCylindrical.cxx +++ b/src/buildblock/ProjDataInfoCylindrical.cxx @@ -3,6 +3,7 @@ Copyright (C) 2000 - 2009-10-18 Hammersmith Imanet Ltd Copyright (C) 2011, Kris Thielemans Copyright (C) 2013, University College London + Copyright (C) 2016, University of Hull This file is part of STIR. @@ -25,6 +26,7 @@ \brief Non-inline implementations of stir::ProjDataInfoCylindrical + \author Nikos Efthimiou \author Kris Thielemans \author Sanida Mustafovic \author PARAPET project @@ -454,6 +456,15 @@ compute_segment_axial_pos_to_ring_pair(const int segment_num, const int axial_po } } +void +ProjDataInfoCylindrical:: +set_tof_mash_factor(const int new_num) +{ + base_type::set_tof_mash_factor(new_num); + //! \todo N.E. Would be nice to have all the points of the scanner in cache. + //initialise_uncompressed_lor_as_point1point2(); +} + void ProjDataInfoCylindrical:: set_num_axial_poss_per_segment(const VectorWithOffset& num_axial_poss_per_segment) @@ -538,7 +549,68 @@ get_LOR(LORInAxialAndNoArcCorrSinogramCoordinates& lor, asin(s_in_mm/get_ring_radius()), get_ring_radius()); } - + +void +ProjDataInfoCylindrical:: +get_LOR_as_two_points(CartesianCoordinate3D& coord_1, + CartesianCoordinate3D& coord_2, + const Bin& bin) const +{ + const float s_in_mm = get_s(bin); + const float m_in_mm = get_m(bin); + const float tantheta = get_tantheta(bin); + const float phi = get_phi(bin); + /* parametrisation of LOR is + X= s*cphi + a*sphi, + Y= s*sphi - a*cphi, + Z= m - a*tantheta + find now min_a, max_a such that end-points intersect the ring + */ + assert(fabs(s_in_mm) < get_ring_radius()); + // a has to be such that X^2+Y^2 == R^2 + const float max_a = sqrt(square(get_ring_radius()) - square(s_in_mm)); + const float min_a = -max_a; + + coord_1.x() = s_in_mm*cos(phi) + min_a*sin(phi); + coord_1.y() = s_in_mm*sin(phi) - max_a*cos(phi); + coord_1.z() = m_in_mm - max_a*tantheta; + + coord_2.x() = s_in_mm*cos(phi) + max_a*sin(phi); + coord_2.y() = s_in_mm*sin(phi) - min_a*cos(phi); + coord_2.z() = m_in_mm - min_a*tantheta; +} + +void +ProjDataInfoCylindrical:: +get_LOR_as_two_points_alt(CartesianCoordinate3D& coord_1, + CartesianCoordinate3D& coord_2, + const Bin& bin) const +{ + const int num_detectors_per_ring = + get_scanner_ptr()->get_num_detectors_per_ring(); + + float h_scanner_height = ( (get_scanner_ptr()->get_ring_spacing() -1) * get_scanner_ptr()->get_num_rings())/2.F; + + // although code maybe doesn't really need the following, + // asserts in the LOR code will break if these conditions are not satisfied. + assert(0<=det1); + assert(det1 cyl_coords(get_scanner_ptr()->get_inner_ring_radius()); + + cyl_coords.p1().psi() = static_cast((2.*_PI/num_detectors_per_ring)*(det1)); + cyl_coords.p2().psi() = static_cast((2.*_PI/num_detectors_per_ring)*(det2)); + + cyl_coords.p1().z() = Ring_A*get_scanner_ptr()->get_ring_spacing() - h_scanner_height; + cyl_coords.p2().z() = Ring_B*get_scanner_ptr()->get_ring_spacing() - h_scanner_height; + + LORAs2Points lor(cyl_coords); + coord_1 = lor.p1(); + coord_2 = lor.p2(); +} + string ProjDataInfoCylindrical::parameter_info() const { diff --git a/src/buildblock/ProjDataInfoCylindricalNoArcCorr.cxx b/src/buildblock/ProjDataInfoCylindricalNoArcCorr.cxx index cc0afd2010..35754c5242 100644 --- a/src/buildblock/ProjDataInfoCylindricalNoArcCorr.cxx +++ b/src/buildblock/ProjDataInfoCylindricalNoArcCorr.cxx @@ -3,6 +3,7 @@ /* Copyright (C) 2000- 2007-10-08, Hammersmith Imanet Ltd Copyright (C) 2011-07-01 - 2011, Kris Thielemans + Copyright (C) 2016, University of Hull This file is part of STIR. This file is free software; you can redistribute it and/or modify @@ -24,6 +25,7 @@ \brief Implementation of non-inline functions of class stir::ProjDataInfoCylindricalNoArcCorr + \author Nikos Efthimiou \author Kris Thielemans */ @@ -496,6 +498,41 @@ find_cartesian_coordinates_given_scanner_coordinates (CartesianCoordinate3D& coord_1, + CartesianCoordinate3D& coord_2, + const int Ring_A,const int Ring_B, + const int det1, const int det2) const +{ + const int num_detectors_per_ring = + get_scanner_ptr()->get_num_detectors_per_ring(); + +// float h_scanner_height = ( (get_scanner_ptr()->get_ring_spacing() -1) * get_scanner_ptr()->get_num_rings())/2.F; + + // although code maybe doesn't really need the following, + // asserts in the LOR code will break if these conditions are not satisfied. + assert(0<=det1); + assert(det1 cyl_coords(get_scanner_ptr()->get_inner_ring_radius()); + + cyl_coords.p1().psi() = static_cast((2.*_PI/num_detectors_per_ring)*(det1)); + cyl_coords.p2().psi() = static_cast((2.*_PI/num_detectors_per_ring)*(det2)); + +// cyl_coords.p1().z() = Ring_A*get_scanner_ptr()->get_ring_spacing() - h_scanner_height; +// cyl_coords.p2().z() = Ring_B*get_scanner_ptr()->get_ring_spacing() - h_scanner_height; + + LORAs2Points lor(cyl_coords); + coord_1 = lor.p1(); + coord_2 = lor.p2(); +} + + void ProjDataInfoCylindricalNoArcCorr:: find_bin_given_cartesian_coordinates_of_detection(Bin& bin, diff --git a/src/include/stir/Bin.h b/src/include/stir/Bin.h index b9e445653e..3b79c91429 100644 --- a/src/include/stir/Bin.h +++ b/src/include/stir/Bin.h @@ -7,7 +7,7 @@ \brief Declaration of class stir::Bin - + \author Nikos Efthimiou \author Sanida Mustafovic \author Kris Thielemans \author Mustapha Sadki @@ -17,6 +17,7 @@ /* Copyright (C) 2000 PARAPET partners Copyright (C) 2000- 2009, Hammersmith Imanet Ltd + Copyright (C) 2016, University of Hull This file is part of STIR. This file is free software; you can redistribute it and/or modify @@ -43,6 +44,17 @@ START_NAMESPACE_STIR \brief A class for storing coordinates and value of a single projection bin. + The timing position reflect the detection time difference between the two events. + It is a multiple of the delta t of the least significant clock bit. + + \details In order to go around the timing pos which is set only in TOF reconstruction + we set it, by default, to zero. If it is zero in both bins then it is omitted from the + comparison. + \warning Comparison between bin with and without timing position is of high risk! + + \warning Constructors with default values were removed. + + \warning Temporarily the timing_pos_num is not taken into account when comparing two bins. */ class Bin @@ -53,7 +65,16 @@ class Bin //! A constructor : constructs a bin with value (defaulting to 0) inline Bin(int segment_num,int view_num, int axial_pos_num, - int tangential_pos_num,float bin_value=0); + int tangential_pos_num,float bin_value); + + inline Bin(int segment_num, int view_num, int axial_pos_num, + int tangential_pos_num); + + inline Bin(int segment_num, int view_num, int axial_pos_num, + int tangential_pos_num, int timing_pos_num, float bin_value); + + inline Bin(int segment_num, int view_num, int axial_pos_num, + int tangential_pos_num, int timing_pos_num); //!get axial position number inline int axial_pos_num()const; @@ -63,6 +84,8 @@ class Bin inline int tangential_pos_num() const; //! get view number inline int view_num() const; + //! get timing position number + inline int timing_pos_num() const; inline int& axial_pos_num(); inline int& segment_num(); @@ -96,7 +119,8 @@ private : int segment; int view; int axial_pos; - int tangential_pos; + int tangential_pos; + int timing_pos; float bin_value; diff --git a/src/include/stir/Bin.inl b/src/include/stir/Bin.inl index 7fe9d8ac3b..4f0e37bd20 100644 --- a/src/include/stir/Bin.inl +++ b/src/include/stir/Bin.inl @@ -7,6 +7,7 @@ \brief Implementations of inline functions of class stir::Bin + \author Nikos Efthimiou \author Sanida Mustafovic \author Kris Thielemans \author PARAPET project @@ -15,6 +16,7 @@ /* Copyright (C) 2000 PARAPET partners Copyright (C) 2000- 2009, Hammersmith Imanet Ltd + Copyright (C) 2016, University of Hull This file is part of STIR. This file is free software; you can redistribute it and/or modify @@ -32,13 +34,29 @@ START_NAMESPACE_STIR -Bin::Bin() +Bin::Bin():segment(0),view(0), + axial_pos(0),tangential_pos(0),timing_pos(0), bin_value(0.0f) {} -Bin::Bin(int segment_num,int view_num, int axial_pos_num,int tangential_pos_num,float bin_value) - :segment(segment_num),view(view_num), - axial_pos(axial_pos_num),tangential_pos(tangential_pos_num),bin_value(bin_value) +Bin::Bin(int segment_num,int view_num, int axial_pos_num,int tangential_pos_num, float bin_value) + :segment(segment_num),view(view_num), + axial_pos(axial_pos_num),tangential_pos(tangential_pos_num), bin_value(bin_value), timing_pos(0) + {} + +Bin::Bin(int segment_num,int view_num, int axial_pos_num,int tangential_pos_num) + :segment(segment_num),view(view_num), + axial_pos(axial_pos_num),tangential_pos(tangential_pos_num),timing_pos(0), bin_value(0.0f) + {} + +Bin::Bin(int segment_num,int view_num, int axial_pos_num,int tangential_pos_num, int timing_pos_num, float bin_value) + :segment(segment_num),view(view_num), + axial_pos(axial_pos_num),tangential_pos(tangential_pos_num), timing_pos(timing_pos_num), bin_value(bin_value) + {} + +Bin::Bin(int segment_num,int view_num, int axial_pos_num,int tangential_pos_num, int timing_pos_num) + :segment(segment_num),view(view_num), + axial_pos(axial_pos_num),tangential_pos(tangential_pos_num), timing_pos(timing_pos_num), bin_value(0.0f) {} @@ -58,6 +76,10 @@ int Bin::view_num() const { return view;} +int +Bin::timing_pos_num() const +{ return timing_pos; } + int& Bin::axial_pos_num() { return axial_pos;} @@ -74,6 +96,10 @@ int& Bin:: view_num() { return view;} +int& +Bin:: timing_pos_num() +{ return timing_pos;} + #if 0 const ProjDataInfo * Bin::get_proj_data_info_ptr() const @@ -86,7 +112,7 @@ Bin Bin::get_empty_copy() const { - Bin copy(segment_num(),view_num(),axial_pos_num(),tangential_pos_num(),0); + Bin copy(segment_num(),view_num(),axial_pos_num(),tangential_pos_num(),timing_pos_num(), 0.f); return copy; } @@ -111,6 +137,7 @@ Bin::operator==(const Bin& bin2) const return segment == bin2.segment && view == bin2.view && axial_pos == bin2.axial_pos && tangential_pos == bin2.tangential_pos && +// && timing_pos == bin2.timing_pos bin_value == bin2.bin_value; } diff --git a/src/include/stir/IO/InputStreamFromROOTFile.h b/src/include/stir/IO/InputStreamFromROOTFile.h index 0ff6b63a4b..30e2d5efa7 100644 --- a/src/include/stir/IO/InputStreamFromROOTFile.h +++ b/src/include/stir/IO/InputStreamFromROOTFile.h @@ -9,6 +9,7 @@ /* * Copyright (C) 2015, 2016 University of Leeds Copyright (C) 2016, UCL + Copyright (C) 2016, University of Hull This file is part of STIR. This file is free software; you can redistribute it and/or modify @@ -158,6 +159,10 @@ class InputStreamFromROOTFile : public RegisteredObject< InputStreamFromROOTFile int offset_dets; int singles_readout_depth; + + // This member will try to give to the continuous time register in GATE + // data, a finite least significant bit. + double least_significant_clock_bit; }; END_NAMESPACE_STIR diff --git a/src/include/stir/ProjData.h b/src/include/stir/ProjData.h index 5f61c0cb4f..1bf1227f84 100644 --- a/src/include/stir/ProjData.h +++ b/src/include/stir/ProjData.h @@ -70,6 +70,8 @@ class Succeeded;
  1. \c tangential_pos_num : indexes different positions in a direction tangential to the scanner cylinder. (sometimes called 'bin' or 'element') +
  2. -

    New examples

    +

    Python (and MATLAB)

      +
    • Examples use stir.ProjData.read_from_file as opposed to stir.ProjData_read_from_file. The former is supported since SWIG 3.0, and the default from SWIG 4.1. +
    • +
    • Addition of DetectionPosition and DetectionPositionPair.
    • +
    • bin.time_frame_num is now no longer a function in Python, but acts like a variable + (as the other Bin members). +
    -

    Python

    +

    New examples

      -
    • Examples use stir.ProjData.read_from_file as opposed to stir.ProjData_read_from_file. The former is supported since SWIG 3.0, and the default from SWIG 4.1. -

    Changed functionality

    diff --git a/src/swig/stir.i b/src/swig/stir.i index 41a9fbe401..4011b44673 100644 --- a/src/swig/stir.i +++ b/src/swig/stir.i @@ -1,6 +1,6 @@ /* Copyright (C) 2011-07-01 - 2012, Kris Thielemans - Copyright (C) 2013, 2014, 2015, 2018 - 2022 University College London + Copyright (C) 2013, 2014, 2015, 2018 - 2023 University College London Copyright (C) 2022 National Physical Laboratory Copyright (C) 2022 Positrigo This file is part of STIR. @@ -30,12 +30,14 @@ #include // for size_t #include #include + #ifdef SWIGOCTAVE // TODO terrible work-around to avoid conflict between stir::error and Octave error // they are in conflict with eachother because we need "using namespace stir" below (swig bug) #define __stir_error_H__ #endif +#include "stir/stream.h" // to get access to stream output for STIR types for ADD_REPR etc #include "stir/num_threads.h" #include "stir/find_STIR_config.h" @@ -61,6 +63,9 @@ #include "stir/copy_fill.h" #include "stir/ProjDataInterfile.h" + #include "stir/Radionuclide.h" + #include "stir/RadionuclideDB.h" + #include "stir/DataSymmetriesForViewSegmentNumbers.h" #include "stir/recon_buildblock/BinNormalisationFromProjData.h" #include "stir/recon_buildblock/BinNormalisationFromAttenuationImage.h" @@ -566,6 +571,10 @@ %ignore *::ask_parameters; %ignore *::create_shared_clone; %ignore *::read_from_stream; +%ignore *::get_data_ptr; +%ignore *::get_const_data_ptr; +%ignore *::release_data_ptr; +%ignore *::release_const_data_ptr; #if defined(SWIGPYTHON) %rename(__assign__) *::operator=; @@ -821,6 +830,7 @@ namespace std { // Macros for adding __repr_()_ for Python and disp() for MATLAB // example usage: ADD_REPR(stir::ImagingModality, %arg($self->get_name())); + // second argument piped to stream, so could be a std::string, but also another type %define ADD_REPR(classname, defrepr) %extend classname { @@ -831,18 +841,20 @@ namespace std { #if defined(SWIGPYTHON) std::string __repr__() { - std::string repr = ""; + return s.str(); } #endif #if defined(SWIGMATLAB) void disp() { - std::string repr = "<" + "classname::"; - repr += defrepr; - repr += ">"; + std::stringstream s; + s << ""; mexPrintf(repr.c_str()); } #endif @@ -931,11 +943,26 @@ namespace std { /* Parse the header files to generate wrappers */ //%include "stir/shared_ptr.h" %include "stir/Succeeded.h" +ADD_REPR(stir::Succeeded, %arg($self->succeeded() ? "yes" : "no")); + %include "stir/NumericType.h" %include "stir/ByteOrder.h" -%include "stir/DetectionPosition.h" %include "stir_coordinates.i" + +#if 0 + // TODO enable this in STIR version 6 (breaks backwards compatibility +%attributeref(stir::LORInAxialAndNoArcCorrSinogramCoordinates, float, z1); +%attributeref(stir::LORInAxialAndNoArcCorrSinogramCoordinates, float, z2); +%attributeref(stir::LORInAxialAndNoArcCorrSinogramCoordinates, float, beta); +%attributeref(stir::LORInAxialAndNoArcCorrSinogramCoordinates, float, phi); +#else +%ignore *::z1() const; +%ignore *::z2() const; +%ignore *::beta() const; +%ignore *::phi() const; +#endif +%ignore *::check_state; %include "stir/LORCoordinates.h" %template(FloatLOR) stir::LOR; @@ -1016,6 +1043,12 @@ namespace std { %shared_ptr(stir::FanProjData); %shared_ptr(stir::GeoData3D); +%ignore operator<<; +%ignore operator>>; +%ignore stir::DetPairData::operator()(const int a, const int b) const; +%ignore stir::DetPairData3D::operator()(const int a, const int b) const; +%ignore stir::FanProjData::operator()(const int, const int, const int, const int) const; +%ignore stir::GeoData3D::operator()(const int, const int, const int, const int) const; %include "stir/ML_norm.h" %shared_ptr(stir::InvertAxis); diff --git a/src/swig/stir_projdata.i b/src/swig/stir_projdata.i index 096882971b..be38c4f0f3 100644 --- a/src/swig/stir_projdata.i +++ b/src/swig/stir_projdata.i @@ -1,6 +1,6 @@ /* Copyright (C) 2011-07-01 - 2012, Kris Thielemans - Copyright (C) 2013, 2014, 2015, 2018 - 2022 University College London + Copyright (C) 2013, 2014, 2015, 2018 - 2022, 2023 University College London Copyright (C) 2022 National Physical Laboratory This file is part of STIR. @@ -34,15 +34,39 @@ %shared_ptr(stir::Sinogram); %shared_ptr(stir::Viewgram); -%newobject stir::Scanner::get_scanner_from_name; -%include "stir/Scanner.h" +%shared_ptr(stir::DetectionPosition); +%shared_ptr(stir::DetectionPositionPair); + +%attributeref(stir::DetectionPosition, unsigned int, tangential_coord); +%attributeref(stir::DetectionPosition, unsigned int, axial_coord); +%attributeref(stir::DetectionPosition, unsigned int, radial_coord); +%include "stir/DetectionPosition.h" +#ifdef STIR_TOF +ADD_REPR(stir::DetectionPosition, %arg(*$self)) +#endif +%template(DetectionPosition) stir::DetectionPosition; + +%attributeref(stir::DetectionPositionPair, stir::DetectionPosition, pos1); +%attributeref(stir::DetectionPositionPair, stir::DetectionPosition, pos2); +%include "stir/DetectionPositionPair.h" +#ifdef STIR_TOF + //ADD_REPR(stir::DetectionPositionPair, %arg(*$self)) +#endif +%template(DetectionPositionPair) stir::DetectionPositionPair; %attributeref(stir::Bin, int, segment_num); %attributeref(stir::Bin, int, axial_pos_num); %attributeref(stir::Bin, int, view_num); %attributeref(stir::Bin, int, tangential_pos_num); +%attributeref(stir::Bin, int, time_frame_num); %attribute(stir::Bin, float, bin_value, get_bin_value, set_bin_value); %include "stir/Bin.h" +#ifdef STIR_TOF +ADD_REPR(stir::Bin, %arg(*$self)) +#endif + +%newobject stir::Scanner::get_scanner_from_name; +%include "stir/Scanner.h" %newobject stir::ProjDataInfo::ProjDataInfoGE; %newobject stir::ProjDataInfo::ProjDataInfoCTI; diff --git a/src/swig/stir_reconstruction.i b/src/swig/stir_reconstruction.i index efc992cbc0..863c2ba0f2 100644 --- a/src/swig/stir_reconstruction.i +++ b/src/swig/stir_reconstruction.i @@ -15,7 +15,7 @@ \author Kris Thielemans \author Markus Jehl */ -%rename (get_inter_iteration_filter) *::get_inter_iteration_filter_sptr; +%ignore *::get_inter_iteration_filter_sptr; %rename (get_subset_sensitivity) *::get_subset_sensitivity_sptr; %rename (set_objective_function) *::set_objective_function_sptr; %ignore *::get_objective_function_sptr; // we have it without _sptr in C++ diff --git a/src/swig/test/python/test_buildblock.py b/src/swig/test/python/test_buildblock.py index d615c0da86..e67a0c05a5 100755 --- a/src/swig/test/python/test_buildblock.py +++ b/src/swig/test/python/test_buildblock.py @@ -4,7 +4,7 @@ # py.test test_buildblock.py -# Copyright (C) 2013 University College London +# Copyright (C) 2013, 2023 University College London # This file is part of STIR. # # SPDX-License-Identifier: Apache-2.0 @@ -187,14 +187,23 @@ def test_zoom_image(): assert abs(zoomed_image[ind]-1./(zoom))<.001 def test_Scanner(): - s=Scanner.get_scanner_from_name("ECAT 962") - assert s.get_num_rings()==32 - assert s.get_num_detectors_per_ring()==576 - #l=s.get_all_names() - #print s + scanner=Scanner.get_scanner_from_name("ECAT 962") + assert scanner.get_num_rings()==32 + assert scanner.get_num_detectors_per_ring()==576 + #l=scanner.get_all_names() + #print scanner # does not work #for a in l: # print a + scanner=Scanner.get_scanner_from_name("SAFIRDualRingPrototype") + scanner.set_scanner_geometry("BlocksOnCylindrical") + scanner.set_up() + d=DetectionPosition(1,1,0) + c=scanner.get_coordinate_for_det_pos(d) + d2=DetectionPosition(); + s=scanner.find_detection_position_given_cartesian_coordinate(d2, c) + assert s.succeeded() + assert d==d2 def test_Bin(): segment_num=1; @@ -214,6 +223,8 @@ def test_Bin(): assert abs(bin.bin_value-bin_value)<.01; bin=Bin(segment_num, view_num, axial_pos_num, tangential_pos_num, bin_value); assert abs(bin.bin_value-bin_value)<.01; + bin.time_frame_num=3; + assert bin.time_frame_num==3; def test_ProjDataInfo(): s=Scanner.get_scanner_from_name("ECAT 962") From 7265b29b05e56f7b1119cf4e25aaac91d748eee5 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sat, 23 Sep 2023 22:08:44 +0100 Subject: [PATCH 324/509] [SWIG] added RadionuclideDB --- documentation/release_5.2.htm | 2 + examples/python/construct_projdata_demo.py | 47 ++++++++++++++++++++++ src/swig/stir_exam.i | 2 + src/swig/test/python/test_buildblock.py | 9 +++++ 4 files changed, 60 insertions(+) create mode 100644 examples/python/construct_projdata_demo.py diff --git a/documentation/release_5.2.htm b/documentation/release_5.2.htm index 98e6a4609d..5b20bcf3b5 100644 --- a/documentation/release_5.2.htm +++ b/documentation/release_5.2.htm @@ -71,10 +71,12 @@

    Python (and MATLAB)

  3. bin.time_frame_num is now no longer a function in Python, but acts like a variable (as the other Bin members).
  4. +
  5. Addition of RadionuclideDB
  6. New examples

      +
    • examples/python/construct_projdata_demo.py illustrates constructing a ProjDataInMemory

    Changed functionality

    diff --git a/examples/python/construct_projdata_demo.py b/examples/python/construct_projdata_demo.py new file mode 100644 index 0000000000..791f222569 --- /dev/null +++ b/examples/python/construct_projdata_demo.py @@ -0,0 +1,47 @@ +# Demo of how to use STIR from Python to construct some projection data from scratch + +# Copyright 2023 - University College London +# This file is part of STIR. +# +# SPDX-License-Identifier: Apache-2.0 +# +# See STIR/LICENSE.txt for details + +#%% Initial imports +import stir + +#%% check list of predefined scanners +print(stir.Scanner.get_names_of_predefined_scanners()) + +#%% create a scanner +scanner=stir.Scanner.get_scanner_from_name("Siemens mMR") + +#%% create a ProjDataInfo, describing the geometry of the data acquired for that scanner +span = 11 +max_ring_diff = 60 +num_views = scanner.get_num_detectors_per_ring()//2 +num_tangential_poss = scanner.get_default_num_arccorrected_bins() +proj_data_info = stir.ProjDataInfo.construct_proj_data_info(scanner, span, max_ring_diff, num_views, num_tangential_poss, False); + +#%% create radionuclide +modality = stir.ImagingModality(stir.ImagingModality.PT) +db = stir.RadionuclideDB() +r = db.get_radionuclide(modality, "^18^Fluorine") + +#%% supported values are determined by your radionuclide JSON database +with open(stir.find_STIR_config_file("radionuclide_names.json")) as f: + print(f.read()) + +#%% create ExamInfo object +exam_info = stir.ExamInfo(modality) +exam_info.set_radionuclide(r) +exam_info.patient_position=stir.PatientPosition(stir.PatientPosition.HFS) +print(exam_info.parameter_info()) + +#%% create projection data in memory, filled with 1 +proj_data = stir.ProjDataInMemory(exam_info, proj_data_info) +proj_data.fill(1) + +#%% write it to file +# STIR can currently only write Interfile projection data +proj_data.write_to_file('ones.hs') diff --git a/src/swig/stir_exam.i b/src/swig/stir_exam.i index 4a14d9c90d..fb2e491020 100644 --- a/src/swig/stir_exam.i +++ b/src/swig/stir_exam.i @@ -16,6 +16,7 @@ %shared_ptr(stir::TimeFrameDefinitions); %shared_ptr(stir::ImagingModality); %shared_ptr(stir::PatientPosition); +%shared_ptr(stir::RadionuclideDB); %shared_ptr(stir::Radionuclide); %shared_ptr(stir::ExamInfo); %shared_ptr(stir::ExamData); @@ -24,6 +25,7 @@ %include "stir/ImagingModality.h" %include "stir/PatientPosition.h" %include "stir/Radionuclide.h" +%include "stir/RadionuclideDB.h" %include "stir/ExamInfo.h" %include "stir/ExamData.h" diff --git a/src/swig/test/python/test_buildblock.py b/src/swig/test/python/test_buildblock.py index e67a0c05a5..2d2472b7dc 100755 --- a/src/swig/test/python/test_buildblock.py +++ b/src/swig/test/python/test_buildblock.py @@ -205,6 +205,15 @@ def test_Scanner(): assert s.succeeded() assert d==d2 +def test_Radionuclide(): + modality = ImagingModality(ImagingModality.PT) + db = RadionuclideDB() + r = db.get_radionuclide(modality, "^18^Fluorine") + assert abs(r.get_half_life() - 6584) < 1 + modality = ImagingModality(ImagingModality.NM) + r = db.get_radionuclide(modality, "^99m^Technetium") + assert abs(r.get_half_life() - 6.0058*3600) < 10 + def test_Bin(): segment_num=1; view_num=2; From b0dfffe4a31425d372d4301fc3a7634aefe230bf Mon Sep 17 00:00:00 2001 From: Markus Jehl Date: Mon, 25 Sep 2023 14:18:29 +0000 Subject: [PATCH 325/509] Changed the projdata comparison to focus on the shape, rather than voxel values. --- src/test/test_interpolate_projdata.cxx | 52 +++++++++++++++++++++++--- 1 file changed, 47 insertions(+), 5 deletions(-) diff --git a/src/test/test_interpolate_projdata.cxx b/src/test/test_interpolate_projdata.cxx index e5a379a975..c6ce349901 100644 --- a/src/test/test_interpolate_projdata.cxx +++ b/src/test/test_interpolate_projdata.cxx @@ -58,6 +58,7 @@ class InterpolationTests : public RunTests void check_symmetry(const SegmentBySinogram& segment); void compare_segment(const SegmentBySinogram& segment1, const SegmentBySinogram& segment2, float maxDiff); + void compare_segment_shape(const SegmentBySinogram& shape_segment, const SegmentBySinogram& test_segment, int erosion); }; void InterpolationTests::check_symmetry(const SegmentBySinogram& segment) @@ -147,6 +148,46 @@ void InterpolationTests::compare_segment(const SegmentBySinogram& segment check_if_less(sumAbsDifference, maxDiff, "difference between segments is larger than expected"); } +void InterpolationTests::compare_segment_shape(const SegmentBySinogram& shape_segment, const SegmentBySinogram& test_segment, int erosion) +{ + auto maxTestValue = test_segment.find_max(); + // compute difference and compare against empirically found value from visually validated sinograms + auto sumVoxelsOutsideMask = 0U; + for (auto axial = test_segment.get_min_axial_pos_num(); axial <= test_segment.get_max_axial_pos_num(); axial++) + { + for (auto view = test_segment.get_min_view_num(); view <= test_segment.get_max_view_num(); view++) + { + for (auto tang = test_segment.get_min_tangential_pos_num(); tang <= test_segment.get_max_tangential_pos_num(); tang++) + { + if (test_segment[axial][view][tang] < 0.1 * maxTestValue) + continue; + + // now go through the erosion neighbourhood of the voxel to see if it is near a non-zero voxel + bool isNearNonZero = false; + for (auto axialShape = std::max(axial - erosion, test_segment.get_min_axial_pos_num()); + axialShape <= std::min(axial + erosion, test_segment.get_max_axial_pos_num()); axialShape++) + { + for (auto viewShape = std::max(view - erosion, test_segment.get_min_view_num()); + viewShape <= std::min(view + erosion, test_segment.get_max_view_num()); viewShape++) + { + for (auto tangShape = std::max(tang - erosion, test_segment.get_min_tangential_pos_num()); + tangShape <= std::min(tang + erosion, test_segment.get_max_tangential_pos_num()); tangShape++) + { + if (shape_segment[axialShape][viewShape][tangShape] > 0) + isNearNonZero = true; + } + } + } + if (isNearNonZero == false) + sumVoxelsOutsideMask++; + } + } + } + + // confirm that the difference is smaller than an empirically found value + check_if_equal(sumVoxelsOutsideMask, 0U, "there were non-zero voxels outside the masked area"); +} + void InterpolationTests::scatter_interpolation_test_blocks() { info("Performing symmetric interpolation test for BlocksOnCylindrical scanner"); @@ -172,8 +213,9 @@ void InterpolationTests::scatter_interpolation_test_blocks() // define a cylinder precisely in the middle of the FOV, such that symmetry can be used for validation auto emission_map = VoxelsOnCartesianGrid(*downsampled_proj_data_info, 1); - auto cylinder = EllipsoidalCylinder(40, 80, 80, CartesianCoordinate3D(emission_map.get_max_z()/2 * emission_map.get_grid_spacing()[1], 0, 0)); + auto cylinder = EllipsoidalCylinder(41, 80, 80, CartesianCoordinate3D(emission_map.get_max_z()/2 * emission_map.get_grid_spacing()[1], 0, 0)); cylinder.construct_volume(emission_map, CartesianCoordinate3D(1, 1, 1)); + write_to_file("downsampled_cylinder_map", emission_map); // project the cylinder onto the downsampled scanner proj data auto pm = ProjMatrixByBinUsingRayTracing(); @@ -316,7 +358,7 @@ void InterpolationTests::scatter_interpolation_test_blocks_asymmetric() interpolated_proj_data.write_to_file("interpolated_sino_asym.hs"); // compare to ground truth - compare_segment(interpolated_proj_data.get_segment_by_sinogram(0), full_size_model_sino.get_segment_by_sinogram(0), 4697); + compare_segment_shape(full_size_model_sino.get_segment_by_sinogram(0), interpolated_proj_data.get_segment_by_sinogram(0), 2); } void InterpolationTests::scatter_interpolation_test_cyl_asymmetric() @@ -333,8 +375,8 @@ void InterpolationTests::scatter_interpolation_test_cyl_asymmetric() // define the original scanner and a downsampled one, as it would be used for scatter simulation auto scanner = Scanner(Scanner::User_defined_scanner, "Some_symmetric_scanner", 96, 30, 150, 150, 127, 4.3, 4.0, 8.0, -0.38956 /* 0.0 */, 5, 1, 6, 6, 1, 1, 1, 0.17, 511, "Cylindrical", 4.0, 16.0, 24.0, 96.0); - auto downsampled_scanner = Scanner(Scanner::User_defined_scanner, "Some_symmetric_scanner", 64, 12, 150, 150, 127, 4.3, - 10.0, 133 * 3.14 / 64, -0.38956 /* 0.0 */, 1, 1, 12, 64, 1, 1, 1, 0.17, 511, "Cylindrical", 10.0, 12.0, 60.0, 72.0); + auto downsampled_scanner = Scanner(Scanner::User_defined_scanner, "Some_symmetric_scanner", 64, 12, 100, 100, 127, 4.3, + 10.0, 12.0 /* 133 * 3.14 / 64 */, -0.38956 /* 0.0 */, 1, 1, 12, 64, 1, 1, 1, 0.17, 511, "Cylindrical", 10.0, 12.0, 60.0, 72.0); auto proj_data_info = shared_ptr(std::move(ProjDataInfo::construct_proj_data_info(std::make_shared(scanner), 1, 29, 48, int(150 * 96 / 192), false))); auto downsampled_proj_data_info = shared_ptr(std::move(ProjDataInfo::construct_proj_data_info(std::make_shared(downsampled_scanner), 1, 0, 32, int(150 * 64 / 192), false))); @@ -384,7 +426,7 @@ void InterpolationTests::scatter_interpolation_test_cyl_asymmetric() interpolated_proj_data.write_to_file("interpolated_sino_cyl_asym.hs"); // compare to ground truth - compare_segment(interpolated_proj_data.get_segment_by_sinogram(0), full_size_model_sino.get_segment_by_sinogram(0), 17500); + compare_segment_shape(full_size_model_sino.get_segment_by_sinogram(0), interpolated_proj_data.get_segment_by_sinogram(0), 2); } void From 7a326462729bfe9aa5203cd28ede9bcad995b307 Mon Sep 17 00:00:00 2001 From: Markus Jehl Date: Tue, 26 Sep 2023 11:58:14 +0000 Subject: [PATCH 326/509] Create one normal distribution per omp thread to avoid data races --- src/include/stir/DetectorCoordinateMap.h | 59 ++++++++++++++++++------ 1 file changed, 46 insertions(+), 13 deletions(-) diff --git a/src/include/stir/DetectorCoordinateMap.h b/src/include/stir/DetectorCoordinateMap.h index 359d6be515..e5d19f3bd0 100644 --- a/src/include/stir/DetectorCoordinateMap.h +++ b/src/include/stir/DetectorCoordinateMap.h @@ -27,6 +27,7 @@ #include #include #include +#include #include "stir/CartesianCoordinate3D.h" #include "stir/DetectionPosition.h" @@ -65,14 +66,32 @@ class DetectorCoordinateMap //! Constructor calls read_detectormap_from_file( filename ). DetectorCoordinateMap(const std::string& filename, double sigma = 0.0) : - sigma(sigma), - distribution(0.0, sigma) - { read_detectormap_from_file( filename ); } + sigma(sigma) + { + read_detectormap_from_file(filename); +#ifdef STIR_OPENMP + distributions.resize(omp_get_max_threads()); + for (auto &distribution : distributions) + distribution = std::normal_distribution(0.0, sigma); +#else + distributions.resize(1); + distributions[0] = std::normal_distribution(0.0, sigma); +#endif + } //! Constructor calls set_detector_map(coord_map). DetectorCoordinateMap(const det_pos_to_coord_type& coord_map, double sigma = 0.0) : - sigma(sigma), - distribution(0.0, sigma) - { set_detector_map( coord_map ); } + sigma(sigma) + { + set_detector_map( coord_map ); +#ifdef STIR_OPENMP + distributions.resize(omp_get_max_threads()); + for (auto &distribution : distributions) + distribution = std::normal_distribution(0.0, sigma); +#else + distributions.resize(1); + distributions[0] = std::normal_distribution(0.0, sigma); +#endif + } //! Reads map from file and stores it. void read_detectormap_from_file( const std::string& filename ); @@ -88,9 +107,15 @@ class DetectorCoordinateMap stir::CartesianCoordinate3D get_coordinate_for_det_pos( const stir::DetectionPosition<>& det_pos ) const { auto coord = det_pos_to_coord.at(det_pos); - coord.x() += static_cast(distribution(generator)); - coord.y() += static_cast(distribution(generator)); - coord.z() += static_cast(distribution(generator)); +#ifdef STIR_OPENMP + auto thread_id = omp_get_thread_num(); +#else + auto thread_id = 0; +#endif + + coord.x() += static_cast(distributions[thread_id](generator)); + coord.y() += static_cast(distributions[thread_id](generator)); + coord.z() += static_cast(distributions[thread_id](generator)); return coord; } //! Returns a cartesian coordinate given an (unsorted) index. @@ -113,9 +138,17 @@ class DetectorCoordinateMap protected: DetectorCoordinateMap(double sigma = 0.0) : - sigma(sigma), - distribution(0.0, sigma) - {} + sigma(sigma) + { +#ifdef STIR_OPENMP + distributions.resize(omp_get_max_threads()); + for (auto &distribution : distributions) + distribution = std::normal_distribution(0.0, sigma); +#else + distributions.resize(1); + distributions[0] = std::normal_distribution(0.0, sigma); +#endif + } private: unsigned num_tangential_coords; unsigned num_axial_coords; @@ -129,7 +162,7 @@ class DetectorCoordinateMap const double sigma; mutable std::default_random_engine generator; - mutable std::normal_distribution distribution; + mutable std::vector> distributions; static det_pos_to_coord_type read_detectormap_from_file_help( const std::string& crystal_map_name ); From dc6843f78abca0eb8bbddcf450cae654d31c8fb0 Mon Sep 17 00:00:00 2001 From: Markus Jehl Date: Wed, 27 Sep 2023 08:36:50 +0000 Subject: [PATCH 327/509] Also storing separate generators per thread. --- src/include/stir/DetectorCoordinateMap.h | 50 +++++++++--------------- 1 file changed, 19 insertions(+), 31 deletions(-) diff --git a/src/include/stir/DetectorCoordinateMap.h b/src/include/stir/DetectorCoordinateMap.h index e5d19f3bd0..66408e3f7e 100644 --- a/src/include/stir/DetectorCoordinateMap.h +++ b/src/include/stir/DetectorCoordinateMap.h @@ -27,7 +27,9 @@ #include #include #include +#ifdef STIR_OPENMP #include +#endif #include "stir/CartesianCoordinate3D.h" #include "stir/DetectionPosition.h" @@ -66,32 +68,16 @@ class DetectorCoordinateMap //! Constructor calls read_detectormap_from_file( filename ). DetectorCoordinateMap(const std::string& filename, double sigma = 0.0) : - sigma(sigma) - { - read_detectormap_from_file(filename); -#ifdef STIR_OPENMP - distributions.resize(omp_get_max_threads()); - for (auto &distribution : distributions) - distribution = std::normal_distribution(0.0, sigma); -#else - distributions.resize(1); - distributions[0] = std::normal_distribution(0.0, sigma); -#endif - } + DetectorCoordinateMap(sigma) + { + read_detectormap_from_file(filename); + } //! Constructor calls set_detector_map(coord_map). DetectorCoordinateMap(const det_pos_to_coord_type& coord_map, double sigma = 0.0) : - sigma(sigma) - { - set_detector_map( coord_map ); -#ifdef STIR_OPENMP - distributions.resize(omp_get_max_threads()); - for (auto &distribution : distributions) - distribution = std::normal_distribution(0.0, sigma); -#else - distributions.resize(1); - distributions[0] = std::normal_distribution(0.0, sigma); -#endif - } + DetectorCoordinateMap(sigma) + { + set_detector_map( coord_map ); + } //! Reads map from file and stores it. void read_detectormap_from_file( const std::string& filename ); @@ -102,20 +88,20 @@ class DetectorCoordinateMap stir::DetectionPosition<> get_det_pos_for_index(const stir::DetectionPosition<>& index) const { return input_index_to_det_pos.at(index); - } + } //! Returns a cartesian coordinate given a detection position. stir::CartesianCoordinate3D get_coordinate_for_det_pos( const stir::DetectionPosition<>& det_pos ) const { auto coord = det_pos_to_coord.at(det_pos); #ifdef STIR_OPENMP - auto thread_id = omp_get_thread_num(); + auto thread_id = omp_get_thread_num(); #else - auto thread_id = 0; + auto thread_id = 0; #endif - coord.x() += static_cast(distributions[thread_id](generator)); - coord.y() += static_cast(distributions[thread_id](generator)); - coord.z() += static_cast(distributions[thread_id](generator)); + coord.x() += static_cast(distributions[thread_id](generators[thread_id])); + coord.y() += static_cast(distributions[thread_id](generators[thread_id])); + coord.z() += static_cast(distributions[thread_id](generators[thread_id])); return coord; } //! Returns a cartesian coordinate given an (unsorted) index. @@ -141,10 +127,12 @@ class DetectorCoordinateMap sigma(sigma) { #ifdef STIR_OPENMP + generators.resize(omp_get_max_threads()); distributions.resize(omp_get_max_threads()); for (auto &distribution : distributions) distribution = std::normal_distribution(0.0, sigma); #else + generators.resize(1); distributions.resize(1); distributions[0] = std::normal_distribution(0.0, sigma); #endif @@ -161,7 +149,7 @@ class DetectorCoordinateMap stir::DetectionPosition<>> detection_position_map_given_cartesian_coord_keys_2_decimal; const double sigma; - mutable std::default_random_engine generator; + mutable std::vector generators; mutable std::vector> distributions; static det_pos_to_coord_type From 6f73ab03c734374eb3e94aa1678411d767343df6 Mon Sep 17 00:00:00 2001 From: Markus Jehl Date: Wed, 27 Sep 2023 13:00:14 +0000 Subject: [PATCH 328/509] Made constructor explicit and added special handling for case with sigma equals zero. --- src/include/stir/DetectorCoordinateMap.h | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/include/stir/DetectorCoordinateMap.h b/src/include/stir/DetectorCoordinateMap.h index 66408e3f7e..0aac7c2359 100644 --- a/src/include/stir/DetectorCoordinateMap.h +++ b/src/include/stir/DetectorCoordinateMap.h @@ -93,6 +93,9 @@ class DetectorCoordinateMap stir::CartesianCoordinate3D get_coordinate_for_det_pos( const stir::DetectionPosition<>& det_pos ) const { auto coord = det_pos_to_coord.at(det_pos); + if (sigma == 0.0) + return coord; + #ifdef STIR_OPENMP auto thread_id = omp_get_thread_num(); #else @@ -123,8 +126,8 @@ class DetectorCoordinateMap protected: - DetectorCoordinateMap(double sigma = 0.0) : - sigma(sigma) + explicit DetectorCoordinateMap(double sigma = 0.0) : + sigma(sigma) { #ifdef STIR_OPENMP generators.resize(omp_get_max_threads()); From b78e6049296164f30955c1ca011b0d7193682991 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sat, 30 Sep 2023 22:53:23 +0100 Subject: [PATCH 329/509] replace .zenodo.json with CITATION.cff (#1266) replace .zenodo.json with CITATION.cff Fixes #193 --- .appveyor.yml | 1 + .github/workflows/build-test.yml | 2 + .github/workflows/cffconvert.yml | 19 +++ .zenodo.json | 89 ------------ CITATION.cff | 229 +++++++++++++++++++++++++++++++ 5 files changed, 251 insertions(+), 89 deletions(-) create mode 100644 .github/workflows/cffconvert.yml delete mode 100644 .zenodo.json create mode 100644 CITATION.cff diff --git a/.appveyor.yml b/.appveyor.yml index 6125a65d9f..326e4515a6 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -3,6 +3,7 @@ shallow_clone: true skip_commits: files: - .github/**/* + - 'CITATION.cff' - '**/*.md' - '**/*.html' - '**/*.htm' diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index cbac5f14e8..2f1341c30f 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -5,6 +5,7 @@ on: branches: [ master ] paths-ignore: - '.appveyor.yml' + - 'CITATION.cff' - '**/*.md' - '**/*.html' - '**/*.htm' @@ -14,6 +15,7 @@ on: branches: [ master ] paths-ignore: - '.appveyor.yml' + - 'CITATION.cff' - '**/*.md' - '**/*.html' - '**/*.htm' diff --git a/.github/workflows/cffconvert.yml b/.github/workflows/cffconvert.yml new file mode 100644 index 0000000000..707a71c4b2 --- /dev/null +++ b/.github/workflows/cffconvert.yml @@ -0,0 +1,19 @@ +name: cffconvert + +on: + push: + paths: + - CITATION.cff + +jobs: + validate: + name: "validate" + runs-on: ubuntu-latest + steps: + - name: Check out a copy of the repository + uses: actions/checkout@v2 + + - name: Check whether the citation metadata from CITATION.cff is valid + uses: citation-file-format/cffconvert-github-action@2.0.0 + with: + args: "--validate" diff --git a/.zenodo.json b/.zenodo.json deleted file mode 100644 index ce2659bc99..0000000000 --- a/.zenodo.json +++ /dev/null @@ -1,89 +0,0 @@ -{ - "title": "STIR: Software for Tomographic Image Reconstruction", - "keywords": [ - "image-reconstruction", "pet", "spect", "medical-imaging", "open-source software"], - "related_identifiers": [ - {"identifier": "10.1088/0031-9155/57/4/867", "relation": "documents"}, - {"identifier": "10.1186/s40658-019-0248-9", "relation": "documents"}, - {"identifier": "10.3390/jimaging8060172", "relation": "documents"}, - {"identifier": "10.1109/NSSMIC.2018.8824341", "relation": "documents"}, - {"identifier": "10.1186/2197-7364-1-s1-a44", "relation": "documents"}, - {"identifier": "10.1109/nssmic.2013.6829258", "relation": "documents"}, - {"identifier": "10.1118/1.4816676", "relation": "documents"}, - {"identifier": "10.1007/s12149-011-0514-y", "relation": "documents"}, - {"identifier": "10.1016/j.compmedimag.2011.01.002", "relation": "documents"}, - {"identifier": "10.1088/1742-6596/317/1/012002", "relation": "documents"}, - {"identifier": "10.1109/nssmic.2008.4774198", "relation": "documents"}, - {"identifier": "10.1109/nssmic.2006.354345", "relation": "documents"}, - {"identifier": "10.1109/nssmic.2005.1596753", "relation": "documents"}, - {"identifier": "10.1109/nssmic.2004.1466455", "relation": "documents"}, - {"identifier": "10.1109/nssmic.2004.1466782", "relation": "documents"}, - {"identifier": "10.1109/nssmic.2002.1239610", "relation": "documents"}, - {"identifier": "10.1109/NSSMIC.2001.1008688", "relation": "documents"}, - {"identifier": "10.1088/0031-9155/45/8/325", "relation": "documents"}, - {"identifier": "10.1007/978-3-642-60125-5_50", "relation": "documents"}, - {"identifier": "10.1088/1361-6420/ab013f", "relation": "documents"}, - {"identifier": "10.1109/TRPMS.2018.2884176", "relation": "documents"}, - {"identifier": "10.1109/nssmic.2018.8824312", "relation": "documents"}, - {"identifier": "10.1109/NSS/MIC42101.2019.9059665", "relation": "documents"}], - "communities": [{"identifier": "ccp-petmr"},{"identifier": "synerbi"}], - "creators": [ - {"name": "Mustafovic, Sanida", "affiliation": "Imperal College London (UK)"}, - {"name": "Efthimiou, Nikos", "orcid": "0000-0003-1947-5033"}, - {"name": "Brown, Richard", "orcid": "0000-0001-6989-9200", "affiliation": "University College London"}, - {"name": "Tsoumpas, Charalampos"}, - {"name": "Twyman, Robert", "affiliation": "University College London"}, - {"name": "Deidda, Daniel", "orcid": "0000-0002-2766-4339"}, - {"name": "Borgeaud, Tim", "affiliation": "Hammersmith Imanet Ltd"}, - {"name": "Falcon, Carles"}, - {"name": "Khateri, Parisa", "affiliation": "ETH Zuerich"}, - {"name": "Wadhwa, Palak", "affiliation": "University of Leeds (UK)"}, - {"name": "Beisel, Tobias"}, - {"name": "Strugari, Matthew", "affiliation": "Dalhousie University (Canada)"}, - {"name": "Jacobson, Matthew"}, - {"name": "Gillman, Ashley", "orcid": "0000-0001-9130-1092"}, - {"name": "Zverovich, Alexey", "affiliation": "Brunel University (UK)"}, - {"name": "Emond, Elise", "affiliation": "University College London"}, - {"name": "Biguri, Ander", "orcid": "0000-0002-2636-3032"}, - {"name": "Fuster Marti, Berta", "affiliation": "University of Barcelona (Spain)"}, - {"name": "Labbe, Claire"}, - {"name": "Fischer, Jannis", "orcid": "0000-0002-8329-0220"}, - {"name": "Jehl, Markus", "affiliation":"Positrigo"}, - {"name": "Roethlisberger, Michael", "affiliation": "ETH Zuerich"}, - {"name": "Aguiar, Pablo"}, - {"name": "Brusaferri, Ludovica", "affiliation": "University College London"}, - {"name": "Bertolli, Ottavia", "affiliation": "University College London"}, - {"name": "Pasca, Edoardo", "orcid": "0000-0001-6957-2160"}, - {"name": "Niknejad, Tahereh"}, - {"name": "Dikaios, Nikos"}, - {"name": "Sadki, Mustapha", "affiliation": "Brunel University (UK)"}, - {"name": "Fardell, Gemma", "orcid": "0000-0003-2388-5211", "affiliation":"UK Research & Innovation"}, - {"name": "Kerrouche, Nacer", "affiliation": "Hammersmith Imanet Ltd"}, - {"name": "Ovtchinnikov, Evgueni"}, - {"name": "Ehrhardt, Matthias J.", "orcid": "0000-0001-8523-353X"}, - {"name": "Schmidtlein, C. Ross"}, - {"name": "Valente, Patrick", "affiliation": "Brunel University (UK)"}, - {"name": "Thomas, Benjamin", "orcid": "0000-0002-9784-1177"}, - {"name": "Schramm, Georg", "affiliation":"Katholieke Universiteit Leuven (Belgium)"}, - {"name": "Völgyes, David"}, - {"name": "Dinelle, Katie"}, - {"name": "Belluzzo, Damiano", "affiliation": "Hospedale San Raffaele Milano (Italy)"}, - {"name": "Ching, Daniel"}, - {"name": "Hague, Darren", "affiliation": "Brunel University (UK)"}, - {"name": "Tunnicliffe, Harry", "affiliation":"University of Leeds (UK)"}, - {"name": "Chen, Gefei"}, - {"name": "Dao, Viet Anh", "affiliation":"University of Leeds (UK)"}, - {"name": "Mikhaylova, Ekaterina", "affiliation":"Positrigo"}, - {"name": "da Costa-Luis, Casper O.", "orcid": "0000-0002-7211-1557"}, - {"name": "Porter, Sam D.", "affiliation": "University College London"}, - {"name": "Rashidnasab, Alaleh", "affiliation": "University College London"}, - {"name": "Whitehead, Alexander C.", "affiliation": "University College London"}, - {"name": "Gillen, Rebecca", "affiliation": "University College London"}, - {"name": "Vavrek, Jayson"} - {"name": "Kohr, Holger"}, - {"name": "Tsai, Yu-jung", "affiliation": "University College London"}, - {"name": "Kohr, Holger"}, - {"name": "tokkot"}, - {"name": "El Katib, Mahmoud"}, - {"name": "Thielemans, Kris", "orcid": "0000-0002-5514-199X", "affiliation": "University College London"}] -} diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 0000000000..70d589c8ce --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,229 @@ +# This CITATION.cff file was generated with cffinit. +# Visit https://bit.ly/cffinit to generate yours today! + +cff-version: 1.2.0 +title: STIR Software for Tomographic Image Reconstruction +message: >- + If you use this software, please cite it using the + metadata from this file. +type: software +authors: + - family-names: Mustafovic + given-names: Sanida + affiliation: Imperial College London (UK) + - family-names: Efthimiou + given-names: Nikos + orcid: 'https://orcid.org/0000-0003-1947-5033' + - family-names: Brown + given-names: Richard + orcid: 'https://orcid.org/0000-0001-6989-9200' + affiliation: University College London + - family-names: Tsoumpas + given-names: Charalampos + - family-names: Twyman + given-names: Robert + affiliation: University College London + - family-names: Deidda + given-names: Daniel + orcid: 'https://orcid.org/0000-0002-2766-4339' + affiliation: National Physics Laboratory (UK) + - family-names: Borgeaud + given-names: Tim + affiliation: Hammersmith Imanet Ltd + - family-names: Falcon + given-names: Carles + - family-names: Khateri + given-names: Parisa + affiliation: ETH Zuerich + - family-names: Wadhwa + given-names: Palak + affiliation: University of Leeds (UK) + - family-names: Beisel + given-names: Tobias + - family-names: Strugari + given-names: Matthew + affiliation: Dalhousie University (Canada) + - family-names: Jacobson + given-names: Matthew + - family-names: Gillman + given-names: Ashley + orcid: 'https://orcid.org/0000-0001-9130-1092' + affiliation: >- + Commonwealth Scientific and Industrial Research + Organisation, and University of Queensland + - family-names: Zverovich + given-names: Alexey + affiliation: Brunel University (UK) + - family-names: Emond + given-names: Elise + affiliation: University College London + - family-names: Biguri + given-names: Ander + orcid: 'https://orcid.org/0000-0002-2636-3032' + affiliation: University College London + - family-names: Fuster Marti + given-names: Berta + affiliation: University of Barcelona (Spain) + - family-names: Labbe + given-names: Claire + - family-names: Fischer + given-names: Jannis + orcid: 'https://orcid.org/0000-0002-8329-0220' + - family-names: Jehl + given-names: Markus + affiliation: Positrigo + - family-names: Roethlisberger + given-names: Michael + affiliation: ETH Zuerich + - family-names: Aguiar + given-names: Pablo + - family-names: Brusaferri + given-names: Ludovica + affiliation: University College London + - family-names: Bertolli + given-names: Ottavia + affiliation: University College London + - family-names: Pasca + given-names: Edoardo + affiliation: UK Research & Innovation + orcid: 'https://orcid.org/0000-0001-6957-2160' + - family-names: Niknejad + given-names: Tahereh + - family-names: Dikaios + given-names: Nikos + - family-names: Sadki + given-names: Mustapha + affiliation: Brunel University (UK) + - family-names: Fardell + given-names: Gemma + orcid: 'https://orcid.org/0000-0003-2388-5211' + affiliation: UK Research & Innovation + - family-names: Kerrouche + given-names: Nacer + affiliation: Hammersmith Imanet Ltd + - family-names: Ovtchinnikov + given-names: Evgueni + orcid: 'https://orcid.org/0000-0002-9359-6514' + affiliation: UK Research & Innovation + - family-names: Ehrhardt + given-names: Matthias J. + orcid: 'https://orcid.org/0000-0001-8523-353X' + - family-names: Schmidtlein + given-names: C Ross + - family-names: Valente + given-names: Patrick + affiliation: Brunel University (UK) + - family-names: Thomas + given-names: Benjamin + orcid: 'https://orcid.org/0000-0002-9784-1177' + affiliation: University College London + - family-names: Schramm + given-names: Georg + affiliation: Katholieke Universiteit Leuven (Belgium) + - family-names: Völgyes + given-names: David + - family-names: Dinelle + given-names: Katie + - family-names: Belluzzo + given-names: Damiano + affiliation: Hospedale San Raffaele Milano (Italy) + - family-names: Ching + given-names: Daniel + - family-names: Hague + given-names: Darren + affiliation: Brunel University (UK) + - family-names: Tunnicliffe + given-names: Harry + affiliation: University of Leeds (UK) + - family-names: Chen + given-names: Gefei + - family-names: Dao + given-names: Viet Anh + affiliation: University of Leeds (UK) + - family-names: Mikhaylova + given-names: Ekaterina + affiliation: Positrigo + - family-names: da Costa-Luis + given-names: Casper O. + orcid: 'https://orcid.org/0000-0002-7211-1557' + - family-names: Porter + given-names: Sam David + affiliation: University College London, National Physics Laboratory (UK) + - family-names: Rashidnasab + given-names: Alaleh + affiliation: University College London + - family-names: Whitehead + given-names: Alexander C. + affiliation: University College London + - family-names: Gillen + given-names: Rebecca + affiliation: University College London + - family-names: Vavrek + given-names: Jayson + - family-names: Kohr + given-names: Holger + - family-names: Tsai + given-names: Yu-jung + affiliation: University College London + - name: tokkot + - family-names: El Katib + given-names: Mahmoud + - family-names: Thielemans + given-names: Kris + orcid: 'https://orcid.org/0000-0002-5514-199X' + affiliation: University College London +identifiers: + - type: doi + value: 10.1088/0031-9155/57/4/867 + - type: doi + value: 10.1186/s40658-019-0248-9 + - type: doi + value: 10.3390/jimaging8060172 + - type: doi + value: 10.1109/NSSMIC.2018.8824341 + - type: doi + value: 10.1186/2197-7364-1-s1-a44 + - type: doi + value: 10.1109/nssmic.2013.6829258 + - type: doi + value: 10.1118/1.4816676 + - type: doi + value: 10.1007/s12149-011-0514-y + - type: doi + value: 10.1016/j.compmedimag.2011.01.002 + - type: doi + value: 10.1088/1742-6596/317/1/012002 + - type: doi + value: 10.1109/nssmic.2008.4774198 + - type: doi + value: 10.1109/nssmic.2006.354345 + - type: doi + value: 10.1109/nssmic.2005.1596753 + - type: doi + value: 10.1109/nssmic.2004.1466455 + - type: doi + value: 10.1109/nssmic.2004.1466782 + - type: doi + value: 10.1109/nssmic.2002.1239610 + - type: doi + value: 10.1109/NSSMIC.2001.1008688 + - type: doi + value: 10.1088/0031-9155/45/8/325 + - type: doi + value: 10.1007/978-3-642-60125-5_50 + - type: doi + value: 10.1088/1361-6420/ab013f + - type: doi + value: 10.1109/TRPMS.2018.2884176 + - type: doi + value: 10.1109/nssmic.2018.8824312 + - type: doi + value: 10.1109/NSS/MIC42101.2019.9059665 +repository-code: 'https://github.com/UCL/STIR' +keywords: + - pet + - spect + - medical imaging + - image reconstruction + - open-source software +license: Apache-2.0 From a6efef6fe971c2e624975925dfc5c561e4c74d58 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sat, 30 Sep 2023 23:08:11 +0100 Subject: [PATCH 330/509] update badges in README.md --- README.md | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index c5213623ae..65096baa2a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,10 @@ # STIR: Software for Tomographic Image Reconstruction. +[![GitHub Actions status](https://github.com/UCL/STIR/actions/workflows/build-test.yml/badge.svg)](https://github.com/UCL/STIR/actions/workflows/build-test.yml) +[![Appveyor Build status](https://ci.appveyor.com/api/projects/status/ga9xd1vsy0ik1soq/branch/master?svg=true)](https://ci.appveyor.com/project/KrisThielemans/stir/branch/master) +[![Codacy Badge](https://app.codacy.com/project/badge/Grade/c561421aa1af41e4a9ef779003f6fff0)](https://app.codacy.com/gh/UCL/STIR/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade) +[![Zenodo DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.6604468.svg)](https://doi.org/10.5281/zenodo.6604468) + STIR is Open Source software for use in tomographic imaging. Its aim is to provide a Multi-Platform Object-Oriented framework for all data manipulations in tomographic imaging. Currently, the emphasis is on @@ -17,10 +22,3 @@ information. This software is distributed under an open source license, see [LICENSE.txt](LICENSE.txt) for details. -## Build and test status of the master branch -- Travis (tests Linux and MacOS) -[![Travis Build Status](https://travis-ci.org/UCL/STIR.svg?branch=master)](https://travis-ci.org/UCL/STIR) - -- Appveyor (tests Windows) -[![Appveyor Build status](https://ci.appveyor.com/api/projects/status/ga9xd1vsy0ik1soq/branch/master?svg=true)](https://ci.appveyor.com/project/KrisThielemans/stir/branch/master) - From 77c7a2f6e0b016453c8e3ca766394aecb0f27545 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sun, 1 Oct 2023 00:26:21 +0100 Subject: [PATCH 331/509] correct README regarding default value of pardir --- examples/Siemens-mMR/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/Siemens-mMR/README.md b/examples/Siemens-mMR/README.md index 85dc711cde..40621c0196 100644 --- a/examples/Siemens-mMR/README.md +++ b/examples/Siemens-mMR/README.md @@ -16,9 +16,9 @@ The scripts don't do proper error handling. If they suddenly stop without diagno and look for a log file. The scripts need template files from this directory. By default -they assume they are located in `~/devel/STIR/examples/Siemens-mMR`. -If that is not the case, you can set the pardir variable as above, or -by doing for instance +they assume they are located in the `Siemens-mMR` directory of the STIR +examples folder, as obtained by `stir_config --examples-dir`. +This can be overridden by doing for instance ```sh pardir=~/STIR/examples/Siemens-mMR From 1eac3fbfd52a0a8bd912931f8251f5eef419d2b2 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sun, 1 Oct 2023 00:29:23 +0100 Subject: [PATCH 332/509] Check for := when parsing Interfile headers we checked only for :, resulting that keywords that contain a : were not possible. --- documentation/release_5.2.htm | 1 + src/buildblock/KeyParser.cxx | 8 ++++++-- src/include/stir/KeyParser.h | 12 ++++++------ 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/documentation/release_5.2.htm b/documentation/release_5.2.htm index 4f4d2eab46..cf8075eb27 100644 --- a/documentation/release_5.2.htm +++ b/documentation/release_5.2.htm @@ -41,6 +41,7 @@

    Changed functionality

    hard-coded variables for the Siemens mMR have been removed. Further testing of this functionality is still required however.
    PR #1182. +
  7. Interfile header parsing now correctly identifies keywords that contain a colon by checking for :=./li>

    New functionality

    diff --git a/src/buildblock/KeyParser.cxx b/src/buildblock/KeyParser.cxx index da19402a71..471cf46f2d 100644 --- a/src/buildblock/KeyParser.cxx +++ b/src/buildblock/KeyParser.cxx @@ -309,8 +309,12 @@ string KeyParser::get_keyword(const string& line) const { // keyword stops at either := or an index [] - // TODO should check that = follows : to allow keywords with colons in there - const string::size_type eok = line.find_first_of(":[",0); + string::size_type eok = line.find_first_of(":[",0); + // check that = follows : to allow keywords containing colons + while (line[eok] == ':' && eok+1 < line.size() && line[eok+1] != '=') + { + eok = line.find_first_of(":[", eok+1); + } return line.substr(0,eok); } diff --git a/src/include/stir/KeyParser.h b/src/include/stir/KeyParser.h index 1bc022303c..175c33765f 100644 --- a/src/include/stir/KeyParser.h +++ b/src/include/stir/KeyParser.h @@ -110,7 +110,9 @@ public : \brief A class to parse Interfile headers Currently, Interfile 3.3 parsing rules are hard-coded, with - some extensions. + some extensions. Note that some functions such as `get_keyword()` are `virtual` + such that a derived class could use non-Interfile parsing (but this might need + more work). KeyParser reads input line by line and parses each line separately. It allows for '\\r' at the end of the line (as in files @@ -349,16 +351,14 @@ protected : //! convert 'rough' keyword into a standardised form - /*! \todo Implementation note: this function is non-static such that it can - be overloaded. Probably a template with a function object would be - better. */ + /*! Calls standardise_interfile_keyword(). +. */ virtual std::string standardise_keyword(const std::string& keyword) const; //! gets a keyword from a string - /*! Implementation note: this function is non-static as it uses - standardise_keyword(). + /*! Find `:=` or `[`. Note that the returned keyword is not standardised yet. */ virtual std::string get_keyword(const std::string&) const; From 9fd6729a0385709a42b4dfcb699c8033d9b58dae Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sun, 1 Oct 2023 00:58:20 +0100 Subject: [PATCH 333/509] cope better with Siemens Interfile date/time keywords make a function that ignores keyword for all (?) time zones --- src/IO/InterfileHeaderSiemens.cxx | 53 +++++++++++++------- src/include/stir/IO/InterfileHeaderSiemens.h | 13 +++++ 2 files changed, 49 insertions(+), 17 deletions(-) diff --git a/src/IO/InterfileHeaderSiemens.cxx b/src/IO/InterfileHeaderSiemens.cxx index ebc4083a4c..1c7e37bb01 100644 --- a/src/IO/InterfileHeaderSiemens.cxx +++ b/src/IO/InterfileHeaderSiemens.cxx @@ -10,7 +10,7 @@ /*! \file \ingroup InterfileIO - \brief implementations for the stir::InterfileHeaderSiemens class + \brief implementations for the stir::InterfileHeaderSiemens classes \author Kris Thielemans \author PARAPET project @@ -163,6 +163,36 @@ void InterfileHeaderSiemens::set_type_of_data() } +void +InterfileHeaderSiemens::ignore_Siemens_date_and_time_keys(const std::string& keyword) +{ + ignore_key(keyword + " date (yyyy:mm:dd)"); + ignore_key(keyword + " time (hh:mm:ss GMT+00:00)"); + ignore_key(keyword + " time (hh:mm:ss GMT+01:00)"); + ignore_key(keyword + " time (hh:mm:ss GMT+02:00)"); + ignore_key(keyword + " time (hh:mm:ss GMT+03:00)"); + ignore_key(keyword + " time (hh:mm:ss GMT+04:00)"); + ignore_key(keyword + " time (hh:mm:ss GMT+05:00)"); + ignore_key(keyword + " time (hh:mm:ss GMT+06:00)"); + ignore_key(keyword + " time (hh:mm:ss GMT+07:00)"); + ignore_key(keyword + " time (hh:mm:ss GMT+08:00)"); + ignore_key(keyword + " time (hh:mm:ss GMT+09:00)"); + ignore_key(keyword + " time (hh:mm:ss GMT+10:00)"); + ignore_key(keyword + " time (hh:mm:ss GMT+11:00)"); + ignore_key(keyword + " time (hh:mm:ss GMT-01:00)"); + ignore_key(keyword + " time (hh:mm:ss GMT-02:00)"); + ignore_key(keyword + " time (hh:mm:ss GMT-03:00)"); + ignore_key(keyword + " time (hh:mm:ss GMT-04:00)"); + ignore_key(keyword + " time (hh:mm:ss GMT-05:00)"); + ignore_key(keyword + " time (hh:mm:ss GMT-06:00)"); + ignore_key(keyword + " time (hh:mm:ss GMT-07:00)"); + ignore_key(keyword + " time (hh:mm:ss GMT-08:00)"); + ignore_key(keyword + " time (hh:mm:ss GMT-09:00)"); + ignore_key(keyword + " time (hh:mm:ss GMT-10:00)"); + ignore_key(keyword + " time (hh:mm:ss GMT-11:00)"); +} + + /**********************************************************************/ InterfileRawDataHeaderSiemens::InterfileRawDataHeaderSiemens() @@ -205,13 +235,11 @@ InterfileRawDataHeaderSiemens::InterfileRawDataHeaderSiemens() ignore_key("%listmode header file"); ignore_key("%listmode data file"); ignore_key("%compressor version"); - ignore_key("%study date (yyyy"); - ignore_key("%study time (hh"); + ignore_Siemens_date_and_time_keys("%study"); ignore_key("isotope gamma halflife (sec)"); ignore_key("isotope branching factor"); ignore_key("radiopharmaceutical"); - ignore_key("%tracer injection date (yyyy"); - ignore_key("%tracer injection time (hh"); + ignore_Siemens_date_and_time_keys("%tracer injection"); ignore_key("relative time of tracer injection (sec)"); ignore_key("tracer activity at time of injection (bq)"); ignore_key("injected volume (ml)"); @@ -223,8 +251,7 @@ InterfileRawDataHeaderSiemens::InterfileRawDataHeaderSiemens() ignore_key("method of scatter correction"); ignore_key("%method of random correction"); ignore_key("%decay correction"); - ignore_key("%decay correction reference date (yyyy"); - ignore_key("%decay correction reference time (hh"); + ignore_Siemens_date_and_time_keys("%decay correction reference"); ignore_key("decay correction factor"); ignore_key("scatter fraction (%)"); ignore_key("scan data type description"); @@ -542,11 +569,7 @@ InterfileNormHeaderSiemens::InterfileNormHeaderSiemens() is_arccorrected = false; // norm data is never arc-corrected ignore_key("data description"); - ignore_key("%expiration date (yyyy:mm:dd)"); - ignore_key("%expiration time (hh:mm:ss GMT-05:00)"); - // currently keywords are truncated at : - ignore_key("%expiration time (hh"); - ignore_key("%expiration date (yyyy"); + ignore_Siemens_date_and_time_keys("%expiration"); ignore_key("%raw normalization scans description"); // remove some standard keys, which Siemens has replaced with similar names @@ -573,11 +596,7 @@ InterfileNormHeaderSiemens::InterfileNormHeaderSiemens() ignore_key("%global scanner calibration factor"); add_key("%scanner quantification factor (Bq*s/ECAT counts)",& calib_factor); add_key("%cross calibration factor",& cross_calib_factor); - ignore_key("%calibration date (yyyy:mm:dd)"); - ignore_key("%calibration time (hh:mm:ss GMT+00:00)"); - // currently keywords are truncated at : - ignore_key("%calibration time (hh"); - ignore_key("%calibration date (yyyy"); + ignore_Siemens_date_and_time_keys("%calibration"); // isotope things are vectorised in norm files and not in other raw data, so we could // fix that, but as we are not interested in it anyway (tends to be Ge-68), let's just ignore it. diff --git a/src/include/stir/IO/InterfileHeaderSiemens.h b/src/include/stir/IO/InterfileHeaderSiemens.h index 4c5b1dd7df..6b2c4c08c3 100644 --- a/src/include/stir/IO/InterfileHeaderSiemens.h +++ b/src/include/stir/IO/InterfileHeaderSiemens.h @@ -52,6 +52,19 @@ class InterfileHeaderSiemens : public InterfileHeader protected: // Returns false if OK, true if not. virtual bool post_processing(); + //! ignore multiple GMT times + /*! + Siemens uses keywords like + \verbatim + %study date (yyyy:mm:dd") := ... + %study time (hh:mm:ss GMT+00:00) := ... + \endvarbatim + You can ignore this for all (?) time zones by using + \code + ignore_Siemens_date_and_time_keys("%study"); + \endcode + */ + void ignore_Siemens_date_and_time_keys(const std::string& keyword); private: From 5bdd30068f28f02b31d3574e0532d27104990161 Mon Sep 17 00:00:00 2001 From: Markus Jehl Date: Mon, 2 Oct 2023 09:08:14 +0000 Subject: [PATCH 334/509] Exposed listmode reconstruction in SWIG and fixed an issue with cache file naming. --- ...lForMeanAndListModeDataWithProjMatrixByBin.cxx | 1 + src/swig/stir.i | 2 ++ src/swig/stir_objectivefunctions.i | 15 ++++++++++++++- 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx index 2f8d11b453..e0240b2f7d 100644 --- a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx +++ b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx @@ -445,6 +445,7 @@ PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBinnum_cache_files = 0; if(this->cache_lm_file) { info("Listmode reconstruction: Creating cache...", 2); diff --git a/src/swig/stir.i b/src/swig/stir.i index 41a9fbe401..301332d5f6 100644 --- a/src/swig/stir.i +++ b/src/swig/stir.i @@ -111,6 +111,8 @@ #include "stir/HUToMuImageProcessor.h" #include "stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.h" +#include "stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeData.h" +#include "stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.h" #include "stir/OSMAPOSL/OSMAPOSLReconstruction.h" #include "stir/OSSPS/OSSPSReconstruction.h" #include "stir/recon_buildblock/ForwardProjectorByBinUsingProjMatrixByBin.h" diff --git a/src/swig/stir_objectivefunctions.i b/src/swig/stir_objectivefunctions.i index 2f5ccd8b81..7876271f1a 100644 --- a/src/swig/stir_objectivefunctions.i +++ b/src/swig/stir_objectivefunctions.i @@ -32,11 +32,16 @@ %shared_ptr(stir::GeneralisedObjectiveFunction); %shared_ptr(stir::PoissonLogLikelihoodWithLinearModelForMean); +%shared_ptr(stir::PoissonLogLikelihoodWithLinearModelForMeanAndListModeData); %shared_ptr(stir::RegisteredParsingObject, stir::GeneralisedObjectiveFunction, stir::PoissonLogLikelihoodWithLinearModelForMean >); +%shared_ptr(stir::RegisteredParsingObject, + stir::GeneralisedObjectiveFunction, + stir::PoissonLogLikelihoodWithLinearModelForMeanAndListModeData >); %shared_ptr(stir::PoissonLogLikelihoodWithLinearModelForMeanAndProjData); +%shared_ptr(stir::PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin); %shared_ptr(stir::SqrtHessianRowSum); @@ -48,6 +53,8 @@ %include "stir/recon_buildblock/GeneralisedObjectiveFunction.h" %include "stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMean.h" %include "stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.h" +%include "stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeData.h" +%include "stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.h" %include "stir/recon_buildblock/SqrtHessianRowSum.h" @@ -57,6 +64,7 @@ %template (GeneralisedObjectiveFunction3DFloat) stir::GeneralisedObjectiveFunction; //%template () stir::GeneralisedObjectiveFunction; %template (PoissonLogLikelihoodWithLinearModelForMean3DFloat) stir::PoissonLogLikelihoodWithLinearModelForMean; +%template (PoissonLogLikelihoodWithLinearModelForMeanAndListModeData3DFloat) stir::PoissonLogLikelihoodWithLinearModelForMeanAndListModeData; // TODO do we really need this name? // Without it we don't see the parsing functions in python... @@ -65,14 +73,19 @@ stir::GeneralisedObjectiveFunction, stir::PoissonLogLikelihoodWithLinearModelForMean >; +%template(RPPoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin3DFloat) stir::RegisteredParsingObject, + stir::GeneralisedObjectiveFunction, + stir::PoissonLogLikelihoodWithLinearModelForMeanAndListModeData >; + %template (PoissonLogLikelihoodWithLinearModelForMeanAndProjData3DFloat) stir::PoissonLogLikelihoodWithLinearModelForMeanAndProjData; +%template (PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin3DFloat) stir::PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin; %inline %{ template stir::PoissonLogLikelihoodWithLinearModelForMeanAndProjData * ToPoissonLogLikelihoodWithLinearModelForMeanAndProjData(stir::GeneralisedObjectiveFunction *b) { return dynamic_cast*>(b); -} + } %} %template(ToPoissonLogLikelihoodWithLinearModelForMeanAndProjData3DFloat) ToPoissonLogLikelihoodWithLinearModelForMeanAndProjData; From 48a0be0cf79c6273817709fc82234086e0021d74 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sun, 8 Oct 2023 18:46:01 +0100 Subject: [PATCH 335/509] [GHA] re-enable GATE ROOT test Fixes #1242 --- .github/workflows/build-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 2f1341c30f..2f5a18405b 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -320,7 +320,7 @@ jobs: ./run_scatter_tests.sh ./run_test_zoom_image.sh ./run_ML_norm_tests.sh - if [[ $BUILD_FLAGS == *"DDISABLE_CERN_ROOT=0"* ]]; then ./run_root_GATE.sh; fi + if test "${{matrix.ROOT}}XX" == "ONXX"; then ./run_root_GATE.sh; fi ./run_tests_modelling.sh cd ${GITHUB_WORKSPACE}/recon_test_pack/SPECT ./run_SPECT_tests.sh From bcf77b3e373a52626c7a8293e64115ee258925b9 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sat, 14 Oct 2023 16:31:01 +0100 Subject: [PATCH 336/509] implemented *Indices classes for projdata indexing --- src/include/stir/Bin.h | 16 ++---- src/include/stir/Bin.inl | 26 ++------- src/include/stir/ProjData.h | 17 +++++- src/include/stir/ProjData.inl | 26 ++++++++- src/include/stir/SegmentIndices.h | 63 +++++++++++++++++++++ src/include/stir/SegmentIndices.inl | 58 ++++++++++++++++++++ src/include/stir/SinogramIndices.h | 68 +++++++++++++++++++++++ src/include/stir/SinogramIndices.inl | 65 ++++++++++++++++++++++ src/include/stir/ViewSegmentNumbers.h | 58 ++++---------------- src/include/stir/ViewSegmentNumbers.inl | 73 ------------------------- src/include/stir/ViewgramIndices.h | 64 ++++++++++++++++++++++ src/include/stir/ViewgramIndices.inl | 64 ++++++++++++++++++++++ 12 files changed, 446 insertions(+), 152 deletions(-) create mode 100644 src/include/stir/SegmentIndices.h create mode 100644 src/include/stir/SegmentIndices.inl create mode 100644 src/include/stir/SinogramIndices.h create mode 100644 src/include/stir/SinogramIndices.inl delete mode 100644 src/include/stir/ViewSegmentNumbers.inl create mode 100644 src/include/stir/ViewgramIndices.h create mode 100644 src/include/stir/ViewgramIndices.inl diff --git a/src/include/stir/Bin.h b/src/include/stir/Bin.h index 32deba80e1..9a087c5546 100644 --- a/src/include/stir/Bin.h +++ b/src/include/stir/Bin.h @@ -17,6 +17,7 @@ /* Copyright (C) 2000 PARAPET partners Copyright (C) 2000- 2009, Hammersmith Imanet Ltd + Copyright (C) 2023, University College London This file is part of STIR. SPDX-License-Identifier: Apache-2.0 AND License-ref-PARAPET-license @@ -27,7 +28,7 @@ #define __stir_Bin_H__ -#include "stir/common.h" +#include "stir/ViewgramIndices.h" START_NAMESPACE_STIR @@ -39,10 +40,11 @@ START_NAMESPACE_STIR handling list mode data. */ -class Bin +class Bin : public ViewgramIndices { + typedef ViewgramIndices base_type; public: - //! default constructor + //! default constructor (leaves most members uninitialised) inline Bin(); //! A constructor : constructs a bin with value (defaulting to 0) @@ -51,19 +53,13 @@ class Bin //!get axial position number inline int axial_pos_num()const; - //! get segmnet number - inline int segment_num()const; //! get tangential position number inline int tangential_pos_num() const; - //! get view number - inline int view_num() const; //! get time-frame number (1-based) inline int time_frame_num() const; inline int& axial_pos_num(); - inline int& segment_num(); inline int& tangential_pos_num(); - inline int& view_num(); inline int& time_frame_num(); @@ -91,8 +87,6 @@ class Bin private : // shared_ptr proj_data_info_ptr; - int segment; - int view; int axial_pos; int tangential_pos; float bin_value; diff --git a/src/include/stir/Bin.inl b/src/include/stir/Bin.inl index 663f9853f6..7b50302041 100644 --- a/src/include/stir/Bin.inl +++ b/src/include/stir/Bin.inl @@ -15,6 +15,7 @@ /* Copyright (C) 2000 PARAPET partners Copyright (C) 2000- 2009, Hammersmith Imanet Ltd + Copyright (C) 2023, University College London This file is part of STIR. SPDX-License-Identifier: Apache-2.0 AND License-ref-PARAPET-license @@ -25,31 +26,24 @@ START_NAMESPACE_STIR Bin::Bin() + : time_frame(1) {} Bin::Bin(int segment_num,int view_num, int axial_pos_num,int tangential_pos_num,float bin_value) - :segment(segment_num),view(view_num), - axial_pos(axial_pos_num),tangential_pos(tangential_pos_num),bin_value(bin_value),time_frame(1) - {} + : ViewgramIndices(view_num, segment_num), + axial_pos(axial_pos_num),tangential_pos(tangential_pos_num),bin_value(bin_value),time_frame(1) +{} int Bin:: axial_pos_num()const { return axial_pos;} -int - Bin::segment_num()const -{return segment;} - int Bin::tangential_pos_num() const { return tangential_pos;} -int - Bin::view_num() const -{ return view;} - int Bin:: time_frame_num() const {return time_frame;} @@ -58,18 +52,10 @@ int& Bin::axial_pos_num() { return axial_pos;} -int& - Bin:: segment_num() -{return segment;} - int& Bin::tangential_pos_num() { return tangential_pos;} -int& - Bin:: view_num() -{ return view;} - int& Bin:: time_frame_num() {return time_frame;} @@ -109,7 +95,7 @@ bool Bin::operator==(const Bin& bin2) const { return - segment == bin2.segment && view == bin2.view && + base_type::operator==(bin2) && axial_pos == bin2.axial_pos && tangential_pos == bin2.tangential_pos && bin_value == bin2.bin_value; } diff --git a/src/include/stir/ProjData.h b/src/include/stir/ProjData.h index c7a9bcee9e..722c045acd 100644 --- a/src/include/stir/ProjData.h +++ b/src/include/stir/ProjData.h @@ -1,7 +1,7 @@ /* Copyright (C) 2000 PARAPET partners Copyright (C) 2000- 2012, Hammersmith Imanet Ltd - Copyright (C) 2013, 2015-2017, 2020, University College London + Copyright (C) 2013, 2015-2017, 2020, 2023 University College London This file is part of STIR. SPDX-License-Identifier: Apache-2.0 AND License-ref-PARAPET-license @@ -28,6 +28,9 @@ #include "stir/Succeeded.h" #include "stir/SegmentBySinogram.h" #include "stir/SegmentByView.h" +#include "stir/SegmentIndices.h" +#include "stir/ViewgramIndices.h" +#include "stir/SinogramIndices.h" //#include #include "stir/ExamData.h" @@ -118,12 +121,17 @@ class ProjData : public ExamData //! Get viewgram virtual Viewgram get_viewgram(const int view, const int segment_num,const bool make_num_tangential_poss_odd = false) const=0; + inline Viewgram + get_viewgram(const ViewgramIndices&); //! Set viewgram virtual Succeeded set_viewgram(const Viewgram&) = 0; //! Get sinogram virtual Sinogram get_sinogram(const int ax_pos_num, const int segment_num,const bool make_num_tangential_poss_odd = false) const=0; + //! Get sinogram + inline Sinogram + get_sinogram(const SinogramIndices&); //! Set sinogram virtual Succeeded set_sinogram(const Sinogram&) = 0; @@ -154,9 +162,16 @@ class ProjData : public ExamData //! Get segment by sinogram virtual SegmentBySinogram get_segment_by_sinogram(const int segment_num) const; + + inline SegmentBySinogram + get_segment_by_sinogram(const SegmentIndices&) const; + //! Get segment by view virtual SegmentByView get_segment_by_view(const int segment_num) const; + inline SegmentByView + get_segment_by_view(const SegmentIndices&) const; + //! Set segment by sinogram virtual Succeeded set_segment(const SegmentBySinogram&); diff --git a/src/include/stir/ProjData.inl b/src/include/stir/ProjData.inl index 7c13787049..168ede9085 100644 --- a/src/include/stir/ProjData.inl +++ b/src/include/stir/ProjData.inl @@ -11,7 +11,7 @@ /* Copyright (C) 2000 PARAPET partners Copyright (C) 2000-2009, Hammersmith Imanet Ltd - Copyright (C) 2013, 2015 University College London + Copyright (C) 2013, 2015, 2023 University College London Copyright (C) 2016, University of Hull This file is part of STIR. @@ -24,6 +24,30 @@ START_NAMESPACE_STIR +SegmentBySinogram +ProjData::get_segment_by_sinogram(const SegmentIndices& si) const +{ + return this->get_segment_by_sinogram(si.segment_num()); +} + +SegmentByView +ProjData::get_segment_by_view(const SegmentIndices& si) const +{ + return this->get_segment_by_view(si.segment_num()); +} + +Viewgram +ProjData::get_viewgram(const ViewgramIndices& vi) +{ + return this->get_viewgram(vi.view_num(), vi.segment_num()); +} + +Sinogram +ProjData::get_sinogram(const SinogramIndices& vi) +{ + return this->get_sinogram(vi.axial_pos_num(), vi.segment_num()); +} + shared_ptr ProjData::get_proj_data_info_sptr() const { diff --git a/src/include/stir/SegmentIndices.h b/src/include/stir/SegmentIndices.h new file mode 100644 index 0000000000..20beec19fd --- /dev/null +++ b/src/include/stir/SegmentIndices.h @@ -0,0 +1,63 @@ +// +// +/*! + \file + \ingroup projdata + + \brief Definition of class stir::SegmentIndices + + \author Kris Thielemans + +*/ +/* + Copyright (C) 2023, University College London + This file is part of STIR. + + SPDX-License-Identifier: Apache-2.0 + + See STIR/LICENSE.txt for details +*/ + +#ifndef __stir_SegmentIndices_h__ +#define __stir_SegmentIndices_h__ + +#include "stir/common.h" + +START_NAMESPACE_STIR + +/*! + \brief A very simple class to store segment numbers and any other + indices that define a segment + \ingroup projdata +*/ +class SegmentIndices +{ +public: + //! constructor segment number as arguments + explicit inline SegmentIndices(const int segment_num = 0); + + //! get segment number for const objects + inline int segment_num() const; + + //! get reference to segment number + inline int& segment_num(); + + //! comparison operator, only useful for sorting + /*! In future, there will be multiple indices, and order will then be based as in + (0,1) < (0,-1) < (1,1) ... + */ + inline bool operator<(const SegmentIndices& other) const; + + //! test for equality + inline bool operator==(const SegmentIndices& other) const; + inline bool operator!=(const SegmentIndices& other) const; + +private: + int _segment; +}; + +END_NAMESPACE_STIR + +#include "stir/SegmentIndices.inl" + +#endif diff --git a/src/include/stir/SegmentIndices.inl b/src/include/stir/SegmentIndices.inl new file mode 100644 index 0000000000..4e9989464c --- /dev/null +++ b/src/include/stir/SegmentIndices.inl @@ -0,0 +1,58 @@ +/*! + \file + \ingroup projdata + + \brief inline implementations for class stir::SegmentIndices + + \author Kris Thielemans + \author Sanida Mustafovic + \author PARAPET project + + +*/ +/* + Copyright (C) 2000 PARAPET partners + Copyright (C) 2000- 2009, Hammersmith Imanet Ltd + This file is part of STIR. + + SPDX-License-Identifier: Apache-2.0 AND License-ref-PARAPET-license + + See STIR/LICENSE.txt for details +*/ + +START_NAMESPACE_STIR + +SegmentIndices::SegmentIndices(const int segment_num) + : _segment(segment_num) +{} + +int +SegmentIndices::segment_num() const +{ + return _segment; +} + +int& +SegmentIndices::segment_num() +{ + return _segment; +} + +bool +SegmentIndices::operator<(const SegmentIndices& other) const +{ + return (_segment < other._segment); +} + +bool +SegmentIndices::operator==(const SegmentIndices& other) const +{ + return (_segment == other._segment); +} + +bool +SegmentIndices::operator!=(const SegmentIndices& other) const +{ + return !(*this == other); +} +END_NAMESPACE_STIR diff --git a/src/include/stir/SinogramIndices.h b/src/include/stir/SinogramIndices.h new file mode 100644 index 0000000000..62c84043bd --- /dev/null +++ b/src/include/stir/SinogramIndices.h @@ -0,0 +1,68 @@ +// +// +/*! + \file + \ingroup projdata + + \brief Definition of class stir::SinogramIndices + + \author Kris Thielemans + +*/ +/* + Copyright (C) 2023, University College London + This file is part of STIR. + + SPDX-License-Identifier: Apache-2.0 + + See STIR/LICENSE.txt for details +*/ + + + +#ifndef __stir_SinogramIndices_h__ +#define __stir_SinogramIndices_h__ + +#include "stir/SegmentIndices.h" + +START_NAMESPACE_STIR + +/*! + \brief A very simple class to store all dincies to get a (2D) Sinogram + \ingroup projdata +*/ +class SinogramIndices : public SegmentIndices +{ + typedef SegmentIndices base_type; +public: + + //! an empty constructor (sets everything to 0) + inline SinogramIndices(); + //! constructor taking view and segment number as arguments + inline SinogramIndices( const int axial_pos_num,const int segment_num); + + //! get view number for const objects + inline int axial_pos_num() const; + + //! get reference to view number + inline int& axial_pos_num(); + + + //! comparison operator, only useful for sorting + /*! order : (0,1) < (0,-1) < (1,1) ...*/ + inline bool operator<(const SinogramIndices& other) const; + + //! test for equality + inline bool operator==(const SinogramIndices& other) const; + inline bool operator!=(const SinogramIndices& other) const; + +private: + int _axial_pos; + +}; + +END_NAMESPACE_STIR + +#include "stir/SinogramIndices.inl" + +#endif diff --git a/src/include/stir/SinogramIndices.inl b/src/include/stir/SinogramIndices.inl new file mode 100644 index 0000000000..df98ca0d62 --- /dev/null +++ b/src/include/stir/SinogramIndices.inl @@ -0,0 +1,65 @@ +/*! + \file + \ingroup projdata + + \brief inline implementations for class stir::SinogramIndices + + \author Kris Thielemans + \author Sanida Mustafovic + \author PARAPET project + + +*/ +/* + Copyright (C) 2000 PARAPET partners + Copyright (C) 2000- 2009, Hammersmith Imanet Ltd + This file is part of STIR. + + SPDX-License-Identifier: Apache-2.0 AND License-ref-PARAPET-license + + See STIR/LICENSE.txt for details +*/ + + +START_NAMESPACE_STIR + +SinogramIndices::SinogramIndices() +:SegmentIndices(),_axial_pos(0) + {} + +SinogramIndices::SinogramIndices( const int axial_pos_num,const int segment_num) + : SegmentIndices(segment_num),_axial_pos(axial_pos_num) + {} + +int +SinogramIndices::axial_pos_num() const +{ + return _axial_pos;} + + +int& +SinogramIndices::axial_pos_num() +{ return _axial_pos;} + +bool +SinogramIndices:: +operator<(const SinogramIndices& other) const +{ + return (_axial_pos< other._axial_pos) || + ((_axial_pos == other._axial_pos) && base_type::operator<(other)); +} + +bool +SinogramIndices:: +operator==(const SinogramIndices& other) const +{ + return (_axial_pos == other._axial_pos) && base_type::operator==(other); +} + +bool +SinogramIndices:: +operator!=(const SinogramIndices& other) const +{ + return !(*this == other); +} +END_NAMESPACE_STIR diff --git a/src/include/stir/ViewSegmentNumbers.h b/src/include/stir/ViewSegmentNumbers.h index 3f4b38fa5f..40bdb6c8c6 100644 --- a/src/include/stir/ViewSegmentNumbers.h +++ b/src/include/stir/ViewSegmentNumbers.h @@ -4,72 +4,38 @@ \file \ingroup projdata - \brief Definition of class stir::ViewSegmentNumbers + \brief Definition of class stir::ViewSegmentNumbers, alias to stir::ViewgramIndices \author Kris Thielemans - \author Sanida Mustafovic - \author PARAPET project - + */ /* - Copyright (C) 2000 PARAPET partners - Copyright (C) 2000- 2009, Hammersmith Imanet Ltd + Copyright (C) 2023, University College London This file is part of STIR. - SPDX-License-Identifier: Apache-2.0 AND License-ref-PARAPET-license + SPDX-License-Identifier: Apache-2.0 See STIR/LICENSE.txt for details */ - - #ifndef __stir_ViewSegmentNumbers_h__ #define __stir_ViewSegmentNumbers_h__ -#include "stir/common.h" +#include "stir/ViewgramIndices.h" START_NAMESPACE_STIR - +//! alias for ViewgramIndices /*! - \brief A very simple class to store view and segment numbers - \ingroup projdata + For backwards compatibility only. + + \deprecated */ -class ViewSegmentNumbers +// Note: needs to be a class due to forward declarations +class ViewSegmentNumbers : public ViewgramIndices { -public: - - //! an empty constructor (sets everything to 0) - inline ViewSegmentNumbers(); - //! constructor taking view and segment number as arguments - inline ViewSegmentNumbers( const int view_num,const int segment_num); - - //! get segment number for const objects - inline int segment_num() const; - //! get view number for const objects - inline int view_num() const; - - //! get reference to segment number - inline int& segment_num(); - //! get reference to view number - inline int& view_num(); - - - //! comparison operator, only useful for sorting - /*! order : (0,1) < (0,-1) < (1,1) ...*/ - inline bool operator<(const ViewSegmentNumbers& other) const; - - //! test for equality - inline bool operator==(const ViewSegmentNumbers& other) const; - inline bool operator!=(const ViewSegmentNumbers& other) const; - -private: - int segment; - int view; - + using ViewgramIndices::ViewgramIndices; }; END_NAMESPACE_STIR -#include "stir/ViewSegmentNumbers.inl" - #endif diff --git a/src/include/stir/ViewSegmentNumbers.inl b/src/include/stir/ViewSegmentNumbers.inl deleted file mode 100644 index c19c0ee63c..0000000000 --- a/src/include/stir/ViewSegmentNumbers.inl +++ /dev/null @@ -1,73 +0,0 @@ -/*! - \file - \ingroup projdata - - \brief inline implementations for class stir::ViewSegmentNumbers - - \author Kris Thielemans - \author Sanida Mustafovic - \author PARAPET project - - -*/ -/* - Copyright (C) 2000 PARAPET partners - Copyright (C) 2000- 2009, Hammersmith Imanet Ltd - This file is part of STIR. - - SPDX-License-Identifier: Apache-2.0 AND License-ref-PARAPET-license - - See STIR/LICENSE.txt for details -*/ - - -START_NAMESPACE_STIR - -ViewSegmentNumbers::ViewSegmentNumbers() -:segment(0),view(0) - {} - -ViewSegmentNumbers::ViewSegmentNumbers( const int view_num,const int segment_num) - : segment(segment_num),view(view_num) - {} - -int -ViewSegmentNumbers::segment_num() const -{ - return segment;} -int -ViewSegmentNumbers::view_num() const -{ - return view;} - - -int& -ViewSegmentNumbers::segment_num() -{ return segment;} - -int& -ViewSegmentNumbers::view_num() -{ return view;} - -bool -ViewSegmentNumbers:: -operator<(const ViewSegmentNumbers& other) const -{ - return (view< other.view) || - ((view == other.view) && (segment > other.segment)); -} - -bool -ViewSegmentNumbers:: -operator==(const ViewSegmentNumbers& other) const -{ - return (view == other.view) && (segment == other.segment); -} - -bool -ViewSegmentNumbers:: -operator!=(const ViewSegmentNumbers& other) const -{ - return !(*this == other); -} -END_NAMESPACE_STIR diff --git a/src/include/stir/ViewgramIndices.h b/src/include/stir/ViewgramIndices.h new file mode 100644 index 0000000000..f8a8fdfc36 --- /dev/null +++ b/src/include/stir/ViewgramIndices.h @@ -0,0 +1,64 @@ +// +// +/*! + \file + \ingroup projdata + + \brief Definition of class stir::ViewgramIndices + + \author Kris Thielemans + +*/ +/* + Copyright (C) 2023, University College London + This file is part of STIR. + + SPDX-License-Identifier: Apache-2.0 + + See STIR/LICENSE.txt for details +*/ + +#ifndef __stir_ViewgramIndices_h__ +#define __stir_ViewgramIndices_h__ + +#include "stir/SegmentIndices.h" + +START_NAMESPACE_STIR + +/*! + \brief A very simple class to store all dincies to get a (2D) Viewgram + \ingroup projdata +*/ +class ViewgramIndices : public SegmentIndices +{ + typedef SegmentIndices base_type; + +public: + //! an empty constructor (sets everything to 0) + inline ViewgramIndices(); + //! constructor taking view and segment number as arguments + inline ViewgramIndices(const int view_num, const int segment_num); + + //! get view number for const objects + inline int view_num() const; + + //! get reference to view number + inline int& view_num(); + + //! comparison operator, only useful for sorting + /*! order : (0,1) < (0,-1) < (1,1) ...*/ + inline bool operator<(const ViewgramIndices& other) const; + + //! test for equality + inline bool operator==(const ViewgramIndices& other) const; + inline bool operator!=(const ViewgramIndices& other) const; + +private: + int _view; +}; + +END_NAMESPACE_STIR + +#include "stir/ViewgramIndices.inl" + +#endif diff --git a/src/include/stir/ViewgramIndices.inl b/src/include/stir/ViewgramIndices.inl new file mode 100644 index 0000000000..8ef0cce182 --- /dev/null +++ b/src/include/stir/ViewgramIndices.inl @@ -0,0 +1,64 @@ +/*! + \file + \ingroup projdata + + \brief inline implementations for class stir::ViewgramIndices + + \author Kris Thielemans + \author Sanida Mustafovic + \author PARAPET project + + +*/ +/* + Copyright (C) 2000 PARAPET partners + Copyright (C) 2000- 2009, Hammersmith Imanet Ltd + This file is part of STIR. + + SPDX-License-Identifier: Apache-2.0 AND License-ref-PARAPET-license + + See STIR/LICENSE.txt for details +*/ + +START_NAMESPACE_STIR + +ViewgramIndices::ViewgramIndices() + : SegmentIndices(), + _view(0) +{} + +ViewgramIndices::ViewgramIndices(const int view_num, const int segment_num) + : SegmentIndices(segment_num), + _view(view_num) +{} + +int +ViewgramIndices::view_num() const +{ + return _view; +} + +int& +ViewgramIndices::view_num() +{ + return _view; +} + +bool +ViewgramIndices::operator<(const ViewgramIndices& other) const +{ + return (_view < other._view) || ((_view == other._view) && base_type::operator<(other)); +} + +bool +ViewgramIndices::operator==(const ViewgramIndices& other) const +{ + return (_view == other._view) && base_type::operator==(other); +} + +bool +ViewgramIndices::operator!=(const ViewgramIndices& other) const +{ + return !(*this == other); +} +END_NAMESPACE_STIR From c8bce5e3954d858cea425ba7ef4aa3b512d34a38 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sun, 15 Oct 2023 00:06:51 +0100 Subject: [PATCH 337/509] Add constructors and get* members using *Indices classes --- src/buildblock/ProjData.cxx | 38 +++++++++++++---- src/buildblock/ProjDataInfo.cxx | 50 ++++++++++++++++++---- src/buildblock/SegmentBySinogram.cxx | 35 ++++++++++++---- src/buildblock/SegmentByView.cxx | 35 +++++++++++----- src/include/stir/ProjData.h | 57 +++++++++++++++++++++----- src/include/stir/ProjDataInfo.h | 21 +++++++++- src/include/stir/RelatedViewgrams.h | 5 ++- src/include/stir/RelatedViewgrams.inl | 15 ++++++- src/include/stir/Segment.h | 9 ++-- src/include/stir/Segment.inl | 12 ++++-- src/include/stir/SegmentBySinogram.h | 18 +++++++- src/include/stir/SegmentBySinogram.inl | 1 + src/include/stir/SegmentByView.h | 19 ++++++++- src/include/stir/SegmentByView.inl | 1 + src/include/stir/Sinogram.h | 27 +++++++++--- src/include/stir/Sinogram.inl | 50 +++++++++++++++------- src/include/stir/ViewSegmentNumbers.h | 7 +++- src/include/stir/Viewgram.h | 25 +++++++++-- src/include/stir/Viewgram.inl | 47 +++++++++++++++------ src/swig/stir_projdata.i | 11 ++++- 20 files changed, 384 insertions(+), 99 deletions(-) diff --git a/src/buildblock/ProjData.cxx b/src/buildblock/ProjData.cxx index 0a8067fe87..57db175355 100644 --- a/src/buildblock/ProjData.cxx +++ b/src/buildblock/ProjData.cxx @@ -58,7 +58,7 @@ #include "stir/IO/GEHDF5Wrapper.h" #endif #include "stir/IO/stir_ecat7.h" -#include "stir/ViewSegmentNumbers.h" +#include "stir/ViewgramIndices.h" #include "stir/is_null_ptr.h" #include #include @@ -261,7 +261,19 @@ ProjData::get_subset(const std::vector& views) const } -Viewgram +Viewgram +ProjData::get_empty_viewgram(const ViewgramIndices& ind) const +{ + return proj_data_info_sptr->get_empty_viewgram(ind); +} + +Sinogram +ProjData::get_empty_sinogram(const SinogramIndices& ind) const +{ + return proj_data_info_sptr->get_empty_sinogram(ind); +} + +Viewgram ProjData::get_empty_viewgram(const int view_num, const int segment_num, const bool make_num_tangential_poss_odd) const { @@ -296,9 +308,20 @@ ProjData::get_empty_segment_by_view(const int segment_num, } -RelatedViewgrams -ProjData::get_empty_related_viewgrams(const ViewSegmentNumbers& view_segmnet_num, - //const int view_num, const int segment_num, +SegmentBySinogram +ProjData::get_empty_segment_by_sinogram(const SegmentIndices& ind) const +{ + return proj_data_info_sptr->get_empty_segment_by_sinogram(ind); +} + +SegmentByView +ProjData::get_empty_segment_by_view(const SegmentIndices& ind) const +{ + return proj_data_info_sptr->get_empty_segment_by_view(ind); +} + +RelatedViewgrams +ProjData::get_empty_related_viewgrams(const ViewgramIndices& view_segmnet_num, const shared_ptr& symmetries_used, const bool make_num_tangential_poss_odd) const { @@ -308,15 +331,14 @@ ProjData::get_empty_related_viewgrams(const ViewSegmentNumbers& view_segmnet_num RelatedViewgrams -ProjData::get_related_viewgrams(const ViewSegmentNumbers& view_segmnet_num, - //const int view_num, const int segment_num, +ProjData::get_related_viewgrams(const ViewgramIndices& viewgram_indices, const shared_ptr& symmetries_used, const bool make_num_bins_odd) const { vector pairs; symmetries_used->get_related_view_segment_numbers( pairs, - ViewSegmentNumbers(view_segmnet_num.view_num(),view_segmnet_num.segment_num()) + viewgram_indices ); vector > viewgrams; diff --git a/src/buildblock/ProjDataInfo.cxx b/src/buildblock/ProjDataInfo.cxx index 4b5bfc87ce..0a0823dc8b 100644 --- a/src/buildblock/ProjDataInfo.cxx +++ b/src/buildblock/ProjDataInfo.cxx @@ -4,7 +4,7 @@ Copyright (C) 2000 PARAPET partners Copyright (C) 2000 - 2009-05-13, Hammersmith Imanet Ltd Copyright (C) 2011-07-01 - 2011, Kris Thielemans - Copyright (C) 2018, 2022, University College London + Copyright (C) 2018, 2022, 2023, University College London Copyright (C) 2018, University of Leeds This file is part of STIR. @@ -262,6 +262,15 @@ ProjDataInfo::get_empty_viewgram(const int view_num, return v; } +Viewgram +ProjDataInfo::get_empty_viewgram(const ViewgramIndices& ind) const +{ + // we can't access the shared ptr, so we have to clone 'this'. + shared_ptr proj_data_info_sptr(this->clone()); + Viewgram v(proj_data_info_sptr, ind); + return v; +} + Sinogram ProjDataInfo::get_empty_sinogram(const int axial_pos_num, const int segment_num, @@ -278,6 +287,15 @@ ProjDataInfo::get_empty_sinogram(const int axial_pos_num, const int segment_num, return s; } +Sinogram +ProjDataInfo::get_empty_sinogram(const SinogramIndices& ind) const +{ + // we can't access the shared ptr, so we have to clone 'this'. + shared_ptr proj_data_info_sptr(this->clone()); + Sinogram s(proj_data_info_sptr, ind); + return s; +} + SegmentBySinogram ProjDataInfo::get_empty_segment_by_sinogram(const int segment_num, const bool make_num_tangential_poss_odd) const @@ -296,6 +314,14 @@ ProjDataInfo::get_empty_segment_by_sinogram(const int segment_num, return s; } +SegmentBySinogram +ProjDataInfo::get_empty_segment_by_sinogram(const SegmentIndices& ind) const +{ + // we can't access the shared ptr, so we have to clone 'this'. + shared_ptr proj_data_info_sptr(this->clone()); + SegmentBySinogram s(proj_data_info_sptr, ind); + return s; +} SegmentByView ProjDataInfo::get_empty_segment_by_view(const int segment_num, @@ -315,17 +341,24 @@ ProjDataInfo::get_empty_segment_by_view(const int segment_num, return s; } +SegmentByView +ProjDataInfo::get_empty_segment_by_view(const SegmentIndices& ind) const +{ + // we can't access the shared ptr, so we have to clone 'this'. + shared_ptr proj_data_info_sptr(this->clone()); + SegmentByView s(proj_data_info_sptr, ind); + return s; +} + RelatedViewgrams -ProjDataInfo::get_empty_related_viewgrams(const ViewSegmentNumbers& view_segmnet_num, - //const int view_num, const int segment_num, +ProjDataInfo::get_empty_related_viewgrams(const ViewgramIndices& viewgram_indices, const shared_ptr& symmetries_used, const bool make_num_tangential_poss_odd) const { + if (make_num_tangential_poss_odd) + error("make_num_tangential_poss_odd is no longer supported"); vector pairs; - symmetries_used->get_related_view_segment_numbers( - pairs, - ViewSegmentNumbers(view_segmnet_num.view_num(),view_segmnet_num.segment_num()) - ); + symmetries_used->get_related_view_segment_numbers(pairs, viewgram_indices); vector > viewgrams; viewgrams.reserve(pairs.size()); @@ -333,8 +366,7 @@ ProjDataInfo::get_empty_related_viewgrams(const ViewSegmentNumbers& view_segmnet for (unsigned int i=0; i(viewgrams, symmetries_used); diff --git a/src/buildblock/SegmentBySinogram.cxx b/src/buildblock/SegmentBySinogram.cxx index 88a53d460a..336790a971 100644 --- a/src/buildblock/SegmentBySinogram.cxx +++ b/src/buildblock/SegmentBySinogram.cxx @@ -35,15 +35,15 @@ template SegmentBySinogram :: SegmentBySinogram(const Array<3,elemT>& v, const shared_ptr& pdi_ptr, - const int segment_num) + const SegmentIndices& ind) : - Segment(pdi_ptr, segment_num), + Segment(pdi_ptr, ind), Array<3,elemT>(v) { assert( get_min_view_num() == pdi_ptr->get_min_view_num()); assert( get_max_view_num() == pdi_ptr->get_max_view_num()); - assert( get_min_axial_pos_num() == pdi_ptr->get_min_axial_pos_num(segment_num)); - assert( get_max_axial_pos_num() == pdi_ptr->get_max_axial_pos_num(segment_num)); + assert( get_min_axial_pos_num() == pdi_ptr->get_min_axial_pos_num(ind.segment_num())); + assert( get_max_axial_pos_num() == pdi_ptr->get_max_axial_pos_num(ind.segment_num())); assert( get_min_tangential_pos_num() == pdi_ptr->get_min_tangential_pos_num()); assert( get_max_tangential_pos_num() == pdi_ptr->get_max_tangential_pos_num()); } @@ -51,22 +51,39 @@ SegmentBySinogram(const Array<3,elemT>& v, template SegmentBySinogram :: SegmentBySinogram(const shared_ptr& pdi_ptr, - const int segment_num) + const SegmentIndices& ind) : - Segment(pdi_ptr, segment_num), - Array<3,elemT>(IndexRange3D(pdi_ptr->get_min_axial_pos_num(segment_num), - pdi_ptr->get_max_axial_pos_num(segment_num), + Segment(pdi_ptr, ind), + Array<3,elemT>(IndexRange3D(pdi_ptr->get_min_axial_pos_num(ind.segment_num()), + pdi_ptr->get_max_axial_pos_num(ind.segment_num()), pdi_ptr->get_min_view_num(), pdi_ptr->get_max_view_num(), pdi_ptr->get_min_tangential_pos_num(), pdi_ptr->get_max_tangential_pos_num())) {} +template +SegmentBySinogram:: +SegmentBySinogram(const Array<3,elemT>& v, + const shared_ptr& pdi_sptr, + int segment_num) + : + SegmentBySinogram(v, pdi_sptr, SegmentIndices(segment_num)) +{} + +template +SegmentBySinogram:: +SegmentBySinogram(const shared_ptr& pdi_sptr, + const int segment_num) + : + SegmentBySinogram(pdi_sptr, SegmentIndices(segment_num)) +{} + template SegmentBySinogram:: SegmentBySinogram(const SegmentByView& s_v ) : Segment(s_v.get_proj_data_info_sptr()->create_shared_clone(), - s_v.get_segment_num()), + s_v.get_segment_indices()), Array<3,elemT> (IndexRange3D (s_v.get_min_axial_pos_num(), s_v.get_max_axial_pos_num(), s_v.get_min_view_num(), s_v.get_max_view_num(), s_v.get_min_tangential_pos_num(), s_v.get_max_tangential_pos_num())) diff --git a/src/buildblock/SegmentByView.cxx b/src/buildblock/SegmentByView.cxx index 5543239884..f08fc61ae4 100644 --- a/src/buildblock/SegmentByView.cxx +++ b/src/buildblock/SegmentByView.cxx @@ -33,15 +33,15 @@ template SegmentByView:: SegmentByView(const Array<3,elemT>& v, const shared_ptr& pdi_ptr, - const int segment_num) + const SegmentIndices& ind) : - Segment(pdi_ptr, segment_num), + Segment(pdi_ptr, ind), Array<3,elemT>(v) { assert( get_min_view_num() == pdi_ptr->get_min_view_num()); assert( get_max_view_num() == pdi_ptr->get_max_view_num()); - assert( get_min_axial_pos_num() == pdi_ptr->get_min_axial_pos_num(segment_num)); - assert( get_max_axial_pos_num() == pdi_ptr->get_max_axial_pos_num(segment_num)); + assert( get_min_axial_pos_num() == pdi_ptr->get_min_axial_pos_num(ind.segment_num())); + assert( get_max_axial_pos_num() == pdi_ptr->get_max_axial_pos_num(ind.segment_num())); assert( get_min_tangential_pos_num() == pdi_ptr->get_min_tangential_pos_num()); assert( get_max_tangential_pos_num() == pdi_ptr->get_max_tangential_pos_num()); @@ -50,22 +50,37 @@ SegmentByView(const Array<3,elemT>& v, template SegmentByView:: SegmentByView(const shared_ptr& pdi_ptr, - const int segment_num) + const SegmentIndices& ind) : - Segment(pdi_ptr, segment_num), + Segment(pdi_ptr, ind), Array<3,elemT>(IndexRange3D(pdi_ptr->get_min_view_num(), pdi_ptr->get_max_view_num(), - pdi_ptr->get_min_axial_pos_num(segment_num), - pdi_ptr->get_max_axial_pos_num(segment_num), + pdi_ptr->get_min_axial_pos_num(ind.segment_num()), + pdi_ptr->get_max_axial_pos_num(ind.segment_num()), pdi_ptr->get_min_tangential_pos_num(), pdi_ptr->get_max_tangential_pos_num())) {} +template +SegmentByView:: +SegmentByView(const Array<3,elemT>& v, + const shared_ptr& pdi_sptr, + const int segment_num) + : + SegmentByView(v, pdi_sptr, SegmentIndices(segment_num)) +{} + +template +SegmentByView:: +SegmentByView(const shared_ptr& pdi_sptr, + const int segment_num) + : SegmentByView(pdi_sptr, SegmentIndices(segment_num)) +{} + template SegmentByView::SegmentByView(const SegmentBySinogram& s_s) : Segment(s_s.get_proj_data_info_sptr()->create_shared_clone(), - s_s.get_segment_num()), - + s_s.get_segment_indices()), Array<3,elemT> (IndexRange3D(s_s.get_min_view_num(),s_s.get_max_view_num(), s_s.get_min_axial_pos_num(),s_s.get_max_axial_pos_num(), s_s.get_min_tangential_pos_num(), s_s.get_max_tangential_pos_num())) diff --git a/src/include/stir/ProjData.h b/src/include/stir/ProjData.h index 722c045acd..e97f41e6f5 100644 --- a/src/include/stir/ProjData.h +++ b/src/include/stir/ProjData.h @@ -44,7 +44,6 @@ template class SegmentBySinogram; template class SegmentByView; template class Viewgram; template class Sinogram; -class ViewSegmentNumbers; class Succeeded; class ProjDataInMemory; //class ExamInfo; @@ -119,14 +118,21 @@ class ProjData : public ExamData inline shared_ptr get_proj_data_info_sptr() const; //! Get viewgram + /*! + \deprecated Use get_viewgram(const ViewgramIndices&) instead. + */ virtual Viewgram get_viewgram(const int view, const int segment_num,const bool make_num_tangential_poss_odd = false) const=0; + //! Get viewgram inline Viewgram get_viewgram(const ViewgramIndices&); //! Set viewgram virtual Succeeded set_viewgram(const Viewgram&) = 0; //! Get sinogram + /*! + \deprecated Use get_sinogram(const SinogramIndices&) instead . + */ virtual Sinogram get_sinogram(const int ax_pos_num, const int segment_num,const bool make_num_tangential_poss_odd = false) const=0; //! Get sinogram @@ -141,34 +147,66 @@ class ProjData : public ExamData get_subset(const std::vector& views) const; //! Get empty viewgram + Viewgram get_empty_viewgram(const ViewgramIndices&) const; + + //! Get empty viewgram + /*! + \deprecated Use get_viewgram(const ViewgramIndices&) instead. + */ Viewgram get_empty_viewgram(const int view, const int segment_num, const bool make_num_tangential_poss_odd = false) const; //! Get empty_sinogram + Sinogram + get_empty_sinogram(const SinogramIndices&) const; + + //! Get empty_sinogram + /*! + \deprecated Use get_sinogram(const SinogramIndices&) instead . + */ Sinogram get_empty_sinogram(const int ax_pos_num, const int segment_num, const bool make_num_tangential_poss_odd = false) const; - //! Get empty segment sino - SegmentByView + //! Get empty segment by view + SegmentByView + get_empty_segment_by_view(const SegmentIndices&) const; + //! Get empty segment by sino + SegmentBySinogram + get_empty_segment_by_sinogram(const SegmentIndices&) const; + //! Get empty segment view + /*! + \deprecated Use get_empty_segment_by_sinogram(const SegmentIndices&) instead . + */ + SegmentByView get_empty_segment_by_view(const int segment_num, const bool make_num_tangential_poss_odd = false) const; - //! Get empty segment view - SegmentBySinogram + //! Get empty segment sino + /*! + \deprecated Use get_empty_segment_by_sinogram(const SegmentIndices&) instead . + */ + SegmentBySinogram get_empty_segment_by_sinogram(const int segment_num, const bool make_num_tangential_poss_odd = false) const; - //! Get segment by sinogram + /*! + \deprecated Use get_segment_by_sinogram(const SegmentIndices&) instead. + */ virtual SegmentBySinogram get_segment_by_sinogram(const int segment_num) const; + //! Get segment by sinogram inline SegmentBySinogram get_segment_by_sinogram(const SegmentIndices&) const; //! Get segment by view - virtual SegmentByView + /*! + \deprecated Use get_segment_by_view(const SegmentIndices&) instead. + */ + virtual SegmentByView get_segment_by_view(const int segment_num) const; + //! Get segment by view inline SegmentByView get_segment_by_view(const SegmentIndices&) const; @@ -181,7 +219,7 @@ class ProjData : public ExamData //! Get related viewgrams virtual RelatedViewgrams - get_related_viewgrams(const ViewSegmentNumbers&, + get_related_viewgrams(const ViewgramIndices&, const shared_ptr&, const bool make_num_tangential_poss_odd = false) const; //! Set related viewgrams @@ -190,8 +228,7 @@ class ProjData : public ExamData //! Get empty related viewgrams, where the symmetries_ptr specifies the symmetries to use RelatedViewgrams - get_empty_related_viewgrams(const ViewSegmentNumbers& view_segmnet_num, - //const int view_num, const int segment_num, + get_empty_related_viewgrams(const ViewgramIndices& viewgram_indices, const shared_ptr& symmetries_ptr, const bool make_num_tangential_poss_odd = false) const; diff --git a/src/include/stir/ProjDataInfo.h b/src/include/stir/ProjDataInfo.h index 64d96cc67d..d0be14fb3b 100644 --- a/src/include/stir/ProjDataInfo.h +++ b/src/include/stir/ProjDataInfo.h @@ -4,7 +4,7 @@ Copyright (C) 2000 PARAPET partners Copyright (C) 2000 - 2011-10-14, Hammersmith Imanet Ltd Copyright (C) 2011-07-01 - 2011, Kris Thielemans - Copyright (C) 2017-2018, 2020, 2022, University College London + Copyright (C) 2017-2018, 2020, 2022, 2023 University College London This file is part of STIR. SPDX-License-Identifier: Apache-2.0 AND License-ref-PARAPET-license @@ -24,6 +24,9 @@ #ifndef __stir_ProjDataInfo_H__ #define __stir_ProjDataInfo_H__ +#include "stir/SegmentIndices.h" +#include "stir/ViewgramIndices.h" +#include "stir/SinogramIndices.h" #include "stir/VectorWithOffset.h" #include "stir/Scanner.h" #include "stir/shared_ptr.h" @@ -347,23 +350,37 @@ class ProjDataInfo //@{ //! Get empty viewgram + Viewgram get_empty_viewgram(const ViewgramIndices&) const; + //! Get empty_sinogram + Sinogram get_empty_sinogram(const SinogramIndices&) const; + //! Get empty segment sino + SegmentByView get_empty_segment_by_view(const SegmentIndices&) const; + //! Get empty segment view + SegmentBySinogram get_empty_segment_by_sinogram(const SegmentIndices&) const; + + //! Get empty viewgram + /*! \deprecated */ Viewgram get_empty_viewgram(const int view_num, const int segment_num, const bool make_num_tangential_poss_odd = false) const; //! Get empty_sinogram + /*! \deprecated */ Sinogram get_empty_sinogram(const int ax_pos_num, const int segment_num, const bool make_num_tangential_poss_odd = false) const; //! Get empty segment sino + /*! \deprecated */ SegmentByView get_empty_segment_by_view(const int segment_num, const bool make_num_tangential_poss_odd = false) const; //! Get empty segment view + /*! \deprecated */ SegmentBySinogram get_empty_segment_by_sinogram(const int segment_num, const bool make_num_tangential_poss_odd = false) const; //! Get empty related viewgrams, where the symmetries_ptr specifies the symmetries to use - RelatedViewgrams get_empty_related_viewgrams(const ViewSegmentNumbers&, + /*! make_num_tangential_poss_odd has to be \c false */ + RelatedViewgrams get_empty_related_viewgrams(const ViewgramIndices&, const shared_ptr&, const bool make_num_tangential_poss_odd = false) const; //@} diff --git a/src/include/stir/RelatedViewgrams.h b/src/include/stir/RelatedViewgrams.h index 52742b642d..7ad3847bff 100644 --- a/src/include/stir/RelatedViewgrams.h +++ b/src/include/stir/RelatedViewgrams.h @@ -85,8 +85,11 @@ class RelatedViewgrams /*! see DataSymmetriesForViewSegmentNumbers for definition of 'basic' */ inline int get_basic_segment_num() const; //! get 'basic' view_segment_num + /*! \deprecated Use get_basic_viewgram_indices() instead. */ + inline ViewgramIndices get_basic_view_segment_num() const; + //! get 'basic' viewgram indices /*! see DataSymmetriesForViewSegmentNumbers for definition of 'basic' */ - inline ViewSegmentNumbers get_basic_view_segment_num() const; + inline ViewgramIndices get_basic_viewgram_indices() const; //! returns the number of viewgrams in this object inline int get_num_viewgrams() const; diff --git a/src/include/stir/RelatedViewgrams.inl b/src/include/stir/RelatedViewgrams.inl index 084cc68da6..6eee8108f7 100644 --- a/src/include/stir/RelatedViewgrams.inl +++ b/src/include/stir/RelatedViewgrams.inl @@ -71,10 +71,21 @@ int RelatedViewgrams::get_basic_segment_num() const } template -ViewSegmentNumbers RelatedViewgrams:: +ViewgramIndices +RelatedViewgrams:: +get_basic_viewgram_indices() const +{ + assert(viewgrams.size()>0); + check_state(); + return viewgrams[0].get_viewgram_indices(); +} + +template +ViewgramIndices +RelatedViewgrams:: get_basic_view_segment_num() const { - return ViewSegmentNumbers(get_basic_view_num(), get_basic_segment_num()); + return this->get_basic_viewgram_indices(); } template diff --git a/src/include/stir/Segment.h b/src/include/stir/Segment.h index 5583ff4007..7b700e7526 100644 --- a/src/include/stir/Segment.h +++ b/src/include/stir/Segment.h @@ -1,6 +1,7 @@ /* Copyright (C) 2000 PARAPET partners Copyright (C) 2000-2012 Hammersmith Imanet Ltd + Copyright (C) 2023, University College London This file is part of STIR. SPDX-License-Identifier: Apache-2.0 AND License-ref-PARAPET-license @@ -21,6 +22,7 @@ #include "stir/ProjDataInfo.h" +#include "stir/SegmentIndices.h" #include "stir/shared_ptr.h" START_NAMESPACE_STIR @@ -33,7 +35,7 @@ template class Viewgram; \ingroup projdata This stores a subset of the data accessible via a ProjData object, - where the segment_num is fixed. + where the SegmentIndices are fixed. At the moment, 2 'storage modes' are supported (and implemented as derived classes). @@ -60,6 +62,7 @@ class Segment get_proj_data_info_sptr() const; virtual StorageOrder get_storage_order() const = 0; + inline SegmentIndices get_segment_indices() const; //! Get the segment number inline int get_segment_num() const; virtual int get_min_axial_pos_num() const = 0; @@ -114,9 +117,9 @@ class Segment protected: shared_ptr proj_data_info_sptr; - int segment_num; + SegmentIndices _indices; - inline Segment(const shared_ptr& proj_data_info_sptr_v,const int s_num); + inline Segment(const shared_ptr& proj_data_info_sptr_v,const SegmentIndices&); }; END_NAMESPACE_STIR diff --git a/src/include/stir/Segment.inl b/src/include/stir/Segment.inl index 7e32fe1b68..80d31d210f 100644 --- a/src/include/stir/Segment.inl +++ b/src/include/stir/Segment.inl @@ -3,6 +3,7 @@ /* Copyright (C) 2000 PARAPET partners Copyright (C) 2000- 2007, IRSL + Copyright (C) 2023, University College London This file is part of STIR. SPDX-License-Identifier: Apache-2.0 AND License-ref-PARAPET-license @@ -25,16 +26,21 @@ START_NAMESPACE_STIR template Segment:: -Segment( const shared_ptr& proj_data_info_sptr_v,const int s_num) +Segment( const shared_ptr& proj_data_info_sptr_v,const SegmentIndices& ind) : proj_data_info_sptr(proj_data_info_sptr_v), - segment_num(s_num) + _indices(ind) {} +template +SegmentIndices +Segment:: get_segment_indices() const +{ return _indices; } + template int Segment:: get_segment_num() const -{ return segment_num; } +{ return _indices.segment_num(); } template shared_ptr diff --git a/src/include/stir/SegmentBySinogram.h b/src/include/stir/SegmentBySinogram.h index 25c420b5f4..a4c8399a68 100644 --- a/src/include/stir/SegmentBySinogram.h +++ b/src/include/stir/SegmentBySinogram.h @@ -3,6 +3,7 @@ /* Copyright (C) 2000 PARAPET partners Copyright (C) 2000- 2012, Hammersmith Imanet Ltd + Copyright (C) 2023, University College London This file is part of STIR. SPDX-License-Identifier: Apache-2.0 AND License-ref-PARAPET-license @@ -38,7 +39,7 @@ template class SegmentByView; /*! \ingroup projdata - \brief A class for storing (3d) projection data with a fixed segment_num. + \brief A class for storing (3d) projection data with fixed SegmentIndices. Storage order is as follows: \code @@ -56,11 +57,26 @@ class SegmentBySinogram : public Segment, public Array<3,elemT> typedef typename Segment::StorageOrder StorageOrder; //! Constructor that sets the data to a given 3d Array + SegmentBySinogram(const Array<3,elemT>& v, + const shared_ptr& proj_data_info_ptr_v, + const SegmentIndices& ind); + + //! Constructor that sets sizes via the ProjDataInfo object, initialising data to 0 + SegmentBySinogram(const shared_ptr& proj_data_info_ptr_v, + const SegmentIndices& ind); + + //! Constructor that sets the data to a given 3d Array + /*! + \deprecated Use version with SegmentIndices instead + */ SegmentBySinogram(const Array<3,elemT>& v, const shared_ptr& proj_data_info_ptr_v, const int segment_num); //! Constructor that sets sizes via the ProjDataInfo object, initialising data to 0 + /*! + \deprecated Use version with SegmentIndices instead + */ SegmentBySinogram(const shared_ptr& proj_data_info_ptr_v, const int segment_num); diff --git a/src/include/stir/SegmentBySinogram.inl b/src/include/stir/SegmentBySinogram.inl index 2500a7e1e5..eb25fc66a6 100644 --- a/src/include/stir/SegmentBySinogram.inl +++ b/src/include/stir/SegmentBySinogram.inl @@ -3,6 +3,7 @@ /* Copyright (C) 2000 PARAPET partners Copyright (C) 2000- 2007, Hammersmith Imanet Ltd + Copyright (C) 2023, University College London This file is part of STIR. SPDX-License-Identifier: Apache-2.0 AND License-ref-PARAPET-license diff --git a/src/include/stir/SegmentByView.h b/src/include/stir/SegmentByView.h index e0e858d829..231a172461 100644 --- a/src/include/stir/SegmentByView.h +++ b/src/include/stir/SegmentByView.h @@ -3,6 +3,7 @@ /* Copyright (C) 2000 PARAPET partners Copyright (C) 2000- 2007, Hammersmith Imanet Ltd + Copyright (C) 2023, University College London This file is part of STIR. SPDX-License-Identifier: Apache-2.0 AND License-ref-PARAPET-license @@ -37,7 +38,7 @@ template class Sinogram; /*! \ingroup projdata - \brief A class for storing (3d) projection data with a fixed segment_num. + \brief A class for storing (3d) projection data with fixed SegmentIndices. Storage order is as follows: \code @@ -55,14 +56,28 @@ template class SegmentByView : public Segment, public Ar typedef typename Segment::StorageOrder StorageOrder; //! Constructor that sets the data to a given 3d Array + SegmentByView(const Array<3,elemT>& v, + const shared_ptr& proj_data_info_sptr, + const SegmentIndices&); + + //! Constructor that sets sizes via the ProjDataInfo object, initialising data to 0 + SegmentByView(const shared_ptr& proj_data_info_sptr, + const SegmentIndices&); + + //! Constructor that sets the data to a given 3d Array + /*! + \deprecated Use version with SegmentIndices instead + */ SegmentByView(const Array<3,elemT>& v, const shared_ptr& proj_data_info_ptr, const int segment_num); //! Constructor that sets sizes via the ProjDataInfo object, initialising data to 0 + /*! + \deprecated Use version with SegmentIndices instead + */ SegmentByView(const shared_ptr& proj_data_info_ptr, const int segment_num); - //! Conversion from 1 storage order to the other SegmentByView(const SegmentBySinogram& ); diff --git a/src/include/stir/SegmentByView.inl b/src/include/stir/SegmentByView.inl index 2ec586ffc6..d222e6a63e 100644 --- a/src/include/stir/SegmentByView.inl +++ b/src/include/stir/SegmentByView.inl @@ -16,6 +16,7 @@ /* Copyright (C) 2000 PARAPET partners Copyright (C) 2000- 2011, Hammersmtih Imanet Ltd + Copyright (C) 2023, University College London This file is part of STIR. SPDX-License-Identifier: Apache-2.0 AND License-ref-PARAPET-license diff --git a/src/include/stir/Sinogram.h b/src/include/stir/Sinogram.h index 1661c685cc..f523dceb13 100644 --- a/src/include/stir/Sinogram.h +++ b/src/include/stir/Sinogram.h @@ -4,6 +4,7 @@ Copyright (C) 2000 PARAPET partners Copyright (C) 2000 - 2007-10-08, Hammersmith Imanet Ltd Copyright (C) 2011-07-01 - 2012, Kris Thielemans + Copyright (C) 2023, University College London This file is part of STIR. SPDX-License-Identifier: Apache-2.0 AND License-ref-PARAPET-license @@ -29,6 +30,7 @@ #include "stir/Array.h" #include "stir/ProjDataInfo.h" +#include "stir/SinogramIndices.h" #include "stir/shared_ptr.h" @@ -40,7 +42,7 @@ START_NAMESPACE_STIR \ingroup projdata \brief A class for 2d projection data. - This represents a subset of the full projection. segment_num and axial_pos_num + This represents a subset of the full projection. SegmentIndices and axial_pos_num are fixed. */ @@ -60,14 +62,30 @@ class Sinogram : public Array<2,elemT> #endif public: - //! Construct sinogram from proj_data_info pointer, ring and segment number. Data are set to 0. + //! Construct sinogram from proj_data_info pointe and indices. Data are set to 0. + inline Sinogram(const shared_ptr& proj_data_info_sptr, + const SinogramIndices&); + + //! Construct sinogram with data set to the array. + inline Sinogram(const Array<2,elemT>& p,const shared_ptr& proj_data_info_sptr, + const SinogramIndices&); + + //! Construct sinogram from proj_data_info pointer, axial position and segment number. Data are set to 0. + /*! + \deprecated Use version with SinogramIndices instead. + */ inline Sinogram(const shared_ptr& proj_data_info_ptr, const int ax_pos_num, const int segment_num); //! Construct sinogram with data set to the array. + /*! + \deprecated Use version with SinogramIndices instead. + */ inline Sinogram(const Array<2,elemT>& p,const shared_ptr& proj_data_info_ptr, const int ax_pos_num, const int segment_num); - + + //! Get indices + inline SinogramIndices get_sinogram_indices() const; //! Get segment number inline int get_segment_num() const; //! Get number of axial positions @@ -129,8 +147,7 @@ class Sinogram : public Array<2,elemT> private: shared_ptr proj_data_info_ptr; - int axial_pos_num; - int segment_num; + SinogramIndices _indices; }; diff --git a/src/include/stir/Sinogram.inl b/src/include/stir/Sinogram.inl index 637f9d6bac..e381c6de94 100644 --- a/src/include/stir/Sinogram.inl +++ b/src/include/stir/Sinogram.inl @@ -3,6 +3,7 @@ /* Copyright (C) 2000 PARAPET partners Copyright (C) 2000- 2007,Hammersmith Imanet Ltd + Copyright (C) 2023, University College London This file is part of STIR. SPDX-License-Identifier: Apache-2.0 AND License-ref-PARAPET-license @@ -28,15 +29,22 @@ START_NAMESPACE_STIR +template +SinogramIndices +Sinogram::get_sinogram_indices() const +{ + return this->_indices; +} + template int Sinogram::get_segment_num() const -{ return segment_num; } +{ return this->_indices.segment_num(); } template int Sinogram::get_axial_pos_num() const -{ return axial_pos_num; } +{ return this->_indices.axial_pos_num(); } template int @@ -74,8 +82,8 @@ template Sinogram Sinogram::get_empty_copy(void) const { - Sinogram copy(proj_data_info_ptr, get_axial_pos_num(), get_segment_num()); - return copy; + Sinogram copy(proj_data_info_ptr, get_sinogram_indices()); + return copy; } template @@ -89,15 +97,14 @@ template Sinogram:: Sinogram(const Array<2,elemT>& p, const shared_ptr& pdi_ptr, - const int ax_pos_num, const int s_num) + const SinogramIndices& ind) : - Array<2,elemT>(p), + Array<2,elemT>(p), proj_data_info_ptr(pdi_ptr), - axial_pos_num(ax_pos_num), - segment_num(s_num) + _indices(ind) { - assert(axial_pos_num <= proj_data_info_ptr->get_max_axial_pos_num(segment_num)); - assert(axial_pos_num >= proj_data_info_ptr->get_min_axial_pos_num(segment_num)); + assert(axial_pos_num <= proj_data_info_ptr->get_max_axial_pos_num(ind.segment_num())); + assert(axial_pos_num >= proj_data_info_ptr->get_min_axial_pos_num(ind.segment_num())); // segment_num is already checked by doing get_max_axial_pos_num(s_num) assert( get_min_view_num() == pdi_ptr->get_min_view_num()); @@ -111,20 +118,33 @@ Sinogram(const Array<2,elemT>& p, template Sinogram:: Sinogram(const shared_ptr& pdi_ptr, - const int ax_pos_num, const int s_num) + const SinogramIndices& ind) : Array<2,elemT>(IndexRange2D (pdi_ptr->get_min_view_num(), pdi_ptr->get_max_view_num(), pdi_ptr->get_min_tangential_pos_num(), pdi_ptr->get_max_tangential_pos_num())), proj_data_info_ptr(pdi_ptr), - axial_pos_num(ax_pos_num), - segment_num(s_num) + _indices(ind) { - assert(axial_pos_num <= proj_data_info_ptr->get_max_axial_pos_num(segment_num)); - assert(axial_pos_num >= proj_data_info_ptr->get_min_axial_pos_num(segment_num)); + assert(axial_pos_num <= proj_data_info_ptr->get_max_axial_pos_num(ind.segment_num())); + assert(axial_pos_num >= proj_data_info_ptr->get_min_axial_pos_num(ind.segment_num())); // segment_num is already checked by doing get_max_axial_pos_num(s_num) } +template +Sinogram:: +Sinogram(const Array<2,elemT>& p, + const shared_ptr& pdi_sptr, + const int ax_pos_num, const int s_num) + : Sinogram(p, pdi_sptr, SinogramIndices(ax_pos_num, s_num)) +{} + +template +Sinogram:: +Sinogram(const shared_ptr& pdi_sptr, + const int ax_pos_num, const int s_num) + : Sinogram(pdi_sptr, SinogramIndices(ax_pos_num, s_num)) +{} END_NAMESPACE_STIR diff --git a/src/include/stir/ViewSegmentNumbers.h b/src/include/stir/ViewSegmentNumbers.h index 40bdb6c8c6..2b359d0b2a 100644 --- a/src/include/stir/ViewSegmentNumbers.h +++ b/src/include/stir/ViewSegmentNumbers.h @@ -30,10 +30,15 @@ START_NAMESPACE_STIR \deprecated */ -// Note: needs to be a class due to forward declarations class ViewSegmentNumbers : public ViewgramIndices { +public: using ViewgramIndices::ViewgramIndices; + // default constructor (needed for Visual Studio) + ViewSegmentNumbers() : ViewgramIndices() {} + ViewSegmentNumbers(const ViewgramIndices& ind) + : ViewgramIndices(ind) + {} }; END_NAMESPACE_STIR diff --git a/src/include/stir/Viewgram.h b/src/include/stir/Viewgram.h index c6dcce6b14..faaf9f7b16 100644 --- a/src/include/stir/Viewgram.h +++ b/src/include/stir/Viewgram.h @@ -4,6 +4,7 @@ Copyright (C) 2000 PARAPET partners Copyright (C) 2000 - 2007-10-08, Hammersmith Imanet Ltd Copyright (C) 2011-07-01 - 2012, Kris Thielemans + Copyright (C) 2023, University College London This file is part of STIR. SPDX-License-Identifier: Apache-2.0 AND License-ref-PARAPET-license @@ -28,7 +29,8 @@ #include "stir/Array.h" -#include "stir/ProjDataInfo.h" +#include "stir/ProjDataInfo.h" +#include "stir/ViewgramIndices.h" #include "stir/IndexRange.h" #include "stir/shared_ptr.h" @@ -39,7 +41,7 @@ START_NAMESPACE_STIR \ingroup projdata \brief A class for 2d projection data. - This represents a subset of the full projection. segment_num and view_num + This represents a subset of the full projection. SegmentIndices and view_num are fixed. */ @@ -60,15 +62,31 @@ class Viewgram : public Array<2,elemT> #endif public: + //! Construct from proj_data_info pointer and indices. Data are set to 0. + inline Viewgram(const shared_ptr& proj_data_info_ptr, + const ViewgramIndices& ind); + + //! Construct with data set to the array. + inline Viewgram(const Array<2,elemT>& p,const shared_ptr& proj_data_info_sptr, + const ViewgramIndices& ind); + //! Construct from proj_data_info pointer, view and segment number. Data are set to 0. + /*! + \deprecated Use version with ViewgramIndices instead + */ inline Viewgram(const shared_ptr& proj_data_info_ptr, const int v_num, const int s_num); //! Construct with data set to the array. + /*! + \deprecated Use version with ViewgramIndices instead + */ inline Viewgram(const Array<2,elemT>& p,const shared_ptr& proj_data_info_ptr, const int v_num, const int s_num); + //! Get indices + inline ViewgramIndices get_viewgram_indices() const; //! Get segment number inline int get_segment_num() const; //! Get number of views @@ -128,8 +146,7 @@ class Viewgram : public Array<2,elemT> private: shared_ptr proj_data_info_sptr; - int view_num; - int segment_num; + ViewgramIndices _indices; }; END_NAMESPACE_STIR diff --git a/src/include/stir/Viewgram.inl b/src/include/stir/Viewgram.inl index 7dd8838934..d8327d9f64 100644 --- a/src/include/stir/Viewgram.inl +++ b/src/include/stir/Viewgram.inl @@ -16,6 +16,7 @@ /* Copyright (C) 2000 PARAPET partners Copyright (C) 2000- 2009, Hammersmith Imanet Ltd + Copyright (C) 2023, University College London This file is part of STIR. SPDX-License-Identifier: Apache-2.0 AND License-ref-PARAPET-license @@ -27,15 +28,22 @@ START_NAMESPACE_STIR +template +ViewgramIndices +Viewgram::get_viewgram_indices() const +{ + return this->_indices; +} + template int Viewgram::get_segment_num() const -{ return segment_num; } +{ return this->_indices.segment_num(); } template int Viewgram::get_view_num() const -{ return view_num; } +{ return this->_indices.view_num(); } template int @@ -74,7 +82,7 @@ template Viewgram Viewgram::get_empty_copy(void) const { - Viewgram copy(proj_data_info_sptr, get_view_num(), get_segment_num()); + Viewgram copy(proj_data_info_sptr, get_viewgram_indices()); return copy; } @@ -90,17 +98,17 @@ template Viewgram:: Viewgram(const Array<2,elemT>& p, const shared_ptr& pdi_sptr, - const int v_num, const int s_num) + const ViewgramIndices& ind) : Array<2,elemT>(p), proj_data_info_sptr(pdi_sptr), - view_num(v_num), segment_num(s_num) + _indices(ind) { assert(view_num <= proj_data_info_sptr->get_max_view_num()); assert(view_num >= proj_data_info_sptr->get_min_view_num()); // segment_num is already checked by doing get_max_axial_pos_num(s_num) - assert( get_min_axial_pos_num() == pdi_sptr->get_min_axial_pos_num(s_num)); - assert( get_max_axial_pos_num() == pdi_sptr->get_max_axial_pos_num(s_num)); + assert( get_min_axial_pos_num() == pdi_sptr->get_min_axial_pos_num(ind.segment_num())); + assert( get_max_axial_pos_num() == pdi_sptr->get_max_axial_pos_num(ind.segment_num())); assert( get_min_tangential_pos_num() == pdi_sptr->get_min_tangential_pos_num()); assert( get_max_tangential_pos_num() == pdi_sptr->get_max_tangential_pos_num()); } @@ -108,20 +116,35 @@ Viewgram(const Array<2,elemT>& p, template Viewgram:: Viewgram(const shared_ptr& pdi_sptr, - const int v_num, const int s_num) + const ViewgramIndices& ind) : - Array<2,elemT>(IndexRange2D (pdi_sptr->get_min_axial_pos_num(s_num), - pdi_sptr->get_max_axial_pos_num(s_num), + Array<2,elemT>(IndexRange2D (pdi_sptr->get_min_axial_pos_num(ind.segment_num()), + pdi_sptr->get_max_axial_pos_num(ind.segment_num()), pdi_sptr->get_min_tangential_pos_num(), pdi_sptr->get_max_tangential_pos_num())), proj_data_info_sptr(pdi_sptr), - view_num(v_num), - segment_num(s_num) + _indices(ind) { assert(view_num <= proj_data_info_sptr->get_max_view_num()); assert(view_num >= proj_data_info_sptr->get_min_view_num()); // segment_num is already checked by doing get_max_axial_pos_num(s_num) } +template +Viewgram:: +Viewgram(const Array<2,elemT>& p, + const shared_ptr& pdi_sptr, + const int v_num, const int s_num) + : + Viewgram(p, pdi_sptr, ViewgramIndices(v_num, s_num)) +{} + +template +Viewgram:: +Viewgram(const shared_ptr& pdi_sptr, + const int v_num, const int s_num) + : + Viewgram(pdi_sptr, ViewgramIndices(v_num, s_num)) +{} END_NAMESPACE_STIR diff --git a/src/swig/stir_projdata.i b/src/swig/stir_projdata.i index be38c4f0f3..e49a52721e 100644 --- a/src/swig/stir_projdata.i +++ b/src/swig/stir_projdata.i @@ -54,12 +54,19 @@ ADD_REPR(stir::DetectionPosition, %arg(*$self)) #endif %template(DetectionPositionPair) stir::DetectionPositionPair; -%attributeref(stir::Bin, int, segment_num); +%attributeref(stir::SegmentIndices, int, segment_num); +#ifdef STIR_TOF +%attributeref(stir::SegmentIndices, int, timing_pos_num); +#endif +%attributeref(stir::ViewgramIndices, int, view_num); +%attributeref(stir::SinogramIndices, int, axial_pos_num); %attributeref(stir::Bin, int, axial_pos_num); -%attributeref(stir::Bin, int, view_num); %attributeref(stir::Bin, int, tangential_pos_num); %attributeref(stir::Bin, int, time_frame_num); %attribute(stir::Bin, float, bin_value, get_bin_value, set_bin_value); +%include "stir/SegmentIndices.h" +%include "stir/ViewgramIndices.h" +%include "stir/SinogramIndices.h" %include "stir/Bin.h" #ifdef STIR_TOF ADD_REPR(stir::Bin, %arg(*$self)) From 1e1a89c558cce2e3639cb968d51ec8a807065368 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sun, 15 Oct 2023 01:10:33 +0100 Subject: [PATCH 338/509] update release notes --- documentation/release_5.2.htm | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/documentation/release_5.2.htm b/documentation/release_5.2.htm index cf8075eb27..631b121222 100644 --- a/documentation/release_5.2.htm +++ b/documentation/release_5.2.htm @@ -75,6 +75,17 @@

    New functionality

    but you could use it to optimise the number of OpenMP threads to use for your data.
    PR #1237.
  8. +
  9. New classes SegmentIndices, ViewgramIndices and SinogramIndices, used by ProjData related classes, as opposed + to having to specify all the elements directly, e.g. in C++ +
    +      auto sinogram = proj_data.get_sinogram(sinogram_indices);
    +    
    + This makes these functions more future proof, in particular for TOF. The older functions are now deprecated. + Note that as Bin is now derived from ViewgramIndices, instantations of Bin can now be used to specify the indices as well in most places. +
    + There is still more work to do here, mostly related to the symmetries. +
    PR #1273. +
  10. Python (and MATLAB)

    @@ -124,6 +135,9 @@

    Deprecated functionality

  11. The following functions (previously used for upsampling the scatter estimate) have been made obsolete or replaced, and will be removed in STIR version 6.0.0: interpolate_axial_position, extend_sinogram_in_views and extend_segment_in_views
  12. +
  13. Constructors/functions in ProjData related classes that explicitly use axial_pos_num, view_num in their arguments are now deprecated, + and should be replaced by their respective versions that use SegmentIndices, ViewgramIndices and SinogramIndices. +
  14. Build system and dependencies

    From 4b5bae0c602ce12c687121b1025e10db34676bac Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Mon, 16 Oct 2023 12:27:04 +0100 Subject: [PATCH 339/509] minimal update to work with ViewgramIndices A lot of work to be done here, but that has to be after the next release. --- .../DataSymmetriesForViewSegmentNumbers.h | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/include/stir/DataSymmetriesForViewSegmentNumbers.h b/src/include/stir/DataSymmetriesForViewSegmentNumbers.h index a3e6cf0e13..c557b4c9de 100644 --- a/src/include/stir/DataSymmetriesForViewSegmentNumbers.h +++ b/src/include/stir/DataSymmetriesForViewSegmentNumbers.h @@ -22,13 +22,11 @@ #ifndef __DataSymmetriesForViewSegmentNumbers_H__ #define __DataSymmetriesForViewSegmentNumbers_H__ -#include "stir/common.h" +#include "stir/ViewSegmentNumbers.h" #include START_NAMESPACE_STIR -class ViewSegmentNumbers; - #if 0 class ViewSegmentIndexRange; #endif @@ -46,6 +44,8 @@ class ViewSegmentIndexRange; The class mainly defines members to find \c basic ViewSegmentNumbers. These form a 'basis' for all ViewSegmentNumbers in the sense that all ViewSegmentNumbers can be obtained by using symmetry operations on the 'basic' ones. + + \par Warning: This class wil be renamed/revised to work with \c ViewgramIndices instead. */ class DataSymmetriesForViewSegmentNumbers { @@ -74,12 +74,26 @@ class DataSymmetriesForViewSegmentNumbers virtual void get_related_view_segment_numbers(std::vector&, const ViewSegmentNumbers& v_s) const = 0; +#if 0 + // not yet, as would need copying of vector + //! fills in a vector with all the view/segments that are related to 'v_s' (including itself) + virtual std::vector + get_related_view_segment_numbers(const ViewgramIndices& ind) const + { + } +#endif + //! returns the number of view_segment_numbers related to 'v_s' /*! The default implementation is in terms of get_related_view_segment_numbers, which will be slow of course */ virtual int num_related_view_segment_numbers(const ViewSegmentNumbers& v_s) const; + std::size_t num_related_viewgram_indices(const ViewgramIndices& ind) const + { + return static_cast(num_related_view_segment_numbers(ind)); + } + /*! \brief given an arbitrary view/segment, find the basic view/segment sets 'v_s' to the corresponding 'basic' view/segment and returns true if From 266a025104ce6727d2363b3eb49dd122bbe661d3 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Mon, 16 Oct 2023 20:49:23 +0100 Subject: [PATCH 340/509] fix assert statement after updating to *Indices --- src/include/stir/Sinogram.inl | 8 ++++---- src/include/stir/Viewgram.inl | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/include/stir/Sinogram.inl b/src/include/stir/Sinogram.inl index e381c6de94..90c3a28590 100644 --- a/src/include/stir/Sinogram.inl +++ b/src/include/stir/Sinogram.inl @@ -103,8 +103,8 @@ Sinogram(const Array<2,elemT>& p, proj_data_info_ptr(pdi_ptr), _indices(ind) { - assert(axial_pos_num <= proj_data_info_ptr->get_max_axial_pos_num(ind.segment_num())); - assert(axial_pos_num >= proj_data_info_ptr->get_min_axial_pos_num(ind.segment_num())); + assert(ind.axial_pos_num() <= proj_data_info_ptr->get_max_axial_pos_num(ind.segment_num())); + assert(ind.axial_pos_num() >= proj_data_info_ptr->get_min_axial_pos_num(ind.segment_num())); // segment_num is already checked by doing get_max_axial_pos_num(s_num) assert( get_min_view_num() == pdi_ptr->get_min_view_num()); @@ -127,8 +127,8 @@ Sinogram(const shared_ptr& pdi_ptr, proj_data_info_ptr(pdi_ptr), _indices(ind) { - assert(axial_pos_num <= proj_data_info_ptr->get_max_axial_pos_num(ind.segment_num())); - assert(axial_pos_num >= proj_data_info_ptr->get_min_axial_pos_num(ind.segment_num())); + assert(ind.axial_pos_num() <= proj_data_info_ptr->get_max_axial_pos_num(ind.segment_num())); + assert(ind.axial_pos_num() >= proj_data_info_ptr->get_min_axial_pos_num(ind.segment_num())); // segment_num is already checked by doing get_max_axial_pos_num(s_num) } diff --git a/src/include/stir/Viewgram.inl b/src/include/stir/Viewgram.inl index d8327d9f64..d675a471cb 100644 --- a/src/include/stir/Viewgram.inl +++ b/src/include/stir/Viewgram.inl @@ -103,8 +103,8 @@ Viewgram(const Array<2,elemT>& p, Array<2,elemT>(p), proj_data_info_sptr(pdi_sptr), _indices(ind) { - assert(view_num <= proj_data_info_sptr->get_max_view_num()); - assert(view_num >= proj_data_info_sptr->get_min_view_num()); + assert(ind.view_num() <= proj_data_info_sptr->get_max_view_num()); + assert(ind.view_num() >= proj_data_info_sptr->get_min_view_num()); // segment_num is already checked by doing get_max_axial_pos_num(s_num) assert( get_min_axial_pos_num() == pdi_sptr->get_min_axial_pos_num(ind.segment_num())); @@ -125,8 +125,8 @@ Viewgram(const shared_ptr& pdi_sptr, proj_data_info_sptr(pdi_sptr), _indices(ind) { - assert(view_num <= proj_data_info_sptr->get_max_view_num()); - assert(view_num >= proj_data_info_sptr->get_min_view_num()); + assert(ind.view_num() <= proj_data_info_sptr->get_max_view_num()); + assert(ind.view_num() >= proj_data_info_sptr->get_min_view_num()); // segment_num is already checked by doing get_max_axial_pos_num(s_num) } From 0e48ad5b32d3c961098b605f8cbc76cc6caaec6f Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Wed, 18 Oct 2023 22:20:57 +0100 Subject: [PATCH 341/509] minor updates to release notes for v5.2 - fixes to text on SegmentIndices etc - flag that we will require C++ 14 in the next major version --- documentation/release_5.2.htm | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/documentation/release_5.2.htm b/documentation/release_5.2.htm index 631b121222..3f06a7bd1a 100644 --- a/documentation/release_5.2.htm +++ b/documentation/release_5.2.htm @@ -135,9 +135,11 @@

    Deprecated functionality

  15. The following functions (previously used for upsampling the scatter estimate) have been made obsolete or replaced, and will be removed in STIR version 6.0.0: interpolate_axial_position, extend_sinogram_in_views and extend_segment_in_views
  16. -
  17. Constructors/functions in ProjData related classes that explicitly use axial_pos_num, view_num in their arguments are now deprecated, - and should be replaced by their respective versions that use SegmentIndices, ViewgramIndices and SinogramIndices. +
  18. Constructors/functions in ProjData related classes that explicitly use axial_pos_num, view_num etc in their arguments are now deprecated, + and should be replaced by their respective versions that use SegmentIndices, ViewgramIndices or SinogramIndices. The former will not be + compatible with TOF information that will be introduced in version 6.0.0.
  19. +
  20. STIR version 6.0.0 will likely require C++ 14 (currently we require C++ 11, but already support C++ 20).
  21. Build system and dependencies

    From 3b1bae5ccb9deacd137d62fa9ff93dd50c030141 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Tue, 24 Oct 2023 21:43:31 +0100 Subject: [PATCH 342/509] SPECTUB: write warning if image radius is larger than detector radius --- src/recon_buildblock/ProjMatrixByBinSPECTUB.cxx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/recon_buildblock/ProjMatrixByBinSPECTUB.cxx b/src/recon_buildblock/ProjMatrixByBinSPECTUB.cxx index 6b70f68bff..c24bc7d78a 100644 --- a/src/recon_buildblock/ProjMatrixByBinSPECTUB.cxx +++ b/src/recon_buildblock/ProjMatrixByBinSPECTUB.cxx @@ -1,7 +1,7 @@ /* Copyright (C) 2013, Institute for Bioengineering of Catalonia Copyright (C) Biomedical Image Group (GIB), Universitat de Barcelona, Barcelona, Spain. - Copyright (C) 2013-2014, 2019, 2020 University College London + Copyright (C) 2013-2014, 2019, 2020, 2023 University College London Copyright (C) 2023 National Physical Laboratory This file is part of STIR. @@ -352,6 +352,15 @@ set_up( const VectorWithOffset radius_all_views = proj_Data_Info_Cylindrical->get_ring_radii_for_all_views(); + { + const auto max_radius = *std::max_element(radius_all_views.begin(), radius_all_views.end()); + const auto max_im_radius = std::max(vol.Xcmd2, vol.Ycmd2)*10; + if (max_im_radius > max_radius) + { + warning("Image radius (" + std::to_string(max_im_radius) + " is larger than max detector radius (" + + std::to_string(max_radius) + "). Are you sure this is correct? (Proceeding anyway)"); + } + } Rrad = new float [ wmh.prj.Nang ]; for ( int i = 0 ; i < wmh.prj.Nang ; i++ ) { // note: convert to cm for UB SPECT library From 594f1130068a89f3d442cce4dbc22f93d271289b Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Wed, 25 Oct 2023 07:55:41 +0100 Subject: [PATCH 343/509] fix buffer-overrun when parsing if no keyword on line --- src/buildblock/KeyParser.cxx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/buildblock/KeyParser.cxx b/src/buildblock/KeyParser.cxx index 471cf46f2d..ab609755a7 100644 --- a/src/buildblock/KeyParser.cxx +++ b/src/buildblock/KeyParser.cxx @@ -309,9 +309,9 @@ string KeyParser::get_keyword(const string& line) const { // keyword stops at either := or an index [] - string::size_type eok = line.find_first_of(":[",0); + auto eok = line.find_first_of(":[",0); // check that = follows : to allow keywords containing colons - while (line[eok] == ':' && eok+1 < line.size() && line[eok+1] != '=') + while (eok != string::npos && line[eok] == ':' && eok+1 < line.size() && line[eok+1] != '=') { eok = line.find_first_of(":[", eok+1); } From 65cb740f33e43f4d20f2cc88315ae024986e86f0 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Wed, 25 Oct 2023 09:27:17 +0100 Subject: [PATCH 344/509] [CMake] avoid CMP0023 warning add PUBLIC in most target_link_libraries statements (except if PRIVATE)_ --- src/IO/CMakeLists.txt | 20 +++++++++---------- src/Shape_buildblock/CMakeLists.txt | 2 +- src/analytic/FBP2D/CMakeLists.txt | 2 +- src/analytic/FBP3DRP/CMakeLists.txt | 2 +- src/buildblock/CMakeLists.txt | 8 ++++---- src/data_buildblock/CMakeLists.txt | 2 +- src/display/CMakeLists.txt | 4 ++-- src/eval_buildblock/CMakeLists.txt | 2 +- src/experimental/buildblock/CMakeLists.txt | 2 +- src/experimental/listmode/CMakeLists.txt | 2 +- src/experimental/motion/CMakeLists.txt | 2 +- .../motion_utilities/CMakeLists.txt | 2 +- .../recon_buildblock/CMakeLists.txt | 2 +- src/iterative/KOSMAPOSL/CMakeLists.txt | 2 +- src/iterative/OSMAPOSL/CMakeLists.txt | 2 +- src/iterative/OSSPS/CMakeLists.txt | 2 +- src/listmode_buildblock/CMakeLists.txt | 2 +- src/modelling_buildblock/CMakeLists.txt | 2 +- src/numerics_buildblock/CMakeLists.txt | 2 +- src/recon_buildblock/CMakeLists.txt | 18 ++++++++--------- src/scatter_buildblock/CMakeLists.txt | 2 +- .../CMakeLists.txt | 2 +- src/swig/CMakeLists.txt | 10 +++++----- src/utilities/UPENN/CMakeLists.txt | 2 +- 24 files changed, 49 insertions(+), 49 deletions(-) diff --git a/src/IO/CMakeLists.txt b/src/IO/CMakeLists.txt index 14401c4092..c225214236 100644 --- a/src/IO/CMakeLists.txt +++ b/src/IO/CMakeLists.txt @@ -83,33 +83,33 @@ endif() include(stir_lib_target) if (LLN_FOUND) - target_link_libraries(IO ${LLN_LIBRARIES}) + target_link_libraries(IO PUBLIC ${LLN_LIBRARIES}) endif() if (CERN_ROOT_FOUND) target_include_directories(IO PRIVATE ${CERN_ROOT_INCLUDE_DIRS}) if (TARGET ROOT::Tree) - target_link_libraries(IO ROOT::Tree) + target_link_libraries(IO PUBLIC ROOT::Tree) else() - target_link_libraries(IO ${CERN_ROOT_LIBRARIES}) + target_link_libraries(IO PUBLIC ${CERN_ROOT_LIBRARIES}) endif() endif() if (HAVE_HDF5) - target_link_libraries(IO ${HDF5_CXX_LIBRARIES}) + target_link_libraries(IO PUBLIC ${HDF5_CXX_LIBRARIES}) endif() if (AVW_FOUND) - target_link_libraries(IO ${AVW_LIBRARIES}) + target_link_libraries(IO PUBLIC ${AVW_LIBRARIES}) endif() if (HAVE_ITK) - target_link_libraries(IO ITKCommon ${ITK_LIBRARIES}) + target_link_libraries(IO PUBLIC ITKCommon ${ITK_LIBRARIES}) endif() if (UPENN_FOUND) target_include_directories(IO PUBLIC ${UPENN_INCLUDE_DIR}) - target_link_libraries(IO ${UPENN_libsss_tof} ${UPENN_libfit} ${UPENN_libdist} ${UPENN_libgeom} + target_link_libraries(IO PUBLIC ${UPENN_libsss_tof} ${UPENN_libfit} ${UPENN_libdist} ${UPENN_libgeom} ${UPENN_liblor} ${UPENN_liblist} ${UPENN_libmhdr} ${JANSSON_LIBRARY} ${ZLIB_LIBRARY_RELEASE} ${UPENN_libimagio} ${UPENN_libimagio++}) endif() @@ -119,7 +119,7 @@ if (HAVE_JSON) # Unfortunately, the simple line below exports the dependency while this is really not # necessary. # - # target_link_libraries(IO PRIVATE "$") + # target_link_libraries(IO PUBLIC PRIVATE "$") # So, we currently use an ugly work-around from # https://gitlab.kitware.com/cmake/cmake/-/issues/15415#note_334852 @@ -131,5 +131,5 @@ if (HAVE_JSON) endif() # currently needed for ParametricDensity (TODO get rid of this somehow?) -target_link_libraries(IO modelling_buildblock ) -target_link_libraries(IO listmode_buildblock) +target_link_libraries(IO PUBLIC modelling_buildblock ) +target_link_libraries(IO PUBLIC listmode_buildblock) diff --git a/src/Shape_buildblock/CMakeLists.txt b/src/Shape_buildblock/CMakeLists.txt index 61e3a51232..05b8136015 100644 --- a/src/Shape_buildblock/CMakeLists.txt +++ b/src/Shape_buildblock/CMakeLists.txt @@ -18,4 +18,4 @@ set(${dir_LIB_SOURCES} include(stir_lib_target) -target_link_libraries(Shape_buildblock buildblock IO numerics_buildblock ) +target_link_libraries(Shape_buildblock PUBLIC buildblock IO numerics_buildblock ) diff --git a/src/analytic/FBP2D/CMakeLists.txt b/src/analytic/FBP2D/CMakeLists.txt index d72dce63e0..533520f440 100644 --- a/src/analytic/FBP2D/CMakeLists.txt +++ b/src/analytic/FBP2D/CMakeLists.txt @@ -13,7 +13,7 @@ set(${dir_LIB_SOURCES} #$(dir)_REGISTRY_SOURCES:= include(stir_lib_target) -target_link_libraries(analytic_FBP2D recon_buildblock IO ) +target_link_libraries(analytic_FBP2D PUBLIC recon_buildblock IO ) set (dir_EXE_SOURCES ${dir}_EXE_SOURCES) diff --git a/src/analytic/FBP3DRP/CMakeLists.txt b/src/analytic/FBP3DRP/CMakeLists.txt index 892b14c445..860bb5156a 100644 --- a/src/analytic/FBP3DRP/CMakeLists.txt +++ b/src/analytic/FBP3DRP/CMakeLists.txt @@ -13,7 +13,7 @@ set(${dir_LIB_SOURCES} #$(dir)_REGISTRY_SOURCES:= include(stir_lib_target) -target_link_libraries(analytic_FBP3DRP analytic_FBP2D recon_buildblock ) +target_link_libraries(analytic_FBP3DRP PUBLIC analytic_FBP2D recon_buildblock ) set (dir_EXE_SOURCES ${dir}_EXE_SOURCES) diff --git a/src/buildblock/CMakeLists.txt b/src/buildblock/CMakeLists.txt index 7842ba7771..1d90c88c6a 100644 --- a/src/buildblock/CMakeLists.txt +++ b/src/buildblock/CMakeLists.txt @@ -130,17 +130,17 @@ endif() # TODO Remove but currently needed for ProjData.cxx, DynamicDisc*cxx, TimeFrameDef if (LLN_FOUND) - target_link_libraries(buildblock ${LLN_LIBRARIES}) + target_link_libraries(buildblock PUBLIC ${LLN_LIBRARIES}) endif() if (RDF_FOUND) # TODO cannot do this as it creates circular dependencies - # target_link_libraries(buildblock local_IO_GE) + # target_link_libraries(buildblock PUBLIC local_IO_GE) endif() # TODO currently needed as filters need fourier -#target_link_libraries(buildblock numerics_buildblock) +#target_link_libraries(buildblock PUBLIC numerics_buildblock) if (STIR_OPENMP) - target_link_libraries(buildblock ${OpenMP_EXE_LINKER_FLAGS}) + target_link_libraries(buildblock PUBLIC ${OpenMP_EXE_LINKER_FLAGS}) endif() diff --git a/src/data_buildblock/CMakeLists.txt b/src/data_buildblock/CMakeLists.txt index de99dbf5a2..e04f5c4529 100644 --- a/src/data_buildblock/CMakeLists.txt +++ b/src/data_buildblock/CMakeLists.txt @@ -35,4 +35,4 @@ endif() include(stir_lib_target) -target_link_libraries(${dir} buildblock) +target_link_libraries(${dir} PUBLIC buildblock) diff --git a/src/display/CMakeLists.txt b/src/display/CMakeLists.txt index dc71408145..05d1a5147b 100644 --- a/src/display/CMakeLists.txt +++ b/src/display/CMakeLists.txt @@ -46,7 +46,7 @@ include(stir_lib_target) if( "${GRAPHICS}" STREQUAL "X") find_package(Curses REQUIRED) INCLUDE_DIRECTORIES(${X11_INCLUDE_DIR} ${CURSES_INCLUDE_DIR}) - target_link_libraries(${dir} ${X11_LIBRARIES} ${CURSES_LIBRARY}) + target_link_libraries(${dir} PUBLIC ${X11_LIBRARIES} ${CURSES_LIBRARY}) endif() -target_link_libraries(${dir} buildblock) +target_link_libraries(${dir} PUBLIC buildblock) diff --git a/src/eval_buildblock/CMakeLists.txt b/src/eval_buildblock/CMakeLists.txt index 15dd6ac419..2f640cf30b 100644 --- a/src/eval_buildblock/CMakeLists.txt +++ b/src/eval_buildblock/CMakeLists.txt @@ -15,6 +15,6 @@ set(${dir_LIB_SOURCES} include(stir_lib_target) -target_link_libraries(eval_buildblock buildblock ) +target_link_libraries(eval_buildblock PUBLIC buildblock ) diff --git a/src/experimental/buildblock/CMakeLists.txt b/src/experimental/buildblock/CMakeLists.txt index d68f8aa9bf..519a6461ef 100644 --- a/src/experimental/buildblock/CMakeLists.txt +++ b/src/experimental/buildblock/CMakeLists.txt @@ -8,5 +8,5 @@ set(${dir_LIB_SOURCES} include(stir_lib_target) -target_link_libraries(${dir} buildblock) +target_link_libraries(${dir} PUBLIC buildblock) diff --git a/src/experimental/listmode/CMakeLists.txt b/src/experimental/listmode/CMakeLists.txt index 30ae63c8e8..845f467f75 100644 --- a/src/experimental/listmode/CMakeLists.txt +++ b/src/experimental/listmode/CMakeLists.txt @@ -16,4 +16,4 @@ endif() include(stir_lib_target) -target_link_libraries(local_listmode_buildblock listmode_buildblock ) +target_link_libraries(local_listmode_buildblock PUBLIC listmode_buildblock ) diff --git a/src/experimental/motion/CMakeLists.txt b/src/experimental/motion/CMakeLists.txt index c9bc2989ef..21e2834088 100644 --- a/src/experimental/motion/CMakeLists.txt +++ b/src/experimental/motion/CMakeLists.txt @@ -11,5 +11,5 @@ set(${dir_LIB_SOURCES} include(stir_lib_target) -target_link_libraries(${dir} numerics_buildblock local_buildblock ) +target_link_libraries(${dir} PUBLIC numerics_buildblock local_buildblock ) diff --git a/src/experimental/motion_utilities/CMakeLists.txt b/src/experimental/motion_utilities/CMakeLists.txt index 7397b263e8..58299a07f9 100644 --- a/src/experimental/motion_utilities/CMakeLists.txt +++ b/src/experimental/motion_utilities/CMakeLists.txt @@ -22,7 +22,7 @@ set(${dir_EXE_SOURCES} #include(stir_exe_targets) foreach(executable ${${dir_EXE_SOURCES}}) add_executable(${executable} ${executable} ${STIR_IO_REGISTRIES} ) - target_link_libraries(${executable} buildblock IO buildblock local_motion_buildblock buildblock IO buildblock listmode_buildblock display) + target_link_libraries(${executable} PUBLIC buildblock IO buildblock local_motion_buildblock buildblock IO buildblock listmode_buildblock display) SET_PROPERTY(TARGET ${executable} PROPERTY FOLDER "Executables") endforeach() diff --git a/src/experimental/recon_buildblock/CMakeLists.txt b/src/experimental/recon_buildblock/CMakeLists.txt index 668004ebc6..7abfbca267 100644 --- a/src/experimental/recon_buildblock/CMakeLists.txt +++ b/src/experimental/recon_buildblock/CMakeLists.txt @@ -22,7 +22,7 @@ set(${dir_LIB_SOURCES} include(stir_lib_target) -target_link_libraries(local_recon_buildblock display buildblock recon_buildblock) +target_link_libraries(local_recon_buildblock PUBLIC display buildblock recon_buildblock) diff --git a/src/iterative/KOSMAPOSL/CMakeLists.txt b/src/iterative/KOSMAPOSL/CMakeLists.txt index 3858baee17..cc59bb67f4 100644 --- a/src/iterative/KOSMAPOSL/CMakeLists.txt +++ b/src/iterative/KOSMAPOSL/CMakeLists.txt @@ -23,5 +23,5 @@ if (STIR_MPI) SET_PROPERTY(TARGET KOSMAPOSL PROPERTY LINK_FLAGS ${MPI_CXX_LINK_FLAGS}) endif() -target_link_libraries(iterative_KOSMAPOSL recon_buildblock ) +target_link_libraries(iterative_KOSMAPOSL PUBLIC recon_buildblock ) diff --git a/src/iterative/OSMAPOSL/CMakeLists.txt b/src/iterative/OSMAPOSL/CMakeLists.txt index 64ef89a2e0..d1f26ec13e 100644 --- a/src/iterative/OSMAPOSL/CMakeLists.txt +++ b/src/iterative/OSMAPOSL/CMakeLists.txt @@ -23,5 +23,5 @@ if (STIR_MPI) SET_PROPERTY(TARGET OSMAPOSL PROPERTY LINK_FLAGS ${MPI_CXX_LINK_FLAGS}) endif() -target_link_libraries(iterative_OSMAPOSL recon_buildblock ) +target_link_libraries(iterative_OSMAPOSL PUBLIC recon_buildblock ) diff --git a/src/iterative/OSSPS/CMakeLists.txt b/src/iterative/OSSPS/CMakeLists.txt index d5f7aafdf8..9e7f77a055 100644 --- a/src/iterative/OSSPS/CMakeLists.txt +++ b/src/iterative/OSSPS/CMakeLists.txt @@ -24,4 +24,4 @@ if (STIR_MPI) SET_PROPERTY(TARGET OSSPS PROPERTY LINK_FLAGS ${MPI_CXX_LINK_FLAGS}) endif() -target_link_libraries(iterative_OSSPS recon_buildblock ) +target_link_libraries(iterative_OSSPS PUBLIC recon_buildblock ) diff --git a/src/listmode_buildblock/CMakeLists.txt b/src/listmode_buildblock/CMakeLists.txt index b21d21d3dc..baed95da42 100644 --- a/src/listmode_buildblock/CMakeLists.txt +++ b/src/listmode_buildblock/CMakeLists.txt @@ -57,4 +57,4 @@ endif() include(stir_lib_target) -target_link_libraries(listmode_buildblock data_buildblock ) +target_link_libraries(listmode_buildblock PUBLIC data_buildblock ) diff --git a/src/modelling_buildblock/CMakeLists.txt b/src/modelling_buildblock/CMakeLists.txt index aab6d86d89..aed39c0c1a 100644 --- a/src/modelling_buildblock/CMakeLists.txt +++ b/src/modelling_buildblock/CMakeLists.txt @@ -15,4 +15,4 @@ set(${dir_LIB_SOURCES} include(stir_lib_target) -target_link_libraries(modelling_buildblock buildblock IO) +target_link_libraries(modelling_buildblock PUBLIC buildblock IO) diff --git a/src/numerics_buildblock/CMakeLists.txt b/src/numerics_buildblock/CMakeLists.txt index 823e78dafd..c6e8f4158c 100644 --- a/src/numerics_buildblock/CMakeLists.txt +++ b/src/numerics_buildblock/CMakeLists.txt @@ -13,4 +13,4 @@ set(${dir_LIB_SOURCES} include(stir_lib_target) -target_link_libraries(${dir} buildblock) +target_link_libraries(${dir} PUBLIC buildblock) diff --git a/src/recon_buildblock/CMakeLists.txt b/src/recon_buildblock/CMakeLists.txt index 711fd42977..89d3c5e272 100644 --- a/src/recon_buildblock/CMakeLists.txt +++ b/src/recon_buildblock/CMakeLists.txt @@ -131,27 +131,27 @@ include(stir_lib_target) if (STIR_MPI) - target_link_libraries(recon_buildblock ${MPI_CXX_LIBRARIES}) + target_link_libraries(recon_buildblock PUBLIC ${MPI_CXX_LIBRARIES}) endif() if (STIR_OPENMP) - target_link_libraries(recon_buildblock ${OpenMP_EXE_LINKER_FLAGS}) + target_link_libraries(recon_buildblock PUBLIC ${OpenMP_EXE_LINKER_FLAGS}) endif() # TODO what to do with IO? # modelling_buildblock currently needed for ParametricDensity and Patlak (TODO get rid of this somehow?) -target_link_libraries(recon_buildblock modelling_buildblock display numerics_buildblock listmode_buildblock data_buildblock buildblock spatial_transformation_buildblock ) +target_link_libraries(recon_buildblock PUBLIC modelling_buildblock display numerics_buildblock listmode_buildblock data_buildblock buildblock spatial_transformation_buildblock ) if (STIR_WITH_NiftyPET_PROJECTOR) - target_link_libraries(recon_buildblock NiftyPET::petprj) - target_link_libraries(recon_buildblock NiftyPET::mmr_auxe) - target_link_libraries(recon_buildblock NiftyPET::mmr_lmproc) - target_link_libraries(recon_buildblock CUDA::cudart) + target_link_libraries(recon_buildblock PUBLIC NiftyPET::petprj) + target_link_libraries(recon_buildblock PUBLIC NiftyPET::mmr_auxe) + target_link_libraries(recon_buildblock PUBLIC NiftyPET::mmr_lmproc) + target_link_libraries(recon_buildblock PUBLIC CUDA::cudart) endif() if (STIR_WITH_Parallelproj_PROJECTOR) - target_link_libraries(recon_buildblock parallelproj::parallelproj_c) + target_link_libraries(recon_buildblock PUBLIC parallelproj::parallelproj_c) if (parallelproj_built_with_CUDA) - target_link_libraries(recon_buildblock parallelproj::parallelproj_cuda) + target_link_libraries(recon_buildblock PUBLIC parallelproj::parallelproj_cuda) endif() endif() diff --git a/src/scatter_buildblock/CMakeLists.txt b/src/scatter_buildblock/CMakeLists.txt index abc5527cf4..1a22da605d 100644 --- a/src/scatter_buildblock/CMakeLists.txt +++ b/src/scatter_buildblock/CMakeLists.txt @@ -22,4 +22,4 @@ set(${dir_LIB_SOURCES} include(stir_lib_target) -target_link_libraries(scatter_buildblock recon_buildblock) +target_link_libraries(scatter_buildblock PUBLIC recon_buildblock) diff --git a/src/spatial_transformation_buildblock/CMakeLists.txt b/src/spatial_transformation_buildblock/CMakeLists.txt index a26196e431..0434d5836c 100644 --- a/src/spatial_transformation_buildblock/CMakeLists.txt +++ b/src/spatial_transformation_buildblock/CMakeLists.txt @@ -23,4 +23,4 @@ set(${dir_LIB_SOURCES} include(stir_lib_target) -target_link_libraries(${dir} buildblock numerics_buildblock ) +target_link_libraries(${dir} PUBLIC buildblock numerics_buildblock ) diff --git a/src/swig/CMakeLists.txt b/src/swig/CMakeLists.txt index 51b3a5abac..984ff9f459 100644 --- a/src/swig/CMakeLists.txt +++ b/src/swig/CMakeLists.txt @@ -156,8 +156,8 @@ if(BUILD_SWIG_PYTHON) endif() endif() SWIG_WORKAROUND(${SWIG_MODULE_stir_REAL_NAME}) - SWIG_LINK_LIBRARIES(stir ${STIR_LIBRARIES} ${STIR_Python_dependency}) - target_link_libraries(${SWIG_MODULE_stir_REAL_NAME} ${OpenMP_EXE_LINKER_FLAGS}) + SWIG_LINK_LIBRARIES(stir PUBLIC ${STIR_LIBRARIES} ${STIR_Python_dependency}) + target_link_libraries(${SWIG_MODULE_stir_REAL_NAME} PUBLIC ${OpenMP_EXE_LINKER_FLAGS}) CONFIGURE_FILE(./pyfragments.swg ./ COPYONLY) set(PYTHON_DEST ${CMAKE_INSTALL_PREFIX}/python CACHE PATH "Destination for python module") @@ -207,7 +207,7 @@ if (BUILD_SWIG_OCTAVE) endif() SET_TARGET_PROPERTIES(${SWIG_MODULE_stiroct_REAL_NAME} PROPERTIES SUFFIX ${OCTAVE_SUFFIX} PREFIX "${OCTAVE_PREFIX}") SWIG_WORKAROUND(${SWIG_MODULE_stiroct_REAL_NAME}) - SWIG_LINK_LIBRARIES(stiroct ${STIR_LIBRARIES} ${OCTAVE_LIBRARIES}) + SWIG_LINK_LIBRARIES(stiroct PUBLIC ${STIR_LIBRARIES} ${OCTAVE_LIBRARIES}) # add OCTAVE_INCFLAGS to swig-generated file only, not to all files as # 1) we don't need it at the moment 2) we'd need to change from -Ibla to bla @@ -242,9 +242,9 @@ if (BUILD_SWIG_MATLAB) LINK_FLAGS "${Matlab_CXXLINKER_FLAGS}" FOLDER "Matlab") SWIG_WORKAROUND(${SWIG_MODULE_stirMATLAB_REAL_NAME}) - target_link_libraries(${SWIG_MODULE_stirMATLAB_REAL_NAME} ${OpenMP_EXE_LINKER_FLAGS}) + target_link_libraries(${SWIG_MODULE_stirMATLAB_REAL_NAME} PUBLIC ${OpenMP_EXE_LINKER_FLAGS}) - SWIG_LINK_LIBRARIES(stirMATLAB ${STIR_LIBRARIES} ${Matlab_LIBRARIES}) + SWIG_LINK_LIBRARIES(stirMATLAB PUBLIC ${STIR_LIBRARIES} ${Matlab_LIBRARIES}) include_directories(${Matlab_INCLUDE_DIRS}) # disabled, as currently set via add_definitions in main CMakeLists.txt diff --git a/src/utilities/UPENN/CMakeLists.txt b/src/utilities/UPENN/CMakeLists.txt index 54fe8d1337..c0d6a0d771 100644 --- a/src/utilities/UPENN/CMakeLists.txt +++ b/src/utilities/UPENN/CMakeLists.txt @@ -12,5 +12,5 @@ set(${dir_EXE_SOURCES} include(stir_exe_targets) -target_link_libraries(conv_UPENN_projdata_to_STIR ${UPENN_libgeom} +target_link_libraries(conv_UPENN_projdata_to_STIR PUBLIC ${UPENN_libgeom} ${UPENN_liblor} ${UPENN_libimagio} ${UPENN_libimagio++} -lboost_system) From dd219430b31a4c1137cb4917c2ecc7d183f1e0ba Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Thu, 26 Oct 2023 07:20:01 +0100 Subject: [PATCH 345/509] use different min/max thresholds for scatter scale for mMR example [ci skip] Fixes #1163 --- documentation/release_5.2.htm | 5 +- examples/Siemens-mMR/scatter_and_recon.sh | 2 +- .../scatter_estimation_par_files/README.md | 19 ++++ .../postfilter_Gaussian_for_mask.par | 12 +++ .../run_reconstruction.par | 44 ++++++++++ .../scatter_estimation.par | 86 +++++++++++++++++++ .../tail_fitting.par | 4 + 7 files changed, 170 insertions(+), 2 deletions(-) create mode 100644 examples/Siemens-mMR/scatter_estimation_par_files/README.md create mode 100644 examples/Siemens-mMR/scatter_estimation_par_files/postfilter_Gaussian_for_mask.par create mode 100644 examples/Siemens-mMR/scatter_estimation_par_files/run_reconstruction.par create mode 100644 examples/Siemens-mMR/scatter_estimation_par_files/scatter_estimation.par create mode 100644 examples/Siemens-mMR/scatter_estimation_par_files/tail_fitting.par diff --git a/documentation/release_5.2.htm b/documentation/release_5.2.htm index 3f06a7bd1a..8314e859cf 100644 --- a/documentation/release_5.2.htm +++ b/documentation/release_5.2.htm @@ -127,7 +127,10 @@

    Changed functionality


    PR #1243.
  22. The Succeeded class has a new method bool succeeded() enabling more concise code (avoiding the need for comparing with Succeeded::yes which is especially verbose in Python). -
  23. + +
  24. The example files for the Siemens mMR now use lower min/max thresholds for the (single) scatter scale. This gives better results, see Issue #1163. +
    PR #1279. +
  25. Deprecated functionality

    diff --git a/examples/Siemens-mMR/scatter_and_recon.sh b/examples/Siemens-mMR/scatter_and_recon.sh index 9442840da0..fa45238f3e 100755 --- a/examples/Siemens-mMR/scatter_and_recon.sh +++ b/examples/Siemens-mMR/scatter_and_recon.sh @@ -75,7 +75,7 @@ echo "Estimating scatter (be patient). Log saved in output/scatter.log" # filename-prefix for additive sino (i.e. "precorrected" sum of scatter and randoms) total_additive_prefix=output/total_additive num_scat_iters=3 -scatter_pardir=${pardir}/../samples/scatter_estimation_par_files +scatter_pardir=${pardir}/scatter_estimation_par_files # you might have to change this for a different scanner than the mMR scatter_recon_num_subiterations=21 scatter_recon_num_subsets=21 diff --git a/examples/Siemens-mMR/scatter_estimation_par_files/README.md b/examples/Siemens-mMR/scatter_estimation_par_files/README.md new file mode 100644 index 0000000000..1bd540f3b0 --- /dev/null +++ b/examples/Siemens-mMR/scatter_estimation_par_files/README.md @@ -0,0 +1,19 @@ +# Example files for running scatter estimation for the Siemens mMR + +Files made by Nikos Efthimou and fine-tuned by Kris Thielemans.
    +Copyright University of Hull 2018-2019
    +copyright University College London 2016, 2020
    +Distributed under the Apache 2.0 License + +These files are almost identical to those in +[examples/samples/scatter_estimation_par_files/](../../samples/scatter_estimation_par_files/README.md), +see there for some more information. + +Currently the only difference are the lower values for +``` +maximum scatter scaling factor := .5 +minimum scatter scaling factor := 0.1 +``` + +These have been shown to work better for mMR data, see e.g. +[STIR issue #1163](https://github.com/UCL/STIR/issues/1163). diff --git a/examples/Siemens-mMR/scatter_estimation_par_files/postfilter_Gaussian_for_mask.par b/examples/Siemens-mMR/scatter_estimation_par_files/postfilter_Gaussian_for_mask.par new file mode 100644 index 0000000000..d6af00a358 --- /dev/null +++ b/examples/Siemens-mMR/scatter_estimation_par_files/postfilter_Gaussian_for_mask.par @@ -0,0 +1,12 @@ +PostFilteringParameters := + Postfilter type := Separable Gaussian +Separable Gaussian Filter Parameters := +x-dir filter FWHM (in mm):= 20 +y-dir filter FWHM (in mm):= 20 +z-dir filter FWHM (in mm):= 15 +; optionally restrict kernel sizes +; x-dir maximum kernel size := 129 +; y-dir maximum kernel size := 129 +; z-dir maximum kernel size := 31 +END Separable Gaussian Filter Parameters := +End PostFiltering Parameters:= diff --git a/examples/Siemens-mMR/scatter_estimation_par_files/run_reconstruction.par b/examples/Siemens-mMR/scatter_estimation_par_files/run_reconstruction.par new file mode 100644 index 0000000000..166bdff2e1 --- /dev/null +++ b/examples/Siemens-mMR/scatter_estimation_par_files/run_reconstruction.par @@ -0,0 +1,44 @@ +Reconstruction Parameters := +reconstruction type := OSMAPOSL +OSMAPOSLParameters := + +objective function type:= PoissonLogLikelihoodWithLinearModelForMeanAndProjData +PoissonLogLikelihoodWithLinearModelForMeanAndProjData Parameters:= +maximum absolute segment number to process := -1 + +projector pair type := Matrix + Projector Pair Using Matrix Parameters := + Matrix type := Ray Tracing + Ray tracing matrix parameters := + number of rays in tangential direction to trace for each bin:= 5 + End Ray tracing matrix parameters := + End Projector Pair Using Matrix Parameters := + +;recompute sensitivity := 0 +;subset sensitivity filenames := scatter_subset_sens_%d.hv + +; reconstruct at large voxel size to save time +zoom := 0.2 + +end PoissonLogLikelihoodWithLinearModelForMeanAndProjData Parameters:= + +; initial estimate := +enforce initial positivity condition:=1 + +number of subsets:= ${scatter_recon_num_subsets} +number of subiterations:=${scatter_recon_num_subiterations} +;save estimates at subiteration intervals:= ${scatter_recon_num_subiterations} + +; smooth a bit as we use a down-sampled scanner (during the scatter estimation resolution can be low) +post-filter type := Separable Gaussian +Separable Gaussian Filter Parameters := + x-dir filter FWHM (in mm):= 15 + y-dir filter FWHM (in mm):= 15 + z-dir filter FWHM (in mm):= 15 +END Separable Gaussian Filter Parameters := +; +; Disable output +; +disable output := 1 +End OSMAPOSLParameters:= +End reconstruction Parameters:= diff --git a/examples/Siemens-mMR/scatter_estimation_par_files/scatter_estimation.par b/examples/Siemens-mMR/scatter_estimation_par_files/scatter_estimation.par new file mode 100644 index 0000000000..771f0daf85 --- /dev/null +++ b/examples/Siemens-mMR/scatter_estimation_par_files/scatter_estimation.par @@ -0,0 +1,86 @@ +Scatter Estimation Parameters := + +;Run in debug mode +;; A new folder called extras will be created, in which many +;; extra files will be stored +run in debug mode := 1 + +; Measured data +input file := ${sino_input} + +; Attenuation Image +attenuation image filename := ${atnimg} + +; Normalisation coefficients & attenuation data +Normalisation type := from ProjData + Bin Normalisation From ProjData := + normalisation projdata filename:= ${NORM} + End Bin Normalisation From ProjData:= + +attenuation correction factors filename := ${acf3d} + +;; Background data (not normalised). +; Should be set to the randoms estimate (unless you precorrected, but we haven't tested that) +background projdata filename := ${randoms3d} + +; Mask for tail-fitting +; It will be computed by masking the attenuation image, and forward projecting that. +; If !recompute mask projdata then the filename must be set. +recompute mask projdata := 1 +mask projdata filename := ${mask_projdata_filename} + +; Input or output filename - depends on recompute +recompute mask image := 1 +mask image filename := ${mask_image} +; threshold to be applied after filtering (in cm^-1). Default value is below +mask attenuation image min threshold := 0.003 +; optional filename to specify a filter before thresholding the attenuation image +; By default a Gaussian filter with FWHM (15,20,20) will be used. Here we use an explicit file as an example. +mask attenuation image filter filename := ${scatter_pardir}/postfilter_Gaussian_for_mask.par +;End of Mask + +;Parameter file for the tail fitting of the scatter data (within the mask) +tail fitting parameter filename := ${scatter_pardir}/tail_fitting.par + +; Run simulation and reconstruction in 2D and export SSRB sinograms (currently required) +run in 2d projdata := 1 + +; ScatterSimulation parameters +; could read from a file, but instead we have them below +; scatter simulation parameter filename := ${scatter_pardir}/scatter_simulation.par +Scatter Simulation type := PET Single Scatter Simulation + PET Single Scatter Simulation Parameters := + ; could change some parameters here if you need to (not recommended) + End PET Single Scatter Simulation Parameters:= + +; next option is the default +use scanner downsampling in scatter simulation := 1 + +; could add parameters below, but reading it from file +; reconstruction type := ... +reconstruction parameter filename := ${scatter_pardir}/run_reconstruction.par + +; +; This is the number of times which the Scatter Estimation will +; iterate. Default is 5 + +number of scatter iterations := ${num_scat_iters} + +; Average the first two activity images +do average at 2 := 1 + +; Export scatter estimates of each iteration +export scatter estimates of each iteration := 1 + +output scatter estimate name prefix := ${scatter_prefix} +output additive estimate name prefix:= ${total_additive_prefix} + +maximum scatter scaling factor := 0.4 +minimum scatter scaling factor := 0.1 + +;Upsample and fit +; defaults to 3. +upsampling half filter width := 3 +remove interleaving before upsampling := 1 + +End Scatter Estimation Parameters := diff --git a/examples/Siemens-mMR/scatter_estimation_par_files/tail_fitting.par b/examples/Siemens-mMR/scatter_estimation_par_files/tail_fitting.par new file mode 100644 index 0000000000..dc0d1459f4 --- /dev/null +++ b/examples/Siemens-mMR/scatter_estimation_par_files/tail_fitting.par @@ -0,0 +1,4 @@ +CreateTailMaskFromACFs := + ACF-threshold := 1.1 + safety-margin := 4 +END CreateTailMaskFromACFs := From 213e2377b953c2cb74baf2da9f2a71293269f161 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Thu, 26 Oct 2023 08:32:37 +0100 Subject: [PATCH 346/509] update warnings - make warning on too small num_tangential_LORs clearer - remove warning on too large voxel-size for backprojection, as the matrix will always give artefacts for backprojecting (unless using many many LORs) --- .../ProjMatrixByBinUsingRayTracing.cxx | 27 +++++++++---------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/src/recon_buildblock/ProjMatrixByBinUsingRayTracing.cxx b/src/recon_buildblock/ProjMatrixByBinUsingRayTracing.cxx index 291aa0823d..1c420c615f 100644 --- a/src/recon_buildblock/ProjMatrixByBinUsingRayTracing.cxx +++ b/src/recon_buildblock/ProjMatrixByBinUsingRayTracing.cxx @@ -1,7 +1,7 @@ /* Copyright (C) 2000 PARAPET partners Copyright (C) 2000-2011, Hammersmith Imanet Ltd - Copyright (C) 2013-2014, University College London + Copyright (C) 2013-2014, 2018, 2019, 2021, 2023 University College London Copyright 2017 ETH Zurich, Institute of Particle Physics and Astrophysics This file is part of STIR. @@ -297,14 +297,11 @@ set_up( if(sampling_distance_of_adjacent_LORs_xy/num_tangential_LORs > voxel_size.x() + 1.E-3 || sampling_distance_of_adjacent_LORs_xy/num_tangential_LORs > voxel_size.y() + 1.E-3) - warning("WARNING: ProjMatrixByBinUsingRayTracing used for pixel size (in x,y) " - "that is smaller than the bin size divided by num_tangential_LORs.\n" - "This matrix will completely miss some voxels for some (or all) views.\n"); - if(sampling_distance_of_adjacent_LORs_xy < voxel_size.x() - 1.E-3 || - sampling_distance_of_adjacent_LORs_xy < voxel_size.y() - 1.E-3) - warning("WARNING: ProjMatrixByBinUsingRayTracing used for pixel size (in x,y) " - "that is larger than the bin size.\n" - "Backprojecting with this matrix might have artefacts at views 0 and 90 degrees.\n"); + warning(boost::format("ProjMatrixByBinUsingRayTracing used for pixel size (x,y)=(%g,%g) " + "that is smaller than the central bin size (%g) divided by num_tangential_LORs (%d).\n" + "This matrix will completely miss some voxels for some (or all) views. It is therefore to best to increase " + "'number of rays in tangential direction to trace for each bin'.") + % voxel_size.x() % voxel_size.y() % sampling_distance_of_adjacent_LORs_xy % num_tangential_LORs); if (use_actual_detector_boundaries) { @@ -315,7 +312,7 @@ set_up( if (proj_data_info_cyl_ptr== 0) { warning("ProjMatrixByBinUsingRayTracing: use_actual_detector_boundaries" - " is reset to false as the projection data should be non-arccorected.\n"); + " is reset to false as the projection data should be non-arccorrected."); use_actual_detector_boundaries = false; } else @@ -345,7 +342,7 @@ set_up( if (proj_data_info_blk_ptr== 0) { warning("ProjMatrixByBinUsingRayTracing: use_actual_detector_boundaries" - " is reset to false as the projection data should be non-arccorected.\n"); + " is reset to false as the projection data should be non-arccorrected."); use_actual_detector_boundaries = false; } else @@ -362,7 +359,7 @@ set_up( if (!nocompression) { warning("ProjMatrixByBinUsingRayTracing: use_actual_detector_boundaries" - " is reset to false as the projection data as either mashed or uses axial compression\n"); + " is reset to false as the projection data as either mashed or uses axial compression."); use_actual_detector_boundaries = false; } } @@ -375,7 +372,7 @@ set_up( if (proj_data_info_blk_ptr== 0) { warning("ProjMatrixByBinUsingRayTracing: use_actual_detector_boundaries" - " is reset to false as the projection data should be non-arccorected.\n"); + " is reset to false as the projection data should be non-arccorrected."); use_actual_detector_boundaries = false; } else @@ -392,14 +389,14 @@ set_up( if (!nocompression) { warning("ProjMatrixByBinUsingRayTracing: use_actual_detector_boundaries" - " is reset to false as the projection data as either mashed or uses axial compression\n"); + " is reset to false as the projection data as either mashed or uses axial compression."); use_actual_detector_boundaries = false; } } } if (use_actual_detector_boundaries) - warning("ProjMatrixByBinUsingRayTracing: use_actual_detector_boundaries==true\n"); + info("ProjMatrixByBinUsingRayTracing: use_actual_detector_boundaries==true.", 3); } From c829412c7bba14e65afc2874e144bcd34bb1f563 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Thu, 26 Oct 2023 11:39:54 +0100 Subject: [PATCH 347/509] convert some warning() to info() in Debug mode ProjData.read_from_file was writing warnings about which file format was going to be used. These are now converted to info() calls at verbosity-level 3. Also cleaned up some old-style calls to error(). --- src/buildblock/ProjData.cxx | 32 ++++++++++++-------------------- 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/src/buildblock/ProjData.cxx b/src/buildblock/ProjData.cxx index 57db175355..e4c86b5d05 100644 --- a/src/buildblock/ProjData.cxx +++ b/src/buildblock/ProjData.cxx @@ -2,7 +2,7 @@ Copyright (C) 2000 PARAPET partners Copyright (C) 2000 - 2010-10-15, Hammersmith Imanet Ltd Copyright (C) 2011-07-01 -2013, Kris Thielemans - Copyright (C) 2015, 2020, 2022 University College London + Copyright (C) 2015, 2020, 2022, 2023 University College London Copyright (C) 2021-2022, Commonwealth Scientific and Industrial Research Organisation Copyright (C) 2021, Rutherford Appleton Laboratory STFC This file is part of STIR. @@ -63,16 +63,13 @@ #include #include #include -#include "stir/warning.h" #include "stir/error.h" -#ifndef STIR_NO_NAMESPACES using std::istream; using std::fstream; using std::ios; using std::string; using std::vector; -#endif START_NAMESPACE_STIR @@ -128,8 +125,7 @@ read_from_file(const string& filename, #ifndef STIR_USE_GE_IO { #ifndef NDEBUG - warning("ProjData::read_from_file trying to read %s as GE Advance file", - filename.c_str()); + info("ProjData::read_from_file trying to read " + filename + " as GE Advance file", 3); #endif return shared_ptr( new ProjDataGEAdvance(input) ); } @@ -137,8 +133,7 @@ read_from_file(const string& filename, #else // use VOLPET { #ifndef NDEBUG - warning("ProjData::read_from_file trying to read %s as GE VOLPET file", - filename.c_str()); + info("ProjData::read_from_file trying to read " + filename + " as GE VOLPET file", 3); #endif delete input;// TODO no longer use pointer after getting rid of ProjDataGEAdvance return shared_ptr( new GE_IO::ProjDataVOLPET(filename, openmode) ); @@ -153,8 +148,7 @@ read_from_file(const string& filename, if (GE_IO::is_IE_signature(signature)) { #ifndef NDEBUG - warning("ProjData::read_from_file trying to read %s as GE IE file", - filename.c_str()); + info("ProjData::read_from_file trying to read " + filename + " as GE IE file", 3); #endif return shared_ptr( new GE_IO::ProjDataIE(filename) ); } @@ -166,22 +160,21 @@ read_from_file(const string& filename, if (strncmp(signature, "MATRIX", 6) == 0) { #ifndef NDEBUG - warning("ProjData::read_from_file trying to read %s as ECAT7", filename.c_str()); + info("ProjData::read_from_file trying to read " + filename + " as ECAT7", 3); #endif USING_NAMESPACE_ECAT; USING_NAMESPACE_ECAT7; if (is_ECAT7_emission_file(actual_filename) || is_ECAT7_attenuation_file(actual_filename)) { - warning("\nReading frame 1, gate 1, data 0, bed 0 from file %s", - actual_filename.c_str()); + info("Reading frame 1, gate 1, data 0, bed 0 from file " + actual_filename, 3); shared_ptr proj_data_sptr(ECAT7_to_PDFS(filename, /*frame_num, gate_num, data_num, bed_num*/1,1,0,0)); return proj_data_sptr; } else { if (is_ECAT7_file(actual_filename)) - warning("ProjData::read_from_file ECAT7 file %s is of unsupported file type", actual_filename.c_str()); + error("ProjData::read_from_file ECAT7 file " + actual_filename + " is of unsupported file type"); } } #endif // HAVE_LLN_MATRIX @@ -190,7 +183,7 @@ read_from_file(const string& filename, if (is_interfile_signature(signature)) { #ifndef NDEBUG - warning("ProjData::read_from_file trying to read %s as Interfile", filename.c_str()); + info("ProjData::read_from_file trying to read " + filename + " as Interfile", 3); #endif shared_ptr ptr(read_interfile_PDFS(filename, openmode)); if (!is_null_ptr(ptr)) @@ -202,7 +195,7 @@ read_from_file(const string& filename, if (GE_IO::is_RDF_file(actual_filename)) { #ifndef NDEBUG - warning("ProjData::read_from_file trying to read %s as RDF", filename.c_str()); + info("ProjData::read_from_file trying to read " + filename + " as RDF", 3); #endif shared_ptr ptr(new GE_IO::ProjDataRDF(filename)); if (!is_null_ptr(ptr)) @@ -214,7 +207,7 @@ read_from_file(const string& filename, if (GE::RDF_HDF5::GEHDF5Wrapper::check_GE_signature(actual_filename)) { #ifndef NDEBUG - warning("ProjData::read_from_file trying to read %s as GE HDF5", filename.c_str()); + info("ProjData::read_from_file trying to read " + filename + " as GE HDF5", 3); #endif shared_ptr ptr(new GE::RDF_HDF5::ProjDataGEHDF5(filename)); if (!is_null_ptr(ptr)) @@ -222,9 +215,8 @@ read_from_file(const string& filename, } #endif // GE HDF5 - error("\nProjData::read_from_file could not read projection data %s.\n" - "Unsupported file format? Aborting.", - filename.c_str()); + error("ProjData::read_from_file could not read projection data " + filename + ".\n" + "Unsupported file format? Aborting."); // need to return something to satisfy the compiler, but we never get here shared_ptr null_ptr; return null_ptr; From 75e7f409bf20ace2253bd29abb812a5328e3c6cb Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Thu, 26 Oct 2023 12:01:41 +0100 Subject: [PATCH 348/509] Avoid re-running set_up() for ProjMatrixByBinUsingRayTracing The `set_up()` method now skips further processing if it was already called with data of the same characteristics. This will means that any cached data will be re-used, potentially leading to a speed-up when re-using it from Python. --- documentation/release_5.2.htm | 7 +++++ .../ProjMatrixByBinUsingRayTracing.cxx | 28 +++++++++++++++---- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/documentation/release_5.2.htm b/documentation/release_5.2.htm index 8314e859cf..58d95054d1 100644 --- a/documentation/release_5.2.htm +++ b/documentation/release_5.2.htm @@ -42,6 +42,13 @@

    Changed functionality


    PR #1182.
  26. Interfile header parsing now correctly identifies keywords that contain a colon by checking for :=./li> +
  27. + The set_up() method of the ray-tracing projection matrix now skips further processing + if it was already called with data of the same characteristics. This will means that any cached data + will be re-used, potentially leading to a speed-up when re-using it from Python. +
    PR #1281. +
  28. +

    New functionality

    diff --git a/src/recon_buildblock/ProjMatrixByBinUsingRayTracing.cxx b/src/recon_buildblock/ProjMatrixByBinUsingRayTracing.cxx index 1c420c615f..5cada6780c 100644 --- a/src/recon_buildblock/ProjMatrixByBinUsingRayTracing.cxx +++ b/src/recon_buildblock/ProjMatrixByBinUsingRayTracing.cxx @@ -269,14 +269,32 @@ set_up( const shared_ptr >& density_info_ptr // TODO should be Info only ) { + auto image_info_ptr = dynamic_cast*> (density_info_ptr.get()); + + if (!image_info_ptr) + error("ProjMatrixByBinUsingRayTracing initialised with wrong type of DiscretisedDensity."); + + if (this->already_setup) + { + if (*this->proj_data_info_ptr == *proj_data_info_ptr_v && + this->voxel_size == image_info_ptr->get_voxel_size() && + this->origin == image_info_ptr->get_origin()) + { + CartesianCoordinate3D new_min_index; + CartesianCoordinate3D new_max_index; + image_info_ptr->get_regular_range(new_min_index, new_max_index); + if (this->max_index == new_max_index && + this->min_index == new_min_index) + { + info("ProjMatrixByBinUsingRayTracing::set_up skipped as already set-up with same characteristics.", 3); + } + return; + } + } + ProjMatrixByBin::set_up(proj_data_info_ptr_v, density_info_ptr); proj_data_info_ptr= proj_data_info_ptr_v; - const VoxelsOnCartesianGrid * image_info_ptr = - dynamic_cast*> (density_info_ptr.get()); - - if (image_info_ptr == NULL) - error("ProjMatrixByBinUsingRayTracing initialised with a wrong type of DiscretisedDensity\n"); voxel_size = image_info_ptr->get_voxel_size(); origin = image_info_ptr->get_origin(); From 837e7dae2d4fc30f329a8d7e5387626163ad22f6 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Thu, 26 Oct 2023 12:12:18 +0100 Subject: [PATCH 349/509] sensible error when wring ProjData with subsets we cannot do this yet, but the error was confusing. also minor code clean-up --- src/IO/interfile.cxx | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/IO/interfile.cxx b/src/IO/interfile.cxx index bccc5feb2a..fb0f824f23 100644 --- a/src/IO/interfile.cxx +++ b/src/IO/interfile.cxx @@ -1,7 +1,7 @@ /* Copyright (C) 2000 PARAPET partners Copyright (C) 2000- 2011, Hammersmith Imanet Ltd - Copyright (C) 2013, 2018, University College London + Copyright (C) 2013, 2018, 2023 University College London Copyright 2017 ETH Zurich, Institute of Particle Physics and Astrophysics This file is part of STIR. @@ -56,9 +56,8 @@ #include #include "stir/ProjDataInfoBlocksOnCylindricalNoArcCorr.h" #include "stir/ProjDataInfoGenericNoArcCorr.h" +#include "stir/ProjDataInfoSubsetByView.h" - -#ifndef STIR_NO_NAMESPACES using std::cerr; using std::endl; using std::ofstream; @@ -70,7 +69,6 @@ using std::vector; using std::istream; using std::string; using std::ios; -#endif START_NAMESPACE_STIR @@ -488,7 +486,7 @@ static void write_interfile_image_data_descriptions(std::ostream& output_header, output_header << "number of image data types := " << data_type_descriptions.size() << '\n'; output_header << "index nesting level := {data type}\n"; - for (int i=0; i file_offsets(image.get_num_params()); VectorWithOffset scaling_factors(image.get_num_params()); - for (int i=1; i<=image.get_num_params(); i++) { + for (int i=1; i<=static_cast(image.get_num_params()); i++) { float scale_to_use = scale; file_offsets[i-1] = output_data.tellp(); write_data(output_data, image.construct_single_density(i), output_type, scale_to_use, @@ -958,7 +956,7 @@ write_basic_interfile(const string& filename, VectorWithOffset file_offsets(image.get_num_time_frames()); VectorWithOffset scaling_factors(image.get_num_time_frames()); - for (int i=1; i<=image.get_num_time_frames(); i++) + for (int i=1; i<=static_cast(image.get_num_time_frames()); i++) { float scale_to_use = scale; file_offsets[i-1] = output_data.tellp(); @@ -1500,8 +1498,14 @@ write_basic_interfile_PDFS_header(const string& header_file_name, << proj_data_info_sptr->get_sampling_in_s(Bin(0,0,0,0))/10. << endl; } // end generic scanner - - else error("write_basic_interfile_PDFS_header: Error casting the projdata to one of its geometries: Cylindrical/BlocksOnCylindrical/Genreic"); + else if (!dynamic_pointer_cast(pdfs.get_proj_data_info_sptr())) + { + error("write_basic_interfile_PDFS_header: cannot write subset data yet. Sorry"); + } + else + { + error("write_basic_interfile_PDFS_header: Error casting the projdata to one of its geometries: Cylindrical/BlocksOnCylindrical/Generic"); + } } } From 792eea57bab19b067448a0027c2315b47fe9b734 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sun, 29 Oct 2023 14:18:09 +0000 Subject: [PATCH 350/509] update developer's overview It was wildly out-of-date w.r.t. C++ ffeatures etc. --- documentation/STIR-developers-overview.tex | 120 ++++++++++----------- documentation/release_5.2.htm | 9 +- 2 files changed, 65 insertions(+), 64 deletions(-) diff --git a/documentation/STIR-developers-overview.tex b/documentation/STIR-developers-overview.tex index b06805c054..35a6d43707 100644 --- a/documentation/STIR-developers-overview.tex +++ b/documentation/STIR-developers-overview.tex @@ -16,7 +16,7 @@ \textbf{{\huge STIR \\ Overview for developers}}\\ \textbf{Kris Thielemans}\\ -\textbf{\textit{version 5.1}} +\textbf{\textit{version 5.2}} \end{center} @@ -60,7 +60,8 @@ \section{ doxygen on the source files that come in the STIR distribution). \subsection{Language support} -STIR is written in C++ and currently requires C++-11, but it should be compatible with nNewer versions of the C++ standard. +STIR is written in C++ and currently requires C++-11, but it is compatible with newer versions of the C++ standard. +We will enforce C++-14 from STIR 6.0. Python and MATLAB support is provided via \R2Lurl{http://www.swig.org/}{SWIG}. This means that Python/MATLAB interfaces follow the C++ classes closely, although some differences are required as these languages do not support templates for instance. @@ -139,31 +140,25 @@ \subsection{ In 3D PET, two data storage modes are generally used for a \textbf{segment}\footnote{{The -GE Advance file format does \textit{not} store the data per segment, -but per view. In addition, the segments are then mixed. However, +GE file formats does \textit{not} store the data per segment, +but per view. However, once read into STIR, this organisation is no longer available. -\\ -The ECAT6 sinograms also have no easy way to understand which -ring-pair corresponds to which sinogram number for 3D PET. This -is the main reason why STIR does not attempt to read ECAT6 sinograms -directly, but leaves this to a separate conversion utility.}} +}} \begin{itemize} \item where the 3D data is ordered by \textbf{sinogram} (i.e \textbf{axial position}, \textbf{view -angle}, \textbf{tangential position}) (CTI terminology: \textit{Volume} mode) +angle}, \textbf{tangential position}) (see Figure \ref{fig:sinogramstorage}) \item where the 3D dataset is ordered by \textbf{view} (i.e \textbf{view angle}, \textbf{axial -position}, \textbf{tangential position}) (CTI terminology: \textit{View} -mode) (see Figure \ref{fig:viewgramstorage}) +position}, \textbf{tangential position}) +(see Figure \ref{fig:viewgramstorage}) \end{itemize} This notation means that for a sinogram, the tangential position runs fastest.\\ In both modes, the 3D dataset has been stored in several segments where the number of axial positions in each \textbf{segment} depends -on the \textbf{axial compression (span)}.\\ -Note that we find the CTI terminology confusing, so you will -see it nowhere in STIR except in this document. +on the \textbf{axial compression (span)}. From both modes, 2 different types of 2D datasets can be obtained @@ -175,6 +170,18 @@ \subsection{ view. \end{itemize} +Note that in STIR version 6.0, Time-of-Flight (TOF) will be supported. This introduces another +index. However, \texttt{Sinogram} and \texttt{Viewgram} will remain 2D objects, and \texttt{Segment*} 3D. +This will also be the case once we have layers and energy windows. +In STIR 5.2, we have therefore introduced new classes +\texttt{SinogramIndices}, \texttt{ViewgramIndices}\footnote{Replacing \texttt{ViewSegmentNumbers} in previous versions of STIR.} +and \texttt{SegmentIndices}, containing all ``other'' +indices necessary to get at the corresponding data. This means that the recommended coding style is now +\begin{verbatim} +ViewgramIndices vg_idx(view_num,segment_num); +auto viewgram = proj_data.get_viewgram(vg_idx); +\end{verbatim} +In the next version of STIR, we will introduce extra methods to be able to conveniently loop over all viewgrams etc. \begin{figure}[htbp] \begin{center} @@ -231,8 +238,8 @@ \subsection{ The file \textit{stir}/\textit{common.h} contains general configuration info and tries to iron out some incompatibilities for certain compilers. If you include any STIR \textit{.h} file, you are guaranteed -to have included \textit{stir}/\textit{common.h} as well, hence it should -never be included explicitly by a 'user'. +to have included \textit{stir}/\textit{common.h} as well, hence there is usually no need +to include it explicitly. \subsection{ Namespace} @@ -241,12 +248,9 @@ \subsection{ are within this namespace. The effect of this is that conflicts with other symbols are impossible (except when somebody else is using the same namespace). STIR also uses sub-namespaces for -certain things. This usage will probably be expanded in the future.\\ -For older compilers, the namespace can be disabled (see \textit{stir}/\textit{common.h}). -For this reason, we have introduced some macros such as \textit{START\_NAMESPACE\_STIR.} You -could use the macros in your own code as well, although people -with a compiler that does not support namespace really should -upgrade. +certain things. This usage will probably be expanded in the future. +In most of the code, we use macros such as \textit{START\_NAMESPACE\_STIR} +(originally used for older compilers). \subsection{ Naming conventions } @@ -262,6 +266,7 @@ \subsection{ with \textit{num}, e.g. \textit{num\_gates}. \item the number of an item in a sequence end with \textit{num}, e.g. \textit{gate\_num}. +\footnote{We are slowly starting to use \texttt{\_idx} in a few places as ``indices'' are a more general concept, and avoid confusion between the pre- and postfix use of \texttt{num}.} \item a relative time (normally with respect to the scan start) end with \textit{rel\_time}, e.g. \textit{tracer\_injection\_rel\_time}. @@ -389,15 +394,14 @@ \subsection{ \subsection{\label{ssect:AdvancedCppFeatures} Advanced (?) C++ features used} -We attempted to follow the ANSI C++ standard as close as possible. -We expect to have some marginal problems with a 'strict' ANSI -C++ compiler, although there should now be very few cases (as -gcc warns about a lot of stuff). We have used some preprocessor -macros (see \textit{stir/common.h}) to isolate work-arounds for older +STIR uses C++-11 an dis compatible with C++-20 to the best of our knowledge. +For legacy reasons, we have some preprocessor +macros (see \textit{stir/common.h}) that were used isolate work-arounds for older compilers. Ideally, all these \#ifdefs should disappear at a -later development stage of the library. We gradually give up -on supporting older compilers anyway. +later development stage of the library. +This section describes some C++ features used in STIR that might not be so familiar to +non-C++ programmers. \subsubsection{Templates} The library uses templates very often. This allows us to write @@ -417,23 +421,15 @@ \subsubsection{Templates} actual definition is in a \textit{.txx} file that you can include.\\ We do use partial class template specialisation in some places. However, (very ugly) work-arounds are provided for compilers -that do not support this feature (although not anymore in recent code).\\ -Very occasionally we use member templates (in such a form that -it could be compiled by Visual C++ 6.0, i.e. inline in the class definition). +that do not support this feature (although these areee being gradually removed).\\ +Very occasionally we use member templates. \subsubsection{Run Time Type Information} Another C++ feature that we use is Run Time Type Information (RTTI). We almost exclusively use this to check validity of pointer (or reference) casts down a hierarchy ('down-casting'). See section \ref{sect:classhierarchies} -for an example. If a compiler does not support -RTTI (but all compilers do these days), it would be possible to declare -a (essentially empty) \texttt{dynamic\_cast} -template function such that the above code would compile. The -resulting programme would become inherently type-unsafe though, -and we recommend that you upgrade your compiler. Some code does -rely on explicit type checking, you would have to check this -in detail if you don't have RTTI. +for an example. \subsubsection{ Iterators} @@ -447,7 +443,7 @@ \subsubsection{ \begin{verbatim} void f(vector& v) { - for (vector::iterator iter = v.begin(); iter != v.end(); ++iter) + for (auto iter = v.begin(); iter != v.end(); ++iter) *iter += 2; } \end{verbatim} @@ -464,7 +460,7 @@ \subsubsection{ template void f(Container& v) { - for (typename Container::iterator iter = v.begin(); iter != v.end(); ++iter) + for (auto iter = v.begin(); iter != v.end(); ++iter) *iter += 2; } \end{verbatim} @@ -483,7 +479,7 @@ \subsubsection{ template void f(Array& a) { - for (typename Array::full_iterator iter = a.begin_all(); + for (auto iter = a.begin_all(); iter != a.end_all(); ++iter) *iter += 2; } @@ -511,18 +507,14 @@ \subsubsection{ (something which you cannot achieve with an ordinary pointer). Generally, the destructor of the smart pointer will make sure that the object it points to is deleted (when appropriate).\\ -\textit{std::auto\_ptr} is a standard smart pointer class which is +\textit{std::unique\_ptr} is a standard smart pointer class which is suitable for pointers to objects where there's only one smart -pointer for each object. Unfortunately, its syntax has been decided -rather late in ANSI C++ and its implementation requires some -advanced C++ features, leading to a non-standard implementation -of \texttt{std::auto\_ptr} in older compilers (including VC 6.0) and it is now -superseded by \texttt{std::unique\_ptr} in C++11. For this -reason, we do not use this smart pointer class too much, and +pointer for each object. Unfortunately, a lot of STIR was written +pre-C++-11 and we do not use this smart pointer class too much, and generally use \texttt{shared\_ptr} instead. This is somewhat unfortunate as these two smart pointers are generally quite different concepts. -\texttt{shared\_ptr} is a STIR (or boost) smart pointer class which +\texttt{shared\_ptr} is a wrapper around the \texttt{std} (or boost\footnote{Boost smart pointer are probably no longer supported, and will no longer be from STIR 6.0.}) smart pointer class which is suitable when there are (potentially) more than one pointer pointing to the same object. It keeps a reference count such that the object is (only) deleted when the last shared pointer @@ -561,7 +553,8 @@ \subsubsection{ to test if a pointer is `null', i.e. does not point to anything. \textit{is\_null\_ptr} works with ordinary pointers, shared\_ptrs and auto\_ptrs. Use it to avoid that your code depends on what type of (smart) pointer -you are using. +you are using. Note however that since C++-11, it is more convenient to use the +automatic converstion to \texttt{bool}. \subsection{Generic functionality of STIR classes} This is a (very incomplete) section describing some functionality that @@ -749,8 +742,9 @@ \subsection{ \end{figure} -Currently missing is support for fan-beam data.\\ -List-mode data is experimentally supported since version 1.2. +Currently missing is support for fan-beam data. + +List-mode data is supported as well. See the \textit{Listmode} base-class and its hierarchy. \subsection{ Data (or image) processor hierarchy} @@ -1046,6 +1040,11 @@ \section{ number = 1; a = 2.4F; } + bool post_processing() // will be renamed to post_parsing() + { + // do some checks and handle some extra variables + return false; // everything was ok + } }; int main(int argc, char **argv) @@ -1088,8 +1087,7 @@ \subsection{Images} You will have to include \texttt{stir/IO/write\_to\_file.h} for this to work. This will write using the default output file format and is equivalent to the following: \begin{verbatim} - typedef DiscretisedDensity<3,float> DataType ; - shared_ptr output_format_sptr = + auto output_format_sptr = OutputFileFormat::default_sptr(); output_format_sptr->write_to_file(filename, density); \end{verbatim} @@ -1189,10 +1187,10 @@ \section{ template void f(DiscretisedDensity<3, elemT>& density) { - VoxelsOnCartesianGrid * voxels_ptr = + auto voxels_ptr = dynamic_cast< VoxelsOnCartesianGrid * > (&density); - if (voxels_ptr == NULL) - error("f: can only handle images of type VoxelsOnCartesianGrid\n"); + if (!voxels_ptr) + error("f: can only handle images of type VoxelsOnCartesianGrid"); ... } \end{verbatim} @@ -1286,7 +1284,7 @@ \section{ ) # declare dependencies on other STIR libraries, for instance -target_link_libraries($(dir) buildblock) +target_link_libraries($(dir) PUBLIC buildblock) # add to list of libraries for STIR to include in linking list(APPEND STIR_LIBRARIES $(dir)) diff --git a/documentation/release_5.2.htm b/documentation/release_5.2.htm index 58d95054d1..550c1ed137 100644 --- a/documentation/release_5.2.htm +++ b/documentation/release_5.2.htm @@ -15,7 +15,8 @@

    Overall summary

    improvements to the documentation. See also the 5.2 milestone on GitHub.

    -

    Overall code management and assistance by Kris Thielemans (UCL and ASC).

    +

    Overall code management and assistance by Kris Thielemans (UCL and ASC). Other main contributors + were Daniel Deidda (NPL) and Markus Jehl (Positrigo).

    Patch release info

      @@ -149,12 +150,12 @@

      Deprecated functionality

      and should be replaced by their respective versions that use SegmentIndices, ViewgramIndices or SinogramIndices. The former will not be compatible with TOF information that will be introduced in version 6.0.0. -
    • STIR version 6.0.0 will likely require C++ 14 (currently we require C++ 11, but already support C++ 20).
    • +
    • STIR version 6.0.0 will require C++ 14 (currently we require C++ 11, but already support C++ 20).

    Build system and dependencies

      -
    • CMake 3.12 is now required on Windows. (It will be required on all platforms in the next version).
    • +
    • CMake 3.12 is now required on Windows. (It will be required on all platforms in STIR 6.0).
    • We now use CMake's OBJECT library feature for the registries. This avoids re-compilation of the registries for every executable and therefore speeds-up building time. Use of STIR in an external project is not affected as long as the recommended practice was followed. This is now documented in the User's Guide.
      PR #1141.
    • @@ -206,10 +207,12 @@

      Minor (?) bug fixes

      Documentation changes

        +
      • Updated the STIR developers guide, which was quite out-of-date w.r.t. C++ features etc.

      recon_test_pack changes

        +
      • Updated headers of most images and projection data to avoid warnings.

      Other changes to tests

      From 9e6fdf690d2dd97e00af09cb1f617a1a5d47167b Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sun, 29 Oct 2023 15:47:11 +0000 Subject: [PATCH 351/509] updated mailmap and CITATION.cff --- .mailmap | 7 ++++-- CITATION.cff | 44 ++++++++++++++++----------------- scripts/maintenance/git-fame.sh | 6 ++--- 3 files changed, 30 insertions(+), 27 deletions(-) diff --git a/.mailmap b/.mailmap index e0b863e5c9..66b9eb55f0 100644 --- a/.mailmap +++ b/.mailmap @@ -45,8 +45,10 @@ Palak Wadhwa Rebecca Gillen <43674917+RebeccaGillen@users.noreply.github.com> Richard Brown <33289025+rijobro@users.noreply.github.com> Richard Brown -Robert Twyman -Robert Twyman +Robert Twyman Skelly +Robert Twyman Skelly +Robert Twyman Skelly +Robert Twyman Skelly <117300855+Robert-PrescientImaging@users.noreply.github.com> Yu-jung Tsai Gemma Fardell Gemma Fardell <47746591+gfardell@users.noreply.github.com> @@ -62,5 +64,6 @@ Nicole Jurjew Tahereh Niknejad Tahereh Niknejad Sam D Porter <92305641+samdporter@users.noreply.github.com> +Sam D Porter <92305641+samdporter@users.noreply.github.com> Matthew Strugari Matthew Strugari <56315593+mastergari@users.noreply.github.com> diff --git a/CITATION.cff b/CITATION.cff index 70d589c8ce..77d74b0396 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -18,31 +18,34 @@ authors: given-names: Richard orcid: 'https://orcid.org/0000-0001-6989-9200' affiliation: University College London + - family-names: Twyman Skelly + given-names: Robert + affiliation: University College London and Prescient Imaging - family-names: Tsoumpas given-names: Charalampos - - family-names: Twyman - given-names: Robert - affiliation: University College London - family-names: Deidda given-names: Daniel orcid: 'https://orcid.org/0000-0002-2766-4339' affiliation: National Physics Laboratory (UK) + - family-names: Falcon + given-names: Carles - family-names: Borgeaud given-names: Tim affiliation: Hammersmith Imanet Ltd - - family-names: Falcon - given-names: Carles - family-names: Khateri given-names: Parisa affiliation: ETH Zuerich + - family-names: Jehl + given-names: Markus + affiliation: Positrigo - family-names: Wadhwa given-names: Palak affiliation: University of Leeds (UK) - - family-names: Beisel - given-names: Tobias - family-names: Strugari given-names: Matthew affiliation: Dalhousie University (Canada) + - family-names: Beisel + given-names: Tobias - family-names: Jacobson given-names: Matthew - family-names: Gillman @@ -69,9 +72,6 @@ authors: - family-names: Fischer given-names: Jannis orcid: 'https://orcid.org/0000-0002-8329-0220' - - family-names: Jehl - given-names: Markus - affiliation: Positrigo - family-names: Roethlisberger given-names: Michael affiliation: ETH Zuerich @@ -80,13 +80,17 @@ authors: - family-names: Brusaferri given-names: Ludovica affiliation: University College London - - family-names: Bertolli - given-names: Ottavia - affiliation: University College London - family-names: Pasca given-names: Edoardo affiliation: UK Research & Innovation orcid: 'https://orcid.org/0000-0001-6957-2160' + - family-names: Thomas + given-names: Benjamin + orcid: 'https://orcid.org/0000-0002-9784-1177' + affiliation: University College London + - family-names: Bertolli + given-names: Ottavia + affiliation: University College London - family-names: Niknejad given-names: Tahereh - family-names: Dikaios @@ -113,10 +117,6 @@ authors: - family-names: Valente given-names: Patrick affiliation: Brunel University (UK) - - family-names: Thomas - given-names: Benjamin - orcid: 'https://orcid.org/0000-0002-9784-1177' - affiliation: University College London - family-names: Schramm given-names: Georg affiliation: Katholieke Universiteit Leuven (Belgium) @@ -143,12 +143,12 @@ authors: - family-names: Mikhaylova given-names: Ekaterina affiliation: Positrigo - - family-names: da Costa-Luis - given-names: Casper O. - orcid: 'https://orcid.org/0000-0002-7211-1557' - family-names: Porter given-names: Sam David affiliation: University College London, National Physics Laboratory (UK) + - family-names: da Costa-Luis + given-names: Casper O. + orcid: 'https://orcid.org/0000-0002-7211-1557' - family-names: Rashidnasab given-names: Alaleh affiliation: University College London @@ -160,11 +160,11 @@ authors: affiliation: University College London - family-names: Vavrek given-names: Jayson - - family-names: Kohr - given-names: Holger - family-names: Tsai given-names: Yu-jung affiliation: University College London + - family-names: Kohr + given-names: Holger - name: tokkot - family-names: El Katib given-names: Mahmoud diff --git a/scripts/maintenance/git-fame.sh b/scripts/maintenance/git-fame.sh index b6823bba47..cd3a26c98e 100755 --- a/scripts/maintenance/git-fame.sh +++ b/scripts/maintenance/git-fame.sh @@ -37,7 +37,7 @@ exit | Darren Hague | 50 # Also, SPECTUB files were checked in by KT, but actually written by Carles Falcon. -# Finally PinholeSPECTUB is attributed (mostly to Carles), but git fame doesn't count his loc for some reason. +# Finally PinholeSPECTUB is now attributed correctly (mostly to Carles) # Just before release 5.1, we have the following loc $ wc -l *SPECTUB*x 1346 PinholeSPECTUB_Tools.cxx @@ -48,12 +48,12 @@ exit 989 SPECTUB_Weight3d.cxx Roughly leading to -| Carles Falcon | 3000 +| Carles Falcon | 3500 | Berta Marti-Fuster | 1000 Summary for corrections to output: | Author | loc -| Carles Falcon | 3000 +| Carles Falcon | 3500 | Berta Marti-Fuster | 1000 | Claire Labbe | 1000 | Mustapha Sadki | 400 From b154f5c6768b3b7eb9c48a82cb29c2ded39674e7 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sun, 29 Oct 2023 18:34:36 +0000 Subject: [PATCH 352/509] update STIR general overview and user's guide The general overview was wildly out-of-date... Now somewhat ok. The User's guide still contained old material as well, now removed. --- documentation/STIR-UsersGuide.tex | 173 ++++++++++++------------ documentation/STIR-general-overview.tex | 118 +++++----------- documentation/release_5.2.htm | 8 +- 3 files changed, 121 insertions(+), 178 deletions(-) diff --git a/documentation/STIR-UsersGuide.tex b/documentation/STIR-UsersGuide.tex index 80a91a9aed..0d14db36be 100644 --- a/documentation/STIR-UsersGuide.tex +++ b/documentation/STIR-UsersGuide.tex @@ -44,7 +44,7 @@ \\[3cm] \textbf{{\huge User's Guide\\ - Version 5.1}} + Version 5.2}} \end{center} \end{spacing} @@ -53,7 +53,7 @@ \noindent K. Thielemans \\ -{\it \small Hammersmith Imanet Ltd; Algorithms and Software Consulting Ltd; University College London}\\ +{\it \small University College London; Algorithms and Software Consulting Ltd (formerly Hammersmith Imanet Ltd}\\ Ch. Tsoumpas\\ {\it \small Hammersmith Imanet Ltd; Imperial College London; King's College London}\\ D. Sauge, C. Labb\'e, C. Morel \\ @@ -142,11 +142,42 @@ \section{ Any comments on documentation, and especially contributions are always welcome. Please use the stir-users mailing list. +\section{Installing STIR via a pre-built version } +\subsection{ +Installing via conda} +The easiest way is to use \texttt{conda} (or one of its derivatives such as \texttt{mamba}): +\begin{verbatim} +conda config --add channels conda-forge +conda create -n stirenv stir matplotlib +conda activate stirenv + +# use STIR executable or python + +# deactivate the environment +conda deactivate +\end{verbatim} +See \url{https://stir.sourceforge.net/wiki/index.php/Installing_STIR_with_conda}{our Wiki page} for +more detail, including on how to get a development version. + +\subsection{SIRF distributions} +CCP SyneRBI distributes SIRF with its dependencies, including STIR, +see \url{https://www.ccpsynerbi.ac.uk/downloads/}{its download page} for +a Virtual Machine, docker etc. However, only the STIR libraries are included in +these distributions. You can easily install ``full'' STIR on the Virtual Machine though, +see \url{https://github.com/SyneRBI/SIRF-SuperBuild/blob/master/VirtualBox/documentation/README.md}{its documentation}. + \section{ -Installation} +Installation from source} This section describes how to install STIR. It is complemented by information on the \url{http://stir.sourceforge.net/wiki}{STIR Wiki} with information for specific systems etc. +Note that as opposed to using the instructions below, you can also +the \url{https://github.com/SyneRBI/SIRF-SuperBuild/blob/master/README.md}{SIRF-Superbuild} +of \url{https://www.ccpsynerbi.ac.uk/}{SyneRBI} to build STIR with CMake. This will take care of all dependencies. +Note that you will need to use advanced CMake variables \texttt{STIR\_BUILD\_EXECUTABLES=ON} +and \texttt{STIR\_BUILD\_SWIG\_PYTHON=ON}. + + \subsection{ Installing source files} @@ -156,26 +187,15 @@ \subsection{ \end{center} or from \url{https://github.com/UCL/STIR/releases}{github.com/UCL/STIR/releases}.\\ Alternatively, if you are feeling adventurous, you can get the most-recent -developer version (no guarantees!) of STIR from\\ +developer version (no guarantees!) of STIR from \url{https://github.com/UCL/STIR}{https://github.com/UCL/STIR}.\\ -Download the source files in the zip format. You can then use unzip from \\ -\url{http://www.info-zip.org/pub/infozip/}{http://www.info-zip.org/pub/infozip/}. -Extract using -\cmdline{unzip -a file.zip} - - -The \texttt{-a} option makes sure that files are extracted using the -end-of-line convention appropriate for your system. Note that -other programs that can handle zip files (such as WinZip) should -work as well, although you might have problems with the EOL convention. - Note that you can put the distribution in any location (it does not have to be your home directory). -The result is a \textbf{STIR} directory and subdirectories, described -in the annex section. +After unpacking, you should have a \textbf{STIR} directory (subdirectories are described +in the doxygen documentation). \subsection{ Installing external software} @@ -183,6 +203,13 @@ \subsection{ On most systems, you should be able to get these using your package manager. Please check the Wiki for most up-to-date information. +\subsubsection{ +C++ Compiler} + +In order to compile and run the \textit{STIR} programs, you will need a compiler +and associated tools such as \texttt{CMake}. These days, any compiler should work. We would love +to hear from any attempts where there are failures. + \subsubsection{BOOST} The only required external library is the well-respected \textit{boost} library. If it is not installed on your system, you can download it from @@ -195,29 +222,19 @@ \subsubsection{BOOST} \subsubsection{JSON} The \texttt{HUToMu} and \texttt{Radionuclide} database functionality parse a file in JSON format. We use the \url{https://github.com/nlohmann/json}{nlohman\_json} library for this. Installation -instructions for this library are available on its github page. +instructions for this library are available on its github page or you can get it via \texttt{conda}. +Without it, we only support a few basic radionuclides. \subsubsection{ -C++ Compiler} +Enabling ECAT 7 support\label{sec:ECAT67support}} +{\em ECAT7 support is no longer tested.} -In order to compile and run the \textit{STIR} programs, you will need a compiler -and associated tools. These days, any compiler should work. See the -\url{http://sourceforge.net/stir}{STIR Wiki} for more -specific information of which compilers we have tried. -We would love -to hear from any attempts of using another compiler. - -\subsubsection{ -Enabling ECAT 7 support} -\label{sec:ECAT67support} Older CTI (Siemens) scanners use a file format called ECAT\texttrademark{}. At present, \textit{STIR} uses parts of the Louvain la Neuve ecat library (called LLN in the rest of this document).\footnote{\textit{STIR} versions 1.? came with specific files for ECAT6 support without the need for the LLN library. However, due to license restrictions this is now no longer the case.} -The library might still be available on -\url{ftp://ftp.topo.ucl.ac.be/pub/ecat }{ftp://ftp.topo.ucl.ac.be/pub/ecat}. -It is also available for GATE users. +The library could be available on the OpenGATE website. You have to download that library and issue 'make --f Makefile.unix' (or 'make --f Makefile.cygwin' if you are using CYGWIN on Windows) @@ -235,10 +252,26 @@ \subsubsection{ For most operating systems this can be done via your package manager which we highly recommend. You could also download from the \url{https://www.hdfgroup.org/downloads/hdf5/}{HDF5 group download page}. +\subsubsection{ +Enabling ROOT support\label{sec:installROOT}} +STIR can read CERN ROOT files from GATE (see section \ref{sec:ROOTIO}). Check the \url{https://root.cern/install/}{installation instructions for ROOT}. + +\subsubsection{ +Enabling ITK support\label{sec:installITK}} +STIR can use \url{http://www.itk.org}{ITK}, a large open source library. Specifying this + enables NRRD, MetaIO and Nifti IO, and DICOM reading. + Use your package manager, including conda or pip, or check the \url{https://itk.org/download/}{installation instructions for ITK}. + +\subsubsection{ +Enabling AVW support\label{sec:installAVW}} +AVW was the library used by the commercial program \url{https://analyzedirect.com/}{Analyze}. +\textit{AVW support has not been tested since about 2010 and we have no idea of STIR still builds with it.} AVW support will be dropped in STIR 6.0. +See \ref{sec:convAVW} for usage. + \subsection{ Building} Since version 2.2, \textit{STIR} contains files to build STIR using the platform-independent -\url{"http://www.cmake.org"}{CMake}. This is now the only to build STIR as +\url{"http://www.cmake.org"}{CMake}. This is now the only option to build STIR as it is easier for configuration and finding system dependencies. \subsubsection{ Using CMake} @@ -309,10 +342,6 @@ \subsubsection{ % ROW {\raggedright \textit{STIR\_OPENMP}} & {\raggedright Toggles between ON, OFF. Enable threaded processing using OPENMP. -\footnote{Since version 2.0, \textit{STIR} contains preliminary code for running \texttt{FBP2D} in threaded mode -(contribution by Tobias Beisel, Univ. of Paderborn). You need a compiler that -supports \texttt{OPENMP}. Note however, that this code doesn't presently result -in a decent speed-up of \texttt{FBP2D}.} See section \ref{sec:RunningWithOPENMP} for information on how to run programs using OPENMP. } \\ \hline @@ -347,14 +376,13 @@ \subsubsection{ capabilities of STIR. \begin{itemize} \item LLN files for ECAT support via \texttt{LLN\_INCLUDE\_DIRS} and \texttt{LLN\_LIBRARIES}. -\item AVW\texttrademark{} (a commercial library\footnote{See \url{http://www.mayo.edu/bir/Software/AVW/AVW1.html} -{www.mayo.edu/bir/Software/AVW/AVW1.html}.}) -via \texttt{AVW\_ROOT\_DIR}. See section \ref{sec:convAVW} for usage.\\ -\textit{AVW support has not been tested since about 2010.} -\item ITK\_DIR, use IO from \url{http://www.itk.org}{ITK}, a large open source library. Specifying this - enables NRRD, MetaIO and Nifti IO. +See section \ref{sec:ECAT67support}. +\item AVW\texttrademark{} [deprecated] (a commercial library, probably no longer available) +via \texttt{AVW\_ROOT\_DIR}. See section \ref{sec:installAVW}. +\item ITK\_DIR, use IO from ITK, see section \ref{sec:installITK}. \item CERN ROOT files used by GEANT and GATE via \texttt{ROOT\_DIR}. If this is not set, we will try to get it from the \texttt{ROOTSYS} environment (or CMake) variable. + See \ref{sec:installROOT}. \item GE RDF\texttrademark{} 9 support via \texttt{HDF5\_ROOT} and related variables (requires the HDF5 library). \end{itemize} @@ -371,14 +399,15 @@ \subsubsection{ \item \textit{CMAKE\_CXX\_STANDARD} can be used to tell CMake to add flags to your compiler to use a particular version of the C++ standard (if it supports it). It is set to \texttt{11} since STIR 5.0. Other possible allowed values are -\texttt{14} and \texttt{17}. More recent version have not yet been tested. +\texttt{14}, \texttt{17} and \textit{20}. More recent version have not yet been tested. \end{itemize} When building the Python code, and you have multiple versions of Python installed, it is often necessary to specify the correct Python executable that you want, together with -the libraries. You can use the \texttt{PYTHON\_EXECUTABLE} variable for this. If you -specify the full path to the actual executable, this should be sufficient. If not, set -\texttt{PYTHON\_LIBRARY} as well. +the libraries. You can use the \texttt{Python\_EXECUTABLE} variable for this. If you +specify the full path to the actual executable, this should be sufficient. +\footnote{If you are using CMake 3.13 or older, use \texttt{PYTHON\_EXECUTABLE}. +In this case, you might have to set \texttt{PYTHON\_LIBRARY} as well.} There are various other variables that can be set, some of which are only visible if you toggled the display of \texttt{Advanced} variables on. They should only be set by advanced users, or if a package @@ -5723,12 +5752,12 @@ \section{Using STIR in an external C++ project \label{sec:ExternalProjectC++}} STIR exports its CMake settings. Therefore, an external project can do \begin{verbatim} -find_package(STIR 5.1 CONFIG) +find_package(STIR 5.2 CONFIG) add_library(my_lib file1.cxx file2.cxx) # my_lib uses STIR functionality -target_link_libraries(my_lib ${STIR_LIBRARIES}) +target_link_libraries(my_lib PUBLIC ${STIR_LIBRARIES}) add_executable(my_exe my_exe.cxx ${STIR_REGISTRIES}) -target_link_libraries(my_exe my_lib) +target_link_libraries(my_exe PUBLIC my_lib) \end{verbatim} In addition, if your CMake is older than 3.12, you need to add \begin{verbatim} @@ -5749,20 +5778,9 @@ \section{ The \textit{STIR} library, in its current state, possesses many capabilities. The developers, however, look forward to still further increases -in the flexibility and power of the software. Some of the developments -being discussed are: - -\begin{itemize} -\item -expanded library of polymorphic classes (e.g. image grids, and -ordered subsets) -\item -additional scanners, also for SPECT -\item -additional data formats support, without conversion to Interfile. -\item -point-spread function reconstruction -\end{itemize} +in the flexibility and power of the software. +Please check the \url{https://github.com/UCL/STIR/milestones}{GitHub milestones} +for some information. While support for the library is on a voluntary basis, users @@ -5771,35 +5789,12 @@ \section{ \url{http://stir.sourceforge.net/ }{http://stir.sourceforge.net}) where they can follow developments of the software and obtain helpful information from other users. Questions will ONLY be -answered (if at all) when directed to the mailing list. +answered (if at all) when creating a \url{https://github.com/UCL/STIR/issues}{GitHub issue} (preferred) or +when directed to the mailing list. Commercial support is available from \url{http://asc.uk.com}{Algorithms and Software Consulting Ltd}. -Below, we list of some of the features that might make it into the next releases. -However, which feature is actually finalised/implemented depends -on the needs of the developers. If you want one of these features -and are willing to help, let us know. -\begin{itemize} -\item -More automatic testing programs -\item -More algorithms: potentially ART, -OSCB [Ben99b] -\item -More projectors -\item -More priors -\item -Extending the parallelisation of OSMAPOSL and OSSPS to FBP3DRP etc, or using OPEMMP -\item -Compatibility of the interpolating backprojector with recent -processors. -\item -More kinetic models: Spectral Analysis, Logan Plot -\end{itemize} - - diff --git a/documentation/STIR-general-overview.tex b/documentation/STIR-general-overview.tex index ca233c4afb..28d8b8626b 100644 --- a/documentation/STIR-general-overview.tex +++ b/documentation/STIR-general-overview.tex @@ -17,7 +17,7 @@ \textbf{{\huge STIR \\ General Overview}}\\ \textbf{Kris Thielemans}\\ -\textbf{\textit{version 2.3}} +\textbf{\textit{version 5.2}} \end{spacing} @@ -57,18 +57,21 @@ \section{ Disclaimer} Many names used below are trademarks owned by various companies. They are -is fully acknowledged, but not explicitly stated. +fully acknowledged, but not explicitly stated. + +This document has been brought somewhat up-to-date for version 5.2, but there is more work to do. \section{ Overview} STIR (\textit{Software for Tomographic Image Reconstruction}) is Open Source software (written in C++) consisting of classes, functions -and utilities for 3D PET image reconstruction, although it is +and utilities for 3D PET and SPECT image reconstruction, although it is general enough to accommodate other imaging modalities. An overview of STIR 2.x is given in [Thi12], which you ideally refer to in your paper. +See the STIR website for more details on how to reference STIR, depending on which functionality you use. -STIR consists of 2 parts. +STIR consists of 3 parts. \begin{itemize} \item A library providing building blocks for image and projection @@ -76,6 +79,7 @@ \section{ \item Applications using this library including basic image manipulations, file format conversions and of course image reconstructions. +\item Python interface to the library via SWIG. \end{itemize} The library has been designed so that it can be used for many @@ -85,8 +89,8 @@ \section{ yet. This will enable the software to be run not only on single processors, but also on massively parallel computers, or on clusters of workstations. \\ -STIR is portable on all systems supporting the GNU C++ compiler -or MS Visual C++ (or hopefully any ANSI C++ compliant compiler). +STIR is portable on all systems supporting the GNU C++ compiler, CLang++, Intel C++, +or MS Visual C++ (or hopefully any C++-11 compliant compiler). The library is fully documented.\\ The object-oriented features make this library very modular and flexible. This means that it is relatively easy to add new algorithms, @@ -94,17 +98,16 @@ \section{ It is even possible to select at run-time which version of these components you want to use.\\ The software is \textbf{freely available} for downloading under the -GNU LPGL (the library) or GNU GPL (the applications) license, -see the 'Registration' section of our web-site. \\ +Apache 2.0 license.\\ \textbf{It is the hope of the collaborators of the STIR project that -other researchers in the PET community will use this library +other researchers in the PET and SPECT will use this library for their own work, extending it and making their work available as well.} Please subscribe to some of our mailing lists if you are interested. \\ In its current status, the software is mainly a research tool. It is probably not friendly enough to use in a clinical setting. In any case, \textbf{STIR should not be used to generate images for diagnostic -purposes}, as there is no warranty, and most definitely no FDA approval +purposes}, as there is no warranty, and most definitely no FDA approval nor CE marking @@ -132,10 +135,7 @@ \section{ algorithm type, etc.); \item multi-dimensional arrays (any dimension) with various operations, including numeric manipulations; -\item reading and writing (I/O) data in Interfile format (for -which a 3D PET extension is proposed), reading of GE Advance -sinogram data, limited reading and writing of ECAT6 and ECAT7 -and conversion between ECAT6 and Interfile; +\item reading of various raw data as well as writing in Interfile format; \item classes of projection data (complete data set, segments, sinograms, viewgrams) and images (2D and 3D); \item various filter transfer functions (1D, 2D and 3D); @@ -156,7 +156,7 @@ \section{ \begin{figure}[htbp] \begin{center} \includegraphics[width=5.667in, height=1.137in]{graphics/STIR-general-overviewFig1} -\caption{Current hierarchy for back projectors.} +\caption{Somewhat outdated hierarchy for back projectors.} \end{center} \end{figure} @@ -193,7 +193,7 @@ \subsection{FBP} \subsection{3DRP} The 3DRP [Kin89] algorithm is often considered the 'reference' algorithm for 3D PET. It is a 3D FBP algorithm which uses reprojection -to fill in the missing data. +to fill in the missing data. However, this is not actively maintained anymore. \subsection{ Ordered Subsets Maximum A Posteriori using the One Step Late @@ -203,11 +203,7 @@ \subsection{ its accelerated variant OSEM (Ordered Set Expectation Maximization) [Hud94] are iterative methods for computing the maximum likelihood estimate of the tracer distribution based on the measured projection -data. Their benefits have been examined in various reports. Notably, -it has been found (e.g. [Rea98b]) that 3D OSEM can produce images -of higher quality, in terms of both resolution and contrast, -than analytic algorithms of common commercial use such as the -reprojection (3DRP) algorithm [Kin89]. +data. One drawback of OSEM is its tendency to develop noise artefacts with increasing iterations. As a remedy, various modifications @@ -371,47 +367,17 @@ \section{ \section{ Currently supported systems} - -These are the systems we tested our software on. We think that -it will work in other cases as well with minor tuning. Experience -will tell\dots - - - -\subsection{ -Computing platforms} +We regularly run STIR on Lniux, MacOS and Windows. Check our GitHub Actions +and Appveyor for details. -\subsubsection{ -Serial versions of algorithms} -A lot of STIR was developed kust before 2000. Therefore, the code -contains in various places work-arounds for deficient C++ compilers -(e.g. GNU g++ 2.95.2 or Microsoft Visual C++ 6.0 service pack 3). -However, we are no longer -testing STIR on such older systems. It might be possible to revive this, -but we strongly recommend that you get a recent compiler instead. -\begin{itemize} -\item -Intel based PCs using Windows XP or higher (NT probably still works if you -find a compiler for it). These were tested with Microsoft Visual C++ 2003, 2005 up to -Visual Studio 2010, -and various GNU g++ versions (up to at least 4.5.3) via CYGWIN (http://cygwin.com). -\item -Linux using GNU g++ (from 3.5 to 4.6). -Intel's C++ compiler 12.1.0 works fine (tested on Linux), older versions had some problems with the Fourier transform code but the reconstruction itself was fine. -\item -MacOS using GNU g++. -\end{itemize} -However, people run STIR on various other systems, including Solaris. Please -check the mailing lists. - \textbf{Warning}: We currently have a problem in the incremental backprojection routines due to different rounding -of floating point calculations on Sparc, HP and Opteron processors. -Thanks to a fix of Bing Bai this problem might have disappeared on your processor, but it is still there on recent 64-bit processors. +of floating point calculations. You will find out if this problem still exists when you run the recon\_text\_pack available on the STIR web-site. Please let us know. +This currently only affects the FBP routines. See the User's Guide for how to use another backprojector. @@ -419,7 +385,8 @@ \subsubsection{ Parallel versions of algorithms} \begin{itemize} \item a parallel version of \texttt{OSMAPOSL} and \texttt{OSSPS} using MPI -\item a threaded version of FBP2D using OPENMP, but for most systems, there is no performance gain. +\item OpenMP versions of many functions +\item CUDA versions for NiftyPET and parallelproj \end{itemize} @@ -427,18 +394,7 @@ \subsubsection{ \subsection{ PET scanners} -\begin{itemize} -\item -GE Advance, Discovery LS, Discovery ST, Discvoery STE, Discovery RX, -Discovery 600 -\item -PRT-1 -\item -ECAT 921, 931, 953, 951, 962, 966 (and most other ECAT systems) -\item -HRRT apparently works as well -\item Philips Allegro (using its convention of having a ``virtual'' extra crystal to cover the gaps). -\end{itemize} +See \texttt{buildblock/Scanners.cxx}. Other cylindrical PET scanners can easily be added. This can be done without modifying the code (see the Wiki). @@ -449,37 +405,27 @@ \subsection{ \begin{itemize} \item An extension of the Interfile standard (see the ''Other info'' section of the STIR website.) +\item +ITK can be used to read many image file formats +\item Siemens ``interfile-like'' data +\item GE RDF9 (if you have the HDF5 libraries) \item The commercial AnalyzeAVW library from the BIR, Mayo University, can be used -to read images via a conversion utility. +to read images via a conversion utility. This will be dropped in version 6.0. \item -GE Advance VOLPET sinogram format, but for reading only (with -very limited support for the header). If you have a research contract with GE, -you might be able to get a STIR extension to read Discovery ST, STE and RX data in both -VOLPET and RDF format. +GE Advance VOLPET sinogram format, but for reading only. This will be dropped in version 6.0. \item -ECAT 7 matrix format for reading only (for reading multiple -frames/gates, an Interfile -header has to be created using a utility we provide). Single -frame/gate/bed images can be directly read or written. Single -frame/gate/bed sinograms can be directly read. +ECAT 7 matrix format for reading only might work but is no longer supported. However, this file format needs the ECAT Matrix library (developed previously by M. Sibomana and C. Michel at Louvain la Neuve, Belgium). This library is no longer maintained however. -\item -ECAT 6 matrix format was supported in previous version of STIR, but -is currently broken on some systems (probably due to a bug in the ECAT -MATRIX library for ECAT 6). ECAT6 was supported by using conversion -utilities to and from -Interfile. Single frame/gate/bed images could be directly read -or written. \end{itemize} See the User's Guide for more detail on the supported file formats.\\ In addition, a separate set of classes is avalailable to read list-mode data. Only a few scanners are currently supported (such as the -ECAT HR+ and HR++), although it should not be too difficult to +ECAT HR+ and HR++, GE RDF9, Siemens mMR and SAFIR), although it should not be too difficult to add your own (if you know the list-mode file format!). diff --git a/documentation/release_5.2.htm b/documentation/release_5.2.htm index 550c1ed137..91258cb749 100644 --- a/documentation/release_5.2.htm +++ b/documentation/release_5.2.htm @@ -141,7 +141,7 @@

      Changed functionality

    -

    Deprecated functionality

    +

    Deprecated functionality and upcoming changes to required tool versions

    • The following functions (previously used for upsampling the scatter estimate) have been made obsolete or replaced, and will be removed in STIR version 6.0.0: interpolate_axial_position, extend_sinogram_in_views and extend_segment_in_views @@ -150,12 +150,14 @@

      Deprecated functionality

      and should be replaced by their respective versions that use SegmentIndices, ViewgramIndices or SinogramIndices. The former will not be compatible with TOF information that will be introduced in version 6.0.0.
    • -
    • STIR version 6.0.0 will require C++ 14 (currently we require C++ 11, but already support C++ 20).
    • +
    • Use of the AVW library to read Analyze files will be removed in 6.0, as this has not been checked in more than 15 years. Use ITK instead.
    • +
    • GE VOLPET and IE support will be removed in 6.0, as we have no files to test this, and it's obsolete anyway.
    • +
    • STIR version 6.0.0 will require C++ 14 (currently we require C++ 11, but already support C++ 20) and CMake 3.14.

    Build system and dependencies

      -
    • CMake 3.12 is now required on Windows. (It will be required on all platforms in STIR 6.0).
    • +
    • CMake 3.12 is now required on Windows.
    • We now use CMake's OBJECT library feature for the registries. This avoids re-compilation of the registries for every executable and therefore speeds-up building time. Use of STIR in an external project is not affected as long as the recommended practice was followed. This is now documented in the User's Guide.
      PR #1141.
    • From 6ae96eaebd57d4252ee05306f7284cfa509b2e8d Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sun, 29 Oct 2023 21:50:07 +0000 Subject: [PATCH 353/509] updated version number to 5.2.0 --- CMakeLists.txt | 6 +++--- VERSION.txt | 2 +- recon_test_pack/run_ML_norm_tests.sh | 12 ++---------- recon_test_pack/run_root_GATE.sh | 2 +- recon_test_pack/run_scatter_tests.sh | 2 +- recon_test_pack/run_test_listmode_recon.sh | 2 +- recon_test_pack/run_test_simulate_and_recon.sh | 2 +- .../run_test_simulate_and_recon_with_motion.sh | 2 +- recon_test_pack/run_test_zoom_image.sh | 2 +- recon_test_pack/run_tests.sh | 2 +- recon_test_pack/simulate_PET_data_for_tests.sh | 2 +- 11 files changed, 14 insertions(+), 22 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cec25b64b3..2873ad6dc5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -56,9 +56,9 @@ endif() ####### Set Version number etc set(VERSION_MAJOR 5) -set(VERSION_MINOR 1) -set(VERSION_PATCH 2) -set(VERSION 050102) # only used in STIRConfig.h.in +set(VERSION_MINOR 2) +set(VERSION_PATCH 0) +set(VERSION 050200) # only used in STIRConfig.h.in set(STIR_VERSION ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}) diff --git a/VERSION.txt b/VERSION.txt index 61fcc87350..91ff57278e 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -5.1.2 +5.2.0 diff --git a/recon_test_pack/run_ML_norm_tests.sh b/recon_test_pack/run_ML_norm_tests.sh index bb4199f3b2..73b3f342f7 100755 --- a/recon_test_pack/run_ML_norm_tests.sh +++ b/recon_test_pack/run_ML_norm_tests.sh @@ -10,22 +10,14 @@ # Copyright (C) 2013, 2020, 2021 University 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.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. +# SPDX-License-Identifier: Apache-2.0 # # See STIR/LICENSE.txt for details # # Author Kris Thielemans # -echo This script should work with STIR version 5.0. If you have +echo This script should work with STIR version 5.2. If you have echo a later version, you might have to update your test pack. echo Please check the web site. echo diff --git a/recon_test_pack/run_root_GATE.sh b/recon_test_pack/run_root_GATE.sh index 0eb0f29c68..070da72cae 100755 --- a/recon_test_pack/run_root_GATE.sh +++ b/recon_test_pack/run_root_GATE.sh @@ -11,7 +11,7 @@ # # Author Nikos Efthimiou, Kris Thielemans -echo This script should work with STIR version ">"3.0. If you have +echo This script should work with STIR version 5.2. If you have echo a later version, you might have to update your test pack. echo Please check the web site. echo diff --git a/recon_test_pack/run_scatter_tests.sh b/recon_test_pack/run_scatter_tests.sh index 8a2ee00f00..8dd29343bb 100755 --- a/recon_test_pack/run_scatter_tests.sh +++ b/recon_test_pack/run_scatter_tests.sh @@ -13,7 +13,7 @@ # Author Kris Thielemans # -echo This script should work with STIR version 5.1. If you have +echo This script should work with STIR version 5.1 and 5.2. If you have echo a later version, you might have to update your test pack. echo Please check the web site. echo diff --git a/recon_test_pack/run_test_listmode_recon.sh b/recon_test_pack/run_test_listmode_recon.sh index 5003040a8b..e3d1ece552 100755 --- a/recon_test_pack/run_test_listmode_recon.sh +++ b/recon_test_pack/run_test_listmode_recon.sh @@ -20,7 +20,7 @@ if [ -n "$TRAVIS" -o -n "$GITHUB_WORKSPACE" ]; then set -e fi -echo This script should work with STIR version ">=" 5.1. If you have +echo This script should work with STIR version 5.1 and 5.2. If you have echo a later version, you might have to update your test pack. echo Please check the web site. echo diff --git a/recon_test_pack/run_test_simulate_and_recon.sh b/recon_test_pack/run_test_simulate_and_recon.sh index d955c58142..b277fa3fe5 100755 --- a/recon_test_pack/run_test_simulate_and_recon.sh +++ b/recon_test_pack/run_test_simulate_and_recon.sh @@ -19,7 +19,7 @@ if [ -n "$TRAVIS" -o -n "$GITHUB_WORKSPACE" ]; then set -e fi -echo This script should work with STIR version 2.2, 2.3, 2.4 and 3.0. If you have +echo This script should work with STIR version 5.2. If you have echo a later version, you might have to update your test pack. echo Please check the web site. echo diff --git a/recon_test_pack/run_test_simulate_and_recon_with_motion.sh b/recon_test_pack/run_test_simulate_and_recon_with_motion.sh index 61fda54345..46527cc989 100755 --- a/recon_test_pack/run_test_simulate_and_recon_with_motion.sh +++ b/recon_test_pack/run_test_simulate_and_recon_with_motion.sh @@ -21,7 +21,7 @@ if [ -n "$TRAVIS" -o -n "$GITHUB_WORKSPACE" ]; then set -e fi -echo This script should work with STIR version 2.4 and 3.0. If you have +echo This script should work with STIR version 5.2. If you have echo a later version, you might have to update your test pack. echo Please check the web site. echo diff --git a/recon_test_pack/run_test_zoom_image.sh b/recon_test_pack/run_test_zoom_image.sh index d46aa80c9f..d53a2280a6 100755 --- a/recon_test_pack/run_test_zoom_image.sh +++ b/recon_test_pack/run_test_zoom_image.sh @@ -18,7 +18,7 @@ if [ -n "$TRAVIS" -o -n "$GITHUB_WORKSPACE" ]; then set -e fi -echo This script should work with STIR version 4.0. If you have +echo This script should work with STIR version 5.2. If you have echo a later version, you might have to update your test pack. echo Please check the web site. echo diff --git a/recon_test_pack/run_tests.sh b/recon_test_pack/run_tests.sh index 7d74b1761e..9aa41f4dff 100755 --- a/recon_test_pack/run_tests.sh +++ b/recon_test_pack/run_tests.sh @@ -19,7 +19,7 @@ if [ -n "$TRAVIS" -o -n "$GITHUB_WORKSPACE" ]; then set -e fi -echo This script should work with STIR version 2.1, 2.2, 2.3, 2.4 and 3.0. If you have +echo This script should work with STIR version 5.2. If you have echo a later version, you might have to update your test pack. echo Please check the web site. echo diff --git a/recon_test_pack/simulate_PET_data_for_tests.sh b/recon_test_pack/simulate_PET_data_for_tests.sh index 6c0d759f13..3eece2ba8a 100755 --- a/recon_test_pack/simulate_PET_data_for_tests.sh +++ b/recon_test_pack/simulate_PET_data_for_tests.sh @@ -17,7 +17,7 @@ # Author Kris Thielemans # -echo This script should work with STIR version 5.x. If you have +echo This script should work with STIR version 5.2. If you have echo a later version, you might have to update your test pack. echo Please check the web site. echo From 5c1cd22ad9cef592f9fdf855118e2f3549913712 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sun, 29 Oct 2023 21:54:23 +0000 Subject: [PATCH 354/509] removed mention of Travis --- CONTRIBUTING.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7210cb7641..3c8ea7411a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -44,7 +44,6 @@ Please by mindful about the resources used by our Continuous Integration (CI) wo - Use specific keywords in the first line of the last commit that you push to prevent CI being run: - `[ci skip]` skips all CI runs (e.g. when you only change documentation, or when your update isn't ready yet) - `[actions skip]` does not run GitHub Actions, see [here](https://github.blog/changelog/2021-02-08-github-actions-skip-pull-request-and-push-workflows-with-skip-ci/). Note: this can be in the main commit message. - - `[travis skip]` does not run Travis-CI, see [here](https://docs.travis-ci.com/user/customizing-the-build/#skipping-a-build). Note: this can be in the main commit message. - `[skip appveyor]` does not run Appveyor, see [here](https://www.appveyor.com/docs/how-to/filtering-commits/#skip-directive-in-commit-message) 8. After acceptance of your PR, go home with a nice warm feeling. From e2a359a10033ffc45f61b969d9148f3bebb8c884 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sun, 29 Oct 2023 21:58:16 +0000 Subject: [PATCH 355/509] add PR template --- .github/PULL_REQUEST_TEMPLATE.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .github/PULL_REQUEST_TEMPLATE.md diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000000..aed2641f0e --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,22 @@ + + +## Changes in this pull request + + +## Testing performed + + +## Related issues + + + +## Checklist before requesting a review + + - [] I have performed a self-review of my code + - [] I have added docstrings/doxygen in line with the guidance in the developer guide + - [] I have implemented unit tests that cover any new or modified functionality (if applicable) + - [] The code builds and runs on my machine + - [] `documentation/release_XXX.md` has been updated with any functionality change (if applicable) From 2086080978ad57bb6492b6a1a0905eecd60956d3 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sun, 29 Oct 2023 22:03:22 +0000 Subject: [PATCH 356/509] update release 5.2.0 dates --- documentation/history.htm | 4 +++- documentation/release_5.2.htm | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/documentation/history.htm b/documentation/history.htm index 076f96d3b3..c4a258e8bb 100644 --- a/documentation/history.htm +++ b/documentation/history.htm @@ -11,6 +11,8 @@

      History of public releases of the STIR software

      The following links give you a summary what has changed. However, the ChangeLog is the definite (but tedious) information.
        +
      • version 5.2.0 (dated 30 October 2023)
      • +
      • version 5.1.2 (dated 10 September 2023)
      • version 5.1.1 (dated 26 Augustus 2023)
      • version 5.1.0 (dated 14 Jan 2023)
      • version 5.0.2 (dated 26 May 2022)
      • @@ -46,7 +48,7 @@

        History of public releases of the PARAPET software

        -Last modified: January 02 2023 +Last modified: 29 October 2023 diff --git a/documentation/release_5.2.htm b/documentation/release_5.2.htm index 91258cb749..287b2ae0c6 100644 --- a/documentation/release_5.2.htm +++ b/documentation/release_5.2.htm @@ -20,7 +20,7 @@

        Overall summary

        Patch release info

          -
        • 5.2.0 released ??/??/2023
        • +
        • 5.2.0 released 30/10/2023

        Summary for end users (also to be read by developers)

        From a92c5e5173b8d3dff3da2862da85127826c3bcd2 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sun, 29 Oct 2023 23:31:49 +0000 Subject: [PATCH 357/509] corrected doxygen and removed some obsolete macros --- src/include/stir/common.h | 57 +++++++-------------------------------- 1 file changed, 9 insertions(+), 48 deletions(-) diff --git a/src/include/stir/common.h b/src/include/stir/common.h index c636c61f56..294404e19a 100644 --- a/src/include/stir/common.h +++ b/src/include/stir/common.h @@ -34,34 +34,23 @@
        • macros for namespace support: - #defines STIR_NO_NAMESPACES if the compiler does not support namespaces - #defines START_NAMESPACE_STIR etc. - -
        • #defines STIR_NO_COVARIANT_RETURN_TYPES when the compiler does not - support virtual functions of a derived class differing only in the return - type. - Define when your compiler does not handle the following: - \code - class A { virtual A* f();} - class B:A { virtual B* f(); } - \endcode + \c \#defines \c START_NAMESPACE_STIR etc. +
        • preprocessor definitions which attempt to determine the operating system this is going to run on. - use as #ifdef __OS_WIN__ ... #elif ... #endif + use as \c "#ifdef __OS_WIN__ ... #elif ... #endif" Possible values are __OS_WIN__, __OS_MAC__, __OS_VAX__, __OS_UNIX__ The __OS_UNIX__ case has 'subbranches'. At the moment we attempt to find out on __OS_AIX__, __OS_SUN__, __OS_OSF__, __OS_LINUX__. (If the attempts fail to determine the correct OS, you can pass the correct value as a preprocessor definition to the compiler) - -
        • #includes cstdio, cstdlib, cstring, cmath - -
        • a trick to get ANSI C++ 'for' scoping rules work for compilers - which do not follow the new standard - -
        • #ifdef STIR_ASSERT, then define our own assert, else include <cassert> +
        • +
        • \c \#includes cstdio, cstdlib, cstring, cmath +
        • +
        • \c \#ifdef \c STIR_ASSERT, then define our own assert, else include <cassert> +

        Speeding up std::copy

        @@ -69,6 +58,7 @@
        • For old compilers (check the source!), overloads of std::copy for built-in types to use memmove (so it's faster) +

        stir namespace members declared here

        @@ -83,8 +73,6 @@

        stir include files included here

        • stir/config.h sets various preprocessor defines (generated from STIRConfig.in)
        • -
        • stir/error.h defining stir::error
        • -
        • stir/warning.h defining stir::warning
        */ #include "stir/config.h" @@ -96,22 +84,12 @@ #include //*************** namespace macros -#ifndef STIR_NO_NAMESPACES - # define START_NAMESPACE_STIR namespace stir { # define END_NAMESPACE_STIR } # define USING_NAMESPACE_STIR using namespace stir; # define START_NAMESPACE_STD namespace std { # define END_NAMESPACE_STD } # define USING_NAMESPACE_STD using namespace std; -#else -# define START_NAMESPACE_STIR -# define END_NAMESPACE_STIR -# define USING_NAMESPACE_STIR -# define START_NAMESPACE_STD -# define END_NAMESPACE_STD -# define USING_NAMESPACE_STD -#endif //*************** define __OS_xxx__ @@ -239,23 +217,6 @@ END_NAMESPACE_STD #endif // #ifdef STIR_SPEED_UP_STD_COPY -//*************** for() scope trick - -/* ANSI C++ (re)defines the scope of variables declared in a for() statement. - Example: the 'i' variable has scope only within the for statement. - for (int i=0; ...) - do_something; - The next trick (by AZ) solves this problem. - At the moment, we only need it for VC++ - */ - -#if defined(STIR_ENABLE_FOR_SCOPE_WORKAROUND) -# ifndef for -# define for if (0) ; else for -# else -# error 'for' is already #defined to something -# endif -#endif //*************** assert From eccfd810d90632e9896e6d0e5e5681a210efd781 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sun, 29 Oct 2023 23:48:29 +0000 Subject: [PATCH 358/509] reduce amount of doxygen warnings --- src/Doxyfile.in | 2 +- .../utilities/list_TAC_ROI_values.cxx | 2 +- src/include/stir/DetectorCoordinateMap.h | 2 +- src/include/stir/GeometryBlocksOnCylindrical.h | 10 +++++----- src/include/stir/HighResWallClockTimer.h | 3 ++- .../ForwardProjectorByBinUsingRayTracing.h | 4 ++-- src/include/stir/recon_buildblock/PLSPrior.h | 2 +- .../ProjMatrixByBinPinholeSPECTUB.h | 18 ++++-------------- .../ProjMatrixByBinUsingRayTracing.h | 4 ++-- .../modelling/BloodFrameData.inl | 3 ++- .../motion/RigidObject3DTransformation.h | 2 +- ...MatrixByDenselOnCartesianGridUsingElement.h | 4 ++-- .../ProjMatrixByDenselUsingRayTracing.h | 4 ++-- ...ProjectorByBinUsingInterpolation_linear.cxx | 2 +- ...yBinUsingInterpolation_piecewise_linear.cxx | 2 +- 15 files changed, 28 insertions(+), 36 deletions(-) diff --git a/src/Doxyfile.in b/src/Doxyfile.in index 612be456c8..9325caa4ec 100644 --- a/src/Doxyfile.in +++ b/src/Doxyfile.in @@ -853,7 +853,7 @@ EXAMPLE_RECURSIVE = NO # that contain images that are to be included in the documentation (see the # \image command). -IMAGE_PATH = @PROJECT_SOURCE_DIR@/../doximages +IMAGE_PATH = @PROJECT_SOURCE_DIR@/doximages # The INPUT_FILTER tag can be used to specify a program that doxygen should # invoke to filter for each input file. Doxygen will invoke the filter program diff --git a/src/experimental/utilities/list_TAC_ROI_values.cxx b/src/experimental/utilities/list_TAC_ROI_values.cxx index de1d8a80af..cd739302b2 100644 --- a/src/experimental/utilities/list_TAC_ROI_values.cxx +++ b/src/experimental/utilities/list_TAC_ROI_values.cxx @@ -23,7 +23,7 @@ \param output_filename a text file sorted in list form of: | Frame_num | Start Time | End Time | Mean | StdDev | CV | \param data_filename should be in ECAT7 format. \param --CV is used to output the Coefficient of Variation as well. - \Note When ROI_filename.par is not given, the user will be asked for the parameters. + Note: When ROI_filename.par is not given, the user will be asked for the parameters. The .par file has the following format \verbatim diff --git a/src/include/stir/DetectorCoordinateMap.h b/src/include/stir/DetectorCoordinateMap.h index 0aac7c2359..ecc79bf199 100644 --- a/src/include/stir/DetectorCoordinateMap.h +++ b/src/include/stir/DetectorCoordinateMap.h @@ -47,7 +47,7 @@ class Succeeded; ring,detector,(layer,)x,y,z An empty line will terminate the reading at that line. - Optionally LOR end-points can be randomly displaced using a Gaussian distribution with standard deviation \sigma (in mm). + Optionally LOR end-points can be randomly displaced using a Gaussian distribution with standard deviation sigma (in mm). */ class DetectorCoordinateMap { diff --git a/src/include/stir/GeometryBlocksOnCylindrical.h b/src/include/stir/GeometryBlocksOnCylindrical.h index a14f5cea5f..fba4b1802d 100644 --- a/src/include/stir/GeometryBlocksOnCylindrical.h +++ b/src/include/stir/GeometryBlocksOnCylindrical.h @@ -36,12 +36,12 @@ START_NAMESPACE_STIR \ingroup projdata \brief A helper class to build the crystal map based on scanner info. - \This class builds two maps between cartesian coordinates (z, y, x) - \ and the corresponding detection position (tangential_num, axial_num, radial_num) for each crystal. - \The crystal map, then, is used in ProjDataInfoBlocksOnCylindrical, ProjDataInfoBlocksOnCylindricalNoArcCorr, and CListRecordSAFIR + This class builds two maps between cartesian coordinates (z, y, x) + and the corresponding detection position (tangential_num, axial_num, radial_num) for each crystal. + The crystal map, then, is used in ProjDataInfoBlocksOnCylindrical, ProjDataInfoBlocksOnCylindricalNoArcCorr, and CListRecordSAFIR - \The center of first ring is the center of coordinates. - \Distances are from center to center of crystals. + The center of first ring is the center of coordinates. + Distances are from center to center of crystals. */ diff --git a/src/include/stir/HighResWallClockTimer.h b/src/include/stir/HighResWallClockTimer.h index a723465790..eb1ce6a6bc 100644 --- a/src/include/stir/HighResWallClockTimer.h +++ b/src/include/stir/HighResWallClockTimer.h @@ -17,7 +17,8 @@ \author Alexey Zverovich \author PARAPET project - +*/ +/* Modification history: diff --git a/src/include/stir/recon_buildblock/ForwardProjectorByBinUsingRayTracing.h b/src/include/stir/recon_buildblock/ForwardProjectorByBinUsingRayTracing.h index ad14ccfde0..b6a36ff389 100644 --- a/src/include/stir/recon_buildblock/ForwardProjectorByBinUsingRayTracing.h +++ b/src/include/stir/recon_buildblock/ForwardProjectorByBinUsingRayTracing.h @@ -44,8 +44,8 @@ class ProjDataInfoCylindrical; \brief This class implements forward projection using Siddon's algorithm for ray tracing. That is, it computes length of intersection with the voxels. - Currently, the LOIs are divided by voxel_size.x(), unless NEWSCALE is - #defined during compilation time of ForwardProjectorByBinUsingRayTracing_Siddon.cxx. + Currently, the LOIs are divided by voxel_size.x(), unless \c NEWSCALE is + \c \#defined during compilation time of ForwardProjectorByBinUsingRayTracing_Siddon.cxx. If the z voxel size is exactly twice the sampling in axial direction, multiple LORs are used, to avoid missing voxels. (TODOdoc describe how). diff --git a/src/include/stir/recon_buildblock/PLSPrior.h b/src/include/stir/recon_buildblock/PLSPrior.h index f2e363991d..018d47c44c 100644 --- a/src/include/stir/recon_buildblock/PLSPrior.h +++ b/src/include/stir/recon_buildblock/PLSPrior.h @@ -48,7 +48,7 @@ START_NAMESPACE_STIR \phi(f) = \sqrt{\alpha^2 + |\nabla f|^2 - {\langle\nabla f,\xi\rangle}^2} \f] where \f$ f \f$ is the PET image, - \f$\nabla \f$ is the finite difference operator (\textrm{not} taking voxel-sizes into account) and + \f$\nabla \f$ is the finite difference operator (\b not taking voxel-sizes into account) and \f$ \xi \f$ is the normalised gradient of the anatomical image calculated as follows: \f[ diff --git a/src/include/stir/recon_buildblock/ProjMatrixByBinPinholeSPECTUB.h b/src/include/stir/recon_buildblock/ProjMatrixByBinPinholeSPECTUB.h index 44d3ad2c23..1b584356b6 100644 --- a/src/include/stir/recon_buildblock/ProjMatrixByBinPinholeSPECTUB.h +++ b/src/include/stir/recon_buildblock/ProjMatrixByBinPinholeSPECTUB.h @@ -3,17 +3,7 @@ Copyright (C) 2021, University College London This file is part of STIR. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. + SPDX-License-Identifier: Apache-2.0 See STIR/LICENSE.txt for details */ @@ -53,7 +43,7 @@ class Bin; \warning this class currently only works with VoxelsOnCartesianGrid. - \Sample parameter file + \par Sample parameter file \verbatim Projection Matrix By Bin Pinhole SPECT UB Parameters:= @@ -83,7 +73,7 @@ class Bin; End Projection Matrix By Bin Pinhole SPECT UB Parameters:= \endverbatim - \Sample detector file + \par Sample detector file \verbatim Information of detector @@ -104,7 +94,7 @@ class Bin; \#…………until here……………\# \endverbatim - \Sample collimator file + \par Sample collimator file \verbatim Information of collimator Comments are allowed here or anywhere in lines not containing parameters. diff --git a/src/include/stir/recon_buildblock/ProjMatrixByBinUsingRayTracing.h b/src/include/stir/recon_buildblock/ProjMatrixByBinUsingRayTracing.h index 6fb89f8af4..54784c2b72 100644 --- a/src/include/stir/recon_buildblock/ProjMatrixByBinUsingRayTracing.h +++ b/src/include/stir/recon_buildblock/ProjMatrixByBinUsingRayTracing.h @@ -38,8 +38,8 @@ class ProjDataInfo; \brief Computes projection matrix elements for VoxelsOnCartesianGrid images by using a Length of Intersection (LOI) model. - Currently, the LOIs are divided by voxel_size.x(), unless NEWSCALE is - #defined during compilation time of ProjMatrixByBinUsingRayTracing.cxx. + Currently, the LOIs are divided by voxel_size.x(), unless \c NEWSCALE is + \c \#defined during compilation time of ProjMatrixByBinUsingRayTracing.cxx. It is possible to use multiple LORs in tangential direction. The result will then be the average of the various contributions. Currently all these diff --git a/src/include/stir_experimental/modelling/BloodFrameData.inl b/src/include/stir_experimental/modelling/BloodFrameData.inl index e526910fe0..5fd4a1a1df 100644 --- a/src/include/stir_experimental/modelling/BloodFrameData.inl +++ b/src/include/stir_experimental/modelling/BloodFrameData.inl @@ -25,7 +25,8 @@ START_NAMESPACE_STIR BloodFrameData::BloodFrameData() { } -//! constructor giving a vector //ChT::ToDO: Better to use iterators +//! constructor giving a vector +//ChT::ToDO: Better to use iterators BloodFrameData::BloodFrameData(const std::vector & blood_plot) {this->_blood_plot=blood_plot;} diff --git a/src/include/stir_experimental/motion/RigidObject3DTransformation.h b/src/include/stir_experimental/motion/RigidObject3DTransformation.h index c3432a83b9..7fc0aa723c 100644 --- a/src/include/stir_experimental/motion/RigidObject3DTransformation.h +++ b/src/include/stir_experimental/motion/RigidObject3DTransformation.h @@ -143,7 +143,7 @@ class RigidObject3DTransformation //! Transform bin from some projection data /*! Finds 'closest' (in some sense) bin to the transformed LOR. - if NEW_ROT is not #defined at compilation time, + if \c NEW_ROT is not \c \#defined at compilation time, it will throw an exception when arc-corrected data is used.*/ void transform_bin(Bin& bin,const ProjDataInfo& out_proj_data_info, const ProjDataInfo& in_proj_data_info) const; diff --git a/src/include/stir_experimental/recon_buildblock/ProjMatrixByDenselOnCartesianGridUsingElement.h b/src/include/stir_experimental/recon_buildblock/ProjMatrixByDenselOnCartesianGridUsingElement.h index 47baf865db..a1b8304b63 100644 --- a/src/include/stir_experimental/recon_buildblock/ProjMatrixByDenselOnCartesianGridUsingElement.h +++ b/src/include/stir_experimental/recon_buildblock/ProjMatrixByDenselOnCartesianGridUsingElement.h @@ -32,8 +32,8 @@ template class DiscretisedDensity; \brief Computes projection matrix elements for VoxelsOnCartesianGrid images by using a Length of Intersection (LOI) model. - Currently, the LOIs are divided by voxel_size.x(), unless NEWSCALE is - #defined during compilation time of ProjMatrixByDenselOnCartesianGridUsingElement.cxx. + Currently, the LOIs are divided by voxel_size.x(), unless \c NEWSCALE is + \c \#defined during compilation time of ProjMatrixByDenselOnCartesianGridUsingElement.cxx. If the z voxel size is exactly twice the sampling in axial direction, multiple LORs are used, to avoid missing voxels. (TODOdoc describe how). diff --git a/src/include/stir_experimental/recon_buildblock/ProjMatrixByDenselUsingRayTracing.h b/src/include/stir_experimental/recon_buildblock/ProjMatrixByDenselUsingRayTracing.h index e3bb0adef7..a1da9e19ec 100644 --- a/src/include/stir_experimental/recon_buildblock/ProjMatrixByDenselUsingRayTracing.h +++ b/src/include/stir_experimental/recon_buildblock/ProjMatrixByDenselUsingRayTracing.h @@ -32,8 +32,8 @@ class DataSymmetriesForDensels_PET_CartesianGrid; \brief Computes projection matrix elements for VoxelsOnCartesianGrid images by using a Length of Intersection (LOI) model. - Currently, the LOIs are divided by voxel_size.x(), unless NEWSCALE is - #defined during compilation time of ProjMatrixByDenselUsingRayTracing.cxx. + Currently, the LOIs are divided by voxel_size.x(), unless \c NEWSCALE is + \c \#defined during compilation time of ProjMatrixByDenselUsingRayTracing.cxx. If the z voxel size is exactly twice the sampling in axial direction, multiple LORs are used, to avoid missing voxels. (TODOdoc describe how). diff --git a/src/recon_buildblock/BackProjectorByBinUsingInterpolation_linear.cxx b/src/recon_buildblock/BackProjectorByBinUsingInterpolation_linear.cxx index 14782cb022..9061e5979e 100644 --- a/src/recon_buildblock/BackProjectorByBinUsingInterpolation_linear.cxx +++ b/src/recon_buildblock/BackProjectorByBinUsingInterpolation_linear.cxx @@ -9,7 +9,7 @@ stir::BackProjectorByBinUsingInterpolation, for the case of piecewise linear interpolation. - \warning This #includes BackProjectorByBinUsingInterpolation_3DCho.cxx + \warning This \c \#includes BackProjectorByBinUsingInterpolation_3DCho.cxx This very ugly way of including a .cxx file is used to avoid replication of a lot of (difficult) code. diff --git a/src/recon_buildblock/BackProjectorByBinUsingInterpolation_piecewise_linear.cxx b/src/recon_buildblock/BackProjectorByBinUsingInterpolation_piecewise_linear.cxx index 716d1cef8c..aa5ed32f44 100644 --- a/src/recon_buildblock/BackProjectorByBinUsingInterpolation_piecewise_linear.cxx +++ b/src/recon_buildblock/BackProjectorByBinUsingInterpolation_piecewise_linear.cxx @@ -9,7 +9,7 @@ stir::BackProjectorByBinUsingInterpolation, for the case of piecewise linear interpolation. - \warning This #includes BackProjectorByBinUsingInterpolation_3DCho.cxx + \warning This \c \#includes BackProjectorByBinUsingInterpolation_3DCho.cxx This very ugly way of including a .cxx file is used to avoid replication of a lot of (difficult) code. From 1e25bf6bf7f47bcf7a6e3d367601d3b5316da64e Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Mon, 30 Oct 2023 08:06:00 +0000 Subject: [PATCH 359/509] tiny fix to html --- documentation/release_5.2.htm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/release_5.2.htm b/documentation/release_5.2.htm index 287b2ae0c6..7b0906ce97 100644 --- a/documentation/release_5.2.htm +++ b/documentation/release_5.2.htm @@ -42,7 +42,7 @@

        Changed functionality

        hard-coded variables for the Siemens mMR have been removed. Further testing of this functionality is still required however.
        PR #1182. -
      • Interfile header parsing now correctly identifies keywords that contain a colon by checking for :=./li> +
      • Interfile header parsing now correctly identifies keywords that contain a colon by checking for :=.
      • The set_up() method of the ray-tracing projection matrix now skips further processing if it was already called with data of the same characteristics. This will means that any cached data From 734a0d713df4318cea10f1a8ec5aea90ca093254 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Mon, 30 Oct 2023 14:05:36 +0000 Subject: [PATCH 360/509] html fix [ci skip] --- documentation/release_5.2.htm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/release_5.2.htm b/documentation/release_5.2.htm index 7b0906ce97..315acfee8e 100644 --- a/documentation/release_5.2.htm +++ b/documentation/release_5.2.htm @@ -7,7 +7,7 @@

        Summary of changes in STIR release 5.2

        -

        This version is 100% backwards compatible with STIR 5.0 as far as usage goes. However, there are changes in the output of scatter estimation and ECAT8 normalisation, see below for more information. +

        This version is 100% backwards compatible with STIR 5.0 as far as usage goes. However, there are changes in the output of scatter estimation and ECAT8 normalisation, see below for more information.

        Overall summary

        From d3870edaf084e4bcb5d639367577c7066b7e418d Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Tue, 7 Nov 2023 05:51:22 +0000 Subject: [PATCH 361/509] remove erroneous asserts introduced in previous merge They didn't make sense... --- src/include/stir/Sinogram.inl | 2 -- src/include/stir/Viewgram.inl | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/include/stir/Sinogram.inl b/src/include/stir/Sinogram.inl index da1c10b66b..d8feda9ced 100644 --- a/src/include/stir/Sinogram.inl +++ b/src/include/stir/Sinogram.inl @@ -116,8 +116,6 @@ Sinogram(const Array<2,elemT>& p, assert( get_max_view_num() == pdi_ptr->get_max_view_num()); assert( get_min_tangential_pos_num() == pdi_ptr->get_min_tangential_pos_num()); assert( get_max_tangential_pos_num() == pdi_ptr->get_max_tangential_pos_num()); - assert( get_min_timing_pos_num() == pdi_sptr->get_min_timing_pos_num()); - assert( get_max_timing_pos_num() == pdi_sptr->get_max_timing_pos_num()); } diff --git a/src/include/stir/Viewgram.inl b/src/include/stir/Viewgram.inl index 5de267d27b..41d2d433a3 100644 --- a/src/include/stir/Viewgram.inl +++ b/src/include/stir/Viewgram.inl @@ -116,8 +116,6 @@ Viewgram(const Array<2,elemT>& p, assert( get_max_axial_pos_num() == pdi_sptr->get_max_axial_pos_num(ind.segment_num())); assert( get_min_tangential_pos_num() == pdi_sptr->get_min_tangential_pos_num()); assert( get_max_tangential_pos_num() == pdi_sptr->get_max_tangential_pos_num()); - assert( get_min_timing_pos_num() == pdi_sptr->get_min_timing_pos_num()); - assert( get_max_timing_pos_num() == pdi_sptr->get_max_timing_pos_num()); } template From 873b28d7a4addcaf4bbc09cb491d9e8ff491fe7a Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Tue, 14 Nov 2023 07:41:25 +0000 Subject: [PATCH 362/509] [SWIG] exposed more LOR classes - add LORAs2Points etc - added .h file to work-around SWIG bug with private typedef self_type - removed some old #ifdefs in the .h file To do this cleanly, move lines from stir.i to separate stir_LOR.i --- src/include/stir/LORCoordinates.h | 54 +++++++++++++------------------ src/swig/stir.i | 22 +------------ src/swig/stir_LOR.i | 49 ++++++++++++++++++++++++++++ 3 files changed, 72 insertions(+), 53 deletions(-) create mode 100644 src/swig/stir_LOR.i diff --git a/src/include/stir/LORCoordinates.h b/src/include/stir/LORCoordinates.h index dead21f25c..bf0ef82ec9 100644 --- a/src/include/stir/LORCoordinates.h +++ b/src/include/stir/LORCoordinates.h @@ -161,7 +161,12 @@ class LORCylindricalCoordinates_z_and_radius template class LORInCylinderCoordinates : public LOR { +#ifdef SWIG + // SWIG needs this typedef to be public + public: +#endif typedef LORInCylinderCoordinates self_type; + private: void check_state() const { assert(_radius>0); @@ -220,13 +225,8 @@ class LORInCylinderCoordinates : public LOR inline LORInCylinderCoordinates(const LORAs2Points&); - virtual -#ifndef STIR_NO_COVARIANT_RETURN_TYPES - self_type* -#else - LOR* -#endif - clone() const { return new self_type(*this); } + self_type* clone() const override + { return new self_type(*this); } virtual Succeeded @@ -259,6 +259,10 @@ class LORInCylinderCoordinates : public LOR template class LORAs2Points : public LOR { +#ifdef SWIG + // SWIG needs this typedef to be public + public: +#endif typedef LORAs2Points self_type; public: const CartesianCoordinate3D& p1() const { return _p1; } @@ -282,13 +286,8 @@ class LORAs2Points : public LOR inline LORAs2Points(const LORInAxialAndNoArcCorrSinogramCoordinates&); - virtual -#ifndef STIR_NO_COVARIANT_RETURN_TYPES - self_type* -#else - LOR* -#endif - clone() const { return new self_type(*this); } + self_type* clone() const override + { return new self_type(*this); } virtual Succeeded @@ -326,7 +325,12 @@ class LORInAxialAndSinogramCoordinates : public LOR, private LORCylindricalCoordinates_z_and_radius { private: +#ifdef SWIG + // SWIG needs this typedef to be public + public: +#endif typedef LORInAxialAndSinogramCoordinates self_type; + private: typedef LORCylindricalCoordinates_z_and_radius private_base_type; // sorry: has to be first to give the compiler a better chance of inlining void check_state() const @@ -371,18 +375,11 @@ class LORInAxialAndSinogramCoordinates inline LORInAxialAndSinogramCoordinates(const LORInAxialAndNoArcCorrSinogramCoordinates&); -#if __cplusplus>= 201103L inline LORInAxialAndSinogramCoordinates(const LORAs2Points&); -#endif - virtual -#ifndef STIR_NO_COVARIANT_RETURN_TYPES - self_type* -#else - LOR* -#endif - clone() const { return new self_type(*this); } + self_type* clone() const override + { return new self_type(*this); } void reset(coordT radius=1) { @@ -506,18 +503,11 @@ class LORInAxialAndNoArcCorrSinogramCoordinates inline LORInAxialAndNoArcCorrSinogramCoordinates(const LORInAxialAndSinogramCoordinates&); -#if __cplusplus>= 201103L inline LORInAxialAndNoArcCorrSinogramCoordinates(const LORAs2Points&); -#endif - virtual -#ifndef STIR_NO_COVARIANT_RETURN_TYPES - self_type* -#else - LOR* -#endif - clone() const { return new self_type(*this); } + self_type* clone() const override + { return new self_type(*this); } virtual Succeeded diff --git a/src/swig/stir.i b/src/swig/stir.i index 48ae6a8af8..734397ecca 100644 --- a/src/swig/stir.i +++ b/src/swig/stir.i @@ -908,9 +908,6 @@ namespace std { %shared_ptr(stir::ParsingObject); %shared_ptr(stir::Verbosity); -%shared_ptr(stir::LORAs2Points); -%shared_ptr(stir::LOR); -%shared_ptr(stir::LORInAxialAndNoArcCorrSinogramCoordinates); // William S Fulton trick for passing templates (with commas) through macro arguments // (already defined in swgmacros.swg) @@ -951,24 +948,7 @@ ADD_REPR(stir::Succeeded, %arg($self->succeeded() ? "yes" : "no")); %include "stir/ByteOrder.h" %include "stir_coordinates.i" - -#if 0 - // TODO enable this in STIR version 6 (breaks backwards compatibility -%attributeref(stir::LORInAxialAndNoArcCorrSinogramCoordinates, float, z1); -%attributeref(stir::LORInAxialAndNoArcCorrSinogramCoordinates, float, z2); -%attributeref(stir::LORInAxialAndNoArcCorrSinogramCoordinates, float, beta); -%attributeref(stir::LORInAxialAndNoArcCorrSinogramCoordinates, float, phi); -#else -%ignore *::z1() const; -%ignore *::z2() const; -%ignore *::beta() const; -%ignore *::phi() const; -#endif -%ignore *::check_state; -%include "stir/LORCoordinates.h" - -%template(FloatLOR) stir::LOR; -%template(FloatLORInAxialAndNoArcCorrSinogramCoordinates) stir::LORInAxialAndNoArcCorrSinogramCoordinates; +%include "stir_LOR.i" %include "stir_array.i" %include "stir_exam.i" diff --git a/src/swig/stir_LOR.i b/src/swig/stir_LOR.i new file mode 100644 index 0000000000..987af0b7c8 --- /dev/null +++ b/src/swig/stir_LOR.i @@ -0,0 +1,49 @@ +/* + Copyright (C) 2023 University College London + Copyright (C) 2022 National Physical Laboratory + This file is part of STIR. + + SPDX-License-Identifier: Apache-2.0 + + See STIR/LICENSE.txt for details +*/ +/*! + \file + \brief Interface file for SWIG: stir::LOR and derived classes + + \author Kris Thielemans + \author Daniel Deidda +*/ + +%shared_ptr(stir::LOR); +%shared_ptr(stir::LORAs2Points); +%shared_ptr(stir::LORInAxialAndNoArcCorrSinogramCoordinates); +%shared_ptr(stir::LORInAxialAndSinogramCoordinates); +%shared_ptr(stir::PointOnCylinder); +%shared_ptr(stir::LORInCylinderCoordinates); + +#if 0 + // TODO enable this in STIR version 6 (breaks backwards compatibility +%attributeref(stir::LORInAxialAndNoArcCorrSinogramCoordinates, float, z1); +%attributeref(stir::LORInAxialAndNoArcCorrSinogramCoordinates, float, z2); +%attributeref(stir::LORInAxialAndNoArcCorrSinogramCoordinates, float, beta); +%attributeref(stir::LORInAxialAndNoArcCorrSinogramCoordinates, float, phi); +#else +%ignore *::z1() const; +%ignore *::z2() const; +%ignore *::beta() const; +%ignore *::phi() const; +#endif +%ignore *::check_state; + +%attributeref(stir::PointOnCylinder, float, z); +%attributeref(stir::PointOnCylinder, float, psi); + +%include "stir/LORCoordinates.h" + +%template(LOR) stir::LOR; +%template(LORInAxialAndNoArcCorrSinogramCoordinates) stir::LORInAxialAndNoArcCorrSinogramCoordinates; +%template(LORAs2Points) stir::LORAs2Points; +%template(LORInAxialAndSinogramCoordinates) stir::LORInAxialAndSinogramCoordinates; +%template(PointOnCylinder) stir::PointOnCylinder; +%template(LORInCylinderCoordinates) stir::LORInCylinderCoordinates; From b2c0c3a448c2d16b42889aeb766965a6d6a41fd1 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Tue, 14 Nov 2023 07:43:20 +0000 Subject: [PATCH 363/509] [SWIG] add ListRecord and CList versions To do this cleanly, move lines from stir.i to separate stir_listmode.i --- src/swig/stir.i | 23 +++++------------ src/swig/stir_listmode.i | 53 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 17 deletions(-) create mode 100644 src/swig/stir_listmode.i diff --git a/src/swig/stir.i b/src/swig/stir.i index 734397ecca..0746e57c60 100644 --- a/src/swig/stir.i +++ b/src/swig/stir.i @@ -70,8 +70,12 @@ #include "stir/recon_buildblock/BinNormalisationFromProjData.h" #include "stir/recon_buildblock/BinNormalisationFromAttenuationImage.h" #include "stir/recon_buildblock/TrivialBinNormalisation.h" - #include "stir/listmode/LmToProjData.h" + #include "stir/listmode/ListRecord.h" + #include "stir/listmode/ListEvent.h" + #include "stir/listmode/CListRecord.h" #include "stir/listmode/ListModeData.h" + #include "stir/listmode/CListModeData.h" + #include "stir/listmode/LmToProjData.h" #include "stir/CartesianCoordinate2D.h" #include "stir/CartesianCoordinate3D.h" @@ -955,6 +959,7 @@ ADD_REPR(stir::Succeeded, %arg($self->succeeded() ? "yes" : "no")); %shared_ptr(stir::DataSymmetriesForViewSegmentNumbers); %include "stir_projdata.i" +%include "stir_listmode.i" %include "stir/DataSymmetriesForViewSegmentNumbers.h" %include "stir_voxels.i" @@ -989,10 +994,6 @@ ADD_REPR(stir::Succeeded, %arg($self->succeeded() ? "yes" : "no")); %include "stir/multiply_crystal_factors.h" %include "stir/decay_correction_factor.h" -%rename (set_template_proj_data_info) *::set_template_proj_data_info_sptr; -%shared_ptr(stir::LmToProjData); -%include "stir/listmode/LmToProjData.h" - %shared_ptr(stir::ScatterSimulation); %shared_ptr(stir::RegisteredParsingObject); @@ -1011,18 +1012,6 @@ ADD_REPR(stir::Succeeded, %arg($self->succeeded() ? "yes" : "no")); %shared_ptr(stir::CreateTailMaskFromACFs); %include "stir/scatter/CreateTailMaskFromACFs.h" -%shared_ptr(stir::ListModeData); -%include "stir/listmode/ListModeData.h" - -%extend stir::ListModeData { - static shared_ptr read_from_file(const std::string& filename) - { - using namespace stir; - shared_ptr ret(read_from_file(filename)); - return ret; - } -} - %shared_ptr(stir::FanProjData); %shared_ptr(stir::GeoData3D); %ignore operator<<; diff --git a/src/swig/stir_listmode.i b/src/swig/stir_listmode.i new file mode 100644 index 0000000000..2818ebe6af --- /dev/null +++ b/src/swig/stir_listmode.i @@ -0,0 +1,53 @@ +/* + Copyright (C) 2023 University College London + Copyright (C) 2022 Positrigo + This file is part of STIR. + + SPDX-License-Identifier: Apache-2.0 + + See STIR/LICENSE.txt for details +*/ +/*! + \file + \brief Interface file for SWIG: stir::ListModeData, stir:LmToProjData, stir::ListRecord + + \author Kris Thielemans + \author Markus Jehl +*/ +%shared_ptr(stir::ListRecord); +%shared_ptr(stir::ListEvent); +%shared_ptr(stir::CListRecord); +%shared_ptr(stir::CListEvent); +%shared_ptr(stir::CListRecordWithGatingInput); + +%include "stir/listmode/ListRecord.h" +%include "stir/listmode/ListEvent.h" +%include "stir/listmode/CListRecord.h" + +%rename (get_empty_record) *::get_empty_record_sptr; + +%rename (set_template_proj_data_info) *::set_template_proj_data_info_sptr; +%shared_ptr(stir::LmToProjData); +%include "stir/listmode/LmToProjData.h" +ADD_REPR_PARAMETER_INFO(stir::LmToProjData); + +%shared_ptr(stir::ListModeData); +%include "stir/listmode/ListModeData.h" + +%extend stir::ListModeData { + static shared_ptr read_from_file(const std::string& filename) + { + using namespace stir; + shared_ptr ret(read_from_file(filename)); + return ret; + } +} + +%extend stir::CListModeData { + static shared_ptr read_from_file(const std::string& filename) + { + using namespace stir; + shared_ptr ret(read_from_file(filename)); + return ret; + } +} From 4a4f3346a71beff53521a89795ed21c00c4368c0 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Tue, 14 Nov 2023 15:56:53 +0000 Subject: [PATCH 364/509] disabled LORInCylinderCoordinates(LORAs2Points&) and related constructors The first was not implemented, and would need an extra radius argument. The others called it, but also would have needed a radius --- src/include/stir/LORCoordinates.h | 7 +++++++ src/include/stir/LORCoordinates.inl | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/include/stir/LORCoordinates.h b/src/include/stir/LORCoordinates.h index bf0ef82ec9..e5ef3f1c97 100644 --- a/src/include/stir/LORCoordinates.h +++ b/src/include/stir/LORCoordinates.h @@ -222,8 +222,11 @@ class LORInCylinderCoordinates : public LOR inline LORInCylinderCoordinates(const LORInAxialAndSinogramCoordinates&); +#if 0 + // disabled as would need radius argument as well, but currently not needed inline LORInCylinderCoordinates(const LORAs2Points&); +#endif self_type* clone() const override { return new self_type(*this); } @@ -375,8 +378,10 @@ class LORInAxialAndSinogramCoordinates inline LORInAxialAndSinogramCoordinates(const LORInAxialAndNoArcCorrSinogramCoordinates&); +#if 0 inline LORInAxialAndSinogramCoordinates(const LORAs2Points&); +#endif self_type* clone() const override { return new self_type(*this); } @@ -503,8 +508,10 @@ class LORInAxialAndNoArcCorrSinogramCoordinates inline LORInAxialAndNoArcCorrSinogramCoordinates(const LORInAxialAndSinogramCoordinates&); +#if 0 inline LORInAxialAndNoArcCorrSinogramCoordinates(const LORAs2Points&); +#endif self_type* clone() const override { return new self_type(*this); } diff --git a/src/include/stir/LORCoordinates.inl b/src/include/stir/LORCoordinates.inl index e17685a0a0..8161d59076 100644 --- a/src/include/stir/LORCoordinates.inl +++ b/src/include/stir/LORCoordinates.inl @@ -269,7 +269,7 @@ LORInAxialAndNoArcCorrSinogramCoordinates(const LORInAxialAndSinogramCoordinates check_state(); } -#if __cplusplus>= 201103L +#if 0 template LORInAxialAndSinogramCoordinates:: LORInAxialAndSinogramCoordinates(const LORAs2Points& coords) From bd4c68dc7ebd795a2f15379362b952df9e60f62f Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Thu, 16 Nov 2023 09:53:03 +0000 Subject: [PATCH 365/509] [SWIG] fix listmode event processing - needed to import ListEvent and ListTime BEFORE ListRecord (SWIG BUG) - add CListModeData, just in case - added example and basic test --- examples/python/listmode_loop_demo.py | 43 ++++++++++++++++++ src/swig/CMakeLists.txt | 1 + src/swig/stir_listmode.i | 8 +++- src/swig/test/python/test_listmode.py | 63 +++++++++++++++++++++++++++ 4 files changed, 113 insertions(+), 2 deletions(-) create mode 100644 examples/python/listmode_loop_demo.py create mode 100644 src/swig/test/python/test_listmode.py diff --git a/examples/python/listmode_loop_demo.py b/examples/python/listmode_loop_demo.py new file mode 100644 index 0000000000..eee41b0cd8 --- /dev/null +++ b/examples/python/listmode_loop_demo.py @@ -0,0 +1,43 @@ +# Demo how to use STIR from Python to access events in a list-mode file + +# Copyright 2023 - University College London +# This file is part of STIR. +# +# SPDX-License-Identifier: Apache-2.0 +# +# See STIR/LICENSE.txt for details + +#%% Initial imports +import stir +import os + +#%% read file +filename = '20170809_NEMA_60min_UCL.l.hdr' +# example using the hroot file in recon_test_pack, which needs some env variables to be set +#os.environ["INPUT_ROOT_FILE"]="test_PET_GATE.root" +#os.environ["EXCLUDE_SCATTERED"] = "1" +#os.environ["EXCLUDE_RANDOM"] = "0" +#filename = "root_header.hroot" + +lm=stir.ListModeData.read_from_file(filename) +# could print some exam info +# print(lm.get_exam_info().parameter_info()) +proj_data_info = lm.get_proj_data_info() +# could print some geometric info +#print(proj_data_info.parameter_info()) + +#%% loop over first few events and print some information +# create some variables for re-use in the loop +record = lm.get_empty_record() +bin = stir.Bin() +for i in range(50): + lm.get_next_record(record) + if (record.is_time()): + print(f"Time: {record.time().get_time_in_millisecs()}") + if (record.is_event()): + event = record.event() + lor = event.get_LOR() + event.get_bin(bin, proj_data_info); + # TODO We can will be able to simply print bin once STIR_TOF is on + print(f"Event: p/d: {event.is_prompt()} LOR: {[lor.p1(), lor.p2()]}, ", + f"bin: s:{bin.segment_num}, a: {bin.axial_pos_num}, v: {bin.view_num}, t:{bin.tangential_pos_num}") diff --git a/src/swig/CMakeLists.txt b/src/swig/CMakeLists.txt index 984ff9f459..fa273cae37 100644 --- a/src/swig/CMakeLists.txt +++ b/src/swig/CMakeLists.txt @@ -108,6 +108,7 @@ set(swig_stir_dependencies stir_objectivefunctions.i stir_priors.i stir_projdata.i + stir_listmode.i stir_projectors.i stir_reconstruction.i stir_shapes.i diff --git a/src/swig/stir_listmode.i b/src/swig/stir_listmode.i index 2818ebe6af..b01ff2bbe7 100644 --- a/src/swig/stir_listmode.i +++ b/src/swig/stir_listmode.i @@ -16,13 +16,15 @@ */ %shared_ptr(stir::ListRecord); %shared_ptr(stir::ListEvent); +%shared_ptr(stir::ListTime); %shared_ptr(stir::CListRecord); %shared_ptr(stir::CListEvent); %shared_ptr(stir::CListRecordWithGatingInput); -%include "stir/listmode/ListRecord.h" %include "stir/listmode/ListEvent.h" -%include "stir/listmode/CListRecord.h" +%include "stir/listmode/ListTime.h" +%include "stir/listmode/ListRecord.h" +%include "stir/listmode/CListRecord.h" // currently also contains CListEvent %rename (get_empty_record) *::get_empty_record_sptr; @@ -33,6 +35,8 @@ ADD_REPR_PARAMETER_INFO(stir::LmToProjData); %shared_ptr(stir::ListModeData); %include "stir/listmode/ListModeData.h" +%shared_ptr(stir::CListModeData); +%include "stir/listmode/CListModeData.h" %extend stir::ListModeData { static shared_ptr read_from_file(const std::string& filename) diff --git a/src/swig/test/python/test_listmode.py b/src/swig/test/python/test_listmode.py new file mode 100644 index 0000000000..3ea2bbd013 --- /dev/null +++ b/src/swig/test/python/test_listmode.py @@ -0,0 +1,63 @@ +# Test file for STIR listmode reading +# Use as follows: +# on command line +# py.test test_listmode.py + +# Copyright 2023 - University College London +# This file is part of STIR. +# +# SPDX-License-Identifier: Apache-2.0 +# +# See STIR/LICENSE.txt for details + +# This file checks the first 2 events in the recon_test_pack/test_PET_GATE.root + +#%% Initial imports +import stir +import os + +#%% read file +# location of script +dir = os.path.dirname(__file__) +# location of ROOT file +dir = os.path.join(dir, "..", "..", "..", "..", "recon_test_pack") +filename = "root_header.hroot" + +os.environ["INPUT_ROOT_FILE"]="test_PET_GATE.root" +os.environ["EXCLUDE_SCATTERED"] = "1" +os.environ["EXCLUDE_RANDOM"] = "0" + +def test_get_record(): + try: + lm=stir.ListModeData.read_from_file(os.path.join(dir, filename)) + proj_data_info = lm.get_proj_data_info() + except: + print(f"Could not open {filename}") + print("ROOT support not enabled?") + return + + record = lm.get_empty_record() + bin = stir.Bin() + # first event + lm.get_next_record(record) + assert record.is_time() + assert record.time().get_time_in_millisecs() == 2 + assert record.is_event() + event = record.event() + lor = event.get_LOR() + event.get_bin(bin, proj_data_info); + diff = stir.FloatCartesianCoordinate3D(lor.p1() - stir.Float3BasicCoordinate((2.03125, -149.102, -299.989))) + assert abs(diff.x()) < .1 and abs(diff.y()) < .1 and abs(diff.z()) < .1 + assert bin.segment_num == 1 and bin.axial_pos_num == 1 and bin.view_num == 112 and bin.tangential_pos_num == -102 + # second event + lm.get_next_record(record) + assert record.is_time() + assert record.time().get_time_in_millisecs() == 3 + assert record.is_event() + event = record.event() + lor = event.get_LOR() + event.get_bin(bin, proj_data_info); + diff = stir.FloatCartesianCoordinate3D(lor.p1() - stir.Float3BasicCoordinate((-2.03125, 325.646, -78.6102))) + assert abs(diff.x()) < .1 and abs(diff.y()) < .1 and abs(diff.z()) < .1 + assert bin.segment_num == -1 and bin.axial_pos_num == 1 and bin.view_num == 12 and bin.tangential_pos_num == -14 + From e97b0f01c37a8e5dbd17a6bbcbffd2919acb7c42 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Thu, 16 Nov 2023 10:00:28 +0000 Subject: [PATCH 366/509] fix location of listmmode .h files explicitly use stir/listmode. It doesn't seem to matter, but is cleaner. --- src/include/stir/listmode/CListRecord.h | 2 +- src/include/stir/listmode/ListEvent.h | 2 -- src/include/stir/listmode/ListRecord.h | 5 ++--- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/include/stir/listmode/CListRecord.h b/src/include/stir/listmode/CListRecord.h index 95d220bc89..b4bd2bf9df 100644 --- a/src/include/stir/listmode/CListRecord.h +++ b/src/include/stir/listmode/CListRecord.h @@ -23,7 +23,7 @@ #ifndef __stir_listmode_CListRecord_H__ #define __stir_listmode_CListRecord_H__ -#include "ListRecord.h" +#include "stir/listmode/ListRecord.h" START_NAMESPACE_STIR class Bin; diff --git a/src/include/stir/listmode/ListEvent.h b/src/include/stir/listmode/ListEvent.h index 6bff69fd2c..8366fbc4bc 100644 --- a/src/include/stir/listmode/ListEvent.h +++ b/src/include/stir/listmode/ListEvent.h @@ -24,8 +24,6 @@ #ifndef __stir_listmode_ListEvent_H__ #define __stir_listmode_ListEvent_H__ -#include "stir/round.h" -#include "stir/Succeeded.h" #include "stir/Bin.h" #include "stir/ProjDataInfo.h" #include "stir/CartesianCoordinate3D.h" diff --git a/src/include/stir/listmode/ListRecord.h b/src/include/stir/listmode/ListRecord.h index 3e82a47b2b..29ee281097 100644 --- a/src/include/stir/listmode/ListRecord.h +++ b/src/include/stir/listmode/ListRecord.h @@ -24,9 +24,8 @@ #ifndef __stir_listmode_ListRecord_H__ #define __stir_listmode_ListRecord_H__ -#include "ListEvent.h" -#include "ListTime.h" -#include "stir/Succeeded.h" +#include "stir/listmode/ListEvent.h" +#include "stir/listmode/ListTime.h" START_NAMESPACE_STIR From 9559bc1a375fcfdb536861000741f2830fdd0c2b Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Thu, 16 Nov 2023 13:14:08 +0000 Subject: [PATCH 367/509] changes to python code recommended by Codacy --- examples/python/listmode_loop_demo.py | 8 ++++---- src/swig/test/python/test_listmode.py | 18 +++++++++--------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/examples/python/listmode_loop_demo.py b/examples/python/listmode_loop_demo.py index eee41b0cd8..7443707eb9 100644 --- a/examples/python/listmode_loop_demo.py +++ b/examples/python/listmode_loop_demo.py @@ -9,11 +9,11 @@ #%% Initial imports import stir -import os #%% read file filename = '20170809_NEMA_60min_UCL.l.hdr' # example using the hroot file in recon_test_pack, which needs some env variables to be set +#import os #os.environ["INPUT_ROOT_FILE"]="test_PET_GATE.root" #os.environ["EXCLUDE_SCATTERED"] = "1" #os.environ["EXCLUDE_RANDOM"] = "0" @@ -29,7 +29,7 @@ #%% loop over first few events and print some information # create some variables for re-use in the loop record = lm.get_empty_record() -bin = stir.Bin() +b = stir.Bin() for i in range(50): lm.get_next_record(record) if (record.is_time()): @@ -37,7 +37,7 @@ if (record.is_event()): event = record.event() lor = event.get_LOR() - event.get_bin(bin, proj_data_info); + event.get_bin(b, proj_data_info); # TODO We can will be able to simply print bin once STIR_TOF is on print(f"Event: p/d: {event.is_prompt()} LOR: {[lor.p1(), lor.p2()]}, ", - f"bin: s:{bin.segment_num}, a: {bin.axial_pos_num}, v: {bin.view_num}, t:{bin.tangential_pos_num}") + f"bin: s:{b.segment_num}, a: {b.axial_pos_num}, v: {b.view_num}, t:{b.tangential_pos_num}") diff --git a/src/swig/test/python/test_listmode.py b/src/swig/test/python/test_listmode.py index 3ea2bbd013..9232c85294 100644 --- a/src/swig/test/python/test_listmode.py +++ b/src/swig/test/python/test_listmode.py @@ -18,9 +18,9 @@ #%% read file # location of script -dir = os.path.dirname(__file__) +loc = os.path.dirname(__file__) # location of ROOT file -dir = os.path.join(dir, "..", "..", "..", "..", "recon_test_pack") +loc = os.path.join(loc, "..", "..", "..", "..", "recon_test_pack") filename = "root_header.hroot" os.environ["INPUT_ROOT_FILE"]="test_PET_GATE.root" @@ -29,15 +29,15 @@ def test_get_record(): try: - lm=stir.ListModeData.read_from_file(os.path.join(dir, filename)) + lm=stir.ListModeData.read_from_file(os.path.join(loc, filename)) proj_data_info = lm.get_proj_data_info() - except: + except RuntimeError: print(f"Could not open {filename}") print("ROOT support not enabled?") return record = lm.get_empty_record() - bin = stir.Bin() + b = stir.Bin() # first event lm.get_next_record(record) assert record.is_time() @@ -45,10 +45,10 @@ def test_get_record(): assert record.is_event() event = record.event() lor = event.get_LOR() - event.get_bin(bin, proj_data_info); + event.get_bin(b, proj_data_info); diff = stir.FloatCartesianCoordinate3D(lor.p1() - stir.Float3BasicCoordinate((2.03125, -149.102, -299.989))) assert abs(diff.x()) < .1 and abs(diff.y()) < .1 and abs(diff.z()) < .1 - assert bin.segment_num == 1 and bin.axial_pos_num == 1 and bin.view_num == 112 and bin.tangential_pos_num == -102 + assert b.segment_num == 1 and b.axial_pos_num == 1 and b.view_num == 112 and b.tangential_pos_num == -102 # second event lm.get_next_record(record) assert record.is_time() @@ -56,8 +56,8 @@ def test_get_record(): assert record.is_event() event = record.event() lor = event.get_LOR() - event.get_bin(bin, proj_data_info); + event.get_bin(b, proj_data_info); diff = stir.FloatCartesianCoordinate3D(lor.p1() - stir.Float3BasicCoordinate((-2.03125, 325.646, -78.6102))) assert abs(diff.x()) < .1 and abs(diff.y()) < .1 and abs(diff.z()) < .1 - assert bin.segment_num == -1 and bin.axial_pos_num == 1 and bin.view_num == 12 and bin.tangential_pos_num == -14 + assert b.segment_num == -1 and b.axial_pos_num == 1 and b.view_num == 12 and b.tangential_pos_num == -14 From 04b6623e3480a4868a63a977459c4f41aa577347 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Tue, 21 Nov 2023 13:53:46 +0000 Subject: [PATCH 368/509] [GHA] corrected tmate session if statement [ci skip] --- .github/workflows/build-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 238969e908..2416232463 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -354,7 +354,7 @@ jobs: - name: Setup tmate session if triggered uses: mxschmitt/action-tmate@v3 timeout-minutes: 15 - if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled }} + if: ${{ github.event_name == 'workflow_dispatch' && inputs.debug_enabled }} - name: examples shell: bash From 56804ace5772e02048b3abf7b633c87f86ed94dd Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Tue, 21 Nov 2023 14:00:04 +0000 Subject: [PATCH 369/509] [GHA] always enable tmate temporarily the `if` statement doesn't work for some reason --- .github/workflows/build-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 2416232463..56c6aa9746 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -354,7 +354,7 @@ jobs: - name: Setup tmate session if triggered uses: mxschmitt/action-tmate@v3 timeout-minutes: 15 - if: ${{ github.event_name == 'workflow_dispatch' && inputs.debug_enabled }} + #if: ${{ github.event_name == 'workflow_dispatch' && inputs.debug_enabled }} - name: examples shell: bash From a4b449395c2d89fc4efd0f22a5721b469c9bfb53 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Tue, 21 Nov 2023 14:40:59 +0000 Subject: [PATCH 370/509] [GHA] add .v and .s to upload (if recon_test_pack failure) --- .github/workflows/build-test.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 56c6aa9746..1be05e812f 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -347,14 +347,17 @@ jobs: if: failure() with: name: recon_test_pack_log_files-${{ matrix.os }}-${{ matrix.compiler }}${{ matrix.compiler_version }}-${{ matrix.BUILD_TYPE }}-pp=${{ matrix.parallelproj }}-ROOT=${{ matrix.ROOT }} - path: ${{ github.workspace }}/recon_test_pack/**/*.log + path: | + ${{ github.workspace }}/recon_test_pack/**/*.log + ${{ github.workspace }}/recon_test_pack/**/my_*v + ${{ github.workspace }}/recon_test_pack/**/my_*s retention-days: 7 # Enable tmate debugging of manually-triggered workflows if the input option was provided - name: Setup tmate session if triggered uses: mxschmitt/action-tmate@v3 timeout-minutes: 15 - #if: ${{ github.event_name == 'workflow_dispatch' && inputs.debug_enabled }} + if: ${{ github.event_name == 'workflow_dispatch' && inputs.debug_enabled == 'true' }} - name: examples shell: bash From 70ebdbeb0535b45fc1bbc57b833cf7ac5d0299ca Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Tue, 5 Dec 2023 16:59:50 +0000 Subject: [PATCH 371/509] correct doc on extra_stir_dirs.cmake To work properly, STIR_REGISTRIES and STIR_LIBRARIES need to be exported to the PARENT_SCOPE. Fixes #1293 --- documentation/STIR-developers-overview.tex | 25 ++++++++++++++-------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/documentation/STIR-developers-overview.tex b/documentation/STIR-developers-overview.tex index 35a6d43707..b9dc39384b 100644 --- a/documentation/STIR-developers-overview.tex +++ b/documentation/STIR-developers-overview.tex @@ -1261,8 +1261,21 @@ \section{ # add my include directory to compiler switches (or better: use target_include_directories) include_directories(include) -# check CMakeLists in next directories -add_subdirectory(buildblock my_buildblock) +# check CMakeLists in next directory +# note: 2nd argument needs to have different name than any of the existing STIR libraries +add_subdirectory(${STIR_LOCAL}/buildblock my_buildblock) + +# add registries (if you have any) +list(APPEND STIR_REGISTRIES + ${STIR_LOCAL}/buildblock/my_registries.cxx) + +# add to list of libraries to include in linking +list(APPEND STIR_LIBRARIES my_buildblock) + +# copy variables to PARENT_SCOPE +SET( STIR_REGISTRIES ${STIR_REGISTRIES} PARENT_SCOPE) +SET( STIR_LIBRARIES ${STIR_LIBRARIES} PARENT_SCOPE) + \end{verbatim} where \textbf{buildblock} is the name of your subdirectory and \textbf{my\_buildblock} is a name for the sub-directory where CMake will build the files (make sure the latter is different from any @@ -1286,12 +1299,6 @@ \section{ # declare dependencies on other STIR libraries, for instance target_link_libraries($(dir) PUBLIC buildblock) -# add to list of libraries for STIR to include in linking -list(APPEND STIR_LIBRARIES $(dir)) - -# add source file for registries (if you have one) -list(APPEND STIR_REGISTRIES my_registry) - include(stir_lib_target) \end{verbatim} @@ -1311,7 +1318,7 @@ \section{ #include "myOutputFileFormat.h" static myOutputFileFormat::RegisterIt dummy1; \end{verbatim} -If you add that to the \texttt{STIR\_REGISTRIES} variable in your \textbf{CMakeLists.txt} +If you add that to the \texttt{STIR\_REGISTRIES} variable in your \textbf{extra\_stir\_dirs.cmake} as above, everything should work as expected. \section{Contributing to STIR} From b71683e4ce7a565e1acad304d95792e4f5919697 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Tue, 5 Dec 2023 17:03:21 +0000 Subject: [PATCH 372/509] remove .ahv files as not used --- recon_test_pack/RPTsens_seg3_PM.ahv | 18 ------------------ recon_test_pack/RPTsens_seg4.ahv | 15 --------------- recon_test_pack/test_image_3.ahv | 18 ------------------ recon_test_pack/test_image_5.ahv | 18 ------------------ recon_test_pack/test_image_PM_MRP_6.ahv | 18 ------------------ recon_test_pack/test_image_PM_QP_6.ahv | 18 ------------------ recon_test_pack/test_image_PM_QPweights_6.ahv | 18 ------------------ 7 files changed, 123 deletions(-) delete mode 100644 recon_test_pack/RPTsens_seg3_PM.ahv delete mode 100644 recon_test_pack/RPTsens_seg4.ahv delete mode 100644 recon_test_pack/test_image_3.ahv delete mode 100644 recon_test_pack/test_image_5.ahv delete mode 100644 recon_test_pack/test_image_PM_MRP_6.ahv delete mode 100644 recon_test_pack/test_image_PM_QP_6.ahv delete mode 100644 recon_test_pack/test_image_PM_QPweights_6.ahv diff --git a/recon_test_pack/RPTsens_seg3_PM.ahv b/recon_test_pack/RPTsens_seg3_PM.ahv deleted file mode 100644 index 5e56e67da0..0000000000 --- a/recon_test_pack/RPTsens_seg3_PM.ahv +++ /dev/null @@ -1,18 +0,0 @@ -!INTERFILE := -!name of data file := RPTsens_seg3_PM.v -!total number of images := 31 -!data offset in bytes := 0 -!imagedata byte order := LITTLEENDIAN -!number format := short float -!number of bytes per pixel := 4 -matrix axis label [1] := x -!matrix size [1] := 60 -scaling factor (mm/pixel) [1] := 4.44114 -matrix axis label [2] := y -!matrix size [2] := 60 -scaling factor (mm/pixel) [2] := 4.44114 -;Correct value is of keyword (commented out) -;!slice thickness (pixels) := 0.759939 -;Value for Analyze -!slice thickness (pixels) := 3.375 -!END OF INTERFILE := diff --git a/recon_test_pack/RPTsens_seg4.ahv b/recon_test_pack/RPTsens_seg4.ahv deleted file mode 100644 index 28ff3cb02f..0000000000 --- a/recon_test_pack/RPTsens_seg4.ahv +++ /dev/null @@ -1,15 +0,0 @@ -!INTERFILE := -!name of data file := RPTsens_seg4.v -!total number of images := 31 -!data offset in bytes := 0 -!imagedata byte order := LITTLEENDIAN -!number format := float -!number of bytes per pixel := 4 -matrix axis label [1] := x -!matrix size [1] := 97 -scaling factor (mm/pixel) [1] := 3.1088 -matrix axis label [2] := y -!matrix size [2] := 97 -scaling factor (mm/pixel) [2] := 3.1088 -!slice thickness (pixels) := 3.375 -!END OF INTERFILE := diff --git a/recon_test_pack/test_image_3.ahv b/recon_test_pack/test_image_3.ahv deleted file mode 100644 index 50167a1d35..0000000000 --- a/recon_test_pack/test_image_3.ahv +++ /dev/null @@ -1,18 +0,0 @@ -!INTERFILE := -!name of data file := test_image_3.v -!total number of images := 31 -!data offset in bytes := 0 -!imagedata byte order := LITTLEENDIAN -!number format := short float -!number of bytes per pixel := 4 -matrix axis label [1] := x -!matrix size [1] := 97 -scaling factor (mm/pixel) [1] := 3.1088 -matrix axis label [2] := y -!matrix size [2] := 97 -scaling factor (mm/pixel) [2] := 3.1088 -;Correct value is of keyword (commented out) -;!slice thickness (pixels) := 1.08563 -;Value for Analyze -!slice thickness (pixels) := 3.375 -!END OF INTERFILE := diff --git a/recon_test_pack/test_image_5.ahv b/recon_test_pack/test_image_5.ahv deleted file mode 100644 index d2c3313b8b..0000000000 --- a/recon_test_pack/test_image_5.ahv +++ /dev/null @@ -1,18 +0,0 @@ -!INTERFILE := -!name of data file := test_image_5.v -!total number of images := 31 -!data offset in bytes := 0 -!imagedata byte order := LITTLEENDIAN -!number format := short float -!number of bytes per pixel := 4 -matrix axis label [1] := x -!matrix size [1] := 97 -scaling factor (mm/pixel) [1] := 3.1088 -matrix axis label [2] := y -!matrix size [2] := 97 -scaling factor (mm/pixel) [2] := 3.1088 -;Correct value is of keyword (commented out) -;!slice thickness (pixels) := 1.08563 -;Value for Analyze -!slice thickness (pixels) := 3.375 -!END OF INTERFILE := diff --git a/recon_test_pack/test_image_PM_MRP_6.ahv b/recon_test_pack/test_image_PM_MRP_6.ahv deleted file mode 100644 index 6b123ade3d..0000000000 --- a/recon_test_pack/test_image_PM_MRP_6.ahv +++ /dev/null @@ -1,18 +0,0 @@ -!INTERFILE := -!name of data file := test_image_PM_MRP_6.v -!total number of images := 31 -!data offset in bytes := 0 -!imagedata byte order := LITTLEENDIAN -!number format := short float -!number of bytes per pixel := 4 -matrix axis label [1] := x -!matrix size [1] := 60 -scaling factor (mm/pixel) [1] := 4.44114 -matrix axis label [2] := y -!matrix size [2] := 60 -scaling factor (mm/pixel) [2] := 4.44114 -;Correct value is of keyword (commented out) -;!slice thickness (pixels) := 0.759939 -;Value for Analyze -!slice thickness (pixels) := 3.375 -!END OF INTERFILE := diff --git a/recon_test_pack/test_image_PM_QP_6.ahv b/recon_test_pack/test_image_PM_QP_6.ahv deleted file mode 100644 index c8d9fe46c2..0000000000 --- a/recon_test_pack/test_image_PM_QP_6.ahv +++ /dev/null @@ -1,18 +0,0 @@ -!INTERFILE := -!name of data file := test_image_PM_QP_6.v -!total number of images := 31 -!data offset in bytes := 0 -!imagedata byte order := LITTLEENDIAN -!number format := short float -!number of bytes per pixel := 4 -matrix axis label [1] := x -!matrix size [1] := 60 -scaling factor (mm/pixel) [1] := 4.44114 -matrix axis label [2] := y -!matrix size [2] := 60 -scaling factor (mm/pixel) [2] := 4.44114 -;Correct value is of keyword (commented out) -;!slice thickness (pixels) := 0.75994 -;Value for Analyze -!slice thickness (pixels) := 3.375 -!END OF INTERFILE := diff --git a/recon_test_pack/test_image_PM_QPweights_6.ahv b/recon_test_pack/test_image_PM_QPweights_6.ahv deleted file mode 100644 index 1cb2f34393..0000000000 --- a/recon_test_pack/test_image_PM_QPweights_6.ahv +++ /dev/null @@ -1,18 +0,0 @@ -!INTERFILE := -!name of data file := test_image_PM_QPweights_6.v -!total number of images := 31 -!data offset in bytes := 0 -!imagedata byte order := LITTLEENDIAN -!number format := short float -!number of bytes per pixel := 4 -matrix axis label [1] := x -!matrix size [1] := 60 -scaling factor (mm/pixel) [1] := 4.44114 -matrix axis label [2] := y -!matrix size [2] := 60 -scaling factor (mm/pixel) [2] := 4.44114 -;Correct value is of keyword (commented out) -;!slice thickness (pixels) := 0.75994 -;Value for Analyze -!slice thickness (pixels) := 3.375 -!END OF INTERFILE := From c950fa0be2633d673446611ddd5a06f7295f562b Mon Sep 17 00:00:00 2001 From: Markus Jehl Date: Thu, 14 Dec 2023 08:42:10 +0000 Subject: [PATCH 373/509] Switched selected warning messages from deprecated function to boost::formatted one. --- src/IO/InterfileHeader.cxx | 145 +++++++++++++++--------------- src/buildblock/RadionuclideDB.cxx | 14 +-- 2 files changed, 80 insertions(+), 79 deletions(-) diff --git a/src/IO/InterfileHeader.cxx b/src/IO/InterfileHeader.cxx index 74b95bf6fa..1cb1916099 100644 --- a/src/IO/InterfileHeader.cxx +++ b/src/IO/InterfileHeader.cxx @@ -36,6 +36,7 @@ #include #include "stir/ProjDataInfoBlocksOnCylindricalNoArcCorr.h" #include "stir/ProjDataInfoGenericNoArcCorr.h" +#include #ifndef STIR_NO_NAMESPACES using std::pair; @@ -1044,38 +1045,38 @@ bool InterfilePDFSHeader::post_processing() { case 192*2: guessed_scanner_ptr.reset(new Scanner( Scanner::E953)); - warning(warning_msg, "ECAT 953"); + warning(boost::format(warning_msg) % "ECAT 953"); break; case 336*2: guessed_scanner_ptr.reset(new Scanner( Scanner::Advance)); - warning(warning_msg, "Advance"); + warning(boost::format(warning_msg) % "Advance"); break; case 288*2: if(num_rings == 104) { //added by Dylan Togane guessed_scanner_ptr.reset(new Scanner( Scanner::HRRT)); - warning(warning_msg, "HRRT"); + warning(boost::format(warning_msg) % "HRRT"); } else if (num_rings == 48) { guessed_scanner_ptr.reset(new Scanner( Scanner::E966)); - warning(warning_msg, "ECAT 966"); + warning(boost::format(warning_msg) % "ECAT 966"); } else if (num_rings == 32) { guessed_scanner_ptr.reset(new Scanner( Scanner::E962)); - warning(warning_msg, "ECAT 962"); + warning(boost::format(warning_msg) % "ECAT 962"); } break; // Dylan Togane [dtogane@camhpet.on.ca] 30/07/2002 bug fix: added break case 256*2: guessed_scanner_ptr.reset(new Scanner( Scanner::E951)); - warning(warning_msg, "ECAT 951"); + warning(boost::format(warning_msg) % "ECAT 951"); break; } if (guessed_scanner_ptr->get_type() == Scanner::Unknown_scanner) - warning("\nInterfile warning: I did not recognise the scanner neither from \n" - "'originating_system' or 'number of detectors per ring' and 'number of rings'.\n"); + warning(std::string("\nInterfile warning: I did not recognise the scanner neither from \n" + "'originating_system' or 'number of detectors per ring' and 'number of rings'.\n")); } bool mismatch_between_header_and_guess = false; @@ -1144,92 +1145,92 @@ bool InterfilePDFSHeader::post_processing() if (num_rings != guessed_scanner_ptr->get_num_rings()) { - warning("Interfile warning: 'number of rings' (%d) is expected to be %d.\n", - num_rings, guessed_scanner_ptr->get_num_rings()); + warning(boost::format("Interfile warning: 'number of rings' (%d) is expected to be %d.\n") % + num_rings % guessed_scanner_ptr->get_num_rings()); mismatch_between_header_and_guess = true; } if (num_detectors_per_ring != guessed_scanner_ptr->get_num_detectors_per_ring()) { - warning("Interfile warning: 'number of detectors per ring' (%d) is expected to be %d.\n", - num_detectors_per_ring, guessed_scanner_ptr->get_num_detectors_per_ring()); + warning(boost::format("Interfile warning: 'number of detectors per ring' (%d) is expected to be %d.\n") % + num_detectors_per_ring % guessed_scanner_ptr->get_num_detectors_per_ring()); mismatch_between_header_and_guess = true; } if (fabs(inner_ring_diameter_in_cm - guessed_scanner_ptr->get_inner_ring_radius()*2/10.) > .001) { - warning("Interfile warning: 'inner ring diameter (cm)' (%f) is expected to be %f.\n", - inner_ring_diameter_in_cm, guessed_scanner_ptr->get_inner_ring_radius()*2/10.); + warning(boost::format("Interfile warning: 'inner ring diameter (cm)' (%f) is expected to be %f.\n") % + inner_ring_diameter_in_cm % (guessed_scanner_ptr->get_inner_ring_radius()*2/10.)); mismatch_between_header_and_guess = true; } if (fabs(average_depth_of_interaction_in_cm - guessed_scanner_ptr->get_average_depth_of_interaction()/10) > .001) { - warning("Interfile warning: 'average depth of interaction (cm)' (%f) is expected to be %f.\n", - average_depth_of_interaction_in_cm, - guessed_scanner_ptr->get_average_depth_of_interaction()/10); + warning(boost::format("Interfile warning: 'average depth of interaction (cm)' (%f) is expected to be %f.\n") % + average_depth_of_interaction_in_cm % + (guessed_scanner_ptr->get_average_depth_of_interaction()/10)); mismatch_between_header_and_guess = true; } if (fabs(distance_between_rings_in_cm-guessed_scanner_ptr->get_ring_spacing()/10) > .001) { - warning("Interfile warning: 'distance between rings (cm)' (%f) is expected to be %f.\n", - distance_between_rings_in_cm, guessed_scanner_ptr->get_ring_spacing()/10); + warning(boost::format("Interfile warning: 'distance between rings (cm)' (%f) is expected to be %f.\n") % + distance_between_rings_in_cm % (guessed_scanner_ptr->get_ring_spacing()/10)); mismatch_between_header_and_guess = true; } if (fabs(default_bin_size_in_cm-guessed_scanner_ptr->get_default_bin_size()/10) > .001) { - warning("Interfile warning: 'default bin size (cm)' (%f) is expected to be %f.\n", - default_bin_size_in_cm, guessed_scanner_ptr->get_default_bin_size()/10); + warning(boost::format("Interfile warning: 'default bin size (cm)' (%f) is expected to be %f.\n") % + default_bin_size_in_cm % (guessed_scanner_ptr->get_default_bin_size()/10)); mismatch_between_header_and_guess = true; } if (max_num_non_arccorrected_bins - guessed_scanner_ptr->get_max_num_non_arccorrected_bins()) { - warning("Interfile warning: 'max_num_non_arccorrected_bins' (%d) is expected to be %d", - max_num_non_arccorrected_bins, guessed_scanner_ptr->get_max_num_non_arccorrected_bins()); + warning(boost::format("Interfile warning: 'max_num_non_arccorrected_bins' (%d) is expected to be %d") % + max_num_non_arccorrected_bins % guessed_scanner_ptr->get_max_num_non_arccorrected_bins()); mismatch_between_header_and_guess = true; } if (default_num_arccorrected_bins - guessed_scanner_ptr->get_default_num_arccorrected_bins()) { - warning("Interfile warning: 'default_num_arccorrected_bins' (%d) is expected to be %d", - default_num_arccorrected_bins, guessed_scanner_ptr->get_default_num_arccorrected_bins()); + warning(boost::format("Interfile warning: 'default_num_arccorrected_bins' (%d) is expected to be %d") % + default_num_arccorrected_bins % guessed_scanner_ptr->get_default_num_arccorrected_bins()); mismatch_between_header_and_guess = true; } if ( guessed_scanner_ptr->get_num_transaxial_blocks_per_bucket()>0 && num_transaxial_blocks_per_bucket != guessed_scanner_ptr->get_num_transaxial_blocks_per_bucket()) { - warning("Interfile warning: num_transaxial_blocks_per_bucket (%d) is expected to be %d.\n", - num_transaxial_blocks_per_bucket, guessed_scanner_ptr->get_num_transaxial_blocks_per_bucket()); + warning(boost::format("Interfile warning: num_transaxial_blocks_per_bucket (%d) is expected to be %d.\n") % + num_transaxial_blocks_per_bucket % guessed_scanner_ptr->get_num_transaxial_blocks_per_bucket()); mismatch_between_header_and_guess = true; } if ( guessed_scanner_ptr->get_num_axial_blocks_per_bucket()>0 && num_axial_blocks_per_bucket != guessed_scanner_ptr->get_num_axial_blocks_per_bucket()) { - warning("Interfile warning: num_axial_blocks_per_bucket (%d) is expected to be %d.\n", - num_axial_blocks_per_bucket, guessed_scanner_ptr->get_num_axial_blocks_per_bucket()); + warning(boost::format("Interfile warning: num_axial_blocks_per_bucket (%d) is expected to be %d.\n") % + num_axial_blocks_per_bucket % guessed_scanner_ptr->get_num_axial_blocks_per_bucket()); mismatch_between_header_and_guess = true; } if ( guessed_scanner_ptr->get_num_axial_crystals_per_block()>0 && num_axial_crystals_per_block!= guessed_scanner_ptr->get_num_axial_crystals_per_block()) { - warning("Interfile warning: num_axial_crystals_per_block (%d) is expected to be %d.\n", - num_axial_crystals_per_block, guessed_scanner_ptr->get_num_axial_crystals_per_block()); + warning(boost::format("Interfile warning: num_axial_crystals_per_block (%d) is expected to be %d.\n") % + num_axial_crystals_per_block % guessed_scanner_ptr->get_num_axial_crystals_per_block()); mismatch_between_header_and_guess = true; } if ( guessed_scanner_ptr->get_num_transaxial_crystals_per_block()>0 && num_transaxial_crystals_per_block!= guessed_scanner_ptr->get_num_transaxial_crystals_per_block()) { - warning("Interfile warning: num_transaxial_crystals_per_block (%d) is expected to be %d.\n", - num_transaxial_crystals_per_block, guessed_scanner_ptr->get_num_transaxial_crystals_per_block()); + warning(boost::format("Interfile warning: num_transaxial_crystals_per_block (%d) is expected to be %d.\n") % + num_transaxial_crystals_per_block % guessed_scanner_ptr->get_num_transaxial_crystals_per_block()); mismatch_between_header_and_guess = true; } if ( guessed_scanner_ptr->get_num_axial_crystals_per_singles_unit() > 0 && num_axial_crystals_per_singles_unit != guessed_scanner_ptr->get_num_axial_crystals_per_singles_unit() ) { - warning("Interfile warning: axial crystals per singles unit (%d) is expected to be %d.\n", - num_axial_crystals_per_singles_unit, + warning(boost::format("Interfile warning: axial crystals per singles unit (%d) is expected to be %d.\n") % + num_axial_crystals_per_singles_unit % guessed_scanner_ptr->get_num_axial_crystals_per_singles_unit()); mismatch_between_header_and_guess = true; } @@ -1237,8 +1238,8 @@ bool InterfilePDFSHeader::post_processing() num_transaxial_crystals_per_singles_unit != guessed_scanner_ptr->get_num_transaxial_crystals_per_singles_unit() ) { - warning("Interfile warning: transaxial crystals per singles unit (%d) is expected to be %d.\n", - num_transaxial_crystals_per_singles_unit, + warning(boost::format("Interfile warning: transaxial crystals per singles unit (%d) is expected to be %d.\n") % + num_transaxial_crystals_per_singles_unit % guessed_scanner_ptr->get_num_transaxial_crystals_per_singles_unit()); mismatch_between_header_and_guess = true; } @@ -1246,8 +1247,8 @@ bool InterfilePDFSHeader::post_processing() guessed_scanner_ptr->get_num_detector_layers()>0 && num_detector_layers != guessed_scanner_ptr->get_num_detector_layers()) { - warning("Interfile warning: num_detector_layers (%d) is expected to be %d.\n", - num_detector_layers, guessed_scanner_ptr->get_num_detector_layers()); + warning(boost::format("Interfile warning: num_detector_layers (%d) is expected to be %d.\n") % + num_detector_layers % guessed_scanner_ptr->get_num_detector_layers()); mismatch_between_header_and_guess = true; } // @@ -1260,18 +1261,18 @@ bool InterfilePDFSHeader::post_processing() { if (energy_resolution != guessed_scanner_ptr->get_energy_resolution()) { - warning("Interfile warning: 'energy resolution' (%4.3f) is expected to be %4.3f. " + warning(boost::format("Interfile warning: 'energy resolution' (%4.3f) is expected to be %4.3f. " "Currently, the energy resolution and the reference energy, are used only in" - " scatter correction.", - energy_resolution, guessed_scanner_ptr->get_energy_resolution()); + " scatter correction.") % + energy_resolution % guessed_scanner_ptr->get_energy_resolution()); // mismatch_between_header_and_guess = true; } if (reference_energy != guessed_scanner_ptr->get_reference_energy()) { - warning("Interfile warning: 'reference energy' (%4.3f) is expected to be %4.3f." + warning(boost::format("Interfile warning: 'reference energy' (%4.3f) is expected to be %4.3f." "Currently, the energy resolution and the reference energy, are used only in" - " scatter correction.", - reference_energy, guessed_scanner_ptr->get_reference_energy()); + " scatter correction.") % + reference_energy % guessed_scanner_ptr->get_reference_energy()); // mismatch_between_header_and_guess = true; } } @@ -1280,29 +1281,29 @@ bool InterfilePDFSHeader::post_processing() if (fabs(axial_distance_between_crystals_in_cm -guessed_scanner_ptr->get_axial_crystal_spacing()/10) > .001) { - warning("Interfile warning: 'distance between crystals in axial direction (cm)' (%f) is expected to be %f.\n", - axial_distance_between_crystals_in_cm, guessed_scanner_ptr->get_axial_crystal_spacing()/10); + warning(boost::format("Interfile warning: 'distance between crystals in axial direction (cm)' (%f) is expected to be %f.\n") % + axial_distance_between_crystals_in_cm % (guessed_scanner_ptr->get_axial_crystal_spacing()/10)); mismatch_between_header_and_guess = true; } if (fabs(transaxial_distance_between_crystals_in_cm -guessed_scanner_ptr->get_transaxial_crystal_spacing()/10) > .001) { - warning("Interfile warning: 'distance between crystals in transaxial direction (cm)' (%f) is expected to be %f.\n", - transaxial_distance_between_crystals_in_cm, guessed_scanner_ptr->get_transaxial_crystal_spacing()/10); + warning(boost::format("Interfile warning: 'distance between crystals in transaxial direction (cm)' (%f) is expected to be %f.\n") % + transaxial_distance_between_crystals_in_cm % (guessed_scanner_ptr->get_transaxial_crystal_spacing()/10)); mismatch_between_header_and_guess = true; } if (fabs(axial_distance_between_blocks_in_cm -guessed_scanner_ptr->get_axial_block_spacing()/10) > .001) { - warning("Interfile warning: 'distance between crystals in axial direction (cm)' (%f) is expected to be %f.\n", - axial_distance_between_blocks_in_cm, guessed_scanner_ptr->get_axial_block_spacing()/10); + warning(boost::format("Interfile warning: 'distance between crystals in axial direction (cm)' (%f) is expected to be %f.\n") % + axial_distance_between_blocks_in_cm % (guessed_scanner_ptr->get_axial_block_spacing()/10)); mismatch_between_header_and_guess = true; } if (fabs(transaxial_distance_between_blocks_in_cm -guessed_scanner_ptr->get_transaxial_block_spacing()/10) > .001) { - warning("Interfile warning: 'distance between crystals in axial direction (cm)' (%f) is expected to be %f.\n", - transaxial_distance_between_blocks_in_cm, guessed_scanner_ptr->get_transaxial_block_spacing()/10); + warning(boost::format("Interfile warning: 'distance between crystals in axial direction (cm)' (%f) is expected to be %f.\n") % + transaxial_distance_between_blocks_in_cm % (guessed_scanner_ptr->get_transaxial_block_spacing()/10)); mismatch_between_header_and_guess = true; } // end of new variables for block geometry @@ -1310,9 +1311,9 @@ bool InterfilePDFSHeader::post_processing() // end of checks. If they failed, we ignore the guess if (mismatch_between_header_and_guess) { - warning("Interfile warning: I have used all explicit settings for the scanner\n" + warning(boost::format("Interfile warning: I have used all explicit settings for the scanner\n" "\tfrom the Interfile header, and remaining fields set from the\n" - "\t%s model.\n", + "\t%s model.\n") % guessed_scanner_ptr->get_name().c_str()); if (!originating_system_was_recognised) guessed_scanner_ptr.reset(new Scanner( Scanner::Unknown_scanner)); @@ -1394,7 +1395,7 @@ bool InterfilePDFSHeader::post_processing() mismatch_between_header_and_guess || !is_consistent) { - warning("Interfile parsing ended up with the following scanner:\n%s\n", + warning(boost::format("Interfile parsing ended up with the following scanner:\n%s\n") % scanner_ptr_from_file->parameter_info().c_str()); } @@ -1412,11 +1413,11 @@ bool InterfilePDFSHeader::post_processing() scanner_ptr_from_file->get_default_bin_size()/10; else if (fabs(effective_central_bin_size_in_cm - scanner_ptr_from_file->get_default_bin_size()/10)>.001) - warning("Interfile warning: unexpected effective_central_bin_size_in_cm\n" + warning(boost::format("Interfile warning: unexpected effective_central_bin_size_in_cm\n" "Value in header is %g while the default for the scanner is %g\n" - "Using value from header.", - effective_central_bin_size_in_cm, - scanner_ptr_from_file->get_default_bin_size()/10); + "Using value from header.") % + effective_central_bin_size_in_cm % + (scanner_ptr_from_file->get_default_bin_size()/10)); data_info_sptr.reset( new ProjDataInfoCylindricalArcCorr ( @@ -1440,11 +1441,11 @@ bool InterfilePDFSHeader::post_processing() fabs(effective_central_bin_size_in_cm - data_info_sptr->get_sampling_in_s(Bin(0,0,0,0))/10.)>.01) { - warning("Interfile warning: inconsistent effective_central_bin_size_in_cm\n" + warning(boost::format("Interfile warning: inconsistent effective_central_bin_size_in_cm\n" "Value in header is %g while I expect %g from the inner ring radius etc\n" - "Ignoring value in header", - effective_central_bin_size_in_cm, - data_info_sptr->get_sampling_in_s(Bin(0,0,0,0))/10.); + "Ignoring value in header") % + effective_central_bin_size_in_cm % + (data_info_sptr->get_sampling_in_s(Bin(0,0,0,0))/10.)); } } } @@ -1461,11 +1462,11 @@ bool InterfilePDFSHeader::post_processing() fabs(effective_central_bin_size_in_cm - data_info_sptr->get_sampling_in_s(Bin(0,0,0,0))/10.)>.01) { - warning("Interfile warning: inconsistent effective_central_bin_size_in_cm\n" + warning(boost::format("Interfile warning: inconsistent effective_central_bin_size_in_cm\n" "Value in header is %g while I expect %g from the inner ring radius etc\n" - "Ignoring value in header", - effective_central_bin_size_in_cm, - data_info_sptr->get_sampling_in_s(Bin(0,0,0,0))/10.); + "Ignoring value in header") % + effective_central_bin_size_in_cm % + (data_info_sptr->get_sampling_in_s(Bin(0,0,0,0))/10.)); } } else // if generic geometry @@ -1481,11 +1482,11 @@ bool InterfilePDFSHeader::post_processing() fabs(effective_central_bin_size_in_cm - data_info_sptr->get_sampling_in_s(Bin(0,0,0,0))/10.)>.01) { - warning("Interfile warning: inconsistent effective_central_bin_size_in_cm\n" + warning(boost::format("Interfile warning: inconsistent effective_central_bin_size_in_cm\n" "Value in header is %g while I expect %g from the inner ring radius etc\n" - "Ignoring value in header", - effective_central_bin_size_in_cm, - data_info_sptr->get_sampling_in_s(Bin(0,0,0,0))/10.); + "Ignoring value in header") % + effective_central_bin_size_in_cm % + (data_info_sptr->get_sampling_in_s(Bin(0,0,0,0))/10.)); } } //cerr << data_info_sptr->parameter_info() << endl; diff --git a/src/buildblock/RadionuclideDB.cxx b/src/buildblock/RadionuclideDB.cxx index 283211fc1d..b99e673055 100644 --- a/src/buildblock/RadionuclideDB.cxx +++ b/src/buildblock/RadionuclideDB.cxx @@ -130,7 +130,7 @@ get_radionuclide_from_json(ImagingModality rmodality, const std::string &rname) case ImagingModality::NM: modality_string = "nucmed"; break; default: - warning("RadionuclideDB::get_radionuclide_from_json called with unknown modality. Returning \"unknown\" radionuclide."); + warning(std::string("RadionuclideDB::get_radionuclide_from_json called with unknown modality. Returning \"unknown\" radionuclide.")); return Radionuclide(); } @@ -151,7 +151,7 @@ get_radionuclide_from_json(ImagingModality rmodality, const std::string &rname) if (rnuclide_entry == all_nuclides.end()) { - warning("RadionuclideDB: radionuclide " + rname + " not found in JSON database. Returning \"unknown\" radionuclide."); + warning(std::string("RadionuclideDB: radionuclide " + rname + " not found in JSON database. Returning \"unknown\" radionuclide.")); return Radionuclide(); } @@ -180,7 +180,7 @@ get_radionuclide_from_json(ImagingModality rmodality, const std::string &rname) if (decay_entry == decays.end()) { - warning("RadionuclideDB: radionuclide " + rname + ": modality " + modality_string + " not found in JSON database. Returning \"unknown\" radionuclide."); + warning(std::string("RadionuclideDB: radionuclide " + rname + ": modality " + modality_string + " not found in JSON database. Returning \"unknown\" radionuclide.")); return Radionuclide(); } @@ -240,7 +240,7 @@ get_radionuclide(ImagingModality rmodality, const std::string& rname) return get_radionuclide(rmodality, "^99m^Technetium"); else { - warning("RadioNuclideDB::get_radionuclide: unknown modality. Returning \"unknown\" radionuclide."); + warning(std::string("RadioNuclideDB::get_radionuclide: unknown modality. Returning \"unknown\" radionuclide.")); return Radionuclide(); } } @@ -256,7 +256,7 @@ get_radionuclide(ImagingModality rmodality, const std::string& rname) if(rmodality.get_modality()==ImagingModality::PT){ if (rname != "^18^Fluorine") { - warning("RadioNuclideDB::get_radionuclide: since STIR was compiled without nlohmann-json-dev, We only have information for ^18^Fluorine for the PET modality. Returning \"unknown\" radionuclide."); + warning(std::string("RadioNuclideDB::get_radionuclide: since STIR was compiled without nlohmann-json-dev, We only have information for ^18^Fluorine for the PET modality. Returning \"unknown\" radionuclide.")); return Radionuclide(); } @@ -268,7 +268,7 @@ get_radionuclide(ImagingModality rmodality, const std::string& rname) }else if(rmodality.get_modality()==ImagingModality::NM){ if (rname != "^99m^Technetium") { - warning("RadioNuclideDB::get_radionuclide: since STIR was compiled without nlohmann-json-dev, We only have information for ^99m^Technetium for the NM modality. Returning \"unknown\" radionuclide."); + warning(std::string("RadioNuclideDB::get_radionuclide: since STIR was compiled without nlohmann-json-dev, We only have information for ^99m^Technetium for the NM modality. Returning \"unknown\" radionuclide.")); return Radionuclide(); } @@ -280,7 +280,7 @@ get_radionuclide(ImagingModality rmodality, const std::string& rname) } else { - warning("RadioNuclideDB::get_radionuclide: unknown modality. Returning \"unknown\" radionuclide."); + warning(std::string("RadioNuclideDB::get_radionuclide: unknown modality. Returning \"unknown\" radionuclide.")); return Radionuclide(); } From ea946e4a5cd898c64cfcf4e9771e31b1003781e1 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Thu, 14 Dec 2023 20:30:14 +0000 Subject: [PATCH 374/509] make _already_set_up protected as opposed to private Derived classes should share the same set_up status as the base-class. --- src/include/stir/recon_buildblock/SqrtHessianRowSum.h | 2 +- src/include/stir/scatter/ScatterEstimation.h | 3 ++- src/include/stir/scatter/ScatterSimulation.h | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/include/stir/recon_buildblock/SqrtHessianRowSum.h b/src/include/stir/recon_buildblock/SqrtHessianRowSum.h index 767191ebd2..420a3039fe 100644 --- a/src/include/stir/recon_buildblock/SqrtHessianRowSum.h +++ b/src/include/stir/recon_buildblock/SqrtHessianRowSum.h @@ -112,9 +112,9 @@ class SqrtHessianRowSum: void compute_approximate_Hessian_row_sum(); protected: + bool _already_setup = false; private: - bool _already_setup = false; //! Objective function object shared_ptr > objective_function_sptr; diff --git a/src/include/stir/scatter/ScatterEstimation.h b/src/include/stir/scatter/ScatterEstimation.h index 75980d1220..ac4f80d42f 100644 --- a/src/include/stir/scatter/ScatterEstimation.h +++ b/src/include/stir/scatter/ScatterEstimation.h @@ -291,10 +291,11 @@ class ScatterEstimation: public ParsingObject std::string output_additive_estimate_prefix; -private: //! variable to check if we have called set_up() bool _already_setup; +private: + //! attenuation in 3D shared_ptr atten_norm_3d_sptr; diff --git a/src/include/stir/scatter/ScatterSimulation.h b/src/include/stir/scatter/ScatterSimulation.h index 6753753def..a4de82ffaa 100644 --- a/src/include/stir/scatter/ScatterSimulation.h +++ b/src/include/stir/scatter/ScatterSimulation.h @@ -435,6 +435,7 @@ class ScatterSimulation : public RegisteredObject int downsample_scanner_dets; bool downsample_scanner_bool; + bool _already_set_up; private: int total_detectors; @@ -446,7 +447,6 @@ class ScatterSimulation : public RegisteredObject // numbers that we don't want to recompute all the time mutable float detector_efficiency_no_scatter; - bool _already_set_up; //! a function that checks if image sizes are ok /*! It will call \c error() if not. From 229c07a7122e59b715d3179fc6e758a2f5e445d5 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Mon, 18 Dec 2023 22:14:12 +0000 Subject: [PATCH 375/509] handle "compressed" projdatainfo as input --- ...CListEventScannerWithDiscreteDetectors.inl | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/include/stir/listmode/CListEventScannerWithDiscreteDetectors.inl b/src/include/stir/listmode/CListEventScannerWithDiscreteDetectors.inl index 41f187c683..aae1394ecf 100644 --- a/src/include/stir/listmode/CListEventScannerWithDiscreteDetectors.inl +++ b/src/include/stir/listmode/CListEventScannerWithDiscreteDetectors.inl @@ -34,25 +34,24 @@ CListEventScannerWithDiscreteDetectors(const shared_ptr& pro if (!proj_data_info_sptr) error("CListEventScannerWithDiscreteDetectors constructor called with zero pointer"); - this->uncompressed_proj_data_info_sptr = std::dynamic_pointer_cast< const ProjDataInfoT >(proj_data_info_sptr->create_shared_clone()); - -#if 0 // TODO: actually create uncompressed. - this->scanner_sptr = scanner_sptr_v; + auto scanner_sptr = proj_data_info_sptr->get_scanner_sptr(); + // get bare pointer of uncompressed ProjDataInfo auto pdi_ptr = - ProjDataInfo::ProjDataInfoCTI(scanner_sptr_v, - 1, scanner_sptr->get_num_rings()-1, - scanner_sptr->get_num_detectors_per_ring()/2, - scanner_sptr->get_max_num_non_arccorrected_bins(), - false); - auto pdi_ptr_cast = - dynamic_cast(pdi_ptr); + ProjDataInfo::construct_proj_data_info(scanner_sptr, + 1, scanner_sptr->get_num_rings()-1, + scanner_sptr->get_num_detectors_per_ring()/2, + scanner_sptr->get_max_num_non_arccorrected_bins(), + /* arc correction = */ false, + /* TOF mashing = */ 1).release(); + // check type + auto pdi_ptr_cast = dynamic_cast(pdi_ptr); if (!pdi_ptr_cast) { delete pdi_ptr; error("CListEventScannerWithDiscreteDetectors constructor called with scanner that gives wrong type of ProjDataInfo"); } + // set shared_ptr from bare pointer (will take ownership) this->uncompressed_proj_data_info_sptr.reset(pdi_ptr_cast); -#endif } template From b579414d66ae678c52e962382f740a184163c098 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Fri, 22 Dec 2023 14:15:10 +0000 Subject: [PATCH 376/509] set default for energy fields when reading .hroot reference_energy and energy_resolution where not initialised when not present in the .hroot file, leading to potentially undefined behaviour. Now setting reference energy to 511, and energy_Resolution to -1 --- src/listmode_buildblock/CListModeDataROOT.cxx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/listmode_buildblock/CListModeDataROOT.cxx b/src/listmode_buildblock/CListModeDataROOT.cxx index bb2fa77c7d..1eb33829ab 100644 --- a/src/listmode_buildblock/CListModeDataROOT.cxx +++ b/src/listmode_buildblock/CListModeDataROOT.cxx @@ -266,6 +266,8 @@ set_defaults() bin_size = -1.f; view_offset = 0.f; tof_mash_factor = 1; + reference_energy = 511.F; + energy_resolution = -1.F; } Succeeded From f27262cd4deebaa1f7d4b72c9fb0ced537ace4a5 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Fri, 22 Dec 2023 15:05:01 +0000 Subject: [PATCH 377/509] set test files to have odd number of TOF bins we currently don't support even number yet. No idea how this ever worked before... --- recon_test_pack/root_header.hroot | 2 +- recon_test_pack/template_for_ROOT_scanner.hs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/recon_test_pack/root_header.hroot b/recon_test_pack/root_header.hroot index 02e1649f30..d1056c2e07 100644 --- a/recon_test_pack/root_header.hroot +++ b/recon_test_pack/root_header.hroot @@ -11,7 +11,7 @@ Default bin size (cm) := 0.208626 Maximum number of non-arc-corrected bins := 344 Default number of arc-corrected bins := 344 View offset (degrees) := 0 -Number of TOF time bins := 410 +Number of TOF time bins := 411 Size of timing bin (ps) := 10.00 Timing resolution (ps) := 400.0 diff --git a/recon_test_pack/template_for_ROOT_scanner.hs b/recon_test_pack/template_for_ROOT_scanner.hs index 7f5323d56e..a70f62d25e 100644 --- a/recon_test_pack/template_for_ROOT_scanner.hs +++ b/recon_test_pack/template_for_ROOT_scanner.hs @@ -39,7 +39,7 @@ Maximum number of non-arc-corrected bins := 344 Default number of arc-corrected bins := 344 Energy resolution := 0 Reference energy (in keV) := 511 -Number of TOF time bins :=410 +Number of TOF time bins :=411 Size of timing bin (ps) := 10 Timing resolution (ps) := 400 Number of blocks per bucket in transaxial direction := 1 From 6f03c22329b37be15c1fbfd52d8ac33cf8f70b0b Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Fri, 22 Dec 2023 23:14:43 +0000 Subject: [PATCH 378/509] fix ProjDataInfo::operator== and >= to check tof_mash_factor --- src/buildblock/ProjDataInfo.cxx | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/buildblock/ProjDataInfo.cxx b/src/buildblock/ProjDataInfo.cxx index 29e842c804..9f6ab2542a 100644 --- a/src/buildblock/ProjDataInfo.cxx +++ b/src/buildblock/ProjDataInfo.cxx @@ -782,8 +782,9 @@ blindly_equals(const root_type * const that) const (get_max_view_num()==proj.get_max_view_num()) && (get_min_tangential_pos_num() ==proj.get_min_tangential_pos_num())&& (get_max_tangential_pos_num() ==proj.get_max_tangential_pos_num())&& - (proj.is_tof_data() && is_tof_data() ? (get_min_tof_pos_num() == proj.get_min_tof_pos_num()) && - (get_max_tof_pos_num() == proj.get_max_tof_pos_num()) : true) && + (get_tof_mash_factor() == proj.get_tof_mash_factor()) && + (get_min_tof_pos_num() == proj.get_min_tof_pos_num()) && + (get_max_tof_pos_num() == proj.get_max_tof_pos_num()) && equal(min_axial_pos_per_seg.begin(), min_axial_pos_per_seg.end(), proj.min_axial_pos_per_seg.begin())&& equal(max_axial_pos_per_seg.begin(), max_axial_pos_per_seg.end(), proj.max_axial_pos_per_seg.begin())&& (*get_scanner_ptr()== *(proj.get_scanner_ptr()))&& @@ -814,7 +815,7 @@ operator !=(const root_type& that) const \c true only if the types are the same, they are equal, or the range for the TOF, segments, axial and tangential positions is at least as large. - \warning Currently view ranges have to be identical. + \warning Currently view and TOF ranges have to be identical. */ bool ProjDataInfo:: @@ -828,13 +829,15 @@ operator>=(const ProjDataInfo& proj_data_info) const if (larger_proj_data_info == proj_data_info) return true; + if (proj_data_info.get_tof_mash_factor() != larger_proj_data_info.get_tof_mash_factor()) + return false; + if (proj_data_info.get_max_segment_num() > larger_proj_data_info.get_max_segment_num() || proj_data_info.get_min_segment_num() < larger_proj_data_info.get_min_segment_num() || proj_data_info.get_max_tangential_pos_num() > larger_proj_data_info.get_max_tangential_pos_num() || proj_data_info.get_min_tangential_pos_num() < larger_proj_data_info.get_min_tangential_pos_num() || - ((proj_data_info.get_min_tof_pos_num() < larger_proj_data_info.get_min_tof_pos_num() || - proj_data_info.get_max_tof_pos_num() > larger_proj_data_info.get_max_tof_pos_num()) && - (proj_data_info.is_tof_data() && larger_proj_data_info.is_tof_data()))) + proj_data_info.get_min_tof_pos_num() < larger_proj_data_info.get_min_tof_pos_num() || + proj_data_info.get_max_tof_pos_num() > larger_proj_data_info.get_max_tof_pos_num()) return false; for (int segment_num=proj_data_info.get_min_segment_num(); From 36d25e346cef83720cc924c402ad25fd53ad631c Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Fri, 22 Dec 2023 23:38:59 +0000 Subject: [PATCH 379/509] fix BinNormalisationFromProjData::set_up to cope with mixing TOF data set_up() checks if norm_proj_data_info >= emission_proj_data_info but needs to handle non-TOF norm data and TOF emssion data. Previously this worked as ProjDataInfo::blindly_equal ignored TOF_mashing, but that is no longer the case, so we need to handle this here. --- .../BinNormalisationFromProjData.cxx | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/recon_buildblock/BinNormalisationFromProjData.cxx b/src/recon_buildblock/BinNormalisationFromProjData.cxx index 060a59558c..7af299da98 100644 --- a/src/recon_buildblock/BinNormalisationFromProjData.cxx +++ b/src/recon_buildblock/BinNormalisationFromProjData.cxx @@ -79,16 +79,25 @@ BinNormalisationFromProjData(const shared_ptr& norm_proj_data_ptr) Succeeded BinNormalisationFromProjData:: -set_up(const shared_ptr& exam_info_sptr, const shared_ptr& proj_data_info_ptr) +set_up(const shared_ptr& exam_info_sptr, const shared_ptr& proj_data_info_sptr) { - base_type::set_up(exam_info_sptr, proj_data_info_ptr); - - if (*(norm_proj_data_ptr->get_proj_data_info_sptr()) == *proj_data_info_ptr) + if (!base_type::set_up(exam_info_sptr, proj_data_info_sptr).succeeded()) + return Succeeded::no; + + const auto& norm_proj = *(norm_proj_data_ptr->get_proj_data_info_sptr()); + // complication: if the emission data is TOF but the norm is not, `apply()` et al. multiply all + // TOF bins with the non-TOF norm. So, we need to allow for that. + auto proj_to_check_sptr = proj_data_info_sptr; + if (!norm_proj.is_tof_data() && proj_data_info_sptr->is_tof_data()) + proj_to_check_sptr = proj_data_info_sptr->create_non_tof_clone(); + const auto& proj = *proj_to_check_sptr; + + if (norm_proj == proj) return Succeeded::yes; else { - const ProjDataInfo& norm_proj = *(norm_proj_data_ptr->get_proj_data_info_sptr()); - const ProjDataInfo& proj = *proj_data_info_ptr; + // Check if the emission data is "smaller" than the norm data (e.g. fewer segments) + // We will require equal tangential_pos ranges as `apply()` currently needs that. bool ok = (norm_proj >= proj) && (norm_proj.get_min_tangential_pos_num() ==proj.get_min_tangential_pos_num())&& @@ -98,7 +107,7 @@ set_up(const shared_ptr& exam_info_sptr, const shared_ptr Date: Fri, 22 Dec 2023 21:59:12 +0000 Subject: [PATCH 380/509] remove (now) unused use_tof member and keyword --- .../GeneralisedObjectiveFunction.h | 6 ------ .../GeneralisedObjectiveFunction.cxx | 15 --------------- 2 files changed, 21 deletions(-) diff --git a/src/include/stir/recon_buildblock/GeneralisedObjectiveFunction.h b/src/include/stir/recon_buildblock/GeneralisedObjectiveFunction.h index b6fbcad915..021089257a 100644 --- a/src/include/stir/recon_buildblock/GeneralisedObjectiveFunction.h +++ b/src/include/stir/recon_buildblock/GeneralisedObjectiveFunction.h @@ -265,9 +265,6 @@ class GeneralisedObjectiveFunction: return exam_info_uptr; } - //! Return the status of TOF - bool get_tof_status() const; - //! Attempts to change the number of subsets. /*! \return The number of subsets that will be used later, which is not guaranteed to be what you asked for. */ @@ -335,9 +332,6 @@ class GeneralisedObjectiveFunction: protected: int num_subsets; - //! If set TOF information will be taken into account. - bool use_tof; - shared_ptr > prior_sptr; //! sets any default values diff --git a/src/recon_buildblock/GeneralisedObjectiveFunction.cxx b/src/recon_buildblock/GeneralisedObjectiveFunction.cxx index 1112d2a2d7..ee25939088 100644 --- a/src/recon_buildblock/GeneralisedObjectiveFunction.cxx +++ b/src/recon_buildblock/GeneralisedObjectiveFunction.cxx @@ -41,7 +41,6 @@ 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; - use_tof = false; } template @@ -50,7 +49,6 @@ GeneralisedObjectiveFunction:: initialise_keymap() { this->parser.add_parsing_key("prior type", &prior_sptr); - this->parser.add_key("Use TOF information", &use_tof); } template @@ -82,11 +80,6 @@ set_up(shared_ptr const& target_data_ptr) return Succeeded::no; } - if (use_tof) - { - info("Time-Of-Flight reconstruction activated!"); - } - return Succeeded::yes; } @@ -183,14 +176,6 @@ get_num_subsets() const return this->num_subsets; } -template -bool -GeneralisedObjectiveFunction:: -get_tof_status() const -{ - return this->use_tof; -} - template double GeneralisedObjectiveFunction:: From 2ea7e18467ec8eb5162302df5d0980156afa0133 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Fri, 22 Dec 2023 22:03:16 +0000 Subject: [PATCH 381/509] more helpful error message in find_STIR_config_file Tell use they can set STIR_CONFIG_DIR env variable --- src/buildblock/find_STIR_config.cxx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/buildblock/find_STIR_config.cxx b/src/buildblock/find_STIR_config.cxx index 77b290a08a..f1bbf9c8af 100644 --- a/src/buildblock/find_STIR_config.cxx +++ b/src/buildblock/find_STIR_config.cxx @@ -26,12 +26,12 @@ START_NAMESPACE_STIR std::string find_STIR_config_file(const std::string& filename){ std::string dir; - dir = get_STIR_config_dir(); //STIR_CONFIG_DIR; + dir = get_STIR_config_dir(); // TODO this might be dangerous on Windows but seems to work const std::string name = (dir+"/"+filename); std::ifstream file(name); if (!file) - error("find_STIR_config_file could not open "+name); + error("find_STIR_config_file could not open " + name + "\nYou can set the STIR_CONFIG_DIR environment variable to help."); if (file.peek() == std::ifstream::traits_type::eof()) error("find_STIR_config_file error opening file for reading (non-existent or empty file). Filename:\n'" + name + "'"); return name; From 351c29dd9700e7108ff631351925248c5ef0064e Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sat, 23 Dec 2023 10:12:46 +0000 Subject: [PATCH 382/509] Fix handling of non-TOF sensitivity calculation for projdata make sure that currect normalisation::set_up is used for either sensitivity, log-likelihood or Hessian calculations Still need to do listmode Partially fixes https://github.com/NikEfth/STIR/issues/56 --- ...elihoodWithLinearModelForMeanAndProjData.h | 14 +- ...ihoodWithLinearModelForMeanAndProjData.cxx | 141 +++++++----------- 2 files changed, 70 insertions(+), 85 deletions(-) diff --git a/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.h b/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.h index 74b094b18a..2b7733d664 100644 --- a/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.h +++ b/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.h @@ -334,7 +334,7 @@ public RegisteredParsingObject additive_proj_data_sptr; shared_ptr normalisation_sptr; - + // TODO doc int frame_num; std::string frame_definition_filename; @@ -390,6 +390,18 @@ public RegisteredParsingObject sens_proj_data_info_sptr; + //! flag to check if we set-up normalisation with the original data or not + mutable bool latest_setup_norm_was_with_orig_data; + //! flag to check if normalisation has been set_up already or not + mutable bool norm_already_setup = false; + + //! helper function to makre sure we set-up normalisation_sptr correctly + void ensure_norm_is_set_up(bool for_original_data = true) const; + //! convenience for ensure_norm_is_set_up(false) + void ensure_norm_is_set_up_for_sensitivity() const; //!@} #if 0 void diff --git a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx index a9ba5c88b3..3ddc7cc81e 100644 --- a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx +++ b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx @@ -1,7 +1,7 @@ /* Copyright (C) 2000 PARAPET partners Copyright (C) 2000-2011, Hammersmith Imanet Ltd - Copyright (C) 2014, 2016-2022 University College London + Copyright (C) 2014, 2016-2023 University College London This file is part of STIR. SPDX-License-Identifier: Apache-2.0 AND License-ref-PARAPET-license @@ -550,13 +550,48 @@ sensitivity_uses_same_projector() const /*************************************************************** set_up() ***************************************************************/ +template +void +PoissonLogLikelihoodWithLinearModelForMeanAndProjData:: +ensure_norm_is_set_up(bool for_original_data) const +{ + + for_original_data = for_original_data || this->sensitivity_uses_same_projector(); + if (for_original_data) + { + if (!this->norm_already_setup || !this->latest_setup_norm_was_with_orig_data) + { + if (this->normalisation_sptr->set_up(proj_data_sptr->get_exam_info_sptr(), this->proj_data_sptr->get_proj_data_info_sptr()) == Succeeded::no) + error("Set_up of norm with original data failed."); + } + } + else + { + if (!this->norm_already_setup || this->latest_setup_norm_was_with_orig_data) + { + if (this->normalisation_sptr->set_up(proj_data_sptr->get_exam_info_sptr(), this->sens_proj_data_info_sptr) == Succeeded::no) + error("Set_up of norm with non-TOF data failed."); + } + } + this->norm_already_setup = true; + this->latest_setup_norm_was_with_orig_data = for_original_data; +} + +template +void +PoissonLogLikelihoodWithLinearModelForMeanAndProjData:: +ensure_norm_is_set_up_for_sensitivity() const +{ + this->ensure_norm_is_set_up(false); +} + template Succeeded PoissonLogLikelihoodWithLinearModelForMeanAndProjData:: set_up_before_sensitivity(shared_ptr const& target_sptr) { if (is_null_ptr(this->proj_data_sptr)) - error("you need to set the input data before calling set_up"); + error("you need to set the input data before calling set_up"); if (this->max_segment_num_to_process==-1) this->max_segment_num_to_process = @@ -565,12 +600,12 @@ set_up_before_sensitivity(shared_ptr const& target_sptr) if (this->max_segment_num_to_process > this->proj_data_sptr->get_max_segment_num()) { error("max_segment_num_to_process (%d) is too large", - this->max_segment_num_to_process); + this->max_segment_num_to_process); return Succeeded::no; } - this->max_timing_pos_num_to_process = - this->proj_data_sptr->get_max_tof_pos_num(); + this->max_timing_pos_num_to_process = + this->proj_data_sptr->get_max_tof_pos_num(); shared_ptr proj_data_info_sptr(this->proj_data_sptr->get_proj_data_info_sptr()->clone()); @@ -602,13 +637,15 @@ set_up_before_sensitivity(shared_ptr const& target_sptr) this->projector_pair_ptr->get_back_projector_sptr()->get_symmetries_used()->clone()); if (is_null_ptr(this->normalisation_sptr)) - { - error("Invalid normalisation object"); - return Succeeded::no; - } + { + error("Invalid normalisation object"); + return Succeeded::no; + } // we postpone calling setup_distributable_computation until we know which projectors we will use this->distributable_computation_already_setup = false; + // similar for norm + this->norm_already_setup = false; if (this->get_recompute_sensitivity()) { @@ -616,13 +653,13 @@ set_up_before_sensitivity(shared_ptr const& target_sptr) { this->sens_backprojector_sptr = projector_pair_ptr->get_back_projector_sptr(); this->sens_symmetries_sptr = this->symmetries_sptr; - if (this->normalisation_sptr->set_up(proj_data_sptr->get_exam_info_sptr(), proj_data_info_sptr) == Succeeded::no) - return Succeeded::no; + this->sens_proj_data_info_sptr = proj_data_info_sptr; } else { // sets non-tof backprojector for sensitivity calculation (clone of the back_projector + set projdatainfo to non-tof) auto pdi_non_tof_sptr = proj_data_info_sptr->create_non_tof_clone(); + this->sens_proj_data_info_sptr = pdi_non_tof_sptr; this->sens_backprojector_sptr.reset(projector_pair_ptr->get_back_projector_sptr()->clone()); if (auto sens_bp_pm_sptr = std::dynamic_pointer_cast(this->sens_backprojector_sptr)) { @@ -633,8 +670,6 @@ set_up_before_sensitivity(shared_ptr const& target_sptr) } this->sens_backprojector_sptr->set_up(pdi_non_tof_sptr, target_sptr); this->sens_symmetries_sptr.reset(this->sens_backprojector_sptr->get_symmetries_used()->clone()); - if (this->normalisation_sptr->set_up(proj_data_sptr->get_exam_info_sptr(), pdi_non_tof_sptr) == Succeeded::no) - return Succeeded::no; } } @@ -683,7 +718,8 @@ actual_compute_subset_gradient_without_penalty(TargetT& gradient, } if (!this->distributable_computation_already_setup) error("PoissonLogLikelihoodWithLinearModelForMeanAndProjData internal error: setup_distributable_computation not called (gradient calculation)"); - + if (add_sensitivity) + this->ensure_norm_is_set_up(); distributable_compute_gradient(this->projector_pair_ptr->get_forward_projector_sptr(), this->projector_pair_ptr->get_back_projector_sptr(), this->symmetries_sptr, @@ -725,6 +761,8 @@ actual_compute_objective_function_without_penalty(const TargetT& current_estimat } if (!this->distributable_computation_already_setup) error("PoissonLogLikelihoodWithLinearModelForMeanAndProjData internal error: setup_distributable_computation not called (function calculation)"); + this->ensure_norm_is_set_up(); + double accum=0.; distributable_accumulate_loglikelihood(this->projector_pair_ptr->get_forward_projector_sptr(), @@ -800,14 +838,9 @@ add_subset_sensitivity(TargetT& sensitivity, const int subset_num) const const int min_segment_num = -this->max_segment_num_to_process; const int max_segment_num = this->max_segment_num_to_process; -#if 1 shared_ptr sensitivity_this_subset_sptr(sensitivity.clone()); - shared_ptr sens_proj_data_sptr; // have to create a ProjData object filled with 1 here because otherwise zero_seg0_endplanes will not be effective - if (!this->sensitivity_uses_same_projector()) - sens_proj_data_sptr.reset(new ProjDataInMemory(this->proj_data_sptr->get_exam_info_sptr(), this->proj_data_sptr->get_proj_data_info_sptr()->create_non_tof_clone())); - else - sens_proj_data_sptr.reset(new ProjDataInMemory(this->proj_data_sptr->get_exam_info_sptr(), this->proj_data_sptr->get_proj_data_info_sptr())); + auto sens_proj_data_sptr = std::make_shared(this->proj_data_sptr->get_exam_info_sptr(), this->sens_proj_data_info_sptr); sens_proj_data_sptr->fill(1.0F); if (this->sensitivity_uses_same_projector() && (!this->distributable_computation_already_setup || !this->latest_setup_distributable_computation_was_with_orig_projectors)) @@ -836,10 +869,11 @@ add_subset_sensitivity(TargetT& sensitivity, const int subset_num) const this->distributable_computation_already_setup = true; this->latest_setup_distributable_computation_was_with_orig_projectors = false; } - if (!this->distributable_computation_already_setup) error("PoissonLogLikelihoodWithLinearModelForMeanAndProjData internal error: setup_distributable_computation not called (sensitivity calculation)"); + this->ensure_norm_is_set_up_for_sensitivity(); + distributable_sensitivity_computation(this->projector_pair_ptr->get_forward_projector_sptr(), this->sens_backprojector_sptr, this->sens_symmetries_sptr, @@ -863,66 +897,8 @@ add_subset_sensitivity(TargetT& sensitivity, const int subset_num) const std::transform(sensitivity.begin_all(), sensitivity.end_all(), sensitivity_this_subset_sptr->begin_all(), sensitivity.begin_all(), std::plus()); -#else - - // 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 -PoissonLogLikelihoodWithLinearModelForMeanAndProjData:: -add_view_seg_to_sensitivity(TargetT& sensitivity, const ViewSegmentNumbers& view_seg_nums) const -{ - int min_timing_pos_num = use_tofsens ? -this->max_timing_pos_num_to_process : 0; - int max_timing_pos_num = use_tofsens ? this->max_timing_pos_num_to_process : 0; - for (int timing_pos_num = min_timing_pos_num; timing_pos_num <= max_timing_pos_num; ++timing_pos_num) - { - RelatedViewgrams viewgrams = - this->proj_data_sptr->get_empty_related_viewgrams(view_seg_nums, - this->symmetries_sptr, false, timing_pos_num); - 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->sens_backprojector_sptr->back_project(sensitivity, viewgrams, min_ax_pos_num, max_ax_pos_num); - } - } - -} -#endif - template std::unique_ptr PoissonLogLikelihoodWithLinearModelForMeanAndProjData:: @@ -962,14 +938,11 @@ actual_add_multiplication_with_approximate_sub_Hessian_without_penalty(TargetT& } } + this->ensure_norm_is_set_up(); + 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()); - this->get_projector_pair().get_forward_projector_sptr()->set_input(input); this->get_projector_pair().get_back_projector_sptr()->start_accumulating_in_new_target(); From e3a4e28af76583999b03ca86e52147ae1bb1b14a Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Wed, 27 Dec 2023 16:49:13 +0000 Subject: [PATCH 383/509] recon_test_pack: fix run_root_GATE.sh and associated files - the script itself was a bit garbled - it failed because the num_tangential_poss in the .hroot was too small compared to the data, such that counts didn't match - the hroot and template.hs were inconsistent (max num non-arccorrected) I've also made the template TOF. --- recon_test_pack/root_header.hroot | 4 +- recon_test_pack/run_root_GATE.sh | 39 +++++--------------- recon_test_pack/template_for_ROOT_scanner.hs | 10 +++-- 3 files changed, 18 insertions(+), 35 deletions(-) diff --git a/recon_test_pack/root_header.hroot b/recon_test_pack/root_header.hroot index 02e1649f30..de1ce1f54f 100644 --- a/recon_test_pack/root_header.hroot +++ b/recon_test_pack/root_header.hroot @@ -8,8 +8,8 @@ Inner ring diameter (cm) := 65.6 Average depth of interaction (cm) := 0.7 Distance between rings (cm) := 0.40625 Default bin size (cm) := 0.208626 -Maximum number of non-arc-corrected bins := 344 -Default number of arc-corrected bins := 344 +Maximum number of non-arc-corrected bins := 501 +Default number of arc-corrected bins := 501 View offset (degrees) := 0 Number of TOF time bins := 410 Size of timing bin (ps) := 10.00 diff --git a/recon_test_pack/run_root_GATE.sh b/recon_test_pack/run_root_GATE.sh index 070da72cae..9b7edd5c5d 100755 --- a/recon_test_pack/run_root_GATE.sh +++ b/recon_test_pack/run_root_GATE.sh @@ -11,7 +11,7 @@ # # Author Nikos Efthimiou, Kris Thielemans -echo This script should work with STIR version 5.2. If you have +echo This script should work with STIR version 6.x. If you have echo a later version, you might have to update your test pack. echo Please check the web site. echo @@ -73,10 +73,7 @@ echo "GATE support detected!" fi -echo Executing tests on ROOT files generated by GATE simulations, with cylindrical PET scanners - - - +echo Executing tests on ROOT files generated by GATE simulations # first delete any files remaining from a previous run @@ -87,22 +84,12 @@ export INPUT_ROOT_FILE=test_PET_GATE.root export INPUT=root_header.hroot export TEMPLATE=template_for_ROOT_scanner.hs -# -# Get the number of events unlisted. -# -echo -echo ------------- Converting ROOT files to ProjData file ------------- -echo -echo Making ProjData for all events +echo "------------------------------" echo Running lm_to_projdata for all events export OUT_PROJDATA_FILE=my_proj_from_lm_all_events export EXCLUDE_SCATTERED=0 export EXCLUDE_RANDOM=0 -lm_to_projdata ./lm_to_projdata.par 2>"./my_root_log_all.log" -all_events=`awk -F ":" '/Number of prompts/ {print $2}' my_root_log_all.log` -echo "Number of prompts stored in this time period:" ${all_events} - log=lm_to_projdata_from_ROOT_all_events.log lm_to_projdata ./lm_to_projdata.par > ${log} 2>&1 if [ $? -ne 0 ]; then @@ -114,17 +101,11 @@ else all_events=$(cat ${log}|grep "Number of prompts stored in this time period" | grep -o -E '[0-9]+') echo Number of prompts stored in this time period: ${all_events} - echo fi -echo Reading all values from ROOT file -# -# Get the number of events directly from the ROOT file -# -echo -echo ------------- Reading values directly from ROOT file ------------- -echo -cat << EOF > my_root.input +echo Reading values directly from ROOT file +log=root_output_all_events.log +${ROOT} -b -l ${INPUT_ROOT_FILE} << EOF >& ${log} Coincidences->Draw(">>eventlist","","goff"); Int_t N = eventlist->GetN(); cout<& ${log} +${ROOT} -b -l ${INPUT_ROOT_FILE} << EOF >& ${log} Coincidences->Draw(">>eventlist","eventID1 == eventID2 && comptonPhantom1 == 0 && comptonPhantom2 == 0","goff"); Int_t N = eventlist->GetN(); cout< Date: Wed, 27 Dec 2023 16:52:16 +0000 Subject: [PATCH 384/509] let constructors error if max_num_nonarccorrected_data is smaller than tang_poss Also small clean-up by re-using constructor (C++11 feature) --- .../ProjDataInfoCylindricalNoArcCorr.cxx | 45 +++++++------------ .../ProjDataInfoGenericNoArcCorr.cxx | 11 ++--- 2 files changed, 21 insertions(+), 35 deletions(-) diff --git a/src/buildblock/ProjDataInfoCylindricalNoArcCorr.cxx b/src/buildblock/ProjDataInfoCylindricalNoArcCorr.cxx index 1c101e24b9..f3cbe6e77d 100644 --- a/src/buildblock/ProjDataInfoCylindricalNoArcCorr.cxx +++ b/src/buildblock/ProjDataInfoCylindricalNoArcCorr.cxx @@ -32,22 +32,15 @@ #include "stir/round.h" #include #include "stir/error.h" - -#ifdef BOOST_NO_STRINGSTREAM -#include -#else #include -#endif #include -#ifndef STIR_NO_NAMESPACES using std::endl; using std::ends; using std::string; using std::pair; using std::vector; -#endif START_NAMESPACE_STIR ProjDataInfoCylindricalNoArcCorr:: @@ -55,26 +48,28 @@ ProjDataInfoCylindricalNoArcCorr() {} ProjDataInfoCylindricalNoArcCorr:: -ProjDataInfoCylindricalNoArcCorr(const shared_ptr scanner_ptr, +ProjDataInfoCylindricalNoArcCorr(const shared_ptr scanner_sptr, const float ring_radius_v, const float angular_increment_v, const VectorWithOffset& num_axial_pos_per_segment, const VectorWithOffset& min_ring_diff_v, const VectorWithOffset& max_ring_diff_v, const int num_views,const int num_tangential_poss, const int tof_mash_factor) -: ProjDataInfoCylindrical(scanner_ptr, +: ProjDataInfoCylindrical(scanner_sptr, num_axial_pos_per_segment, min_ring_diff_v, max_ring_diff_v, num_views, num_tangential_poss), ring_radius(ring_radius_v), angular_increment(angular_increment_v) { - if (num_tangential_poss > scanner_ptr->get_max_num_non_arccorrected_bins()) - error("Configured tangential positions exceed the maximum number of non arc-corrected bins set for the scanner."); - + if (!scanner_sptr) + error("ProjDataInfoCylindricalNoArcCorr: first argument (scanner_ptr) is zero"); + if (num_tangential_poss > scanner_sptr->get_max_num_non_arccorrected_bins()) + error("ProjDataInfoCylindricalNoArcCorr: number of tangential positions exceeds the maximum number of non arc-corrected bins set for the scanner."); + uncompressed_view_tangpos_to_det1det2_initialised = false; det1det2_to_uncompressed_view_tangpos_initialised = false; - if(scanner_ptr->is_tof_ready()) + if(scanner_sptr->is_tof_ready()) set_tof_mash_factor(tof_mash_factor); #ifdef STIR_OPENMP_SAFE_BUT_SLOW this->initialise_uncompressed_view_tangpos_to_det1det2(); @@ -83,30 +78,20 @@ ProjDataInfoCylindricalNoArcCorr(const shared_ptr scanner_ptr, } ProjDataInfoCylindricalNoArcCorr:: -ProjDataInfoCylindricalNoArcCorr(const shared_ptr scanner_ptr, +ProjDataInfoCylindricalNoArcCorr(const shared_ptr scanner_sptr, const VectorWithOffset& num_axial_pos_per_segment, const VectorWithOffset& min_ring_diff_v, const VectorWithOffset& max_ring_diff_v, const int num_views, const int num_tangential_poss, const int tof_mash_factor) -: ProjDataInfoCylindrical(scanner_ptr, + : ProjDataInfoCylindricalNoArcCorr(scanner_sptr, + scanner_sptr ? scanner_sptr->get_effective_ring_radius() : 0.F, // avoid segfault if scanner_sptr==0 + scanner_sptr ? static_cast(_PI/scanner_sptr->get_num_detectors_per_ring()) : 0.F, num_axial_pos_per_segment, min_ring_diff_v, max_ring_diff_v, - num_views, num_tangential_poss) -{ - assert(!is_null_ptr(scanner_ptr)); - ring_radius = scanner_ptr->get_effective_ring_radius(); - angular_increment = static_cast(_PI/scanner_ptr->get_num_detectors_per_ring()); - uncompressed_view_tangpos_to_det1det2_initialised = false; - det1det2_to_uncompressed_view_tangpos_initialised = false; - - if(scanner_ptr->is_tof_ready()) - set_tof_mash_factor(tof_mash_factor); -#ifdef STIR_OPENMP_SAFE_BUT_SLOW - this->initialise_uncompressed_view_tangpos_to_det1det2(); - this->initialise_det1det2_to_uncompressed_view_tangpos(); -#endif -} + num_views, num_tangential_poss, + tof_mash_factor) +{} diff --git a/src/buildblock/ProjDataInfoGenericNoArcCorr.cxx b/src/buildblock/ProjDataInfoGenericNoArcCorr.cxx index 6ea435619a..e900897d24 100644 --- a/src/buildblock/ProjDataInfoGenericNoArcCorr.cxx +++ b/src/buildblock/ProjDataInfoGenericNoArcCorr.cxx @@ -52,20 +52,21 @@ ProjDataInfoGenericNoArcCorr() {} ProjDataInfoGenericNoArcCorr:: -ProjDataInfoGenericNoArcCorr(const shared_ptr scanner_ptr, +ProjDataInfoGenericNoArcCorr(const shared_ptr scanner_sptr, const VectorWithOffset& num_axial_pos_per_segment, const VectorWithOffset& min_ring_diff_v, const VectorWithOffset& max_ring_diff_v, const int num_views,const int num_tangential_poss) -: ProjDataInfoGeneric(scanner_ptr, +: ProjDataInfoGeneric(scanner_sptr, num_axial_pos_per_segment, min_ring_diff_v, max_ring_diff_v, num_views, num_tangential_poss) { - if (num_tangential_poss > scanner_ptr->get_max_num_non_arccorrected_bins()) - error("Configured tangential positions exceed the maximum number of non arc-corrected bins set for the scanner."); + if (!scanner_sptr) + error("ProjDataInfoGenericNoArcCorr: first argument (scanner_ptr) is zero"); + if (num_tangential_poss > scanner_sptr->get_max_num_non_arccorrected_bins()) + error("ProjDataInfoGenericNoArcCorr: number of tangential positions exceeds the maximum number of non arc-corrected bins set for the scanner."); - assert(!is_null_ptr(scanner_ptr)); uncompressed_view_tangpos_to_det1det2_initialised = false; det1det2_to_uncompressed_view_tangpos_initialised = false; #ifdef STIR_OPENMP_SAFE_BUT_SLOW From 4222556b6e516f9a11cea182b172e952e920da8b Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Thu, 28 Dec 2023 11:43:10 +0000 Subject: [PATCH 385/509] fix scatter templates: set max_num_nonarccorrected bins this is now required --- documentation/release_notes_TOF.htm | 8 +++++--- examples/PET_simulation/scatter_template.hs | 6 +++--- recon_test_pack/scatter_cylinder.hs | 2 +- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/documentation/release_notes_TOF.htm b/documentation/release_notes_TOF.htm index 96aadb367e..068ccd28f1 100644 --- a/documentation/release_notes_TOF.htm +++ b/documentation/release_notes_TOF.htm @@ -7,12 +7,12 @@

        Summary of changes in STIR release 6.0

        -

        This version is 95% backwards compatible with STIR 4.0 for the user (see below). +

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

        Overall summary

        -

        +

        This release is a major upgrade adding Time of Flight (TOF) capabilities to STIR.

        @@ -58,6 +58,8 @@

        New functionality

        Changed functionality

        • + We now always check (ProjDataInfo*NoArcCorr) if number of tangential positions in the projection data exceeds the maximum number + of non arc-corrected bins set for the scanner. If it is, an error is raised.
        @@ -77,7 +79,7 @@

        Known problems

        Minor bug fixes

          -
        • +
        diff --git a/examples/PET_simulation/scatter_template.hs b/examples/PET_simulation/scatter_template.hs index 9929fc413c..e8086c80fd 100644 --- a/examples/PET_simulation/scatter_template.hs +++ b/examples/PET_simulation/scatter_template.hs @@ -32,10 +32,10 @@ Number of detectors per ring := 64 Inner ring diameter (cm) := 88.62 Average depth of interaction (cm) := 0.84 Distance between rings (cm) := 1.962 -Default bin size (cm) := -1 +Default bin size (cm) := -1 ; unused as not arc-correcting View offset (degrees) := 0 -Maximum number of non-arc-corrected bins := 0 -Default number of arc-corrected bins := 0 +Maximum number of non-arc-corrected bins := 35 +Default number of arc-corrected bins := 0 ; unused as not arc-correcting Number of blocks per bucket in transaxial direction := 0 Number of blocks per bucket in axial direction := 0 Number of crystals per block in axial direction := 0 diff --git a/recon_test_pack/scatter_cylinder.hs b/recon_test_pack/scatter_cylinder.hs index 6f8ebc079a..25d78514a1 100644 --- a/recon_test_pack/scatter_cylinder.hs +++ b/recon_test_pack/scatter_cylinder.hs @@ -34,7 +34,7 @@ Average depth of interaction (cm) := 0.84 Distance between rings (cm) := 1.962 Default bin size (cm) := -1 View offset (degrees) := 0 -Maximum number of non-arc-corrected bins := 0 +Maximum number of non-arc-corrected bins := 35 Default number of arc-corrected bins := 0 Number of blocks per bucket in transaxial direction := 1 Number of blocks per bucket in axial direction := 1 From 8af448ba0594c22d225e25930be451a881252545 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Wed, 27 Dec 2023 16:54:00 +0000 Subject: [PATCH 386/509] minor changes in list-mode tests insert break such that the script doesn't continue with tests that will fail --- recon_test_pack/lm_to_projdata.par | 5 +++-- recon_test_pack/run_test_listmode_recon.sh | 5 ++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/recon_test_pack/lm_to_projdata.par b/recon_test_pack/lm_to_projdata.par index f2361ef1c7..f5b1c291c5 100644 --- a/recon_test_pack/lm_to_projdata.par +++ b/recon_test_pack/lm_to_projdata.par @@ -16,5 +16,6 @@ lm_to_projdata Parameters:= List event coordinates := 0 ; if you're short of RAM (i.e. a single projdata does not fit into memory), ; you can use this to process the list mode data in multiple passes. - num_segments_in_memory := -1 -End := +; num_segments_in_memory := 10 +End := + diff --git a/recon_test_pack/run_test_listmode_recon.sh b/recon_test_pack/run_test_listmode_recon.sh index 59999ca27c..b52df0e951 100755 --- a/recon_test_pack/run_test_listmode_recon.sh +++ b/recon_test_pack/run_test_listmode_recon.sh @@ -157,7 +157,7 @@ for TOForNOT in "nonTOF" "TOF"; do echo "=== Reconstruct listmode data without cache" export filename=my_output_t_lm_pr_seg${MAX_SEG_NUM}_${suffix} - SENS_lm=my_sens_t_lm_pr_seg${MAX_SEG_NUM}.hv + SENS_lm=my_sens_t_lm_pr_seg${MAX_SEG_NUM}_${suffix}.hv export cache=0 export recompute_cache=0 export recompute_sensitivity=1 @@ -169,6 +169,7 @@ for TOForNOT in "nonTOF" "TOF"; do echo "---- There were problems here!" ThereWereErrors=1; ErrorLogs="$ErrorLogs $logfile" + break fi echo "=== Reconstruct listmode data with cache and store it on disk" @@ -186,6 +187,7 @@ for TOForNOT in "nonTOF" "TOF"; do echo "---- There were problems here!" ThereWereErrors=1; ErrorLogs="$ErrorLogs $logfile" + break fi echo "=== Compare reconstructed images with and without caching LM file" @@ -212,6 +214,7 @@ for TOForNOT in "nonTOF" "TOF"; do echo "---- There were problems here!" ThereWereErrors=1; ErrorLogs="$ErrorLogs $logfile" + break fi echo "=== Compare reconstructed images without caching LM file and with loading cache from disk" From f017c9a49c40035c46e3667039ab47434ccb3fad Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Wed, 27 Dec 2023 16:55:57 +0000 Subject: [PATCH 387/509] upgrade STIR_VERSION to 6.0.0 had to fix some files accordingly --- CMakeLists.txt | 6 +++--- src/buildblock/extend_projdata.cxx | 2 +- ...LinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx | 2 +- src/swig/CMakeLists.txt | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2873ad6dc5..fc7b94e512 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -55,10 +55,10 @@ else() endif() ####### Set Version number etc -set(VERSION_MAJOR 5) -set(VERSION_MINOR 2) +set(VERSION_MAJOR 6) +set(VERSION_MINOR 0) set(VERSION_PATCH 0) -set(VERSION 050200) # only used in STIRConfig.h.in +set(VERSION 060000) # only used in STIRConfig.h.in and swig/CMakeLists.txt set(STIR_VERSION ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}) diff --git a/src/buildblock/extend_projdata.cxx b/src/buildblock/extend_projdata.cxx index 1258abd8e4..774a62e965 100644 --- a/src/buildblock/extend_projdata.cxx +++ b/src/buildblock/extend_projdata.cxx @@ -30,9 +30,9 @@ START_NAMESPACE_STIR +#if STIR_VERSION < 060000 namespace detail { -#if STIR_VERSION < 060000 /* This function takes symmetries in the sinogram space into account to find data in the negative segment if necessary. However, it needs testing if it would work for non-direct sinograms. diff --git a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx index bb1230eb53..94bd7c288e 100644 --- a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx +++ b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx @@ -349,9 +349,9 @@ PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBinproj_data_info_sptr = this->list_mode_data_sptr->get_proj_data_info_sptr()->create_shared_clone(); +#if STIR_VERSION < 060000 if (this->get_max_segment_num_to_process() < 0) this->set_max_ring_difference(this->max_ring_difference_num_to_process); else diff --git a/src/swig/CMakeLists.txt b/src/swig/CMakeLists.txt index 98acbdb3c8..6ebccab49b 100644 --- a/src/swig/CMakeLists.txt +++ b/src/swig/CMakeLists.txt @@ -25,7 +25,7 @@ if(BUILD_SWIG_PYTHON OR BUILD_SWIG_OCTAVE OR BUILD_SWIG_MATLAB) FIND_PACKAGE(SWIG 3.0 REQUIRED) INCLUDE("${SWIG_USE_FILE}") - SET(CMAKE_SWIG_FLAGS -DSTART_NAMESPACE_STIR=\"namespace stir {\" -DEND_NAMESPACE_STIR=\"}\" -DSTIR_DEPRECATED="" -DSTIR_TOF=1) + SET(CMAKE_SWIG_FLAGS -DSTART_NAMESPACE_STIR=\"namespace stir {\" -DEND_NAMESPACE_STIR=\"}\" -DSTIR_DEPRECATED="" -DSTIR_TOF=1 -DSTIR_VERSION=${VERSION}) # Manually add some flags (really should get this from elsewhere) if (HAVE_LLN_MATRIX) SET(CMAKE_SWIG_FLAGS ${CMAKE_SWIG_FLAGS} -DHAVE_LLN_MATRIX) From 3846284c46bbb7f8c2aaf518b206bd506571d082 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Thu, 28 Dec 2023 11:40:50 +0000 Subject: [PATCH 388/509] minor corrections in GATE test files - updated README (was still referring to test_root_view_offset) - needed a small Python fix. Also changed label strategy to be clearer. - --- .../ROOT_STIR_consistency/.gitignore | 1 + .../debug_consistency_with_root.py | 18 ++++++++--------- .../ROOT_STIR_consistency/README.md | 20 +++++++++++-------- 3 files changed, 22 insertions(+), 17 deletions(-) create mode 100644 examples/ROOT_files/ROOT_STIR_consistency/.gitignore diff --git a/examples/ROOT_files/ROOT_STIR_consistency/.gitignore b/examples/ROOT_files/ROOT_STIR_consistency/.gitignore new file mode 100644 index 0000000000..72b44edd97 --- /dev/null +++ b/examples/ROOT_files/ROOT_STIR_consistency/.gitignore @@ -0,0 +1 @@ +pretest_output/ diff --git a/examples/ROOT_files/ROOT_STIR_consistency/DebugScripts/debug_consistency_with_root.py b/examples/ROOT_files/ROOT_STIR_consistency/DebugScripts/debug_consistency_with_root.py index 82b0cb163e..ef06e73959 100644 --- a/examples/ROOT_files/ROOT_STIR_consistency/DebugScripts/debug_consistency_with_root.py +++ b/examples/ROOT_files/ROOT_STIR_consistency/DebugScripts/debug_consistency_with_root.py @@ -87,15 +87,15 @@ def point_cloud_3D(data_handler): ax = fig.add_subplot(projection='3d') # Plot all the points (intensity increases for multiple points) - ax.scatter(data_handler.voxel_coords[:, 0], data_handler.voxel_coords[:, 1], data_handler.voxel_coords[:, 2]) + ax.scatter(data_handler.voxel_coords[:, 0], data_handler.voxel_coords[:, 1], data_handler.voxel_coords[:, 2], label='Most Likely Positions') # Plot the original point and tolerance ox = data_handler.original_coord[0] oy = data_handler.original_coord[1] oz = data_handler.original_coord[2] tol = data_handler.tolerance - ax.plot(ox, oy, oz, c='r', marker='o') - # plot tolerence around original point + ax.plot(ox, oy, oz, c='r', marker='o', label='Origin and Tolerance') + # plot tolerance around original point ax.plot([ox + tol, ox - tol], [oy, oy], [oz, oz], c='r', marker="_", label='_nolegend_') ax.plot([ox, ox], [oy + tol, oy - tol], [oz, oz], c='r', marker="_", label='_nolegend_') ax.plot([ox, ox], [oy, oy], [oz + tol, oz - tol], c='r', marker="_", label='_nolegend_') @@ -107,7 +107,7 @@ def point_cloud_3D(data_handler): xerror = np.std(data_handler.voxel_coords[:, 0]) yerror = np.std(data_handler.voxel_coords[:, 1]) zerror = np.std(data_handler.voxel_coords[:, 2]) - ax.plot(fx, fy, fz, linestyle="None", marker="o", c='g') + ax.plot(fx, fy, fz, linestyle="None", marker="o", c='g', label='Mean coords and stddev') ax.plot([fx + xerror, fx - xerror], [fy, fy], [fz, fz], marker="_", c='g', label='_nolegend_') ax.plot([fx, fx], [fy + yerror, fy - yerror], [fz, fz], marker="_", c='g', label='_nolegend_') ax.plot([fx, fx], [fy, fy], [fz + zerror, fz - zerror], marker="_", c='g', label='_nolegend_') @@ -115,7 +115,7 @@ def point_cloud_3D(data_handler): ax.set_xlabel('x (mm)') ax.set_ylabel('y (mm)') ax.set_zlabel('z (mm)') - ax.legend(['Voxel Positions', 'Origin and Tolerance', 'Mean coords and stddev']) + ax.legend() plt.show() @@ -173,7 +173,7 @@ def point_cloud_3D_all(dict_of_data_handlers): plt.show() -def __extract_data_from_csv_file(filename): +def extract_data_from_csv_file(filename): """ Loads data from a csv file and returns a numpy array containing the data. Assumes first column is the row's key. @@ -222,7 +222,7 @@ def __init__(self, filename): print(f"Loading data: {self.filename}") # Extract the original coordinate and voxel coordinates as 2D numpy arrays - self.original_coord, self.voxel_coords, self.tolerance = __extract_data_from_csv_file(filename) + self.original_coord, self.voxel_coords, self.tolerance = extract_data_from_csv_file(filename) if self.tolerance is None: raise Exception("No tolerance information found in file") @@ -387,8 +387,8 @@ def TOF_evaluation(filename_prefix, file_extension=".csv"): # Main Script # ===================================================================================================== def main(): - print("\nUSAGE: After `make test` or `test_view_offset_GATE` has been run,\n" - "run `debug_view_offset_consistency` from `ROOT_STIR_consistency` directory or input that directory as an " + print("\nUSAGE: After `make test` or `test_consistency_with_GATE` has been run,\n" + "run `debug_consistency_with_root.py` from `ROOT_STIR_consistency` directory or input that directory as an " "argument.\n") # Optional argument to set the directory of the output of the test_consistency_with_root CTest. diff --git a/examples/ROOT_files/ROOT_STIR_consistency/README.md b/examples/ROOT_files/ROOT_STIR_consistency/README.md index 476fa0f7d5..e2798f3681 100644 --- a/examples/ROOT_files/ROOT_STIR_consistency/README.md +++ b/examples/ROOT_files/ROOT_STIR_consistency/README.md @@ -8,14 +8,17 @@ See STIR/LICENSE.txt for details These files were included to : * Test non-TOF ROOT and STIR consistency, particularly the rotation. -* Test the TOF STIR implementation was correct (NOT YET IMPLEMENTED). +* Test the TOF STIR implementation is correct. + +See src/recon_test/test_consistency_with_GATE.cxx. This test is run +automatically when using ctest. Directories ----- -- `SourceFiles/`: Contains 8 `generate_image` parameter files and GATE macro files for the emission source positions. One pair of files for each test. +- `SourceFiles/`: Contains 8 `generate_image` parameter files and GATE macro files for the emission source positions. One pair of files for each simulation. - `Gate_macros/`: Contains the GATE macro files for generating the data. -- `DebugScripts`: Contains scripts for better understanding the tests. +- `DebugScripts/`: Contains scripts for better understanding the tests. FILES @@ -30,14 +33,15 @@ ______ Methodology ---- - 1. Generate the ROOT data. - 1. Run `./run_pretest_script.sh` in the terminal to generate the ROOT files (requires Gate) for different point sources, or - 2. Download the ROOT data and proceed without Gate simulation. + 1. Get the ROOT data: either + - Run `./run_pretest_script.sh` in the terminal to generate the ROOT files (requires Gate) for different point sources, or + - Download the ROOT data and proceed without Gate simulation. This is done + by ctest when the STIR build was configured with `DOWNLOAD_ZENODO_TEST_DATA=ON`. - 2. Run the STIR test: `src/recon_test/test_view_offset_root`. + 2. Run the STIR test: `src/recon_test/test_consistency_with_GATE` (Best via ctest). This test should tell you whether it failed or not by testing if the LOR passes by, or close to, the original point source position. - 3. Run the python scripts in `DebugScripts` to better understand erros and to give a more in-depth analysis. + 3. Run the python scripts in `DebugScripts` to better understand errors and to give a more in-depth analysis. _____ From 6b7a85e12121322dfa83506b60899019eaba5f82 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Fri, 29 Dec 2023 23:22:53 +0000 Subject: [PATCH 389/509] tell git *.l and *.root are binary --- .gitattributes | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitattributes b/.gitattributes index 7d845e399e..dc8338a6ac 100644 --- a/.gitattributes +++ b/.gitattributes @@ -12,6 +12,8 @@ *.v -text -diff *.s -text -diff *.scn -text -diff +*.l -text -diff +*.root -text -diff *.gz -text -diff *.nii -text -diff *.sh eol=lf From 06bb31d7a3b68d0403485983c57d875bc2c2bb99 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sun, 31 Dec 2023 00:39:23 +0000 Subject: [PATCH 390/509] use ROOT tests with TOF mashing = 1 Motivation is twofold: - it doesn't make a lot of sense to have TOF mashing keyword in a ROOT file - run_root_GATE.sh failed due to events that have very large time diff in the test ROOT file. ( see https://github.com/NikEfth/STIR/pull/52#issuecomment-1872631113 for details) --- recon_test_pack/root_header.hroot | 6 ++---- recon_test_pack/template_for_ROOT_scanner.hs | 6 +++--- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/recon_test_pack/root_header.hroot b/recon_test_pack/root_header.hroot index a36d11838d..390167acee 100644 --- a/recon_test_pack/root_header.hroot +++ b/recon_test_pack/root_header.hroot @@ -11,12 +11,10 @@ Default bin size (cm) := 0.208626 Maximum number of non-arc-corrected bins := 501 Default number of arc-corrected bins := 501 View offset (degrees) := 0 -Number of TOF time bins := 411 -Size of timing bin (ps) := 10.00 +Number of TOF time bins := 5 +Size of timing bin (ps) := 820.00 Timing resolution (ps) := 400.0 -%TOF mashing factor:= 82 - GATE scanner type := GATE_Cylindrical_PET GATE_Cylindrical_PET Parameters := diff --git a/recon_test_pack/template_for_ROOT_scanner.hs b/recon_test_pack/template_for_ROOT_scanner.hs index a276a6f39a..997e57ac22 100644 --- a/recon_test_pack/template_for_ROOT_scanner.hs +++ b/recon_test_pack/template_for_ROOT_scanner.hs @@ -27,7 +27,7 @@ matrix axis label [1] := tangential coordinate !matrix size [1] := 501 minimum ring difference per segment := { -3,-2,-1,0,1,2,3} maximum ring difference per segment := { -3,-2,-1,0,1,2,3} -%TOF mashing factor:= 82 +%TOF mashing factor := 1 Scanner parameters:= Scanner type := ROOT_demo_scanner Number of rings := 4 @@ -41,8 +41,8 @@ Maximum number of non-arc-corrected bins := 501 Default number of arc-corrected bins := 501 Energy resolution := 0 Reference energy (in keV) := 511 -Number of TOF time bins :=411 -Size of timing bin (ps) := 10 +Number of TOF time bins :=5 +Size of timing bin (ps) := 820 Timing resolution (ps) := 400 Number of blocks per bucket in transaxial direction := 1 Number of blocks per bucket in axial direction := 1 From 52808c3a5b2c201df81c7172e67d47e315a634cc Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Fri, 6 Oct 2023 11:55:13 +0100 Subject: [PATCH 391/509] fix TOF swap in find_cartesian_coordinates_given_scanner_coordinates --- src/buildblock/ProjDataInfoCylindricalNoArcCorr.cxx | 4 +++- .../stir/listmode/CListEventScannerWithDiscreteDetectors.inl | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/buildblock/ProjDataInfoCylindricalNoArcCorr.cxx b/src/buildblock/ProjDataInfoCylindricalNoArcCorr.cxx index f3cbe6e77d..db0f5573cf 100644 --- a/src/buildblock/ProjDataInfoCylindricalNoArcCorr.cxx +++ b/src/buildblock/ProjDataInfoCylindricalNoArcCorr.cxx @@ -501,6 +501,7 @@ find_cartesian_coordinates_given_scanner_coordinates (CartesianCoordinate3Dget_num_detectors_per_ring(); int d1, d2, r1, r2; + int tpos = timing_pos_num; this->initialise_det1det2_to_uncompressed_view_tangpos_if_not_done_yet(); @@ -510,6 +511,7 @@ find_cartesian_coordinates_given_scanner_coordinates (CartesianCoordinate3D:: get_LOR() const { LORAs2Points lor; - const bool swap = this->get_delta_time() < 0.F; + const bool swap = true;// this->get_delta_time() < 0.F; // provide somewhat shorter names for the 2 coordinates, taking swap into account CartesianCoordinate3D& coord_1 = swap ? lor.p2() : lor.p1(); CartesianCoordinate3D& coord_2 = swap ? lor.p1() : lor.p2(); From 64962b6fc4a8eadbff0be090ef4a15eb5e93fc82 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Fri, 6 Oct 2023 11:55:38 +0100 Subject: [PATCH 392/509] correct typos in test Two lines were copy-paste, and therefore didn't test anything extra. --- src/test/test_time_of_flight.cxx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/test_time_of_flight.cxx b/src/test/test_time_of_flight.cxx index ae53efceaf..d172a1fdbb 100644 --- a/src/test/test_time_of_flight.cxx +++ b/src/test/test_time_of_flight.cxx @@ -310,12 +310,12 @@ void TOF_Tests::test_CListEventROOT() if (det_pos_swapped.timing_pos() == det_pos.timing_pos()) { check_if_equal(det_pos_swapped.pos1(), det_pos.pos1(), "CListEventROOT: get_detection_position with swapped detectors: equal timing_pos, but different pos1"); - check_if_equal(det_pos_swapped.pos1(), det_pos.pos1(), "CListEventROOT: get_detection_position with swapped detectors: equal timing_pos, but different pos1"); + check_if_equal(det_pos_swapped.pos2(), det_pos.pos2(), "CListEventROOT: get_detection_position with swapped detectors: equal timing_pos, but different pos2"); } else if (det_pos_swapped.timing_pos() == -det_pos.timing_pos()) { - check_if_equal(det_pos_swapped.pos2(), det_pos.pos1(), "CListEventROOT: get_detection_position with swapped detectors: equal timing_pos, but different pos1"); - check_if_equal(det_pos_swapped.pos2(), det_pos.pos1(), "CListEventROOT: get_detection_position with swapped detectors: equal timing_pos, but different pos1"); + check_if_equal(det_pos_swapped.pos2(), det_pos.pos1(), "CListEventROOT: get_detection_position with swapped detectors: opposite timing_pos, but different pos1/2"); + check_if_equal(det_pos_swapped.pos1(), det_pos.pos2(), "CListEventROOT: get_detection_position with swapped detectors: opposite timing_pos, but different pos1/2"); } else { From d458d3645f035e3bf5bdecb1a6957143ba1def4a Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sun, 31 Dec 2023 09:05:15 +0000 Subject: [PATCH 393/509] set VERSION.txt to 6.0.0 as well --- VERSION.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION.txt b/VERSION.txt index 91ff57278e..4196253011 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -5.2.0 +6.0.0-pre From 809e59dee37d6551a94eecc6702657ba3a0dde27 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sun, 31 Dec 2023 09:05:44 +0000 Subject: [PATCH 394/509] removed script as obsolete and not working anyway --- recon_test_pack/run_test_time_of_flight.sh | 114 --------------------- 1 file changed, 114 deletions(-) delete mode 100755 recon_test_pack/run_test_time_of_flight.sh diff --git a/recon_test_pack/run_test_time_of_flight.sh b/recon_test_pack/run_test_time_of_flight.sh deleted file mode 100755 index 73fff7a628..0000000000 --- a/recon_test_pack/run_test_time_of_flight.sh +++ /dev/null @@ -1,114 +0,0 @@ -#! /bin/sh -# A script to check to see if Time Of Flight data are binned and used properly -# -# Copyright (C) 2016, University of Leeds -# Copyright (C) 2017, University of Hull -# 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 -# -# Author Nikos Efthimiou -# - -# Scripts should exit with error code when a test fails: -if [ -n "$TRAVIS" ]; then - # The code runs inside Travis - set -e -fi - -echo "This test is currently broken. Do not use." 1>&2 -# Things that are wrong -# - relies on LmToProjData.cxx to be compiled with a test function (which shouldn't be there) -# - relies on output of list_projdata_info by TOF bin etc, which isn't the case anymore - -exit 1 - -echo This script should work with STIR version '>'6.0. If you have -echo a later version, you might have to update your test pack. -echo Please check the web site. -echo - -if [ $# -eq 1 ]; then - echo "Prepending $1 to your PATH for the duration of this script." - PATH=$1:$PATH -fi - -# first need to set this to the C locale, as this is what the STIR utilities use -# otherwise, awk might interpret floating point numbers incorrectly -LC_ALL=C -export LC_ALL - -echo "=== create template sinogram. We'll use a test_scanner which is small and -has TOF info" -template_sino=my_test_scanner_template.hs -cat > my_input.txt < my_create_${template_sino}.log 2>&1 -if [ $? -ne 0 ]; then - echo "ERROR running create_projdata_template. Check my_create_${template_sino}.log"; exit 1; -fi - -export INPUT_ROOT_FILE=test_PET_GATE.root -export EXCLUDE_RANDOM=1 -export EXCLUDE_SCATTERED=1 - -INPUT=root_header.hroot TEMPLATE=$template_sino OUT_PROJDATA_FILE=my_tof_sinogram lm_to_projdata --test_timing_positions lm_to_projdata.par > my_write_TOF_values_${template_sino}.log 2>&1 - -if [ $? -ne 0 ]; then - echo "ERROR running lm_to_projdata --test_timing_positions. Check my_write_TOF_values_${template_sino}.log"; exit 1; -fi - -echo "Comparing values in TOF sinogram ..." -list_projdata_info --all my_tof_sinogram_f179g1d0b0.hs > my_sino_values_$template_sino.log 2>&1 -if [ $? -ne 0 ]; then - echo "ERROR running list_projdata_info. Check my_sino_values_$template_sino.log"; - exit 1; -fi - - -TOF_bins=$(grep 'Total number of timing positions' my_sino_values_$template_sino.log | awk -F ':' '{ print $2 }') -echo "Total number of TOF bins:" $TOF_bins - -Timming_Locations=$(grep 'Timing location' my_sino_values_$template_sino.log | awk -F ':' '{ print $2 }') -echo "Timming_Locations:" $Timming_Locations - -Data_mins=$(grep 'Data min' my_sino_values_$template_sino.log | awk -F ':' '{ print $2 }') -echo "Data mins:" $Data_mins - -Data_maxs=$(grep 'Data max' my_sino_values_$template_sino.log | awk -F ':' '{ print $2 }') -echo "Data maxs:" $Data_maxs - -for i in $(seq 5) -do - if [ $(( $((i-1 - TOF_bins/2)) - Data_mins[i])) -ne 0 ]; then - echo "Wrong values in TOF sinogram. Error. $(( $(($((i-1)) -TOF_bins/2)) - Data_mins[i]))" - exit 1 - fi -done - - -echo -echo '--------------- End of Time-Of-Flight tests -------------' -echo -echo "Everything seems to be fine !" -echo 'You could remove all generated files using "rm -f my_* *.log"' -exit 0 - From 910490f812864f2a3c41fc1dbe8906a4a6a490ad Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sun, 31 Dec 2023 09:08:16 +0000 Subject: [PATCH 395/509] Siemens Vision to Vision 600 names were not specific enough --- src/buildblock/Scanner.cxx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/buildblock/Scanner.cxx b/src/buildblock/Scanner.cxx index fe7062a41b..0844572957 100644 --- a/src/buildblock/Scanner.cxx +++ b/src/buildblock/Scanner.cxx @@ -259,10 +259,10 @@ Scanner::Scanner(Type scanner_type) // Siemens uses 50 views (why??) // Vision number of TOF bins seems always 33. However, the sinogram headers say this is with TOF mashing factor 8, // so we create the scanner with 33*8 possible TOF bins, and rely on InterfileHeaderSiemens - // to create a ProjDataInfo with mashin g8 + // to create a ProjDataInfo with mashing 8 set_params( Siemens_Vision_600, // type - string_list("Siemens Vision", "Vision", "1208"), // names + string_list("Siemens Vision 600", "Vision 600", "1208"), // names 80, // rings 520, // max n non-arc-corr bins 520, // default n arc-corr bins From c7a2b156b09f616ca5d5228a905abbd8fad20112 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sun, 31 Dec 2023 20:57:24 +0000 Subject: [PATCH 396/509] removed full_event from CListRecord it was introduced for ROOT only, and is now no longer used. --- src/include/stir/listmode/CListRecord.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/include/stir/listmode/CListRecord.h b/src/include/stir/listmode/CListRecord.h index 15168d8847..7a47a197f0 100644 --- a/src/include/stir/listmode/CListRecord.h +++ b/src/include/stir/listmode/CListRecord.h @@ -64,15 +64,15 @@ class CListEvent : public ListEvent }; /*-coincidence event*/ +//! Class for records in a PET list mode file +/*! \ingroup listmode + + Currently identical to ListRecord. Maybe this class will be removed. +*/ class CListRecord : public ListRecord { public: - - //! Used in TOF reconstruction to get both the geometric and the timing - //! component of the event - virtual void full_event(Bin&, const ProjDataInfo&) const - {error("CListRecord::full_event() is implemented only for records which " - "hold timing and spatial information.");} + }; class CListRecordWithGatingInput : public CListRecord From dd7cca8ca03656c49751b7b2f3986aa267fe3e75 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Mon, 1 Jan 2024 17:31:28 +0000 Subject: [PATCH 397/509] Interfile parsing fixes for TOF and clarifications - If TOF data (i.e. 5 dimensions), default to TOF mashing factor 1 - Check if number of TOF bins matches constructed proj_data_info - list_projdata_info outputs TOF mashing factor - calculate_attenuation_factors: nicer message when template is TOF --- recon_test_pack/template_for_ROOT_scanner.hs | 2 +- src/IO/InterfileHeader.cxx | 19 +++++++++++++++++-- src/buildblock/ProjDataInfo.cxx | 9 ++------- .../calculate_attenuation_coefficients.cxx | 6 +++++- 4 files changed, 25 insertions(+), 11 deletions(-) diff --git a/recon_test_pack/template_for_ROOT_scanner.hs b/recon_test_pack/template_for_ROOT_scanner.hs index 997e57ac22..015989b0d2 100644 --- a/recon_test_pack/template_for_ROOT_scanner.hs +++ b/recon_test_pack/template_for_ROOT_scanner.hs @@ -27,7 +27,7 @@ matrix axis label [1] := tangential coordinate !matrix size [1] := 501 minimum ring difference per segment := { -3,-2,-1,0,1,2,3} maximum ring difference per segment := { -3,-2,-1,0,1,2,3} -%TOF mashing factor := 1 +;%TOF mashing factor := 1 Scanner parameters:= Scanner type := ROOT_demo_scanner Number of rings := 4 diff --git a/src/IO/InterfileHeader.cxx b/src/IO/InterfileHeader.cxx index 4844694788..a36c86d6be 100644 --- a/src/IO/InterfileHeader.cxx +++ b/src/IO/InterfileHeader.cxx @@ -2,7 +2,7 @@ Copyright (C) 2000 PARAPET partners Copyright (C) 2000 - 2009-04-30, Hammersmith Imanet Ltd Copyright (C) 2011-07-01 - 2012, Kris Thielemans - Copyright (C) 2013, 2016, 2018, 2020 University College London + Copyright (C) 2013, 2016, 2018, 2020, 2023 University College London Copyright 2017 ETH Zurich, Institute of Particle Physics and Astrophysics This file is part of STIR. @@ -32,6 +32,7 @@ #include "stir/info.h" #include "stir/warning.h" #include "stir/error.h" +#include #include #include #include "stir/ProjDataInfoBlocksOnCylindricalNoArcCorr.h" @@ -613,7 +614,7 @@ InterfilePDFSHeader::InterfilePDFSHeader() add_key("Reference energy (in keV)", &reference_energy); - tof_mash_factor=-1; + tof_mash_factor = 1; add_key("%TOF mashing factor", &tof_mash_factor); max_num_timing_poss = -1; @@ -694,6 +695,13 @@ int InterfilePDFSHeader::find_storage_order() return true; } + if (num_dimensions == 4) + { + // non-TOF + num_timing_poss = 1; + tof_mash_factor = 0; + } + if (matrix_labels[0] != "tangential coordinate") { // use error message with index [1] as that is what the user sees. @@ -723,6 +731,8 @@ int InterfilePDFSHeader::find_storage_order() num_rings_per_segment = matrix_size[1]; #endif } + else + error("Interfile header parsing: currently need 'matrix axis label [5] := timing positions' for TOF data"); } else { @@ -1544,6 +1554,11 @@ bool InterfilePDFSHeader::post_processing() data_info_sptr->get_sampling_in_s(Bin(0,0,0,0))/10.); } } + if (data_info_sptr->get_num_tof_poss() != num_timing_poss) + error(boost::format("Interfile header parsing with TOF: inconsistency between number of TOF bins in data (%d), " + "TOF mashing factor (%d) and max number of TOF bins in scanner info (%d)") + % num_timing_poss % tof_mash_factor % scanner_sptr_from_file->get_max_num_timing_poss()); + //cerr << data_info_sptr->parameter_info() << endl; // Set the bed position diff --git a/src/buildblock/ProjDataInfo.cxx b/src/buildblock/ProjDataInfo.cxx index 9f6ab2542a..bfbc6af9c8 100644 --- a/src/buildblock/ProjDataInfo.cxx +++ b/src/buildblock/ProjDataInfo.cxx @@ -325,20 +325,15 @@ string ProjDataInfo::parameter_info() const { -#ifdef BOOST_NO_STRINGSTREAM - // dangerous for out-of-range, but 'old-style' ostrstream seems to need this - char str[30000]; - ostrstream s(str, 30000); -#else std::ostringstream s; -#endif s << scanner_ptr->parameter_info(); s << "\n"; s << "start vertical bed position (mm) := " << get_bed_position_vertical() << endl; s << "start horizontal bed position (mm) := " << get_bed_position_horizontal() << endl; - s << "\nNumber of TOF positions in data: " << get_num_tof_poss() << '\n'; + s << "\nTOF mashing factor in data: " << get_tof_mash_factor() << '\n'; + s << "Number of TOF positions in data: " << get_num_tof_poss() << '\n'; s << "\nSegment_num range: (" << get_min_segment_num() << ", " << get_max_segment_num() << ")\n"; diff --git a/src/utilities/calculate_attenuation_coefficients.cxx b/src/utilities/calculate_attenuation_coefficients.cxx index 0cbe3afbe9..b1ea2c69d7 100644 --- a/src/utilities/calculate_attenuation_coefficients.cxx +++ b/src/utilities/calculate_attenuation_coefficients.cxx @@ -43,6 +43,10 @@ The attenuation_image has to contain an estimate of the mu-map for the image. It will be used to estimate attenuation factors as exp(-forw_proj(*attenuation_image_ptr)). + \par Note + + Output is non-TOF, even if a TOF template is used. + \warning attenuation image data are supposed to be in units cm^-1. Reference: water has mu .096 cm^-1. @@ -158,7 +162,7 @@ main (int argc, char * argv[]) if (template_proj_data_ptr->get_proj_data_info_sptr()->is_tof_data()) { - warning("The scanner template provided contains timing information. The calculation of the attenuation coefficients will not take them into consideration.\n"); + info("The scanner template provided contains TOF information. The calculation of the attenuation coefficients will be non-TOF anyway."); } const std::string output_file_name = argv[1]; From 386a797be6e0e3bda71c26d615669dd5de8033de Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Mon, 1 Jan 2024 17:40:15 +0000 Subject: [PATCH 398/509] reinstate check on axial_pos range --- src/recon_buildblock/BinNormalisationFromProjData.cxx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/recon_buildblock/BinNormalisationFromProjData.cxx b/src/recon_buildblock/BinNormalisationFromProjData.cxx index 7af299da98..bb5dd985bd 100644 --- a/src/recon_buildblock/BinNormalisationFromProjData.cxx +++ b/src/recon_buildblock/BinNormalisationFromProjData.cxx @@ -102,7 +102,15 @@ set_up(const shared_ptr& exam_info_sptr, const shared_ptr= proj) && (norm_proj.get_min_tangential_pos_num() ==proj.get_min_tangential_pos_num())&& (norm_proj.get_max_tangential_pos_num() ==proj.get_max_tangential_pos_num()); - + + for (int segment_num=proj.get_min_segment_num(); + ok && segment_num<=proj.get_max_segment_num(); + ++segment_num) + { + ok = + norm_proj.get_min_axial_pos_num(segment_num) == proj.get_min_axial_pos_num(segment_num) && + norm_proj.get_max_axial_pos_num(segment_num) == proj.get_max_axial_pos_num(segment_num); + } if (ok) return Succeeded::yes; else From b0fac1e219b73cb72c6322d8a70dc81f2d7f5f69 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Mon, 1 Jan 2024 18:23:39 +0000 Subject: [PATCH 399/509] clean-up of now obsolete TOF functionality --- .../BackProjectorByBinUsingProjMatrixByBin.h | 22 ++++++----------- ...BackProjectorByBinUsingProjMatrixByBin.cxx | 24 +++---------------- 2 files changed, 10 insertions(+), 36 deletions(-) diff --git a/src/include/stir/recon_buildblock/BackProjectorByBinUsingProjMatrixByBin.h b/src/include/stir/recon_buildblock/BackProjectorByBinUsingProjMatrixByBin.h index 9f34bd6507..6d9ea61389 100644 --- a/src/include/stir/recon_buildblock/BackProjectorByBinUsingProjMatrixByBin.h +++ b/src/include/stir/recon_buildblock/BackProjectorByBinUsingProjMatrixByBin.h @@ -1,7 +1,3 @@ -// -// - - #ifndef _BackProjectorByBinUsingProjMatrixByBin_ #define _BackProjectorByBinUsingProjMatrixByBin_ @@ -65,7 +61,7 @@ class BackProjectorByBinUsingProjMatrixByBin: virtual void set_up( const shared_ptr& proj_data_info_ptr, const shared_ptr >& density_info_ptr // TODO should be Info only - ); + ) override; const DataSymmetriesForViewSegmentNumbers * get_symmetries_used() const; @@ -73,30 +69,26 @@ class BackProjectorByBinUsingProjMatrixByBin: virtual void actual_back_project(DiscretisedDensity<3,float>& image, const RelatedViewgrams&, const int min_axial_pos_num, const int max_axial_pos_num, - const int min_tangential_pos_num, const int max_tangential_pos_num); + const int min_tangential_pos_num, const int max_tangential_pos_num) override; shared_ptr & get_proj_matrix_sptr(){ return proj_matrix_ptr ;} - void enable_tof(ProjMatrixElemsForOneBin* ); - - BackProjectorByBinUsingProjMatrixByBin* clone() const; + BackProjectorByBinUsingProjMatrixByBin* clone() const override; protected: shared_ptr proj_matrix_ptr; + // currently not exposed, but leaving this ine for the future void actual_back_project(DiscretisedDensity<3,float>& image, const Bin& bin); private: - virtual void set_defaults(); - virtual void initialise_keymap(); - virtual bool post_processing(); - - ProjMatrixElemsForOneBin* tof_row; - + virtual void set_defaults() override; + virtual void initialise_keymap() override; + virtual bool post_processing() override; }; diff --git a/src/recon_buildblock/BackProjectorByBinUsingProjMatrixByBin.cxx b/src/recon_buildblock/BackProjectorByBinUsingProjMatrixByBin.cxx index 06e418dd61..b8edeb6b5e 100644 --- a/src/recon_buildblock/BackProjectorByBinUsingProjMatrixByBin.cxx +++ b/src/recon_buildblock/BackProjectorByBinUsingProjMatrixByBin.cxx @@ -246,27 +246,9 @@ BackProjectorByBinUsingProjMatrixByBin:: actual_back_project(DiscretisedDensity<3,float>& image, const Bin& bin) { - if (proj_matrix_ptr->is_cache_enabled() /*&& !tof_enabled*/) - { - ProjMatrixElemsForOneBin proj_matrix_row; - proj_matrix_ptr->get_proj_matrix_elems_for_one_bin(proj_matrix_row, bin); - proj_matrix_row.back_project(image, bin); - } - else if (proj_matrix_ptr->is_cache_enabled()/* && tof_enabled*/) - { - tof_row->back_project(image, bin); - } - else - error("BackProjectorByBinUsingProjMatrixByBin: Symmetries should be handled by ProjMatrix. Abort. "); - -} - -void -BackProjectorByBinUsingProjMatrixByBin:: -enable_tof(ProjMatrixElemsForOneBin * for_row) -{ - tof_row = for_row; -// tof_enabled = true; + ProjMatrixElemsForOneBin proj_matrix_row; + proj_matrix_ptr->get_proj_matrix_elems_for_one_bin(proj_matrix_row, bin); + proj_matrix_row.back_project(image, bin); } BackProjectorByBinUsingProjMatrixByBin* From b068bc8565c61ae0604866f059c672b5ea0dc783 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Mon, 1 Jan 2024 19:23:49 +0000 Subject: [PATCH 400/509] removed unused/unimplemented method --- src/include/stir/recon_buildblock/BackProjectorByBin.h | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/include/stir/recon_buildblock/BackProjectorByBin.h b/src/include/stir/recon_buildblock/BackProjectorByBin.h index 0c0aab8c0b..4b9c694042 100644 --- a/src/include/stir/recon_buildblock/BackProjectorByBin.h +++ b/src/include/stir/recon_buildblock/BackProjectorByBin.h @@ -188,13 +188,6 @@ void back_project(const RelatedViewgrams&, shared_ptr _proj_data_info_sptr; private: - void do_segments(DiscretisedDensity<3,float>& image, - const ProjData& proj_data_org, - const int start_segment_num, const int end_segment_num, - const int start_axial_pos_num, const int end_axial_pos_num, - const int start_tang_pos_num,const int end_tang_pos_num, - const int start_view, const int end_view, - const int start_timing_pos_num = 0, const int end_timing_pos_num = 0); #ifdef STIR_OPENMP //! A vector of back projected images that will be used with openMP. There will be as many images as openMP threads From 0e5b7b380a1f1e9975ad798711098e6294f1e283 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Mon, 1 Jan 2024 22:31:22 +0000 Subject: [PATCH 401/509] remove member that clashes with the one in the base class --- src/include/stir/listmode/CListModeDataECAT.h | 3 --- src/listmode_buildblock/CListModeDataECAT.cxx | 16 ++++------------ 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/src/include/stir/listmode/CListModeDataECAT.h b/src/include/stir/listmode/CListModeDataECAT.h index c40f70aa7f..b9817aaa00 100644 --- a/src/include/stir/listmode/CListModeDataECAT.h +++ b/src/include/stir/listmode/CListModeDataECAT.h @@ -78,9 +78,6 @@ class CListModeDataECAT : public CListModeData /*! \todo this might depend on the acquisition parameters */ virtual bool has_delayeds() const { return true; } - virtual - shared_ptr get_proj_data_info_sptr() const; - private: std::string listmode_filename_prefix; mutable unsigned int current_lm_file; diff --git a/src/listmode_buildblock/CListModeDataECAT.cxx b/src/listmode_buildblock/CListModeDataECAT.cxx index f0e3fa4f52..6029cc512b 100644 --- a/src/listmode_buildblock/CListModeDataECAT.cxx +++ b/src/listmode_buildblock/CListModeDataECAT.cxx @@ -115,13 +115,13 @@ CListModeDataECAT(const std::string& listmode_filename_prefix) /* arc_correction*/false)); this->set_proj_data_info_sptr( tmp ); - if ((get_proj_data_info_sptr()->get_scanner_ptr()->get_type() == Scanner::E966 && typeid(CListRecordT) != typeid(CListRecordECAT966)) || - (get_proj_data_info_sptr()->get_scanner_ptr()->get_type() == Scanner::E962 && typeid(CListRecordT) != typeid(CListRecordECAT962))) + if ((this->get_proj_data_info_sptr()->get_scanner_ptr()->get_type() == Scanner::E966 && typeid(CListRecordT) != typeid(CListRecordECAT966)) || + (this->get_proj_data_info_sptr()->get_scanner_ptr()->get_type() == Scanner::E962 && typeid(CListRecordT) != typeid(CListRecordECAT962))) { error("Data in %s is from a %s scanner, but reading with wrong type of CListModeData", - listmode_filename_prefix.c_str(), get_proj_data_info_sptr()->get_scanner_ptr()->get_name().c_str()); + listmode_filename_prefix.c_str(), this->get_proj_data_info_sptr()->get_scanner_ptr()->get_name().c_str()); } - else if (get_proj_data_info_sptr()->get_scanner_ptr()->get_type() != Scanner::E966 && get_proj_data_info_sptr()->get_scanner_ptr()->get_type() != Scanner::E962) + else if (this->get_proj_data_info_sptr()->get_scanner_ptr()->get_type() != Scanner::E966 && this->get_proj_data_info_sptr()->get_scanner_ptr()->get_type() != Scanner::E962) { error("CListModeDataECAT: Unsupported scanner in %s", listmode_filename_prefix.c_str()); } @@ -328,14 +328,6 @@ get_num_records() const #endif -template -shared_ptr -CListModeDataECAT:: -get_proj_data_info_sptr() const -{ - assert(!is_null_ptr(proj_data_info_sptr)); - return proj_data_info_sptr; -} // instantiations template class CListModeDataECAT; From 2bcdc3e7ddde0d7282bbd316094504aba520aca3 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Tue, 2 Jan 2024 08:13:28 +0000 Subject: [PATCH 402/509] reinstated deleting of arrays in SPECTUB destructor --- src/recon_buildblock/ProjMatrixByBinSPECTUB.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/recon_buildblock/ProjMatrixByBinSPECTUB.cxx b/src/recon_buildblock/ProjMatrixByBinSPECTUB.cxx index 01c5870385..1dd61a4203 100644 --- a/src/recon_buildblock/ProjMatrixByBinSPECTUB.cxx +++ b/src/recon_buildblock/ProjMatrixByBinSPECTUB.cxx @@ -702,7 +702,7 @@ ProjMatrixByBinSPECTUB::clone() const ProjMatrixByBinSPECTUB:: ~ProjMatrixByBinSPECTUB() { - // delete_UB_SPECT_arrays(); + delete_UB_SPECT_arrays(); } void From 005f70a23fb8d12272b0e361573ffc6b7f2c0605 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Tue, 2 Jan 2024 08:22:37 +0000 Subject: [PATCH 403/509] removed get_bin_ptr and made get_bin()/get_densel() return const& --- src/include/stir/recon_buildblock/ProjMatrixByBin.inl | 4 ++-- .../stir/recon_buildblock/ProjMatrixElemsForOneBin.h | 4 +--- .../stir/recon_buildblock/ProjMatrixElemsForOneBin.inl | 9 +-------- .../stir/recon_buildblock/ProjMatrixElemsForOneDensel.h | 2 +- .../recon_buildblock/ProjMatrixElemsForOneDensel.inl | 2 +- 5 files changed, 6 insertions(+), 15 deletions(-) diff --git a/src/include/stir/recon_buildblock/ProjMatrixByBin.inl b/src/include/stir/recon_buildblock/ProjMatrixByBin.inl index 88fa0a77b0..f9cb32c4ac 100644 --- a/src/include/stir/recon_buildblock/ProjMatrixByBin.inl +++ b/src/include/stir/recon_buildblock/ProjMatrixByBin.inl @@ -135,8 +135,8 @@ ProjMatrixByBin::apply_tof_kernel(ProjMatrixElemsForOneBin& probabilities) STIR_ Coordinate3D c(element_ptr->get_coords()); const float d2 = -inner_product(image_info_sptr->get_physical_coordinates_for_indices (c) - middle, diff_unit_vector); - const float low_dist = ((proj_data_info_sptr->tof_bin_boundaries_mm[probabilities.get_bin_ptr()->timing_pos_num()].low_lim - d2)); - const float high_dist = ((proj_data_info_sptr->tof_bin_boundaries_mm[probabilities.get_bin_ptr()->timing_pos_num()].high_lim - d2)); + const float low_dist = ((proj_data_info_sptr->tof_bin_boundaries_mm[probabilities.get_bin().timing_pos_num()].low_lim - d2)); + const float high_dist = ((proj_data_info_sptr->tof_bin_boundaries_mm[probabilities.get_bin().timing_pos_num()].high_lim - d2)); *element_ptr = ProjMatrixElemsForOneBin::value_type(c,element_ptr->get_value() * get_tof_value(low_dist, high_dist)); } diff --git a/src/include/stir/recon_buildblock/ProjMatrixElemsForOneBin.h b/src/include/stir/recon_buildblock/ProjMatrixElemsForOneBin.h index af33dc5bfd..ca6f5e8257 100644 --- a/src/include/stir/recon_buildblock/ProjMatrixElemsForOneBin.h +++ b/src/include/stir/recon_buildblock/ProjMatrixElemsForOneBin.h @@ -112,11 +112,9 @@ class ProjMatrixElemsForOneBin Succeeded check_state() const; //! get the bin coordinates corresponding to this row - inline Bin get_bin() const; + inline const Bin& get_bin() const; //! and set the bin coordinates inline void set_bin(const Bin&); - //! get a ref to the bin - inline Bin* get_bin_ptr(); //! functions for allowing iterator access inline iterator begin() ; diff --git a/src/include/stir/recon_buildblock/ProjMatrixElemsForOneBin.inl b/src/include/stir/recon_buildblock/ProjMatrixElemsForOneBin.inl index 946f571d49..e74e2cf9f2 100644 --- a/src/include/stir/recon_buildblock/ProjMatrixElemsForOneBin.inl +++ b/src/include/stir/recon_buildblock/ProjMatrixElemsForOneBin.inl @@ -25,20 +25,13 @@ START_NAMESPACE_STIR -Bin +const Bin& ProjMatrixElemsForOneBin:: get_bin() const { return bin; } -Bin* -ProjMatrixElemsForOneBin:: -get_bin_ptr() -{ - return &bin; -} - void ProjMatrixElemsForOneBin:: set_bin(const Bin& new_bin) diff --git a/src/include/stir/recon_buildblock/ProjMatrixElemsForOneDensel.h b/src/include/stir/recon_buildblock/ProjMatrixElemsForOneDensel.h index 4a28dc19f8..d3e09c5c64 100644 --- a/src/include/stir/recon_buildblock/ProjMatrixElemsForOneDensel.h +++ b/src/include/stir/recon_buildblock/ProjMatrixElemsForOneDensel.h @@ -104,7 +104,7 @@ class ProjMatrixElemsForOneDensel Succeeded check_state() const; //! get the Densel coordinates corresponding to this row - inline Densel get_densel() const; + inline const Densel& get_densel() const; //! and set the Densel coordinates inline void set_densel(const Densel&); diff --git a/src/include/stir/recon_buildblock/ProjMatrixElemsForOneDensel.inl b/src/include/stir/recon_buildblock/ProjMatrixElemsForOneDensel.inl index d0d3ce5cf3..1339f91257 100644 --- a/src/include/stir/recon_buildblock/ProjMatrixElemsForOneDensel.inl +++ b/src/include/stir/recon_buildblock/ProjMatrixElemsForOneDensel.inl @@ -22,7 +22,7 @@ START_NAMESPACE_STIR -Densel +const Densel& ProjMatrixElemsForOneDensel:: get_densel() const { From 51d8c0bcc8f0cf6fa96bba8991dcff964d965b04 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Tue, 2 Jan 2024 08:27:38 +0000 Subject: [PATCH 404/509] removed remnants of non-functional methods --- .../PostsmoothingBackProjectorByBin.h | 4 ---- .../PresmoothingForwardProjectorByBin.h | 2 -- .../PostsmoothingBackProjectorByBin.cxx | 16 ---------------- 3 files changed, 22 deletions(-) diff --git a/src/include/stir/recon_buildblock/PostsmoothingBackProjectorByBin.h b/src/include/stir/recon_buildblock/PostsmoothingBackProjectorByBin.h index 9f7e545e83..fd46b267db 100644 --- a/src/include/stir/recon_buildblock/PostsmoothingBackProjectorByBin.h +++ b/src/include/stir/recon_buildblock/PostsmoothingBackProjectorByBin.h @@ -79,10 +79,6 @@ class PostsmoothingBackProjectorByBin : // class has other behaviour). const DataSymmetriesForViewSegmentNumbers * get_symmetries_used() const; - void update_filtered_density_image(DiscretisedDensity<3, float>&); - - void init_filtered_density_image(DiscretisedDensity<3, float> &); - BackProjectorByBin* get_original_back_projector_ptr() const; PostsmoothingBackProjectorByBin* clone() const; diff --git a/src/include/stir/recon_buildblock/PresmoothingForwardProjectorByBin.h b/src/include/stir/recon_buildblock/PresmoothingForwardProjectorByBin.h index 01152d86ae..b8fe4d8674 100644 --- a/src/include/stir/recon_buildblock/PresmoothingForwardProjectorByBin.h +++ b/src/include/stir/recon_buildblock/PresmoothingForwardProjectorByBin.h @@ -57,8 +57,6 @@ class PresmoothingForwardProjectorByBin : PresmoothingForwardProjectorByBin(); ~ PresmoothingForwardProjectorByBin(); - -// void update_filtered_density_image(const DiscretisedDensity<3,float>&); //! Stores all necessary geometric info /*! Note that the density_info_ptr is not stored in this object. It's only used to get some info on sizes etc. diff --git a/src/recon_buildblock/PostsmoothingBackProjectorByBin.cxx b/src/recon_buildblock/PostsmoothingBackProjectorByBin.cxx index 35447520b1..1281ccedc3 100644 --- a/src/recon_buildblock/PostsmoothingBackProjectorByBin.cxx +++ b/src/recon_buildblock/PostsmoothingBackProjectorByBin.cxx @@ -66,14 +66,6 @@ PostsmoothingBackProjectorByBin:: set_defaults(); } -void PostsmoothingBackProjectorByBin:: -update_filtered_density_image(DiscretisedDensity<3, float> &density) -{ -// image_processor_ptr->apply(*filtered_density_sptr); -// density += *filtered_density_sptr; -// filtered_density_sptr->fill(0.f); -} - BackProjectorByBin* PostsmoothingBackProjectorByBin::get_original_back_projector_ptr() const { @@ -88,14 +80,6 @@ PostsmoothingBackProjectorByBin::clone() const return sptr; } -void PostsmoothingBackProjectorByBin:: -init_filtered_density_image(DiscretisedDensity<3, float> &density) -{ - filtered_density_sptr.reset( - density.get_empty_discretised_density()); - assert(density.get_index_range() == filtered_density_sptr->get_index_range()); -} - PostsmoothingBackProjectorByBin:: PostsmoothingBackProjectorByBin( const shared_ptr& original_back_projector_ptr, From 1d38eb93b36943b0bb59a3fc30b6c83826312620 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Tue, 2 Jan 2024 08:50:27 +0000 Subject: [PATCH 405/509] removed unused function --- src/include/stir/geometry/line_distances.h | 23 ---------------------- 1 file changed, 23 deletions(-) diff --git a/src/include/stir/geometry/line_distances.h b/src/include/stir/geometry/line_distances.h index 6d2a0f4189..35efdbf0c2 100644 --- a/src/include/stir/geometry/line_distances.h +++ b/src/include/stir/geometry/line_distances.h @@ -184,27 +184,4 @@ project_point_on_a_line( } -template -inline void -project_point_on_a_line2( - const CartesianCoordinate3D& p1, - const CartesianCoordinate3D& p2, - CartesianCoordinate3D& r1, - bool& sign) -{ - - const CartesianCoordinate3D difference = p2 - p1; - - const CartesianCoordinate3D r10 = r1 - p1; - - const float u = inner_product(r10, difference) / - inner_product(difference, difference); - - r1[3] = u * difference[3]; - r1[2] = u * difference[2]; - r1[1] = u * difference[1]; - - sign = u > 0 ? true : false; -} - END_NAMESPACE_STIR From f3963316d93efb884790e7d5944d865732a1578f Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Tue, 2 Jan 2024 09:25:29 +0000 Subject: [PATCH 406/509] removed unused class --- src/test/test_time_of_flight.cxx | 35 -------------------------------- 1 file changed, 35 deletions(-) diff --git a/src/test/test_time_of_flight.cxx b/src/test/test_time_of_flight.cxx index d172a1fdbb..06ff5e8f33 100644 --- a/src/test/test_time_of_flight.cxx +++ b/src/test/test_time_of_flight.cxx @@ -45,41 +45,6 @@ START_NAMESPACE_STIR -//! A helper class to keep the combination of a view, a segment and -//! a key tight. -//! \author Nikos Efthimiou -//! -class cache_index{ -public: - cache_index(): - key(0){ - view_num = 0; - seg_num = 0; - } - - inline bool operator==(const cache_index& Y) const - { - return view_num == Y.view_num && - seg_num == Y.seg_num && - key == Y.key; - } - - inline bool operator!=(const cache_index& Y) const - { - return !(*this == Y); - } - - inline bool operator< (const cache_index& Y) const - { - return view_num < Y.view_num && - seg_num < Y.seg_num && - key < Y.key; - } - - int view_num; - int seg_num; - boost::uint32_t key; -}; // Helper class. class FloatFloat{ From 3ef019ce4e9f634b9bcf88455d87a6e9d9c96990 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Tue, 2 Jan 2024 09:26:51 +0000 Subject: [PATCH 407/509] replaced get_scanner_sptr() with get_scanner() for ListModeData --- src/include/stir/listmode/ListModeData.h | 7 +++---- src/listmode_buildblock/ListModeData.cxx | 11 ++++++----- src/listmode_buildblock/LmToProjData.cxx | 6 +++--- src/listmode_utilities/list_lm_events.cxx | 2 +- src/listmode_utilities/lm_fansums.cxx | 8 ++++---- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/include/stir/listmode/ListModeData.h b/src/include/stir/listmode/ListModeData.h index 5fb2301801..4254854c75 100644 --- a/src/include/stir/listmode/ListModeData.h +++ b/src/include/stir/listmode/ListModeData.h @@ -209,12 +209,11 @@ class ListModeData : public ExamData virtual Succeeded set_get_position(const SavedPosition&) = 0; - //! Get scanner pointer - /*! Returns a pointer to a scanner object that is appropriate for the + //! Get reference to scanner + /*! Returns a reference to a scanner object that is appropriate for the list mode data that is being read. - \warning This member is obsolete and might be removed soon. */ - virtual shared_ptr get_scanner_ptr() const ; + const Scanner& get_scanner() const; //! Return if the file stores delayed events as well (as opposed to prompts) virtual bool has_delayeds() const = 0; diff --git a/src/listmode_buildblock/ListModeData.cxx b/src/listmode_buildblock/ListModeData.cxx index 1834c7afc4..b49a8214ba 100644 --- a/src/listmode_buildblock/ListModeData.cxx +++ b/src/listmode_buildblock/ListModeData.cxx @@ -33,13 +33,14 @@ ListModeData:: ~ListModeData() {} -shared_ptr +const Scanner& ListModeData:: -get_scanner_ptr() const +get_scanner() const { - if(is_null_ptr(proj_data_info_sptr)) - error("ListModeData: ProjDataInfo has not been set."); - return proj_data_info_sptr->get_scanner_sptr(); + auto pdi = get_proj_data_info_sptr(); + if(is_null_ptr(pdi)) + error("ListModeData: ProjDataInfo has not been set."); + return *pdi->get_scanner_ptr(); } void diff --git a/src/listmode_buildblock/LmToProjData.cxx b/src/listmode_buildblock/LmToProjData.cxx index 14f8e79206..af160d36bc 100644 --- a/src/listmode_buildblock/LmToProjData.cxx +++ b/src/listmode_buildblock/LmToProjData.cxx @@ -636,15 +636,15 @@ process_data() Scanner const * const scanner_ptr = template_proj_data_info_ptr->get_scanner_ptr(); - if (*scanner_ptr != *lm_data_ptr->get_scanner_ptr()) + if (*scanner_ptr != lm_data_ptr->get_scanner()) { error("LmToProjData:\nScanner from list mode data (%s) is different from\n" "scanner from template projdata (%s).\n" "Full definition of scanner from list mode data:\n%s\n" "Full definition of scanner from template:\n%s\n", - lm_data_ptr->get_scanner_ptr()->get_name().c_str(), + lm_data_ptr->get_scanner().get_name().c_str(), scanner_ptr->get_name().c_str(), - lm_data_ptr->get_scanner_ptr()->parameter_info().c_str(), + lm_data_ptr->get_scanner().parameter_info().c_str(), scanner_ptr->parameter_info().c_str()); } diff --git a/src/listmode_utilities/list_lm_events.cxx b/src/listmode_utilities/list_lm_events.cxx index 3c99264dcf..65802b44ac 100644 --- a/src/listmode_utilities/list_lm_events.cxx +++ b/src/listmode_utilities/list_lm_events.cxx @@ -112,7 +112,7 @@ int main(int argc, char *argv[]) shared_ptr lm_data_ptr(read_from_file(argv[0])); - cout << "Scanner: " << lm_data_ptr->get_scanner_ptr()->get_name() << endl; + cout << "Scanner: " << lm_data_ptr->get_scanner().get_name() << endl; unsigned long num_listed_events = 0; { diff --git a/src/listmode_utilities/lm_fansums.cxx b/src/listmode_utilities/lm_fansums.cxx index 61f46b9351..d292dd34c4 100644 --- a/src/listmode_utilities/lm_fansums.cxx +++ b/src/listmode_utilities/lm_fansums.cxx @@ -126,7 +126,7 @@ post_processing() read_from_file(input_filename); const int num_rings = - lm_data_ptr->get_scanner_ptr()->get_num_rings(); + lm_data_ptr->get_scanner().get_num_rings(); if (max_segment_num_to_process==-1) max_segment_num_to_process = num_rings-1; else @@ -134,7 +134,7 @@ post_processing() min(max_segment_num_to_process, num_rings-1); const int max_fan_size = - lm_data_ptr->get_scanner_ptr()->get_max_num_non_arccorrected_bins(); + lm_data_ptr->get_scanner().get_max_num_non_arccorrected_bins(); if (fan_size==-1) fan_size = max_fan_size; else @@ -164,9 +164,9 @@ compute() //*********** get Scanner details const int num_rings = - lm_data_ptr->get_scanner_ptr()->get_num_rings(); + lm_data_ptr->get_scanner().get_num_rings(); const int num_detectors_per_ring = - lm_data_ptr->get_scanner_ptr()->get_num_detectors_per_ring(); + lm_data_ptr->get_scanner().get_num_detectors_per_ring(); //*********** Finally, do the real work From db42d852dc6f02b52a2151706ca9771fe48bc482 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Tue, 2 Jan 2024 09:31:50 +0000 Subject: [PATCH 408/509] added Vereos but disabled for now as unchecked [ci skip] I took these values from https://github.com/NikEfth/STIR/pull/11, but cannot check them. --- src/buildblock/Scanner.cxx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/buildblock/Scanner.cxx b/src/buildblock/Scanner.cxx index 0844572957..084a44647e 100644 --- a/src/buildblock/Scanner.cxx +++ b/src/buildblock/Scanner.cxx @@ -644,6 +644,17 @@ case PETMR_Signa: 0, 0.F, 0.F); break; +#if 0 + case Vereos: + // Courtesy of Jesus Silva, Molecular Imaging Research Group, Health Research Institute of Santiago de Compostela, Galicia, Spain + // However, unchecked and possibly incompatible with recent changes, so currently commented out by KT + set_params(Vereos, string_list("Philips Vereos", "Vereos"), + 40, 306, 612, + 382.0F, 11.0F, 4.1026F, 2.2876F, 0.0F, + 1, 1, 40, 34, 1, 1, 1); + break; +#endif + case GeminiTF: set_params(GeminiTF,string_list("GeminiTF", "Philips GeminiTF"), 44, 322, 287, // Based on GATE output - Normally it is 644 detectors at each of the 44 rings From a82bd9011e430e7cdedcd0938636d2b0ff2eb528 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Wed, 3 Jan 2024 08:52:19 +0000 Subject: [PATCH 409/509] tiny bit of TOF release notes [ci skip] --- documentation/release_notes_TOF.htm | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/documentation/release_notes_TOF.htm b/documentation/release_notes_TOF.htm index 068ccd28f1..3d1487af28 100644 --- a/documentation/release_notes_TOF.htm +++ b/documentation/release_notes_TOF.htm @@ -50,7 +50,7 @@

        Bug fixes

        New functionality

          -
        • +
        • projectors now have a clone() member, currently returning a bare pointer (like other STIR classes)
        @@ -63,6 +63,12 @@

        Changed functionality

      +

      Changed functionality breaking backwards incompatibility

      +
        +
      • virtual ListModeData::get_scanner_ptr() is replaced by ListModeData::get_scanner(). +
      • +
      +

      Build system

        From 3e1561a2844a087965c5d45209841121b56e59ff Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Wed, 3 Jan 2024 08:59:20 +0000 Subject: [PATCH 410/509] add KeyParser::add_alias_key Add facility to add an alias for a keyword. This just works by looking-up the keyword in a map. This meansss type and callback remain the same. Also minor clean-up of test_KeyParser --- documentation/release_notes_TOF.htm | 9 +++- src/buildblock/KeyParser.cxx | 53 ++++++++++++++++++----- src/include/stir/KeyParser.h | 36 ++++++++++++---- src/test/test_KeyParser.cxx | 67 ++++++++++++++++++++++++----- 4 files changed, 132 insertions(+), 33 deletions(-) diff --git a/documentation/release_notes_TOF.htm b/documentation/release_notes_TOF.htm index 3d1487af28..b9eebe3645 100644 --- a/documentation/release_notes_TOF.htm +++ b/documentation/release_notes_TOF.htm @@ -50,8 +50,9 @@

        Bug fixes

        New functionality

          -
        • projectors now have a clone() member, currently returning a bare pointer (like other STIR classes) -
        • +
        • + projectors now have a clone() member, currently returning a bare pointer (like other STIR classes) +
        @@ -125,6 +126,10 @@

        New functionality

        • Bin can now be output to stream as text
        • added RunTests::check_if_equal for Bin
        • +
        • + KeyParser has a new facility to add an alias to a keyword. This can be used to rename a keyword + for instance while remaining backwards compatible. By default, a warning will be written, but this can be disabled. +
        diff --git a/src/buildblock/KeyParser.cxx b/src/buildblock/KeyParser.cxx index ab609755a7..fb8b707053 100644 --- a/src/buildblock/KeyParser.cxx +++ b/src/buildblock/KeyParser.cxx @@ -2,7 +2,7 @@ Copyright (C) 2000 PARAPET partners Copyright (C) 2000 - 2009-04-30, Hammersmith Imanet Ltd Copyright (C) 2011-07-01 - 2012-01-29, Kris Thielemans - Copyright (C) 2020, 2021 University College London + Copyright (C) 2020, 2021, 2023, 2024 University College London This file is part of STIR. SPDX-License-Identifier: Apache-2.0 AND License-ref-PARAPET-license @@ -30,20 +30,13 @@ #include "stir/error.h" #include #include +#include #include #include #include "stir/warning.h" -# ifdef BOOST_NO_STDC_NAMESPACE - namespace std { using ::getenv; } -# endif - #include - -#ifndef BOOST_NO_STRINGSTREAM #include -#endif -#ifndef STIR_NO_NAMESPACES using std::ifstream; using std::cerr; using std::cout; @@ -58,7 +51,6 @@ using std::list; using std::pair; using std::istream; using std::ostream; -#endif START_NAMESPACE_STIR @@ -276,6 +268,12 @@ bool KeyParser::parse(const char * const filename, const bool write_warning) return parse(hdr_stream, write_warning); } +bool KeyParser::parse(const std::string& s, const bool write_warning) +{ + std::stringstream hdr_stream(s); + return parse(hdr_stream, write_warning); +} + bool KeyParser::parse(istream& f, const bool write_warning) { // print_keywords_to_stream(cerr); @@ -564,6 +562,15 @@ void KeyParser::add_key(const string& keyword, add_in_keymap(keyword, map_element(t, &KeyParser::set_variable, variable, vectorised_key_level, list_of_values)); } +void KeyParser::add_alias_key(const std::string& keyword, const std::string& alias, bool deprecated_key) +{ + const auto std_alias = standardise_keyword(alias); + const auto std_kw = standardise_keyword(keyword); + if (deprecated_key) + this->deprecated_alias_map[std_alias] = std_kw; + else + this->alias_map[std_alias] = std_kw; +} void KeyParser::print_keywords_to_stream(ostream& out) const @@ -623,6 +630,30 @@ Succeeded KeyParser::parse_header(const bool write_warning) } +std::string KeyParser::resolve_alias(const std::string& kw) const +{ + // search in alias_map + { + auto iter = alias_map.find(kw); + if (iter != alias_map.end()) + { + return iter->second; + } + } + // search in deprecated_alias_map + { + auto iter = deprecated_alias_map.find(kw); + if (iter != deprecated_alias_map.end()) + { + warning("KeyParser: found deprecated keyword '" + kw + "'. Replace with '" + iter->second + + "' to disable this warning and for future compatibility."); + return iter->second; + } + } + // not found: return original + return kw; +} + Succeeded KeyParser::read_and_parse_line(const bool write_warning) { string line; @@ -647,7 +678,7 @@ Succeeded KeyParser::read_and_parse_line(const bool write_warning) } // gets keyword - keyword=standardise_keyword(get_keyword(line)); + keyword=resolve_alias(standardise_keyword(get_keyword(line))); return parse_value_in_line(line, write_warning); } diff --git a/src/include/stir/KeyParser.h b/src/include/stir/KeyParser.h index 175c33765f..c6768bb958 100644 --- a/src/include/stir/KeyParser.h +++ b/src/include/stir/KeyParser.h @@ -1,7 +1,7 @@ /* Copyright (C) 2000 PARAPET partners Copyright (C) 2000 - 2007-10-08, Hammersmith Imanet Ltd - Copyright (C) 2013, 2020, 2021 University College London + Copyright (C) 2013, 2020, 2021, 2023, 2024 University College London This file is part of STIR. SPDX-License-Identifier: Apache-2.0 AND License-ref-PARAPET-license @@ -25,9 +25,10 @@ #include "stir/shared_ptr.h" #include "stir/Array.h" +#include "stir/warning.h" #include "boost/any.hpp" -//#include +#include #include #include #include @@ -156,11 +157,13 @@ class KeyParser virtual ~KeyParser(); //! parse() returns false if there is some error, true otherwise - /*! if \a write_warnings is \c false, warnigns about undefined keywords will be supressed.*/ + /*! if \a write_warnings is \c false, warnings about undefined keywords will be supressed.*/ bool parse(std::istream& f, const bool write_warnings=true); //! parse() returns false if there is some error, true otherwise - /*! if \a write_warnings is \c false, warnigns about undefined keywords will be supressed.*/ + /*! if \a write_warnings is \c false, warnings about undefined keywords will be supressed.*/ bool parse(const char * const filename, const bool write_warnings=true); + /*! if \a write_warnings is \c false, warnings about undefined keywords will be supressed.*/ + bool parse(const std::string&, const bool write_warnings=true); ////// functions to add keys and their actions @@ -306,6 +309,16 @@ class KeyParser ); } + //! Removes a key from the kep map + /*! \return \c true if it was found, \c false otherwise */ + bool remove_key(const std::string& keyword); + + //! Add an alias for keyword + /*! If \c deprecated_key if \c true, a warning will be written when the alias is encountered. + + \c parameter_info() will use the \c keyword, not the \c alias. + */ + void add_alias_key(const std::string& keyword, const std::string& alias, bool deprecated_key = true); //! Prints all keywords (in random order) to the stream void print_keywords_to_stream(std::ostream&) const; @@ -395,11 +408,6 @@ protected : void add_key(const std::string& keyword, KeyArgument::type t, void* variable, const int vectorised_key_level, const ASCIIlist_type * const list = 0); - //! Removes a key from the kep map - /*! \return \c true if it was found, \c false otherwise */ - bool remove_key(const std::string& keyword); - - public: ////// predefined call_back functions //! callback function to start parsing, has to be set by first keyword @@ -433,10 +441,20 @@ private : typedef std::list > Keymap; Keymap kmap; + + //! typedef for a map of keyword aliases. Key is alias, value is the "real" keyword + typedef std::map AliasMap; + + AliasMap alias_map; + AliasMap deprecated_alias_map; + // KT 01/05/2001 new functions to allow a list type map_element* find_in_keymap(const std::string& keyword); void add_in_keymap(const std::string& keyword, const map_element& new_element); + //! check if keyword is in either alias_map or deprecated_alias_map, and return new value + std::string resolve_alias(const std::string& keyword) const; + std::istream * input; map_element* current; int current_index; diff --git a/src/test/test_KeyParser.cxx b/src/test/test_KeyParser.cxx index fec7146638..5d9f08827b 100644 --- a/src/test/test_KeyParser.cxx +++ b/src/test/test_KeyParser.cxx @@ -1,7 +1,7 @@ // // /* - Copyright (C) 2020, University College London + Copyright (C) 2020, 2024, University College London This file is part of STIR. SPDX-License-Identifier: Apache-2.0 @@ -21,6 +21,8 @@ #include "stir/KeyParser.h" #include +#include +#include #include "stir/RunTests.h" START_NAMESPACE_STIR @@ -37,10 +39,19 @@ class TestKP : public KeyParser add_start_key("start"); add_stop_key("stop"); add_key("scalar", &scalar_v); + add_alias_key("scalar", "new alias", false); + add_alias_key("scalar", "deprecated alias", true); + add_alias_key("vector", "new vector alias", false); + add_alias_key("vector", "deprecated vector alias", true); add_vectorised_key("vector", &vector_v); } elemT scalar_v; std::vector vector_v; + bool operator==(TestKP& other) const + { + return scalar_v == other.scalar_v && + std::equal(vector_v.begin(),vector_v.end(), other.vector_v.begin()); + } }; /*! @@ -59,13 +70,13 @@ void KeyParserTests::run_tests() { std::cerr << "Tests for KeyParser\n"; - std::cerr << "... int parsing\n"; + std::cerr << "\n..... int parsing\n"; run_tests_one_type(); - std::cerr << "... unsigned int parsing\n"; + std::cerr << "\n..... unsigned int parsing\n"; run_tests_one_type(); - std::cerr << "... float parsing\n"; + std::cerr << "\n..... float parsing\n"; run_tests_one_type(); - std::cerr << "... double parsing\n"; + std::cerr << "\n..... double parsing\n"; run_tests_one_type(); } @@ -73,21 +84,54 @@ template void KeyParserTests::run_tests_one_type() { - - TestKP parser; // basic test if parsing ok { + TestKP parser; + TestKP parser2; std::stringstream str; str << "start:=\n" << "scalar:=2\n" << "vector[1] := 3\n" << "stop :=\n"; parser.parse(str); - check_if_equal(parser.scalar_v, static_cast(2), "parsing int"); - check_if_equal(parser.vector_v[0], static_cast(3), "parsing int vector"); + check_if_equal(parser.scalar_v, static_cast(2), "parsing scalar"); + check_if_equal(parser.vector_v[0], static_cast(3), "parsing vector"); + parser2.parse(parser.parameter_info()); + check(parser == parser2, "check parsing of parameter_info()"); + } + // test alias + { + TestKP parser; + TestKP parser2; + std::stringstream str; + str << "start:=\n" + << "new alias:=2\n" + << "new vector alias[1] := 3\n" + << "stop :=\n"; + parser.parse(str); + check_if_equal(parser.scalar_v, static_cast(2), "parsing scalar with alias"); + check_if_equal(parser.vector_v[0], static_cast(3), "parsing vector with alias"); + check(parser.parameter_info().find("alias") == std::string::npos, "check alias is not in parameter_info()"); + // check if parsing back parameter_info() gives same results + parser2.parse(parser.parameter_info()); + check(parser == parser2, "check parsing of parameter_info() with alias"); + + // same with deprecated alias + std::cerr << "\nNext test should write warnings about deprecated alias\n"; + str << "start:=\n" + << "deprecated alias:=3\n" + << "deprecated vector alias[1] := 4\n" + << "stop :=\n"; + parser.parse(str); + check_if_equal(parser.scalar_v, static_cast(3), "parsing scalar with deprecated alias"); + check_if_equal(parser.vector_v[0], static_cast(4), "parsing vector with deprecated alias"); + check(parser.parameter_info().find("alias") == std::string::npos, "check alias is not in parameter_info()"); + parser2.parse(parser.parameter_info()); + check(parser == parser2, "check parsing of parameter_info() with alias"); } // test 1 if parsing catches errors { + TestKP parser; std::stringstream str; str << "start:=\n" << "scalar[1]:=2\n" @@ -95,7 +139,7 @@ KeyParserTests::run_tests_one_type() << "stop :=\n"; try { - std::cerr << "Next test should write an error (but not crash!)" << std::endl; + std::cerr << "\nNext test should write an error (but not crash!)" << std::endl; parser.parse(str); check(false, "parsing non-vectorised key with vector should have failed"); } @@ -106,6 +150,7 @@ KeyParserTests::run_tests_one_type() } // test 2 if parsing catches errors { + TestKP parser; std::stringstream str; str << "start:=\n" << "scalar:=2\n" @@ -113,7 +158,7 @@ KeyParserTests::run_tests_one_type() << "stop :=\n"; try { - std::cerr << "Next test should write an error (but not crash!)" << std::endl; + std::cerr << "\nNext test should write an error (but not crash!)" << std::endl; parser.parse(str); check(false, "parsing vectorised key with non-vector should have failed"); } From 4192f33769011dce502aed8034e56ee8bc3ac68c Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Wed, 3 Jan 2024 09:37:39 +0000 Subject: [PATCH 411/509] changed TOF-related keywords for Interfile parsing use hopefully clearer names: - new: "TOF mashing factor", old: "%TOF mashing factor" - new: "Maximum number of (unmashed) TOF time bins", old: "Number of TOF time bins" - new: "Size of unmashed TOF time bins (ps)", old: "Size of timing bin (ps)" Old keywords are entered as aliases until STIR 7.0 --- src/IO/InterfileHeader.cxx | 22 +++++++++---- src/buildblock/Scanner.cxx | 16 +++------ src/include/stir/listmode/CListModeDataROOT.h | 23 ++++++++++--- src/listmode_buildblock/CListModeDataROOT.cxx | 33 +++++++++++++++---- 4 files changed, 66 insertions(+), 28 deletions(-) diff --git a/src/IO/InterfileHeader.cxx b/src/IO/InterfileHeader.cxx index db4d33f70e..06ce4af7bc 100644 --- a/src/IO/InterfileHeader.cxx +++ b/src/IO/InterfileHeader.cxx @@ -546,7 +546,14 @@ InterfilePDFSHeader::InterfilePDFSHeader() (KeywordProcessor)&InterfilePDFSHeader::resize_segments_and_set, &max_ring_difference); - + tof_mash_factor = 1; + add_key("TOF mashing factor", + &tof_mash_factor); +#if STIR_VERSION < 070000 + add_alias_key("TOF mashing factor", "%TOF mashing factor"); +#endif + + // Scanner keys // warning these keys should match what is in Scanner::parameter_info() // TODO get Scanner to parse these ignore_key("Scanner parameters"); @@ -615,15 +622,18 @@ InterfilePDFSHeader::InterfilePDFSHeader() add_key("Reference energy (in keV)", &reference_energy); - tof_mash_factor = 1; - add_key("%TOF mashing factor", - &tof_mash_factor); max_num_timing_poss = -1; - add_key("Number of TOF time bins", + add_key("Maximum number of (unmashed) TOF time bins", &max_num_timing_poss); +#if STIR_VERSION < 070000 + add_alias_key("Maximum number of (unmashed) TOF time bins", "Number of TOF time bins"); +#endif size_of_timing_pos = -1.f; - add_key("Size of timing bin (ps)", + add_key("Size of unmashed TOF time bins (ps)", &size_of_timing_pos); +#if STIR_VERSION < 070000 + add_alias_key("Size of unmashed TOF time bins (ps)", "Size of timing bin (ps)"); +#endif timing_resolution = -1.f; add_key("Timing resolution (ps)", &timing_resolution); diff --git a/src/buildblock/Scanner.cxx b/src/buildblock/Scanner.cxx index 084a44647e..6ba82971ae 100644 --- a/src/buildblock/Scanner.cxx +++ b/src/buildblock/Scanner.cxx @@ -1061,9 +1061,9 @@ initialise_max_FOV_radius() { { // for other geometries, loop through all detectors and set the radius to the largest found distance max_FOV_radius = inner_ring_radius; - for (auto tangential_pos = 0; tangential_pos < detector_map_sptr->get_num_tangential_coords(); tangential_pos++) - for (auto axial_pos = 0; axial_pos < detector_map_sptr->get_num_axial_coords(); axial_pos++) - for (auto radial_pos = 0; radial_pos < detector_map_sptr->get_num_radial_coords(); radial_pos++) + for (auto tangential_pos = 0U; tangential_pos < detector_map_sptr->get_num_tangential_coords(); tangential_pos++) + for (auto axial_pos = 0U; axial_pos < detector_map_sptr->get_num_axial_coords(); axial_pos++) + for (auto radial_pos = 0U; radial_pos < detector_map_sptr->get_num_radial_coords(); radial_pos++) { auto coord = detector_map_sptr->get_coordinate_for_det_pos(stir::DetectionPosition<>(tangential_pos, axial_pos, radial_pos)); const auto detector_radius = sqrt(coord.x() * coord.x() + coord.y() * coord.y()) - average_depth_of_interaction; @@ -1402,13 +1402,7 @@ string Scanner::parameter_info() const { // warning: these should match the parsing keywords in InterfilePDFSHeader -#ifdef BOOST_NO_STRINGSTREAM - // dangerous for out-of-range, but 'old-style' ostrstream seems to need this - char str[10000]; - ostrstream s(str, 10000); -#else std::ostringstream s; -#endif s << "Scanner parameters:= "<<'\n'; s << "Scanner type := " << get_name() <<'\n'; @@ -1433,8 +1427,8 @@ Scanner::parameter_info() const if (is_tof_ready()) { - s << "Number of TOF time bins :=" << get_max_num_timing_poss() << "\n"; - s << "Size of timing bin (ps) :=" << get_size_of_timing_pos() << "\n"; + s << "Maximum number of (unmashed) TOF time bins :=" << get_max_num_timing_poss() << "\n"; + s << "Size of unmashed TOF time bins (ps) :=" << get_size_of_timing_pos() << "\n"; s << "Timing resolution (ps) :=" << get_timing_resolution() << "\n"; } diff --git a/src/include/stir/listmode/CListModeDataROOT.h b/src/include/stir/listmode/CListModeDataROOT.h index 3e353af270..1e79739a54 100644 --- a/src/include/stir/listmode/CListModeDataROOT.h +++ b/src/include/stir/listmode/CListModeDataROOT.h @@ -149,12 +149,14 @@ class CListModeDataROOT : public CListModeData //! Pointer to the listmode data shared_ptr root_file_sptr; -//! \name Variables that can be set in the hroot file to define a scanner's geometry. +//! \name Variables that can be set in the hroot file to define a scanner's geometry etc. //! They are compared to the Scanner (if set) and the InputStreamFromROOTFile //! geometry, as given by the repeaters. Can be used to check for inconsistencies. //@{ //! The name of the originating scanner std::string originating_system; + //! \name Geometry + //@{ //! Number of rings, set in the hroot file (optional) int num_rings; //! Number of detectors per ring, set in the hroot file (optional) @@ -173,19 +175,30 @@ class CListModeDataROOT : public CListModeData float ring_spacing; //! Bin size, set in the hroot file (optional) float bin_size; - + //@} + + //! \name TOF information + /*! These describe the maximum capabilities of the scanner, i.e. in + list-mode data, even if vendors often never construct sinogram with + this TOF resolution. + */ + //@{ int max_num_timing_bins; float size_timing_bin; float timing_resolution; + int tof_mash_factor; + //@} + + //! \name energy information + //@{ float energy_resolution; float reference_energy; -//@} - - int tof_mash_factor; + //@} + //@} KeyParser parser; diff --git a/src/listmode_buildblock/CListModeDataROOT.cxx b/src/listmode_buildblock/CListModeDataROOT.cxx index 1eb33829ab..dec07a58fb 100644 --- a/src/listmode_buildblock/CListModeDataROOT.cxx +++ b/src/listmode_buildblock/CListModeDataROOT.cxx @@ -1,7 +1,7 @@ /* Copyright (C) 2015, 2016 University of Leeds Copyright (C) 2017, 2018 University of Hull - Copyright (C) 2016, 2017, 2020 University College London + Copyright (C) 2016, 2017, 2020, 2023, 2024 University College London Copyright (C) 2018 University of Hull This file is part of STIR. @@ -31,6 +31,10 @@ START_NAMESPACE_STIR +constexpr static char max_num_timing_bins_keyword[] = "Maximum number of (unmashed) TOF time bins"; +constexpr static char size_timing_bin_keyword[] = "Size of unmashed TOF time bins (ps)"; +constexpr static char timing_resolution_keyword[] = "Timing resolution (ps)"; + CListModeDataROOT:: CListModeDataROOT(const std::string& hroot_filename) : hroot_filename(hroot_filename) @@ -62,11 +66,22 @@ CListModeDataROOT(const std::string& hroot_filename) this->parser.add_key("energy resolution", &this->energy_resolution); this->parser.add_key("reference energy", &this->reference_energy); - this->parser.add_key("number of TOF time bins", &this->max_num_timing_bins); - this->parser.add_key("Size of timing bin (ps)", &this->size_timing_bin); - this->parser.add_key("Timing resolution (ps)", &this->timing_resolution); - - this->parser.add_key("%TOF mashing factor", &this->tof_mash_factor); + this->parser.add_key(max_num_timing_bins_keyword, + &max_num_timing_bins); +#if STIR_VERSION < 070000 + this->parser.add_alias_key(max_num_timing_bins_keyword, "Number of TOF time bins"); +#endif + this->parser.add_key(size_timing_bin_keyword, + &size_timing_bin); +#if STIR_VERSION < 070000 + this->parser.add_alias_key(size_timing_bin_keyword, "Size of timing bin (ps)"); +#endif + this->parser.add_key(timing_resolution_keyword, &this->timing_resolution); + + this->parser.add_key("TOF mashing factor", &this->tof_mash_factor); +#if STIR_VERSION < 070000 + this->parser.add_alias_key("TOF mashing factor", "%TOF mashing factor"); +#endif // // ROOT related @@ -265,6 +280,8 @@ set_defaults() ring_spacing = -.1f; bin_size = -1.f; view_offset = 0.f; + max_num_timing_bins = -1; + size_timing_bin = -1.F; tof_mash_factor = 1; reference_energy = 511.F; energy_resolution = -1.F; @@ -373,6 +390,10 @@ check_scanner_definition(std::string& ret) return Succeeded::no; } + if (max_num_timing_bins <= 0 || size_timing_bin <= 1.F || timing_resolution <= 0.F) + info(boost::format("CListModeDataROOT: TOF information is missing. Set relevant keywords if you need TOF:\n\t%s\n\t%s\n\t%s") + % max_num_timing_bins_keyword % size_timing_bin_keyword % timing_resolution_keyword); + return Succeeded::yes; } From 22949cb9d5e4d0e6d32ab8e9596c2ce4bb2c5a1f Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Wed, 3 Jan 2024 23:01:44 +0000 Subject: [PATCH 412/509] update glossary for TOF and a few other things [ci skip] --- documentation/STIR-glossary.tex | 134 ++++++++++++++++++++------------ 1 file changed, 86 insertions(+), 48 deletions(-) diff --git a/documentation/STIR-glossary.tex b/documentation/STIR-glossary.tex index d291e3b6d9..b4786d0d79 100644 --- a/documentation/STIR-glossary.tex +++ b/documentation/STIR-glossary.tex @@ -14,7 +14,7 @@ \end{center} \begin{center} -\textit{Version 3.0}\\ +\textit{Version 6.0}\\ Originally based on PARAPET Deliverable 4.1, Extended for Quantitative Reconstruction and motion compensation @@ -30,35 +30,47 @@ \section*{Introduction} of data concepts. However, this is all kept brief. Please read additional material, for instance [Fah2002]. +Most of specifics of this document related to PET scanners, but a lot of terminology has clear +correspondence for SPECT. However, most SPECT scanners rotate one or more gamma cameras around +the patient, as opposed to have a ring of detectors. + \section*{Basics} \begin{description} -% ROW 2 + \item[Geometry] A cylindrical geometry has been chosen to describe positron tomographs made of a number of adjacent detector rings and reconstructed image volumes. The geometry supports consequently two principal directions \textit{axially} along the scanner cylinder and \textit{transaxially} perpendicular to the cylinder axis. -% ROW 3 + \item[Scanner] Geometrically associated to the cylindrical volume defined by the inner dimensions of the positron tomograph. -% ROW 4 + \item[Detector ring ] Geometrically associated to the cylindrical volume defined by the dimensions of a detector ring. Note that because currently Depth of Interaction is not taken into account, the effective ring radius used in the building blocks is the sum of the inner ring radius and an average depth of interaction (e.g. \ensuremath{\sim} 1cm for BGO). -% ROW 5 + \item[Detector] Sometimes called \textbf{detector crystal}. Geometrically associated to the inner face of a detector element. The \textbf{scanner} is then considered as a tessellation of \textbf{detectors} constructing adjacent \textbf{rings}. For many scanners, detectors are organized in a block. For instance, -on the HR+ scanner, a detector block consists of 8x8 detectors. -% ROW 6 +on the HR+ scanner, a detector block consists of 8x8 detectors. Current scanners +have mini-blocks, related to the read-out. +Blocks are often organised in \textbf{modules}, currently called \textbf{buckets} in STIR. + +\item[Time of Flight (TOF) for PET] +Many modern PET scanners measure the difference in arrival time of the 2 gamma photons with +a certain \textbf{TOF timing resolution} (often expressed in ps). In current scanners, +the TOF information is discretised into \textbf{TOF bins}. + + \item[LOR (Line-Of-Response)] Line joining the centres of two \textbf{detectors}. Ignoring scatter, attenuation and other physical effects, the average number of @@ -69,12 +81,13 @@ \section*{Basics} integral approximation works best for LORs that do not run parallel to edges within the object. We say that the projector that uses this model is a \textbf{ray tracing} projector. -% ROW 7 + \item[TOR (Tube of response)] Tube joining two \textbf{detectors}. -% ROW 8 + \item[Sinogram] -Set of \textbf{bins} corresponding to 1 segment and 1 \textbf{axial} position. +Set of \textbf{bins} corresponding to 1 \textbf{segment} and 1 \textbf{axial} position and (in STIR) +1 TOF bin. Before \textbf{axial compression,} this corresponds to LORs in a \textbf{detector} \textbf{ring} (\textit{direct} \textit{sinogram}) or between two different \textbf{detector} \textbf{rings} (\textit{oblique} \textit{sinogram}). For a \textbf{scanner} of $n$ \textbf{detector @@ -83,26 +96,33 @@ \section*{Basics} compression}, the number of \textbf{direct sinograms} is $2n-1$. Conventionally, the \textbf{view} angle in an oblique sinogram runs only over 180 degrees, meaning that only half of the detectors in each ring -are covered. The other half corresponds to the \textbf{sinogram} in -the opposite \textbf{segment} (with minus the average ring difference), -% ROW 9 +are covered\footnote{In SPECT, rotations often cover 360 degrees.}. +The other half corresponds to the \textbf{sinogram} in +the opposite \textbf{segment} (with minus the average ring difference). + +In PET, the number of tangential positions determines the ``fan-size''. Its maximum is +equal to the number of detectors per ring. Scanners use far less +you don’t want to look at coincidences between neighbouring crystals!). + \item[View] The azimuthal angle of an \textbf{LOR} (ignoring \textbf{interleaving}, -see the documentation of the ProjDataInfoCylindricalNoArcCorr -class, and \textbf{mashing}). -% ROW 10 +see the documentation of the \texttt{ProjDataInfoCylindricalNoArcCorr} +class). +The maximum number of views is half the number of detectors per ring +(this is again due to interleaving). + \item[Bin] A single element in a sinogram, completely specified by its \textbf{segment, -axial} \textbf{position, view} and \textbf{tangential position}. -% ROW 11 -\item[Ring difference] +axial} \textbf{position, view}, \textbf{tangential position} and (in PET) \textbf{TOF bin}. + +\item[Ring difference (in PET)] Number of \textbf{rings} between two \textbf{rings} associated to a \textbf{sinogram}. If \textit{ringA} and \textit{ringB} are the ring numbers, the \textbf{ring difference} is given by \textit{ringB} -- \textit{ringA}. Thus there can be \textit{positive} -and \textit{negative} \textbf{ring differences}. \linebreak +and \textit{negative} \textbf{ring differences}.\\ The (average) \textbf{ring difference} of a \textbf{direct sinogram} is zero. -% ROW 12 + \item[Michelogram] Representation of \textbf{sinograms} on a square grid as shown in Annex 1. If \textit{ringA} and \textit{ringB} are the ring numbers associated @@ -110,14 +130,14 @@ \section*{Basics} axis and \textit{ringB} on the vertical axis. \textbf{Positive ring differences} are below the line representing \textbf{direct sinograms} and \textbf{negative ring differences} above this line. -% ROW 13 + \item[Segment] Set of \textbf{merged} \textbf{sinograms} with a common average \textbf{ring difference} as shown in Annex 1. -% ROW 14 + \item[Viewgram] Set of equal azimuth \textbf{merged} \textbf{LORs} of a \textbf{segment}. -% ROW 15 + \item[Projection data] The set of all (measured) LORs, normally split into \textbf{segments} etc. The word ``projection'' is used because after various corrections and @@ -129,38 +149,38 @@ \section*{Basics} cases, the term is also used for the smaller volume for which there is at least 1 \textbf{bin} with non-zero detection probability for every \textbf{view}. The latter FOV is usually cylindrical. -% ROW 16 + \item[Image slice] Geometrically associated to a cylindrical volume defined by a slice of the \textbf{FOV}. By convention, a \textbf{slice} is half the width of a \textbf{ring}. For a \textbf{scanner} of \textit{n} \textbf{detector} \textbf{rings}, there are 2\textit{n}--1 \textbf{image slices}. -% ROW 17 + \item[Direct plane] \textbf{Image slice} centered on a \textbf{ring}. For a \textbf{scanner} of \textit{n} \textbf{detector} \textbf{rings}, there are \textit{n} \textbf{direct planes}. The \textbf{FOV} is ended by two \textbf{direct planes} centered on the first and last \textbf{rings}. -% ROW 18 + \item[Cross plane] \textbf{Image slice} in between two consecutive \textbf{direct planes}. \textbf{Direct planes} are adjacent to \textbf{cross planes}. For a \textbf{scanner} of \textit{n} \textbf{detector} \textbf{rings}, there are 2\textit{n}--1 \textbf{cross planes}. \end{description} -\section*{Different data compressions used in PET data} +\section*{Different (lossy) data compressions used} \begin{description} -% ROW 21 + \item[Trimming] Reduction of the number of \textbf{bins} in tangential direction without changing the size of \textbf{bins}. \textbf{Trimming} is a type of \textbf{bin} truncation. -% ROW 22 -\item[Angular compression (Mashing)] + +\item[Angular compression (view mashing)] Reduction of the number of \textbf{views} by a multiple of two. As an example, doing a \textbf{mashing} of 2 means that pairs of \textbf{views} have been added 2 by 2 to form only one \textbf{view}. -% ROW 23 -\item[Axial compression (Span)] + +\item[Axial compression (Span), PET] Reduction of the number of \textbf{sinograms} at different \textbf{ring differences} as shown in Annex 1. \textbf{Span} is a number used by Siemens/CTI to say how much axial compression has been used. @@ -173,57 +193,75 @@ \section*{Different data compressions used in PET data} \textbf{STIR} supports any even span (where segment $0$ has effective $\mathrm{span}+1$). Finally, for historical reasons \textbf{STIR} also support a different mixed -format where segment $0$ has span $3$ but higher segments have span $1$. +format where segment $0$ has span $3$ but higher segments have span $1$, +or indeed any mix ``spans-per-segment''. + +\item[SSRB] +Originally, single slice rebinning was developed to collapse 3D PET data into non-oblique sinograms. +In STIR, it is generalised to combine segments, optionally keeping some oblique segments. +this effectively increases the \textbf{span}. + +\item[TOF mashing (PET)] +Reduction of the number of \textbf{TOF bins} by combining adjacent bins. The \textbf{TOF mashing factor} +is defined as the ratio of the \textbf{Maximum number of (unmashed) TOF time bins} supported +by the scanner (in list-mode) over the actual number of TOF bins. Currently in STIR, this +ratio has to be an integer. The size of a TOF bin is computed by multiplying the +\textbf{TOF mashing factor} with the \textbf{size of unmashed TOF time bins}, with the latter +defined as a scanner property.\\ +Note that many PET scanners use a \textbf{TOF mashing factor} greater than 1 +for their standard histogrammed projection data. + \end{description} \section*{Terms used in quantitative PET reconstruction} \begin{description} -% ROW 24 + \item[Scatter Point] Coordinate where a scatter event takes place. -% ROW 25 + \item[SSS - Single scatter simulation] Estimation of the probability to measure a coincidence event that one of the two photons has been scattered only once. -% ROW 26 + \item[B-Splines] Basis splines are a set of polynomial functions that have minimal support with respect to a given degree, smoothness, and domain partition. In imaging they are useful for performing very fast multidimensional interpolation calculations. -% ROW 27 + + \item[Inverse-SSRB] It is the pseudo-inverse operation of single slice rebinning which can be used as the simplest way to extrapolate direct sinograms into indirect sinograms. -% ROW 28 + \item[Plasma Data] Radioactivity concentration in plasma (and blood) during the scanning acquisition. Usually it is measured in $\mathit{kBq/cm^3}$ over a time window of 1 second. -% ROW 29 + \item[Dynamic Data/Images] A stack of projection data or images through time. -% ROW 30 + \item[Kinetic Model] The kinetic model describe the tracer exchange between plasma and tissue and between tissue compartments. -% ROW 31 + \item[Kinetic Parameters] The parameters of the kinetic model which are estimated such that the model is in agreement with the acquired data. -% ROW 32 + \item[(Kinetic) Model Matrix] Linear kinetic models can be written with compact matrix operations, which relate the dynamic images and the kinetic parameters with the kinetic model matrix. This matrix can be seen as the application of the transformation from parametric domain to the temporal domain. -% ROW 33 + \item[Patlak Plot] For irreversible tracers, after a certain period from tracer injection, the free tracer in tissue reaches equilibrium with the radiotracer in plasma and then the original model simplifies to a linear plot known as the Patlak Plot. -% ROW 34 + \item[Parametric Image] An image whose voxels hold the values the kinetic parameters. -% ROW 35 + \item[Parametric Image Reconstruction (PIR)] Estimation of the kinetic parameters from dynamic images for each voxel (indirect PIR). The parametric images can also be reconstructed directly from dynamic projection data. @@ -262,8 +300,8 @@ \section*{Terms used in motion-compensated reconstruction} lines connecting the dots indicate the sinograms that are added together. The illustration is for \textbf{span} 7=4+3 (this terminology was introduced because for some axial positions, 4 sinograms -are added, while for others only 3. Note that \textbf{span} is always -odd.). +are added, while for others only 3. Note that for even \textbf{span}, segment 0 +has one more set of axial positions added than the oblique ones.). \begin{figure}[htbp] From 27a2516d0f7fa8591c99ea4686a0ad38f7452882 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Thu, 4 Jan 2024 08:04:05 +0000 Subject: [PATCH 413/509] make ProjDataInfo*NoArcCorr::get_bin_for_det_pair private Use get_bin_for_det_pos_pair instead. Addresses https://github.com/UCL/STIR/issues/105 --- documentation/release_notes_TOF.htm | 3 ++- .../ProjDataInfoBlocksOnCylindricalNoArcCorr.cxx | 6 ++---- src/experimental/test/test_proj_data_info_LOR.cxx | 10 ++++------ src/experimental/utilities/interpolate_blocks.cxx | 9 ++++----- src/experimental/utilities/set_blocks_to_value.cxx | 9 ++++----- src/include/stir/ProjDataInfoCylindricalNoArcCorr.h | 4 +++- src/include/stir/ProjDataInfoGenericNoArcCorr.h | 4 +++- src/test/test_proj_data_info.cxx | 10 ++++++---- src/utilities/UPENN/conv_UPENN_projdata_to_STIR.cxx | 4 ++-- src/utilities/list_detector_and_bin_info.cxx | 4 ++-- 10 files changed, 32 insertions(+), 31 deletions(-) diff --git a/documentation/release_notes_TOF.htm b/documentation/release_notes_TOF.htm index b9eebe3645..6cc9626158 100644 --- a/documentation/release_notes_TOF.htm +++ b/documentation/release_notes_TOF.htm @@ -118,7 +118,8 @@

        Major bugs fixed

        Backward incompatibities

          -
        • +
        • ProjDataInfo*NoArcCorr::get_bin_for_det_pair is now private. + Use get_bin_for_det_pos_pair instead.
        diff --git a/src/buildblock/ProjDataInfoBlocksOnCylindricalNoArcCorr.cxx b/src/buildblock/ProjDataInfoBlocksOnCylindricalNoArcCorr.cxx index c90fca539e..9964d8bc7d 100644 --- a/src/buildblock/ProjDataInfoBlocksOnCylindricalNoArcCorr.cxx +++ b/src/buildblock/ProjDataInfoBlocksOnCylindricalNoArcCorr.cxx @@ -170,10 +170,8 @@ find_bin_given_cartesian_coordinates_of_detection(Bin& bin, ring_a>=get_scanner_ptr()->get_num_rings() || ring_b<0 || ring_b>=get_scanner_ptr()->get_num_rings())); - - if (get_bin_for_det_pair(bin, - det_num_a, ring_a, - det_num_b, ring_b) == Succeeded::no || + const DetectionPositionPair<> det_pos_pair(DetectionPosition<>(det_num_a, ring_a), DetectionPosition<>(det_num_b, ring_b)); + if (!get_bin_for_det_pos_pair(bin, det_pos_pair).succeeded() || bin.tangential_pos_num() < get_min_tangential_pos_num() || bin.tangential_pos_num() > get_max_tangential_pos_num()) bin.set_bin_value(-1); diff --git a/src/experimental/test/test_proj_data_info_LOR.cxx b/src/experimental/test/test_proj_data_info_LOR.cxx index 7babb78547..dad41dd8ac 100644 --- a/src/experimental/test/test_proj_data_info_LOR.cxx +++ b/src/experimental/test/test_proj_data_info_LOR.cxx @@ -132,9 +132,8 @@ main(int argc,char *argv[]) ring1_0 = bin0.axial_pos_num()/2; ring2_0 = bin0.axial_pos_num()/2; Bin bin_check_0; - proj_data_cyl_no_arc_ptr->get_bin_for_det_pair(bin_check_0, - det1_0,ring1_0, - det2_0,ring2_0); + const DetectionPositionPair<> det_pos_pair_0(DetectionPosition<>(det1_0, ring1_0), DetectionPosition<>(det2_0, ring2_0)); + proj_data_cyl_no_arc_ptr->get_bin_for_det_pos_pair(bin_check_0, det_pos_pair_0); bin_check_0.set_bin_value(1); assert(bin0 == bin_check_0); proj_data_cyl_no_arc_ptr->get_det_num_pair_for_view_tangential_pos_num(det1_90,det2_90, @@ -144,9 +143,8 @@ main(int argc,char *argv[]) ring2_90 = bin90.axial_pos_num()/2; Bin bin_check_90; - proj_data_cyl_no_arc_ptr->get_bin_for_det_pair(bin_check_90, - det1_90,ring1_90, - det2_90,ring2_90); + const DetectionPositionPair<> det_pos_pair_90(DetectionPosition<>(det1_90, ring1_90), DetectionPosition<>(det2_90, ring2_90)); + proj_data_cyl_no_arc_ptr->get_bin_for_det_pos_pair(bin_check_90, det_pos_pair_90); bin_check_90.set_bin_value(1); assert(bin90 ==bin_check_90); #endif diff --git a/src/experimental/utilities/interpolate_blocks.cxx b/src/experimental/utilities/interpolate_blocks.cxx index 0b7d824bfc..0a0e16e240 100644 --- a/src/experimental/utilities/interpolate_blocks.cxx +++ b/src/experimental/utilities/interpolate_blocks.cxx @@ -67,14 +67,13 @@ void do_block(vector& list_of_bins_in_block, for (int other_det=0; other_det det_pos_pair(DetectionPosition<>(det, ring), DetectionPosition<>(other_det, other_ring)); + const Succeeded success = + proj_data_info.get_bin_for_det_pos_pair(bin, det_pos_pair); if (success == Succeeded::yes) list_of_bins_in_block.push_back(bin); } diff --git a/src/experimental/utilities/set_blocks_to_value.cxx b/src/experimental/utilities/set_blocks_to_value.cxx index d809ceae02..53b689bf16 100644 --- a/src/experimental/utilities/set_blocks_to_value.cxx +++ b/src/experimental/utilities/set_blocks_to_value.cxx @@ -67,13 +67,12 @@ void do_block(vector& list_of_bins_in_block, for (int other_det=0; other_det det_pos_pair(DetectionPosition<>(det, ring), DetectionPosition<>(other_det, other_ring)); + const Succeeded success = + proj_data_info.get_bin_for_det_pos_pair(bin, det_pos_pair); if (success == Succeeded::yes) list_of_bins_in_block.push_back(bin); } diff --git a/src/include/stir/ProjDataInfoCylindricalNoArcCorr.h b/src/include/stir/ProjDataInfoCylindricalNoArcCorr.h index e70e6eb4ad..0bd2d06787 100644 --- a/src/include/stir/ProjDataInfoCylindricalNoArcCorr.h +++ b/src/include/stir/ProjDataInfoCylindricalNoArcCorr.h @@ -224,6 +224,8 @@ class ProjDataInfoCylindricalNoArcCorr : public ProjDataInfoCylindrical get_all_det_pos_pairs_for_bin(std::vector >&, const Bin&) const; + private: + // old function, now private. Use get_bin_for_det_pos_pair instead. //! This gets Bin coordinates for a particular detector pair /*! \return Succeeded::yes when a corresponding segment is found @@ -235,7 +237,7 @@ class ProjDataInfoCylindricalNoArcCorr : public ProjDataInfoCylindrical const int det1_num, const int ring1_num, const int det2_num, const int ring2_num, const int timing_pos_num = 0) const; - + public: //! This routine gets the detector pair corresponding to a bin. /*! \see get_det_pair_for_view_tangential_pos_num() for diff --git a/src/include/stir/ProjDataInfoGenericNoArcCorr.h b/src/include/stir/ProjDataInfoGenericNoArcCorr.h index 17a1caf3c4..771142fad2 100644 --- a/src/include/stir/ProjDataInfoGenericNoArcCorr.h +++ b/src/include/stir/ProjDataInfoGenericNoArcCorr.h @@ -197,6 +197,8 @@ class ProjDataInfoGenericNoArcCorr : public ProjDataInfoGeneric get_all_det_pos_pairs_for_bin(std::vector >&, const Bin&) const; + private: + // old function, now private. Use get_bin_for_det_pos_pair instead. //! This gets Bin coordinates for a particular detector pair /*! \return Succeeded::yes when a corresponding segment is found @@ -208,7 +210,7 @@ class ProjDataInfoGenericNoArcCorr : public ProjDataInfoGeneric const int det1_num, const int ring1_num, const int det2_num, const int ring2_num) const; - + public: //! This routine gets the detector pair corresponding to a bin. /*! \see get_det_pair_for_view_tangential_pos_num() for diff --git a/src/test/test_proj_data_info.cxx b/src/test/test_proj_data_info.cxx index 2d6ffdd564..1c7801cc23 100644 --- a/src/test/test_proj_data_info.cxx +++ b/src/test/test_proj_data_info.cxx @@ -878,11 +878,11 @@ ProjDataInfoTests::run_lor_get_s_test() Cdet2 = scannerCyl_ptr->get_num_detectors_per_ring() / 2 + Cdet1; if (Cdet2 >= scannerCyl_ptr->get_num_detectors_per_ring()) Cdet2 = Cdet1 - scannerCyl_ptr->get_num_detectors_per_ring() / 2; - - proj_data_info_cyl_ptr->get_bin_for_det_pair(bin, Cdet1, Cring1, Cdet2, Cring2); + const DetectionPositionPair<> det_pos_pair(DetectionPosition<>(Cdet1, Cring1), DetectionPosition<>(Cdet2, Cring2)); + proj_data_info_cyl_ptr->get_bin_for_det_pos_pair(bin, det_pos_pair); proj_data_info_cyl_ptr->get_LOR(lorC, bin); - proj_data_info_blocks_ptr->get_bin_for_det_pair(bin, Cdet1, Cring1, Cdet2, Cring2); + proj_data_info_blocks_ptr->get_bin_for_det_pos_pair(bin, det_pos_pair); proj_data_info_blocks_ptr->get_LOR(lorB, bin); check_if_equal(0., lorC.s(), @@ -911,7 +911,9 @@ ProjDataInfoTests::run_lor_get_s_test() Cring2 = 0; Cdet2 = 2 * Ctb + det_id_diff + Ctb - 1 - i; - proj_data_info_blocks_ptr->get_bin_for_det_pair(bin, Cdet1, Cring1, Cdet2, Cring1); + const DetectionPositionPair<> det_pos_pair(DetectionPosition<>(Cdet1, Cring1), DetectionPosition<>(Cdet2, Cring2)); + + proj_data_info_blocks_ptr->get_bin_for_det_pos_pair(bin, det_pos_pair); proj_data_info_blocks_ptr->get_LOR(lorB, bin); /*float R=block_trans_spacing*(sin(_PI*5/12)+sin(_PI/4)+sin(_PI/12)); float s=R*cos(_PI/3)+ diff --git a/src/utilities/UPENN/conv_UPENN_projdata_to_STIR.cxx b/src/utilities/UPENN/conv_UPENN_projdata_to_STIR.cxx index 3778d5f4d6..11b923a58e 100755 --- a/src/utilities/UPENN/conv_UPENN_projdata_to_STIR.cxx +++ b/src/utilities/UPENN/conv_UPENN_projdata_to_STIR.cxx @@ -275,8 +275,8 @@ int main(int argc, const char *argv[]) } Bin tmp_bin; - - out_projdata_info_sptr->get_bin_for_det_pair(tmp_bin, d1, dr1, d2, dr2); + const DetectionPositionPair<> det_pos_pair(DetectionPosition<>(d1, dr1), DetectionPosition<>(d2, dr2)); + out_projdata_info_sptr->get_bin_for_det_pos_pair(tmp_bin, det_pos_pair); const int index = i_phi * mh_isino.numray + i_tang; float val = tmp_slice[index]; diff --git a/src/utilities/list_detector_and_bin_info.cxx b/src/utilities/list_detector_and_bin_info.cxx index a2662e7c35..27ddaaeec5 100644 --- a/src/utilities/list_detector_and_bin_info.cxx +++ b/src/utilities/list_detector_and_bin_info.cxx @@ -81,8 +81,8 @@ int main(int argc, char *argv[]) DetectionPositionPair<> det_pos; LORInAxialAndNoArcCorrSinogramCoordinates lor; - proj_data_info_sptr->get_bin_for_det_pair (bin, - det_num_a, ring_a, det_num_b, ring_b); + const DetectionPositionPair<> det_pos_pair(DetectionPosition<>(det_num_a, ring_a), DetectionPosition<>(det_num_b, ring_b)); + proj_data_info_sptr->get_bin_for_det_pos_pair(bin, det_pos_pair); cout << "bin: (segment " << bin.segment_num() << ", axial pos " << bin.axial_pos_num() << ", view = " << bin.view_num() << ", tangential_pos_num = " << bin.tangential_pos_num() << ")\n"; From 572509a0af2d667bbd5b425c9e48daef50831e91 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Thu, 4 Jan 2024 08:17:54 +0000 Subject: [PATCH 414/509] document that get_bin_for_det_pair takes mashed_tof_bin arg --- src/include/stir/ProjDataInfoCylindricalNoArcCorr.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/include/stir/ProjDataInfoCylindricalNoArcCorr.h b/src/include/stir/ProjDataInfoCylindricalNoArcCorr.h index 0bd2d06787..22c7f91721 100644 --- a/src/include/stir/ProjDataInfoCylindricalNoArcCorr.h +++ b/src/include/stir/ProjDataInfoCylindricalNoArcCorr.h @@ -231,12 +231,13 @@ class ProjDataInfoCylindricalNoArcCorr : public ProjDataInfoCylindrical \return Succeeded::yes when a corresponding segment is found \see get_view_tangential_pos_num_for_det_num_pair() for restrictions \obsolete + \warning Takes a "TOF-mashed" bin as argument ATM. */ inline Succeeded get_bin_for_det_pair(Bin&, const int det1_num, const int ring1_num, const int det2_num, const int ring2_num, - const int timing_pos_num = 0) const; + const int mashed_timing_pos_num = 0) const; public: //! This routine gets the detector pair corresponding to a bin. /*! From 4a285ad4ae602c5ba5cd18febb4f2009a167623f Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Thu, 4 Jan 2024 12:27:11 +0000 Subject: [PATCH 415/509] remove defaults for some TOF-range to avoid unexpected problems --- .../stir/recon_buildblock/DataSymmetriesForBins.h | 9 ++------- .../stir/recon_buildblock/TrivialDataSymmetriesForBins.h | 9 ++------- src/include/stir/recon_buildblock/distributable.h | 2 +- .../stir/recon_buildblock/distributableMPICacheEnabled.h | 2 +- 4 files changed, 6 insertions(+), 16 deletions(-) diff --git a/src/include/stir/recon_buildblock/DataSymmetriesForBins.h b/src/include/stir/recon_buildblock/DataSymmetriesForBins.h index a095a5c374..2fd7350149 100644 --- a/src/include/stir/recon_buildblock/DataSymmetriesForBins.h +++ b/src/include/stir/recon_buildblock/DataSymmetriesForBins.h @@ -78,12 +78,7 @@ class DataSymmetriesForBins : public DataSymmetriesForViewSegmentNumbers virtual ~DataSymmetriesForBins(); virtual -#ifndef STIR_NO_COVARIANT_RETURN_TYPES - DataSymmetriesForBins -#else - DataSymmetriesForViewSegmentNumbers -#endif - * clone() const = 0; + DataSymmetriesForBins * clone() const = 0; #if 0 TODO! @@ -109,7 +104,7 @@ class DataSymmetriesForBins : public DataSymmetriesForViewSegmentNumbers get_related_bins(std::vector&, const Bin& b, const int min_axial_pos_num, const int max_axial_pos_num, const int min_tangential_pos_num, const int max_tangential_pos_num, - const int min_timing_pos_num = 0, const int max_timing_pos_num = 0) const; + const int min_timing_pos_num, const int max_timing_pos_num) const; //! fills in a vector with the axial and tangential position numbers related to this bin /*! range for axial_pos_num and tangential_pos_num is taken from the ProjDataInfo object diff --git a/src/include/stir/recon_buildblock/TrivialDataSymmetriesForBins.h b/src/include/stir/recon_buildblock/TrivialDataSymmetriesForBins.h index dc3be78a96..b39dab211d 100644 --- a/src/include/stir/recon_buildblock/TrivialDataSymmetriesForBins.h +++ b/src/include/stir/recon_buildblock/TrivialDataSymmetriesForBins.h @@ -38,18 +38,13 @@ class TrivialDataSymmetriesForBins : public DataSymmetriesForBins TrivialDataSymmetriesForBins(const shared_ptr& proj_data_info_ptr); virtual -#ifndef STIR_NO_COVARIANT_RETURN_TYPES - TrivialDataSymmetriesForBins -#else - DataSymmetriesForViewSegmentNumbers -#endif - * clone() const; + TrivialDataSymmetriesForBins * clone() const; virtual void get_related_bins(std::vector&, const Bin& b, const int min_axial_pos_num, const int max_axial_pos_num, const int min_tangential_pos_num, const int max_tangential_pos_num, - const int min_timing_pos_num = 0, const int max_timing_pos_num = 0) const; + const int min_timing_pos_num, const int max_timing_pos_num0) const; virtual void get_related_bins_factorised(std::vector&, const Bin& b, diff --git a/src/include/stir/recon_buildblock/distributable.h b/src/include/stir/recon_buildblock/distributable.h index 4eac6a1836..498601724d 100644 --- a/src/include/stir/recon_buildblock/distributable.h +++ b/src/include/stir/recon_buildblock/distributable.h @@ -176,7 +176,7 @@ void distributable_computation( const double end_time_of_frame, RPC_process_related_viewgrams_type * RPC_process_related_viewgrams, DistributedCachingInformation* caching_info_ptr, - int min_timing_pos_num = 0, int max_timing_pos_num = 0); + int min_timing_pos_num, int max_timing_pos_num); /*! \brief This function essentially implements a loop over a cached listmode file diff --git a/src/include/stir/recon_buildblock/distributableMPICacheEnabled.h b/src/include/stir/recon_buildblock/distributableMPICacheEnabled.h index 8544e8e9d3..e3bd20f4f6 100644 --- a/src/include/stir/recon_buildblock/distributableMPICacheEnabled.h +++ b/src/include/stir/recon_buildblock/distributableMPICacheEnabled.h @@ -66,7 +66,7 @@ void distributable_computation_cache_enabled( const double end_time_of_frame, RPC_process_related_viewgrams_type * RPC_process_related_viewgrams, DistributedCachingInformation* caching_info_ptr, - int min_timing_pos_num = 0, int max_timing_pos_num = 0 + int min_timing_pos_num, int max_timing_pos_num ); From 53dbdc1ce9c4186c28d846288b20b823040b203b Mon Sep 17 00:00:00 2001 From: Robert-PrescientImaging <117300855+Robert-PrescientImaging@users.noreply.github.com> Date: Thu, 4 Jan 2024 12:28:09 -0800 Subject: [PATCH 416/509] some TOF Codacy warning fixes for the GATE consistency test Fixes line width in python documentation, markdown bullet point spacing and Prettier formatting, and default nonTOF_distance_threshold in consistency test to 0.f --- .../debug_consistency_with_root.py | 2 +- .../ROOT_STIR_consistency/README.md | 71 ++++++++----------- src/recon_test/test_consistency_with_GATE.cxx | 4 +- 3 files changed, 34 insertions(+), 43 deletions(-) diff --git a/examples/ROOT_files/ROOT_STIR_consistency/DebugScripts/debug_consistency_with_root.py b/examples/ROOT_files/ROOT_STIR_consistency/DebugScripts/debug_consistency_with_root.py index ef06e73959..9a38f598e7 100644 --- a/examples/ROOT_files/ROOT_STIR_consistency/DebugScripts/debug_consistency_with_root.py +++ b/examples/ROOT_files/ROOT_STIR_consistency/DebugScripts/debug_consistency_with_root.py @@ -124,7 +124,7 @@ def point_cloud_3D_all(dict_of_data_handlers): """ generates a single plot with pointclouds of generated LOR-positions, original source-positions and tolerance-whiskers for all sources Args: - dict_of_data_handlers (dictionary containing objects of type ROOTConsistencyDataHandler): dictionary of objects containing all relevant data for the plot + dict_of_data_handlers (dict[int, ROOTConsistencyDataHandler]): dictionary of objects containing all relevant data for the plot """ fig = plt.figure(figsize=(10, 10)) diff --git a/examples/ROOT_files/ROOT_STIR_consistency/README.md b/examples/ROOT_files/ROOT_STIR_consistency/README.md index e2798f3681..96bdd6e181 100644 --- a/examples/ROOT_files/ROOT_STIR_consistency/README.md +++ b/examples/ROOT_files/ROOT_STIR_consistency/README.md @@ -1,65 +1,57 @@ # TOF consistency checks for STIR + ## Authors: Elise Emond & Robert Twyman -Copyright (C) 2022, University College London - This file is part of STIR. +Copyright (C) 2022, 2024 University College London +This file is part of STIR. SPDX-License-Identifier: Apache-2.0 See STIR/LICENSE.txt for details -These files were included to : -* Test non-TOF ROOT and STIR consistency, particularly the rotation. -* Test the TOF STIR implementation is correct. +These files are included to : + +- Test non-TOF ROOT and STIR consistency, particularly the rotation. +- Test the TOF STIR implementation is correct. -See src/recon_test/test_consistency_with_GATE.cxx. This test is run -automatically when using ctest. +See src/recon_test/test_consistency_with_GATE.cxx. This test is run automatically when using ctest. -Directories ------ +## Directories - `SourceFiles/`: Contains 8 `generate_image` parameter files and GATE macro files for the emission source positions. One pair of files for each simulation. - `Gate_macros/`: Contains the GATE macro files for generating the data. - `DebugScripts/`: Contains scripts for better understanding the tests. - -FILES ----- +## FILES - `README.md`: This file. - `root_header_test_template.hroot`: Template file for generating root header files for each of the test list mode data files that are generated by GATE. - `run_pretest_script.sh`: The main script for generating the data (requires GATE). This script runs GATE simulations for each test emission and generates the root header files for them. +--- -______ +## Methodology -Methodology ----- - 1. Get the ROOT data: either - - Run `./run_pretest_script.sh` in the terminal to generate the ROOT files (requires Gate) for different point sources, or - - Download the ROOT data and proceed without Gate simulation. This is done - by ctest when the STIR build was configured with `DOWNLOAD_ZENODO_TEST_DATA=ON`. - - 2. Run the STIR test: `src/recon_test/test_consistency_with_GATE` (Best via ctest). - This test should tell you whether it failed or not by testing if the LOR passes by, - or close to, the original point source position. - 3. Run the python scripts in `DebugScripts` to better understand errors and to give a more in-depth analysis. +1. Get the ROOT data: either + - Run `./run_pretest_script.sh` in the terminal to generate the ROOT files (requires Gate) for different point sources, or + - Download the ROOT data and proceed without Gate simulation. This is done by ctest when the STIR build was configured with `DOWNLOAD_ZENODO_TEST_DATA=ON`. +2. Run the STIR test: `src/recon_test/test_consistency_with_GATE` (Best via ctest). This test should tell you whether it failed or not by testing if the LOR passes by, or close to, the original point source position. +3. Run the python scripts in `DebugScripts` to better understand errors and to give a more in-depth analysis. +--- -_____ +## SOURCE POSITION CONFIGURATION -SOURCE POSITION CONFIGURATION ------ In GATE coordinates (`mm`), the point sources positions are as follows: -| ID | x | y | z | comment | -| :---: | :---: | :----: | :---: | :---: | -| **1** | 0 | 0 | 0 | center scanner | -| **2** | 190 | 0 | 0 | +x | -| **3** | 0 | 190 | 0 | +y | -| **4** | 95 | 95 | 0 | +x +y | -| **5** | 0 | 0 | 70 | +z | -| **6** | 190 | 0 | 70 | +x +z | -| **7** | 0 | 190 | 70 | +y +z | -| **8** | 95 | 95 | 70 | +x +y +z | +| ID | x | y | z | comment | +| :---: | :-: | :-: | :-: | :------------: | +| **1** | 0 | 0 | 0 | center scanner | +| **2** | 190 | 0 | 0 | +x | +| **3** | 0 | 190 | 0 | +y | +| **4** | 95 | 95 | 0 | +x +y | +| **5** | 0 | 0 | 70 | +z | +| **6** | 190 | 0 | 70 | +x +z | +| **7** | 0 | 190 | 70 | +y +z | +| **8** | 95 | 95 | 70 | +x +y +z | _Note_: The activity of testIDs 5-8 are 10x that of 1-4 because of the large z-shift. @@ -70,9 +62,8 @@ This is given by: ``` stir_z = (L-d)/2 + gate_z -``` +``` where `L` is the scanner z-length (`157.16 mm`) and `d = 6.54mm` is the distance between rings . For the data currently used in this test, `(L-d)/2 = 75.31mm`. -There are no translations in x or y. - +There are no translations in x or y. diff --git a/src/recon_test/test_consistency_with_GATE.cxx b/src/recon_test/test_consistency_with_GATE.cxx index 14327c418b..c04abcbc02 100644 --- a/src/recon_test/test_consistency_with_GATE.cxx +++ b/src/recon_test/test_consistency_with_GATE.cxx @@ -164,7 +164,7 @@ class GATEConsistencyTests : public RunTests //! The number of nonTOF LORs that failed the closest approach test. int num_failed_nonTOF_lor_events = 0; //! The threshold distance for the closest approach test. - double nonTOF_distance_threshold; + double nonTOF_distance_threshold = 0.f; /// TOF VARIABLES //! A vector to store the coordinates of the maximum voxels for each LOR. @@ -226,7 +226,7 @@ setup() num_events_tested = 0; // Find threshold for failure - nonTOF_distance_threshold = 1.5 * norm(grid_spacing); // Using norm(grid_spacing) as a nonTOF_distance_threshold + nonTOF_distance_threshold = 1.5 * norm(grid_spacing); { // With the default files, we found that 3.3*norm(grid_spacing) is a reasonable limit. // We try to generalise this to other data (although it will likely need further From 4088ec399decf3b90337c5ff6a728e8d6afdcc61 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Fri, 5 Jan 2024 07:35:32 +0000 Subject: [PATCH 417/509] removed some test-scanners from Scanner --- src/buildblock/Scanner.cxx | 40 ++------------------------------ src/include/stir/Scanner.h | 4 ++-- src/test/test_time_of_flight.cxx | 6 ++--- 3 files changed, 6 insertions(+), 44 deletions(-) diff --git a/src/buildblock/Scanner.cxx b/src/buildblock/Scanner.cxx index 6ba82971ae..a2ec9d6564 100644 --- a/src/buildblock/Scanner.cxx +++ b/src/buildblock/Scanner.cxx @@ -394,20 +394,6 @@ Scanner::Scanner(Type scanner_type) (float)(0.F)); break; - case ntest_TOF_50: // dummy - // 8x8 blocks, 1 virtual "crystal", 56 blocks along the ring, 8 blocks in axial direction - // Transaxial blocks have 8 physical crystals and a gap at the - // 9th crystal where the counts are zero. - set_params(ntest_TOF_50, string_list("ntest_TOF_50"), - 24, 320, 320,666, - 424.5F, 7.0F, 4.16F, 2.0F, 0.0F, - 1, 1, 24, 1, 24, 1, 1, - 0.0f, 511.f, - (short int)(2999), - (float)(1.0F), - (float)(81.2) ); // TODO bucket/singles info incorrect? 224 buckets in total, but not sure how di$ - break; - case DiscoveryRX: set_params(DiscoveryRX, string_list("GE Discovery RX", "Discovery RX"), @@ -445,7 +431,7 @@ Scanner::Scanner(Type scanner_type) 1, 1.F, 1.F); break; -case PETMR_Signa: + case PETMR_Signa: set_params(PETMR_Signa, string_list("GE Signa PET/MR", "PET/MR Signa", "Signa PET/MR"), 45, 357, @@ -462,32 +448,10 @@ case PETMR_Signa: 0.105F, // energy resolution from Levin et al. TMI 2016 511.F, (short int)(351), - (float)(89.0F/13.0F), //TODO + (float)(89.0F/13.0F), (float)(390.0F) ); break; - case PETMR_Signa_nonTOF: - - set_params(PETMR_Signa_nonTOF, string_list("GE PET/MR Signa nonTOF", "GE PET/MR Signa nonTOF"), - 45, - 357, - 331, // TODO - 2 * 224, - 311.8F, - 8.5F, - 5.56F, - 2.01565F, // TO CHECK - static_cast(-5.23*_PI/180),//sign? TODO value - 5, - 4, - 9, 4, 1, 1, 1, - 0.105F, // energy resolution from Levin et al. TMI 2016 - 511.F, - (short int)(0), - (float)(0), //TODO - (float)(0) ); - break; - case Discovery690: // same as 710 set_params(Discovery690, string_list("GE Discovery 690", "Discovery 690", diff --git a/src/include/stir/Scanner.h b/src/include/stir/Scanner.h index 81cfa810da..067bf22ddf 100644 --- a/src/include/stir/Scanner.h +++ b/src/include/stir/Scanner.h @@ -139,10 +139,10 @@ class Scanner any given parameters. */ enum Type {E931, E951, E953, E921, E925, E961, E962, E966, E1080, test_scanner, Siemens_mMR,Siemens_mCT, Siemens_Vision_600, RPT,HiDAC, - Advance, DiscoveryLS, DiscoveryST, DiscoverySTE, DiscoveryRX, Discovery600, PETMR_Signa, PETMR_Signa_nonTOF, + Advance, DiscoveryLS, DiscoveryST, DiscoverySTE, DiscoveryRX, Discovery600, PETMR_Signa, Discovery690, DiscoveryMI3ring, DiscoveryMI4ring, DiscoveryMI5ring, HZLR, RATPET, PANDA, HYPERimage, nanoPET, HRRT, Allegro, GeminiTF, SAFIRDualRingPrototype, UPENN_5rings, UPENN_5rings_no_gaps, UPENN_6rings, UPENN_6rings_no_gaps, - User_defined_scanner,ntest_TOF_50, + User_defined_scanner, Unknown_scanner}; virtual ~Scanner() {} diff --git a/src/test/test_time_of_flight.cxx b/src/test/test_time_of_flight.cxx index 06ff5e8f33..26f48b9c95 100644 --- a/src/test/test_time_of_flight.cxx +++ b/src/test/test_time_of_flight.cxx @@ -107,8 +107,6 @@ class TOF_Tests : public RunTests shared_ptr test_scanner_sptr; shared_ptr test_proj_data_info_sptr; - - shared_ptr test_nonTOF_scanner_sptr; shared_ptr test_nonTOF_proj_data_info_sptr; shared_ptr > test_discretised_density_sptr; @@ -121,7 +119,6 @@ TOF_Tests::run_tests() { // New Scanner test_scanner_sptr.reset(new Scanner(Scanner::PETMR_Signa)); - test_nonTOF_scanner_sptr.reset(new Scanner(Scanner::PETMR_Signa_nonTOF)); // New Proj_Data_Info const int test_tof_mashing_factor = 39; // to have 9 TOF bins (381/39=9) @@ -132,11 +129,12 @@ TOF_Tests::run_tests() /* arc_correction*/false)); test_proj_data_info_sptr->set_tof_mash_factor(test_tof_mashing_factor); - test_nonTOF_proj_data_info_sptr.reset(ProjDataInfo::ProjDataInfoCTI(test_nonTOF_scanner_sptr, + test_nonTOF_proj_data_info_sptr.reset(ProjDataInfo::ProjDataInfoCTI(test_scanner_sptr, 1,test_scanner_sptr->get_num_rings() -1, test_scanner_sptr->get_num_detectors_per_ring()/2, test_scanner_sptr->get_max_num_non_arccorrected_bins(), /* arc_correction*/false)); + test_nonTOF_proj_data_info_sptr->set_tof_mash_factor(0); test_tof_proj_data_info(); // test_tof_geometry_1(); From ddd3e0c73f4ca8b8bb78a20c98cca05a12b069ea Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Fri, 5 Jan 2024 07:15:51 +0000 Subject: [PATCH 418/509] TOF Interfile header writing fix We were still writing "%TOF mashing factor". --- src/IO/interfile.cxx | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/src/IO/interfile.cxx b/src/IO/interfile.cxx index dd77e45d17..b7928a8507 100644 --- a/src/IO/interfile.cxx +++ b/src/IO/interfile.cxx @@ -1302,9 +1302,8 @@ write_basic_interfile_PDFS_header(const string& header_file_name, // it's PET data if we get here // N.E. Added timing locations - pdfs.get_proj_data_info_sptr()->get_num_tof_poss()>1 ? - output_header << "number of dimensions := 5\n" : - output_header << "number of dimensions := 4\n"; + const bool is_TOF = pdfs.get_proj_data_info_sptr()->get_num_tof_poss()>1; + output_header << "number of dimensions := " + std::to_string(is_TOF ? 5: 4) + "\n"; // TODO support more ? { @@ -1386,15 +1385,10 @@ write_basic_interfile_PDFS_header(const string& header_file_name, output_header << "!matrix size [" << order_of_bin << "] := " <get_num_tangential_poss() << "\n"; - // If TOF is supported; add this in the header. - if (pdfs.get_proj_data_info_sptr()->get_scanner_ptr()->is_tof_ready() && - pdfs.get_proj_data_info_sptr()->get_num_tof_poss() > 1) + if (is_TOF) { - // Moved in scanner section - // output_header << "%number of TOF time bins :=" << - // pdfs.get_proj_data_info_ptr()->get_scanner_ptr()->get_num_max_of_timing_bins() << "\n"; - output_header << "%TOF mashing factor := " << - pdfs.get_proj_data_info_sptr()->get_tof_mash_factor() << "\n"; + output_header << "TOF mashing factor := " << + pdfs.get_proj_data_info_sptr()->get_tof_mash_factor() << "\n"; } } From a93870d6a06204e81b58b38125d3024fc4b2ba8f Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Fri, 5 Jan 2024 07:38:12 +0000 Subject: [PATCH 419/509] change defaults in ProjDataInfo::ask_parameters and remove span=0 also add changes to this function to release notes --- documentation/release_notes_TOF.htm | 24 ++++++++-- ...run_test_simulate_and_recon_with_motion.sh | 6 +-- recon_test_pack/run_test_zoom_image.sh | 9 ++-- .../simulate_PET_data_for_tests.sh | 7 ++- recon_test_pack/template_for_ROOT_scanner.hs | 47 +++++++++---------- src/buildblock/ProjDataInfo.cxx | 26 ++++------ 6 files changed, 61 insertions(+), 58 deletions(-) diff --git a/documentation/release_notes_TOF.htm b/documentation/release_notes_TOF.htm index 6cc9626158..47238e1ec6 100644 --- a/documentation/release_notes_TOF.htm +++ b/documentation/release_notes_TOF.htm @@ -66,8 +66,18 @@

        Changed functionality

        Changed functionality breaking backwards incompatibility

          -
        • virtual ListModeData::get_scanner_ptr() is replaced by ListModeData::get_scanner(). -
        • +
        • + ProjDataInfo::ask_parameters() and therefore create_projdata_template + has changed: +
            +
          1. If the scanner definition in STIR has TOF capabilities, it will ask for the TOF mashing factor.
          2. +
          3. The default for arc-correction has changed to N, i.e. false.
          4. +
          5. Default value for span is now 11 for Siemens and 2 for GE scanners.
          6. +
          7. The span=0 case (i.e. span-3 for segment 0, span=1 for oblique ones, erroneously + by STIR used for the GE Advance) is no longer allowed. GE uses span=2.
            + (Reading a "span=0" case is still supported")
          8. +
          +
        @@ -118,9 +128,13 @@

        Major bugs fixed

        Backward incompatibities

          -
        • ProjDataInfo*NoArcCorr::get_bin_for_det_pair is now private. - Use get_bin_for_det_pos_pair instead. -
        • +
        • + virtual ListModeData::get_scanner_ptr() is replaced by ListModeData::get_scanner(). +
        • +
        • + ProjDataInfo*NoArcCorr::get_bin_for_det_pair is now private. + Use get_bin_for_det_pos_pair instead. +

        New functionality

        diff --git a/recon_test_pack/run_test_simulate_and_recon_with_motion.sh b/recon_test_pack/run_test_simulate_and_recon_with_motion.sh index 46527cc989..2dec1319a7 100755 --- a/recon_test_pack/run_test_simulate_and_recon_with_motion.sh +++ b/recon_test_pack/run_test_simulate_and_recon_with_motion.sh @@ -113,16 +113,16 @@ if [ $? -ne 0 ]; then echo "ERROR running warp_and_accumulate_gated_images"; exit 1; fi -echo "=== create template sinogram (DSTE in 3D with max ring diff 2 to save time)" -template_sino=my_DSTE_3D_rd2_template.hs +echo "=== create template sinogram (DSTE in 3D with max ring diff 3 to save time)" +template_sino=my_DSTE_3D_rd3_template.hs cat > my_input.txt < my_create_${template_sino}.log 2>&1 if [ $? -ne 0 ]; then diff --git a/recon_test_pack/run_test_zoom_image.sh b/recon_test_pack/run_test_zoom_image.sh index f411f8277c..9d1d42cd62 100755 --- a/recon_test_pack/run_test_zoom_image.sh +++ b/recon_test_pack/run_test_zoom_image.sh @@ -143,17 +143,16 @@ fi echo "=== make attenuation image" generate_image generate_atten_cylinder.par -echo "=== create template sinogram (DSTE in 3D with max ring diff 2 to save time)" -template_sino=my_DSTE_3D_rd2_template.hs +echo "=== create template sinogram (DSTE in 3D with max ring diff 3 to save time)" +template_sino=my_DSTE_3D_rd3_template.hs cat > my_input.txt < my_create_${template_sino}.log 2>&1 if [ $? -ne 0 ]; then diff --git a/recon_test_pack/simulate_PET_data_for_tests.sh b/recon_test_pack/simulate_PET_data_for_tests.sh index dfc29ecd7b..cf711ee1df 100755 --- a/recon_test_pack/simulate_PET_data_for_tests.sh +++ b/recon_test_pack/simulate_PET_data_for_tests.sh @@ -77,17 +77,16 @@ generate_image generate_uniform_cylinder.par echo "=== make attenuation image" generate_image generate_atten_cylinder.par if [ "$TOF" -eq 0 ]; then - echo "=== create template sinogram (DSTE in 3D with max ring diff 2 to save time)" - template_sino=my_DSTE_3D_rd2_template.hs + echo "=== create template sinogram (DSTE in 3D with max ring diff 3 to save time)" + template_sino=my_DSTE_3D_rd3_template.hs cat > my_input.txt < scanner_ptr(Scanner::ask_parameters()); - const bool is_Advance = - scanner_ptr->get_type() == Scanner::Advance || - scanner_ptr->get_type() == Scanner::DiscoveryLS; - const bool is_DiscoveryST = - scanner_ptr->get_type() == Scanner::DiscoveryST; - const bool is_GE = - is_Advance || is_DiscoveryST; - const int num_views = scanner_ptr->get_max_num_views()/ ask_num("Mash factor for views",1, scanner_ptr->get_max_num_views(),1); const int tof_mash_factor = scanner_ptr->is_tof_ready() ? ask_num("Time-of-flight mash factor:", 0, - scanner_ptr->get_max_num_timing_poss(), 25) : 0; + scanner_ptr->get_max_num_timing_poss(), 1) : 0; const bool arc_corrected = - ask("Is the data arc-corrected?",true); + ask("Is the data arc-corrected?",false); const int num_tangential_poss= ask_num("Number of tangential positions",1, @@ -739,22 +731,22 @@ ProjDataInfo* ProjDataInfo::ask_parameters() arc_corrected ? scanner_ptr->get_default_num_arccorrected_bins() : scanner_ptr->get_max_num_non_arccorrected_bins()); - - int span = is_GE ? 0 : 1; + + const bool is_GE = scanner_ptr->get_name().substr(0,2) == "GE"; + const bool is_Siemens = scanner_ptr->get_name().substr(0,6) == "Siemens"; + int span = is_GE ? 2 : (is_Siemens ? 11 : 1); span = - ask_num("Span value (use 0 for mixed-span case of the Advance) : ", 0,scanner_ptr->get_num_rings()-1,span); + ask_num("Span value : ", 0,scanner_ptr->get_num_rings()-1,span); const int max_delta = ask_num("Max. ring difference acquired : ", 0, scanner_ptr->get_num_rings()-1, - is_Advance ? 11 : scanner_ptr->get_num_rings()-1); + scanner_ptr->get_num_rings()-1); ProjDataInfo * pdi_ptr = - span==0 - ? ProjDataInfoGE(scanner_ptr,max_delta,num_views,num_tangential_poss,arc_corrected, tof_mash_factor) - : ProjDataInfoCTI(scanner_ptr,span,max_delta,num_views,num_tangential_poss,arc_corrected, tof_mash_factor); + ProjDataInfoCTI(scanner_ptr,span,max_delta,num_views,num_tangential_poss,arc_corrected, tof_mash_factor); cout << pdi_ptr->parameter_info() < Date: Fri, 5 Jan 2024 07:44:47 +0000 Subject: [PATCH 420/509] changed Interfile header keyword to "TOF timing resolution (ps)" was "Timing resolution (ps)" --- src/IO/InterfileHeader.cxx | 6 +++++- src/buildblock/Scanner.cxx | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/IO/InterfileHeader.cxx b/src/IO/InterfileHeader.cxx index 06ce4af7bc..22f6a12da4 100644 --- a/src/IO/InterfileHeader.cxx +++ b/src/IO/InterfileHeader.cxx @@ -635,8 +635,12 @@ InterfilePDFSHeader::InterfilePDFSHeader() add_alias_key("Size of unmashed TOF time bins (ps)", "Size of timing bin (ps)"); #endif timing_resolution = -1.f; - add_key("Timing resolution (ps)", + add_key("TOF timing resolution (ps)", &timing_resolution); +#if STIR_VERSION < 070000 + add_alias_key("TOF timing resolution (ps)", "timing resolution (ps)"); +#endif + // new keys for block geometry scanner_geometry = "Cylindrical"; add_key("Scanner geometry (BlocksOnCylindrical/Cylindrical/Generic)", diff --git a/src/buildblock/Scanner.cxx b/src/buildblock/Scanner.cxx index a2ec9d6564..98661c3162 100644 --- a/src/buildblock/Scanner.cxx +++ b/src/buildblock/Scanner.cxx @@ -1393,7 +1393,7 @@ Scanner::parameter_info() const { s << "Maximum number of (unmashed) TOF time bins :=" << get_max_num_timing_poss() << "\n"; s << "Size of unmashed TOF time bins (ps) :=" << get_size_of_timing_pos() << "\n"; - s << "Timing resolution (ps) :=" << get_timing_resolution() << "\n"; + s << "TOF timing resolution (ps) :=" << get_timing_resolution() << "\n"; } // block/bucket description From 293d0f671cc8bb4df84aa785023fd2803d98ee8a Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Fri, 5 Jan 2024 07:46:48 +0000 Subject: [PATCH 421/509] decrease precision in checking of TOF keywords also use new names for the keywords --- src/IO/InterfileHeader.cxx | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/IO/InterfileHeader.cxx b/src/IO/InterfileHeader.cxx index 22f6a12da4..b5c5252970 100644 --- a/src/IO/InterfileHeader.cxx +++ b/src/IO/InterfileHeader.cxx @@ -1367,23 +1367,23 @@ bool InterfilePDFSHeader::post_processing() if (guessed_scanner_ptr->is_tof_ready()) { if (max_num_timing_poss != guessed_scanner_ptr->get_max_num_timing_poss()) - { - warning("Interfile warning: 'Number of TOF time bins' (%d) is expected to be %d.", - max_num_timing_poss, guessed_scanner_ptr->get_max_num_timing_poss()); + { + warning(boost::format("Interfile warning: 'Maximum number of (unmashed) TOF time bins' (%d) is expected to be %d.") % + max_num_timing_poss % guessed_scanner_ptr->get_max_num_timing_poss()); mismatch_between_header_and_guess = true; - } - if (size_of_timing_pos != guessed_scanner_ptr->get_size_of_timing_pos()) - { - warning("Interfile warning: 'Size of timing bin (ps)' (%f) is expected to be %f.", - size_of_timing_pos, guessed_scanner_ptr->get_size_of_timing_pos()); + } + if (abs(size_of_timing_pos - guessed_scanner_ptr->get_size_of_timing_pos()) > 0.001F) + { + warning(boost::format("Interfile warning: 'Size of unmashed TOF timing bin (ps)' (%f) is expected to be %f.") % + size_of_timing_pos % guessed_scanner_ptr->get_size_of_timing_pos()); mismatch_between_header_and_guess = true; - } - if (timing_resolution != guessed_scanner_ptr->get_timing_resolution()) - { - warning("Interfile warning: 'Timing resolution (ps)' (%f) is expected to be %f.", - timing_resolution, guessed_scanner_ptr->get_timing_resolution()); + } + if (abs(timing_resolution - guessed_scanner_ptr->get_timing_resolution()) > 0.01F) + { + warning(boost::format("Interfile warning: 'TOF timing resolution (ps)' (%f) is expected to be %f.") % + timing_resolution % guessed_scanner_ptr->get_timing_resolution()); mismatch_between_header_and_guess = true; - } + } } // end of checks. If they failed, we ignore the guess From 0818f693f2e74e788f92e8137f0108057d5c94b2 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Fri, 5 Jan 2024 07:53:52 +0000 Subject: [PATCH 422/509] nicer formatting of Scanner::parameter_info --- src/buildblock/Scanner.cxx | 60 +++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/src/buildblock/Scanner.cxx b/src/buildblock/Scanner.cxx index 98661c3162..4ee8cc8a39 100644 --- a/src/buildblock/Scanner.cxx +++ b/src/buildblock/Scanner.cxx @@ -1367,74 +1367,74 @@ Scanner::parameter_info() const { // warning: these should match the parsing keywords in InterfilePDFSHeader std::ostringstream s; - s << "Scanner parameters:= "<<'\n'; + s << "Scanner parameters:="<<'\n'; - s << "Scanner type := " << get_name() <<'\n'; + s << " Scanner type := " << get_name() <<'\n'; - s << "Number of rings := " << num_rings << '\n'; - s << "Number of detectors per ring := " << get_num_detectors_per_ring() << '\n'; + s << " Number of rings := " << num_rings << '\n'; + s << " Number of detectors per ring := " << get_num_detectors_per_ring() << '\n'; - s << "Inner ring diameter (cm) := " << get_inner_ring_radius()*2./10 << '\n' - << "Average depth of interaction (cm) := " << get_average_depth_of_interaction() / 10 << '\n' - << "Distance between rings (cm) := " << get_ring_spacing()/10 << '\n' - << "Default bin size (cm) := " << get_default_bin_size()/10. << '\n' - << "View offset (degrees) := " << get_intrinsic_azimuthal_tilt()*180/_PI << '\n'; - s << "Maximum number of non-arc-corrected bins := " + s << " Inner ring diameter (cm) := " << get_inner_ring_radius()*2./10 << '\n' + << " Average depth of interaction (cm) := " << get_average_depth_of_interaction() / 10 << '\n' + << " Distance between rings (cm) := " << get_ring_spacing()/10 << '\n' + << " Default bin size (cm) := " << get_default_bin_size()/10. << '\n' + << " View offset (degrees) := " << get_intrinsic_azimuthal_tilt()*180/_PI << '\n'; + s << " Maximum number of non-arc-corrected bins := " << get_max_num_non_arccorrected_bins() << '\n' - << "Default number of arc-corrected bins := " + << " Default number of arc-corrected bins := " << get_default_num_arccorrected_bins() << '\n'; if (get_energy_resolution() >= 0 && get_reference_energy() >= 0) { - s << "Energy resolution := " << get_energy_resolution() << '\n'; - s << "Reference energy (in keV) := " << get_reference_energy() << '\n'; + s << " Energy resolution := " << get_energy_resolution() << '\n'; + s << " Reference energy (in keV) := " << get_reference_energy() << '\n'; } if (is_tof_ready()) { - s << "Maximum number of (unmashed) TOF time bins :=" << get_max_num_timing_poss() << "\n"; - s << "Size of unmashed TOF time bins (ps) :=" << get_size_of_timing_pos() << "\n"; - s << "TOF timing resolution (ps) :=" << get_timing_resolution() << "\n"; + s << " Maximum number of (unmashed) TOF time bins := " << get_max_num_timing_poss() << '\n'; + s << " Size of unmashed TOF time bins (ps) := " << get_size_of_timing_pos() << '\n'; + s << " TOF timing resolution (ps) := " << get_timing_resolution() << '\n'; } // block/bucket description - s << "Number of blocks per bucket in transaxial direction := " + s << " Number of blocks per bucket in transaxial direction := " << get_num_transaxial_blocks_per_bucket() << '\n' - << "Number of blocks per bucket in axial direction := " + << " Number of blocks per bucket in axial direction := " << get_num_axial_blocks_per_bucket() << '\n' - << "Number of crystals per block in axial direction := " + << " Number of crystals per block in axial direction := " << get_num_axial_crystals_per_block() << '\n' - << "Number of crystals per block in transaxial direction := " + << " Number of crystals per block in transaxial direction := " << get_num_transaxial_crystals_per_block() << '\n' - << "Number of detector layers := " + << " Number of detector layers := " << get_num_detector_layers() << '\n' - << "Number of crystals per singles unit in axial direction := " + << " Number of crystals per singles unit in axial direction := " << get_num_axial_crystals_per_singles_unit() << '\n' - << "Number of crystals per singles unit in transaxial direction := " + << " Number of crystals per singles unit in transaxial direction := " << get_num_transaxial_crystals_per_singles_unit() << '\n'; //block and generic geometry description if (crystal_map_file_name != "") - s << "Name of crystal map := " + s << " Name of crystal map := " << get_crystal_map_file_name() << '\n'; if (get_scanner_geometry() != "") { - s << "Scanner geometry (BlocksOnCylindrical/Cylindrical/Generic) := " + s << " Scanner geometry (BlocksOnCylindrical/Cylindrical/Generic) := " <=0) - s << "Distance between crystals in axial direction (cm) := " + s << " Distance between crystals in axial direction (cm) := " << get_axial_crystal_spacing()/10 << '\n'; if (get_transaxial_crystal_spacing() >=0) - s << "Distance between crystals in transaxial direction (cm) := " + s << " Distance between crystals in transaxial direction (cm) := " << get_transaxial_crystal_spacing()/10 << '\n'; if (get_axial_block_spacing() >=0) - s << "Distance between blocks in axial direction (cm) := " + s << " Distance between blocks in axial direction (cm) := " << get_axial_block_spacing()/10 << '\n'; if (get_transaxial_block_spacing() >=0) - s << "Distance between blocks in transaxial direction (cm) := " + s << " Distance between blocks in transaxial direction (cm) := " << get_transaxial_block_spacing()/10 << '\n'; - s << "end scanner parameters:=\n"; + s << "End scanner parameters:=\n"; return s.str(); } From be414b2336abfed8574e22c4a4d0df0e485e59cb Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Fri, 5 Jan 2024 08:47:00 +0000 Subject: [PATCH 423/509] add example TOF interfile header --- .../PET_TOF_Interfile_header_Signa_PETMR.hs | 62 +++++++++++++++++++ .../PET_TOF_Interfile_header_Signa_PETMR.s | 0 2 files changed, 62 insertions(+) create mode 100644 examples/samples/PET_TOF_Interfile_header_Signa_PETMR.hs create mode 100644 examples/samples/PET_TOF_Interfile_header_Signa_PETMR.s diff --git a/examples/samples/PET_TOF_Interfile_header_Signa_PETMR.hs b/examples/samples/PET_TOF_Interfile_header_Signa_PETMR.hs new file mode 100644 index 0000000000..29cead4dc6 --- /dev/null +++ b/examples/samples/PET_TOF_Interfile_header_Signa_PETMR.hs @@ -0,0 +1,62 @@ +!INTERFILE := +; sample Interfile header, created with create_projdata_template for the GE Signa PET/MR +; with span=2 and full ring difference (which is how GE compresses their sinograms) +; (and some edits for clarification) +; Check PET_Interfile_header.hs for more information and some other keywords + +!imaging modality := PT +name of data file := PET_TOF_Interfile_header_Signa_PETMR.s +originating system := GE Signa PET/MR +!version of keys := STIR6.0 +!GENERAL DATA := +!GENERAL IMAGE DATA := +!type of data := PET +imagedata byte order := LITTLEENDIAN +!PET STUDY (General) := +!PET data type := Emission +applied corrections := {None} +!number format := float +!number of bytes per pixel := 4 +number of dimensions := 5 +matrix axis label [5] := timing positions +!matrix size [5] := 27 +matrix axis label [4] := segment +!matrix size [4] := 45 +matrix axis label [3] := view +!matrix size [3] := 224 +matrix axis label [2] := axial coordinate +!matrix size [2] := { 1,5,9,13,17,21,25,29,33,37,41,45,49,53,57,61,65,69,73,77,81,85,89,85,81,77,73,69,65,61,57,53,49,45,41,37,33,29,25,21,17,13,9,5,1} +matrix axis label [1] := tangential coordinate +!matrix size [1] := 357 +TOF mashing factor := 13 +minimum ring difference per segment := { -44,-43,-41,-39,-37,-35,-33,-31,-29,-27,-25,-23,-21,-19,-17,-15,-13,-11,-9,-7,-5,-3,-1,2,4,6,8,10,12,14,16,18,20,22,24,26,28,30,32,34,36,38,40,42,44} +maximum ring difference per segment := { -44,-42,-40,-38,-36,-34,-32,-30,-28,-26,-24,-22,-20,-18,-16,-14,-12,-10,-8,-6,-4,-2,1,3,5,7,9,11,13,15,17,19,21,23,25,27,29,31,33,35,37,39,41,43,44} +Scanner parameters:= + Scanner type := GE Signa PET/MR + Number of rings := 45 + Number of detectors per ring := 448 + Inner ring diameter (cm) := 62.36 + Average depth of interaction (cm) := 0.85 + Distance between rings (cm) := 0.556 + Default bin size (cm) := 0.201565 + View offset (degrees) := -5.23 + Maximum number of non-arc-corrected bins := 357 + Default number of arc-corrected bins := 331 + Energy resolution := 0.105 + Reference energy (in keV) := 511 + Maximum number of (unmashed) TOF time bins := 351 + Size of unmashed TOF time bins (ps) := 6.84615 + TOF timing resolution (ps) := 390 + Number of blocks per bucket in transaxial direction := 4 + Number of blocks per bucket in axial direction := 5 + Number of crystals per block in axial direction := 9 + Number of crystals per block in transaxial direction := 4 + Number of detector layers := 1 + Number of crystals per singles unit in axial direction := 1 + Number of crystals per singles unit in transaxial direction := 1 +End scanner parameters:= + +number of time frames := 1 +start vertical bed position (mm) := 0 +start horizontal bed position (mm) := 0 +!END OF INTERFILE := diff --git a/examples/samples/PET_TOF_Interfile_header_Signa_PETMR.s b/examples/samples/PET_TOF_Interfile_header_Signa_PETMR.s new file mode 100644 index 0000000000..e69de29bb2 From d7330dcc15a66234b794fb1f89c95bea06d9e3e5 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Fri, 5 Jan 2024 08:48:05 +0000 Subject: [PATCH 424/509] use STIR6.0 when output an Interfile header to denote TOF changes this isn't currently used for parsing though --- documentation/release_notes_TOF.htm | 8 ++++++-- src/IO/interfile.cxx | 6 +++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/documentation/release_notes_TOF.htm b/documentation/release_notes_TOF.htm index 47238e1ec6..775d04fa28 100644 --- a/documentation/release_notes_TOF.htm +++ b/documentation/release_notes_TOF.htm @@ -58,10 +58,14 @@

        New functionality

        Changed functionality

          -
        • +
        • We now always check (ProjDataInfo*NoArcCorr) if number of tangential positions in the projection data exceeds the maximum number of non arc-corrected bins set for the scanner. If it is, an error is raised. -
        • + +
        • + Write STIR6.0 as Interfile key version to denote TOF changes. + This is currently ignored for parsing though. +

        Changed functionality breaking backwards incompatibility

        diff --git a/src/IO/interfile.cxx b/src/IO/interfile.cxx index b7928a8507..0f7aaad1fd 100644 --- a/src/IO/interfile.cxx +++ b/src/IO/interfile.cxx @@ -568,9 +568,9 @@ write_basic_interfile_image_header(const string& header_file_name, if (is_spect) output_header << "!version of keys := 3.3\n"; else - output_header << "!version of keys := STIR4.0\n"; + output_header << "!version of keys := STIR6.0\n"; #else - output_header << "!version of keys := STIR4.0\n"; + output_header << "!version of keys := STIR6.0\n"; #endif output_header << "name of data file := " << data_file_name_in_header << endl; @@ -1209,7 +1209,7 @@ write_basic_interfile_PDFS_header(const string& header_file_name, if (is_spect) output_header << "!version of keys := 3.3\n"; else - output_header << "!version of keys := STIR4.0\n"; + output_header << "!version of keys := STIR6.0\n"; output_header << "!GENERAL DATA :=\n"; output_header << "!GENERAL IMAGE DATA :=\n"; From c6b36ed36dd66f29726986a7c09b831b3159ec7d Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Fri, 5 Jan 2024 12:54:34 +0000 Subject: [PATCH 425/509] revert change and allow span=0 and use it in the scatter tests this is a work_around for https://github.com/UCL/STIR/issues/1302 --- documentation/release_notes_TOF.htm | 2 +- recon_test_pack/simulate_PET_data_for_tests.sh | 4 ++-- src/buildblock/ProjDataInfo.cxx | 7 ++++--- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/documentation/release_notes_TOF.htm b/documentation/release_notes_TOF.htm index 775d04fa28..6356d8e788 100644 --- a/documentation/release_notes_TOF.htm +++ b/documentation/release_notes_TOF.htm @@ -78,7 +78,7 @@

        Changed functionality breaking backwards incompatibility

      • The default for arc-correction has changed to N, i.e. false.
      • Default value for span is now 11 for Siemens and 2 for GE scanners.
      • The span=0 case (i.e. span-3 for segment 0, span=1 for oblique ones, erroneously - by STIR used for the GE Advance) is no longer allowed. GE uses span=2.
        + by STIR used for the GE Advance) is no deprecated. GE uses span=2.
        (Reading a "span=0" case is still supported")
  29. \c timing_pos_num : indexes different positions in the LOR, based on + the photon detection time difference. The number of axial positions is allowed to depend on segment_num. @@ -276,6 +278,14 @@ class ProjData : public ExamData inline int get_num_views() const; //! Get number of tangential positions inline int get_num_tangential_poss() const; + //! Get number of TOF positions + inline int get_num_timing_poss() const; + //! Get the index of the first timing position + inline int get_min_timing_pos_num() const; + //! Get the index of the last timgin position. + inline int get_max_timing_pos_num() const; + //! Get TOG mash factor + inline int get_tof_mash_factor() const; //! Get minimum segment number inline int get_min_segment_num() const; //! Get maximum segment number diff --git a/src/include/stir/ProjData.inl b/src/include/stir/ProjData.inl index 4007518aa0..c48e200f8a 100644 --- a/src/include/stir/ProjData.inl +++ b/src/include/stir/ProjData.inl @@ -3,6 +3,7 @@ \ingroup projdata \brief Implementations for inline functions of class stir::ProjData + \author Nikos Efthimiou \author Sanida Mustafovic \author Kris Thielemans \author PARAPET project @@ -11,6 +12,7 @@ Copyright (C) 2000 PARAPET partners Copyright (C) 2000-2009, Hammersmith Imanet Ltd Copyright (C) 2013, 2015 University College London + Copyright (C) 2016, University of Hull This file is part of STIR. This file is free software; you can redistribute it and/or modify @@ -66,6 +68,12 @@ int ProjData::get_num_views() const int ProjData::get_num_tangential_poss() const { return proj_data_info_ptr->get_num_tangential_poss(); } +int ProjData::get_num_timing_poss() const +{ return proj_data_info_ptr->get_num_timing_poss(); } + +int ProjData::get_tof_mash_factor() const +{ return proj_data_info_ptr->get_tof_mash_factor(); } + int ProjData::get_min_segment_num() const { return proj_data_info_ptr->get_min_segment_num(); } @@ -90,6 +98,12 @@ int ProjData::get_min_tangential_pos_num() const int ProjData::get_max_tangential_pos_num() const { return proj_data_info_ptr->get_max_tangential_pos_num(); } +int ProjData::get_min_timing_pos_num() const +{ return proj_data_info_ptr->get_min_timing_pos_num(); } + +int ProjData::get_max_timing_pos_num() const +{ return proj_data_info_ptr->get_max_timing_pos_num(); } + int ProjData::get_num_sinograms() const { int num_sinos = proj_data_info_ptr->get_num_axial_poss(0); diff --git a/src/include/stir/ProjDataInfo.h b/src/include/stir/ProjDataInfo.h index d1a453f641..0d6af07307 100644 --- a/src/include/stir/ProjDataInfo.h +++ b/src/include/stir/ProjDataInfo.h @@ -4,6 +4,7 @@ Copyright (C) 2000 PARAPET partners Copyright (C) 2000 - 2011-10-14, Hammersmith Imanet Ltd Copyright (C) 2011-07-01 - 2011, Kris Thielemans + Copyright (C) 2016, University of Hull This file is part of STIR. This file is free software; you can redistribute it and/or modify @@ -23,6 +24,7 @@ \brief Declaration of class stir::ProjDataInfo + \author Nikos Efthimiou \author Sanida Mustafovic \author Kris Thielemans \author PARAPET project @@ -103,6 +105,13 @@ class ProjDataInfo const int num_views, const int num_tangential_poss); + //! Overloaded Contructor with TOF initialisation + ProjDataInfo(const shared_ptr& scanner_ptr, + const VectorWithOffset& num_axial_pos_per_segment, + const int num_views, + const int num_tangential_poss, + const int tof_mash_factor); + //! Standard trick for a 'virtual copy-constructor' virtual ProjDataInfo* clone() const = 0; @@ -158,6 +167,8 @@ class ProjDataInfo //! Set maximum tangential position number /*! This function is virtual in case a derived class needs to know the number changed. */ virtual void set_max_tangential_pos_num(const int max_tang_poss); + //! The the tof mashing factor. Min and Max timing position will be recalculated. + virtual void set_tof_mash_factor(const int new_num); //@} //! \name Functions that return info on the data size @@ -186,6 +197,20 @@ class ProjDataInfo inline int get_min_tangential_pos_num() const; //! Get maximum tangential position number inline int get_max_tangential_pos_num() const; + //! Get number of TOF positions + inline int get_num_timing_poss() const; + //! Get TOG mash factor + inline int get_tof_mash_factor() const; + //! Get the index of the first timing position + inline int get_min_timing_pos_num() const; + //! Get the index of the last timgin position. + inline int get_max_timing_pos_num() const; + //! Get the coincide window in pico seconds + //! \warning Proposed convension: If the scanner is not TOF ready then + //! the coincidence windowis in the timing bin size. + inline float get_coincidence_window_in_pico_sec() const; + //! Get the total width of the coincide window in mm + inline float get_coincidence_window_width() const; //@} //| \name Functions that return geometrical info for a Bin @@ -227,6 +252,10 @@ class ProjDataInfo normal to the projection plane */ virtual float get_s(const Bin&) const =0; + //! Get value ot the timing location along the LOR (in mm) + //! k is a line segment connecting the centers of the two detectors. + float get_k(const Bin&) const; + //! Get LOR corresponding to a given bin /*! \see get_bin() @@ -266,6 +295,9 @@ class ProjDataInfo \endcode */ virtual float get_sampling_in_s(const Bin&) const; + + //! Get sampling distance in the k \c coordinate + float get_sampling_in_k(const Bin&) const; //@} @@ -326,6 +358,12 @@ class ProjDataInfo //! Return a string describing the object virtual std::string parameter_info() const; + + //! Struct which holds two floating numbers + struct Float1Float2 { float low_lim; float high_lim; }; + + //! Vector which holds the lower and higher boundary for each timing position, for faster access. + mutable VectorWithOffset timing_bin_boundaries; protected: virtual bool blindly_equals(const root_type * const) const = 0; @@ -336,6 +374,14 @@ class ProjDataInfo int max_view_num; int min_tangential_pos_num; int max_tangential_pos_num; + //! Minimum timing pos + int min_timing_pos_num; + //! Maximum timing pos + int max_timing_pos_num; + //! TOF mash factor. + int tof_mash_factor; + //! Finally (with any mashing factor) timing bin increament. + float timing_increament_in_mm; VectorWithOffset min_axial_pos_per_seg; VectorWithOffset max_axial_pos_per_seg; From f14178f9af185a778700a1e3da48a1e820e7ebbb Mon Sep 17 00:00:00 2001 From: NikEfth Date: Thu, 22 Dec 2016 10:53:27 +0000 Subject: [PATCH 032/509] More code Clean and better documented --- .../ProjDataInfoCylindricalNoArcCorr.cxx | 2 +- src/buildblock/Scanner.cxx | 1 + src/include/stir/ProjDataInfo.inl | 39 +++++- src/include/stir/ProjDataInfoCylindrical.h | 19 +++ .../stir/ProjDataInfoCylindricalNoArcCorr.h | 7 ++ src/include/stir/Scanner.h | 89 ++++++++++++-- src/include/stir/Scanner.inl | 37 ++++++ src/include/stir/geometry/line_distances.h | 30 +++++ src/include/stir/listmode/CListModeDataROOT.h | 7 ++ src/include/stir/listmode/CListRecord.h | 21 ++++ src/include/stir/listmode/CListRecordROOT.h | 53 ++++++--- .../GeneralisedObjectiveFunction.h | 7 ++ .../stir/recon_buildblock/ProjMatrixByBin.h | 42 ++++++- .../stir/recon_buildblock/ProjMatrixByBin.inl | 111 ++++++++++++++++++ src/listmode_buildblock/CListModeDataROOT.cxx | 19 ++- ...wardProjectorByBinUsingProjMatrixByBin.cxx | 2 +- .../GeneralisedObjectiveFunction.cxx | 19 ++- src/recon_buildblock/ProjMatrixByBin.cxx | 19 ++- 18 files changed, 491 insertions(+), 33 deletions(-) diff --git a/src/buildblock/ProjDataInfoCylindricalNoArcCorr.cxx b/src/buildblock/ProjDataInfoCylindricalNoArcCorr.cxx index 35754c5242..3a0ee2c858 100644 --- a/src/buildblock/ProjDataInfoCylindricalNoArcCorr.cxx +++ b/src/buildblock/ProjDataInfoCylindricalNoArcCorr.cxx @@ -498,7 +498,7 @@ find_cartesian_coordinates_given_scanner_coordinates (CartesianCoordinate3Dis_tof_ready()? (scanner_ptr->get_num_max_of_timing_bins() * + scanner_ptr->get_size_of_timing_bin()) + :(scanner_ptr->get_size_of_timing_bin()); +} + +float +ProjDataInfo::get_coincidence_window_width() const +{ + // Speed of light 0.299792458 mm / psec. + return get_coincidence_window_in_pico_sec() * 0.299792458f; +} + float ProjDataInfo::get_costheta(const Bin& bin) const { diff --git a/src/include/stir/ProjDataInfoCylindrical.h b/src/include/stir/ProjDataInfoCylindrical.h index 225064dbe1..cdbae24267 100644 --- a/src/include/stir/ProjDataInfoCylindrical.h +++ b/src/include/stir/ProjDataInfoCylindrical.h @@ -3,6 +3,7 @@ Copyright (C) 2000-2009, Hammersmith Imanet Ltd Copyright (C) 2013, University College London Copyright (C) 2013, Institute for Bioengineering of Catalonia + Copyright (C) 2016, University of Hull This file is part of STIR. This file is free software; you can redistribute it and/or modify @@ -23,6 +24,7 @@ \ingroup projdata \brief Declaration of class stir::ProjDataInfoCylindrical + \author Nikos Efthimiou \author Sanida Mustafovic \author Kris Thielemans \author Berta Marti Fuster @@ -33,6 +35,7 @@ #include "stir/ProjDataInfo.h" +#include "stir/CartesianCoordinate3D.h" #include #include @@ -96,6 +99,20 @@ class ProjDataInfoCylindrical: public ProjDataInfo get_LOR(LORInAxialAndNoArcCorrSinogramCoordinates& lor, const Bin& bin) const; + //! This function returns the two points connecting the two detectors of the LOR. + //! \warning there is not a specific guarantee that these are going to be the two + //! central points. This might be in the future a source of error. + void + get_LOR_as_two_points(CartesianCoordinate3D& coord_1, + CartesianCoordinate3D& coord_2, + const Bin& bin) const; + + //! This function is the same as get_LOR_as_two_points() but should faster. + //! \warning More testing needed. + void + get_LOR_as_two_points_alt(CartesianCoordinate3D& coord_1, + CartesianCoordinate3D& coord_2, + const Bin& bin) const; void set_azimuthal_angle_sampling(const float angle); @@ -109,6 +126,8 @@ class ProjDataInfoCylindrical: public ProjDataInfo virtual void set_num_views(const int new_num_views); + virtual void set_tof_mash_factor(const int new_num); + //! Get the azimuthal sampling (in radians) inline float get_azimuthal_angle_sampling() const; virtual inline float get_sampling_in_t(const Bin&) const; diff --git a/src/include/stir/ProjDataInfoCylindricalNoArcCorr.h b/src/include/stir/ProjDataInfoCylindricalNoArcCorr.h index 1a03be4af2..ce11e41793 100644 --- a/src/include/stir/ProjDataInfoCylindricalNoArcCorr.h +++ b/src/include/stir/ProjDataInfoCylindricalNoArcCorr.h @@ -3,6 +3,7 @@ /* Copyright (C) 2000- 2011-06-24, Hammersmith Imanet Ltd Copyright (C) 2011-07-01 - 2011, Kris Thielemans + Copyright (C) 2016, University of Hull This file is part of STIR. This file is free software; you can redistribute it and/or modify @@ -23,6 +24,7 @@ \brief Declaration of class stir::ProjDataInfoCylindricalNoArcCorr + \author Nikos Efthimiou \author Kris Thielemans */ @@ -270,6 +272,11 @@ class ProjDataInfoCylindricalNoArcCorr : public ProjDataInfoCylindrical Succeeded find_scanner_coordinates_given_cartesian_coordinates(int& det1, int& det2, int& ring1, int& ring2, const CartesianCoordinate3D& c1, const CartesianCoordinate3D& c2) const; + + void find_cartesian_coordinates_given_scanner_coordinates_of_the_front_surface(CartesianCoordinate3D& coord_1, + CartesianCoordinate3D& coord_2, + const int Ring_A,const int Ring_B, + const int det1, const int det2) const; void find_cartesian_coordinates_of_detection(CartesianCoordinate3D& coord_1, CartesianCoordinate3D& coord_2, diff --git a/src/include/stir/Scanner.h b/src/include/stir/Scanner.h index 6256f509f2..3cf517932e 100644 --- a/src/include/stir/Scanner.h +++ b/src/include/stir/Scanner.h @@ -3,6 +3,7 @@ Copyright (C) 2000-2010, Hammersmith Imanet Ltd Copyright (C) 2011-2013, King's College London Copyright (C) 2016, UCL + Copyright (C) 2016, University of Hull This file is part of STIR. @@ -25,6 +26,7 @@ \brief Declaration of class stir::Scanner + \author Nikos Efthimiou \author Claire Labbe \author Kris Thielemans \author Sanida Mustafovic @@ -78,6 +80,8 @@ class Succeeded; for which we can get singles rates. \warning This information is only sensible for discrete detector-based scanners. + \warning Currently, in a TOF compatible scanner template, the last three types have to + be explicitly defined to avoid ambiguity. \todo Some scanners do not have all info filled in at present. Values are then set to 0. @@ -109,6 +113,7 @@ class Scanner any given parameters. */ enum Type {E931, E951, E953, E921, E925, E961, E962, E966, E1080, Siemens_mMR, RPT,HiDAC, + /*Type_mCT, Type_mCT_TOF, Type_mCT_TOF_100, Type_mCT_TOF_200, Type_mCT_TOF_400,*/ Advance, DiscoveryLS, DiscoveryST, DiscoverySTE, DiscoveryRX, Discovery600, HZLR, RATPET, PANDA, HYPERimage, nanoPET, HRRT, Allegro, GeminiTF, User_defined_scanner, Unknown_scanner}; @@ -156,6 +161,43 @@ class Scanner float reference_energy_v = -1.0f); + //! TOF constructor -(list of names) + Scanner(Type type_v, const std::list& list_of_names_v, + int num_detectors_per_ring_v, int num_rings_v, + int max_num_non_arccorrected_bins_v, + int default_num_arccorrected_bins_v, + float inner_ring_radius_v, float average_depth_of_interaction_v, + float ring_spacing_v, float bin_size_v, float intrinsic_tilt_v, + int num_axial_blocks_per_bucket_v, int num_transaxial_blocks_per_bucket_v, + int num_axial_crystals_per_block_v, int num_transaxial_crystals_per_block_v, + int num_axial_crystals_per_singles_unit_v, + int num_transaxial_crystals_per_singles_unit_v, + int num_detector_layers_v, + int max_num_of_timing_bins, + float size_timing_bin, + float timing_resolution, + float energy_resolution_v = -1.0f, + float reference_energy_v = -1.0f); + + //! TOF constructor ( a single name) + Scanner(Type type_v, const std::string& name, + int num_detectors_per_ring_v, int num_rings_v, + int max_num_non_arccorrected_bins_v, + int default_num_arccorrected_bins_v, + float inner_ring_radius_v, float average_depth_of_interaction_v, + float ring_spacing_v, float bin_size_v, float intrinsic_tilt_v, + int num_axial_blocks_per_bucket_v, int num_transaxial_blocks_per_bucket_v, + int num_axial_crystals_per_block_v, int num_transaxial_crystals_per_block_v, + int num_axial_crystals_per_singles_unit_v, + int num_transaxial_crystals_per_singles_unit_v, + int num_detector_layers_v, + int max_num_of_timing_bins, + float size_timing_bin, + float timing_resolution, + float energy_resolution_v = -1.0f, + float reference_energy_v = -1.0f); + + //! get scanner parameters as a std::string std::string parameter_info() const; @@ -261,7 +303,12 @@ class Scanner inline int get_num_transaxial_singles_units() const; /* inline int get_num_layers_singles_units() const; */ inline int get_num_singles_units() const; - + //! Get the maximum number of TOF bins. + inline int get_num_max_of_timing_bins() const; + //! Get the delta t which correspnds to the max number of TOF bins in picosecs. + inline float get_size_of_timing_bin() const; + //! Get the timing resolution of the scanner. + inline float get_timing_resolution() const; //@} (end of block/bucket info) @@ -326,21 +373,27 @@ class Scanner //! set the reference energy of the energy resolution //! A negative value indicates, unknown || not set inline void set_reference_energy(const float new_num); + //! Set the maximum number of TOF bins. + inline void set_num_max_of_timing_bins(int new_num); + //! Set the delta t which correspnds to the max number of TOF bins. + inline void set_size_of_timing_bin(float new_num); + //! Set timing resolution + inline void set_timing_resolution(float new_num_in_ps); //@} (end of set info) //@} (end of set info) - // Calculate a singles bin index from axial and transaxial singles bin coordinates. + //! Calculate a singles bin index from axial and transaxial singles bin coordinates. inline int get_singles_bin_index(int axial_index, int transaxial_index) const; - // Method used to calculate a singles bin index from - // a detection position. + //! Method used to calculate a singles bin index from + //! a detection position. inline int get_singles_bin_index(const DetectionPosition<>& det_pos) const; - // Get the axial singles bin coordinate from a singles bin. + //! Get the axial singles bin coordinate from a singles bin. inline int get_axial_singles_unit(int singles_bin_index) const; - // Get the transaxial singles bin coordinate from a singles bin. + //! Get the transaxial singles bin coordinate from a singles bin. inline int get_transaxial_singles_unit(int singles_bin_index) const; @@ -367,7 +420,7 @@ class Scanner int num_axial_crystals_per_singles_unit; int num_transaxial_crystals_per_singles_unit; - //! + //! //! \brief energy_resolution //! \author Nikos Efthimiou //! \details This is the energy resolution of the system. @@ -382,8 +435,26 @@ class Scanner //! A negative value indicates, unknown. float reference_energy; + //! + //! \brief timing_resolution + //! \author Nikos Efthimiou + //! \details The timing resolution of the scanner, in psec. + float timing_resolution; + + //! + //! \brief num_tof_bins + //! \author Nikos Efthimiou + //! \details The number of TOF bins. Without any mash factors + int max_num_of_timing_bins; + + //! + //! \brief size_timing_bin + //! \author Nikos Efthimiou + //! \details This number corresponds the the least significant clock digit. + float size_timing_bin; + - // ! set all parameters, case where default_num_arccorrected_bins==max_num_non_arccorrected_bins + //! set all parameters, case where default_num_arccorrected_bins==max_num_non_arccorrected_bins void set_params(Type type_v, const std::list& list_of_names_v, int num_rings_v, int max_num_non_arccorrected_bins_v, @@ -400,7 +471,7 @@ class Scanner float energy_resolution_v = -1.0f, float reference_energy = -1.0f); - // ! set all parameters + //! set all parameters void set_params(Type type_v, const std::list& list_of_names_v, int num_rings_v, int max_num_non_arccorrected_bins_v, diff --git a/src/include/stir/Scanner.inl b/src/include/stir/Scanner.inl index e5cadcd67d..952dce0db1 100644 --- a/src/include/stir/Scanner.inl +++ b/src/include/stir/Scanner.inl @@ -229,6 +229,28 @@ Scanner::get_reference_energy() const return reference_energy; } +int Scanner::get_num_max_of_timing_bins() const +{ + return max_num_of_timing_bins; +} + +float Scanner::get_size_of_timing_bin() const +{ + return size_timing_bin; +} + +float Scanner::get_timing_resolution() const +{ + return timing_resolution; +} + +bool Scanner::is_tof_ready() const +{ + return (max_num_of_timing_bins > 0 + && timing_resolution > 0.0f + && timing_resolution > 0.0f); +} + //************************ set ******************************8 void Scanner::set_type(const Type & new_type) @@ -334,6 +356,21 @@ Scanner::set_reference_energy(const float new_num) reference_energy = new_num; } +void Scanner::set_num_max_of_timing_bins(const int new_num) +{ + max_num_of_timing_bins = new_num; +} + +void Scanner::set_size_of_timing_bin(const float new_num) +{ + size_timing_bin = new_num; +} + +void Scanner::set_timing_resolution(const float new_num_in_ps) +{ + timing_resolution = new_num_in_ps; +} + /******** Calculate singles bin index from detection position *********/ diff --git a/src/include/stir/geometry/line_distances.h b/src/include/stir/geometry/line_distances.h index 949d310eec..76331eb52f 100644 --- a/src/include/stir/geometry/line_distances.h +++ b/src/include/stir/geometry/line_distances.h @@ -6,10 +6,12 @@ \ingroup geometry \brief A few functions to compute distances between lines etc \todo move implementations to .cxx + \author Nikos Efthimiou \author Kris Thielemans */ /* Copyright (C) 2005- 2005, Hammersmith Imanet Ltd + Copyright (C) 2016, University of Hull This file is part of STIR. @@ -163,4 +165,32 @@ distance_between_line_and_point( } } +/*! \ingroup geometry + \brief Project a point on a line. + + \author Nikos Efthimiou +*/ +template +inline void +project_point_on_a_line( + const CartesianCoordinate3D& p1, + const CartesianCoordinate3D& p2, + CartesianCoordinate3D& r1 ) +{ + + const CartesianCoordinate3D& r0 = p1; + const CartesianCoordinate3D difference = p2 - p1; + + const CartesianCoordinate3D r10 = r1-r0; + + float inner_prod = inner_product(difference, difference); + + const coordT u = inner_product(r10,difference) / inner_prod ; + + r1.x() = p1.x() + u * difference.x(); + r1.y() = p1.y() + u * difference.y(); + r1.z() = p1.z() + u * difference.z(); + +} + END_NAMESPACE_STIR diff --git a/src/include/stir/listmode/CListModeDataROOT.h b/src/include/stir/listmode/CListModeDataROOT.h index 104ff24290..c8c82cc885 100644 --- a/src/include/stir/listmode/CListModeDataROOT.h +++ b/src/include/stir/listmode/CListModeDataROOT.h @@ -1,6 +1,7 @@ /* * Copyright (C) 2015, 2016 University of Leeds Copyright (C) 2016, UCL + Copyright (C) 2016, University of Hull This file is part of STIR. This file is free software; you can redistribute it and/or modify @@ -98,6 +99,12 @@ class CListModeDataROOT : public CListModeData // int number_of_views; // int number_of_segments; + int max_num_timing_bins; + float size_timing_bin; + float timing_resolution; + + int tof_mash_factor; + KeyParser parser; //! Name of input chain which is going to be used. diff --git a/src/include/stir/listmode/CListRecord.h b/src/include/stir/listmode/CListRecord.h index 014bb653e7..85ed55ef16 100644 --- a/src/include/stir/listmode/CListRecord.h +++ b/src/include/stir/listmode/CListRecord.h @@ -6,11 +6,13 @@ \brief Declarations of classes stir::CListRecord, stir::CListTime and stir::CListEvent which are used for list mode data. + \author Nikos Efthimiou \author Kris Thielemans */ /* Copyright (C) 2003- 2011, Hammersmith Imanet Ltd + Copyright (C) 2016, University of Hull This file is part of STIR. This file is free software; you can redistribute it and/or modify @@ -139,6 +141,19 @@ class CListTime return set_time_in_millisecs(time_in_millisecs); } + virtual inline int get_timing_bin() const + { + error("Function CListTime::get_timing_bin() currently is implemented only " + "ROOT data. Abort."); + } + + //! Get the timing component of the bin. + virtual inline void get_bin(Bin&, const ProjDataInfo&) const + { + error("CListTime::get_bin() currently is implemented only " + "ROOT data. Abort."); + } + }; //! A class recording external input to the scanner (normally used for gating) @@ -189,6 +204,12 @@ class CListRecord virtual bool operator==(const CListRecord& e2) const = 0; bool operator!=(const CListRecord& e2) const { return !(*this == e2); } + //! Used in TOF reconstruction to get both the geometric and the timing + //! component of the event + virtual void full_event(Bin&, const ProjDataInfo&) const + {error("CListRecord::full_event() is implemented only for records which " + "hold timing and spatial information.");} + }; class CListRecordWithGatingInput : public CListRecord diff --git a/src/include/stir/listmode/CListRecordROOT.h b/src/include/stir/listmode/CListRecordROOT.h index cc41e000ff..1c10bf2b25 100644 --- a/src/include/stir/listmode/CListRecordROOT.h +++ b/src/include/stir/listmode/CListRecordROOT.h @@ -1,6 +1,7 @@ /* Copyright (C) 2015-2016 University of Leeds Copyright (C) 2016 UCL + Copyright (C) 2016, University of Hull This file is part of STIR. This file is free software; you can redistribute it and/or modify @@ -81,10 +82,10 @@ class CListEventROOT : public CListEventCylindricalScannerWithDiscreteDetectors class CListTimeROOT : public CListTime { public: - void init_from_data(double time1, double time2) + void init_from_data(float _timeA, float _delta_time) { - timeA = time1; - timeB = time2; + timeA = _timeA; + delta_time = _delta_time; } //! Returns always true @@ -96,35 +97,46 @@ class CListTimeROOT : public CListTime { return timeA * 1e3; } //! Get the detection time of the first photon //! in milliseconds - inline double get_timeA_in_millisecs() const + inline unsigned long get_timeA_in_millisecs() const { return timeA * 1e3; } //! Get the detection time of the second photon //! in milliseconds - inline double get_timeB_in_millisecs() const - { return timeB * 1e3; } + inline unsigned long get_timeB_in_millisecs() const + { return (delta_time - timeA) * 1e3; } //! Get the delta Time between the two events - inline double get_delta_time_in_millisecs() const - { return (timeB - timeA) * 1e3; } + inline unsigned long get_delta_time_in_millisecs() const + { return delta_time * 1e3; } //! Get delta time in picoseconds - inline double get_delta_time_in_picosecs() const + inline unsigned long get_delta_time_in_picosecs() const { return (timeB - timeA) * 1e12; } + inline Succeeded set_time_in_millisecs(const unsigned long time_in_millisecs) { warning("set_time_in_millisecs: Not implemented for ROOT files. Aborting."); return Succeeded::no; } + virtual inline void get_bin(Bin& bin, const ProjDataInfo& proj_data_info) const + { + delta_timing_bin > 0 ? + bin.timing_pos_num() = static_cast ( ( delta_timing_bin / proj_data_info.get_tof_mash_factor()) + 0.5) + : bin.timing_pos_num() = static_cast ( ( delta_timing_bin / proj_data_info.get_tof_mash_factor()) - 0.5); + + if (bin.timing_pos_num() < proj_data_info.get_min_timing_pos_num() || + bin.timing_pos_num() > proj_data_info.get_max_timing_pos_num()) + { + bin.set_bin_value(-1.f); + } + } + private: //! //! \brief timeA //! \details The detection time of the first of the two photons, in seconds - double timeA; + float timeA; - //! - //! \brief timeB - //! \details The detection time of the second of the two photons - double timeB; + float delta_time; }; //! A class for a general element of a listmode file for a Siemens scanner using the ROOT files @@ -173,7 +185,7 @@ class CListRecordROOT : public CListRecord // currently no gating yet const int& ring2, const int& crystal1, const int& crystal2, - double time1, double time2, + float time1, float delta_time, const int& event1, const int& event2) { /// \warning ROOT data are time and event at the same time. @@ -181,8 +193,15 @@ class CListRecordROOT : public CListRecord // currently no gating yet this->event_data.init_from_data(ring1, ring2, crystal1, crystal2); - this->time_data.init_from_data( - time1,time2); + if(!this->event_data.is_swapped()) + this->time_data.init_from_data( + time1, delta_timing_bin); + else + { +// delta_timing_bin = -delta_timing_bin; + this->time_data.init_from_data( + time1, -delta_timing_bin); + } // We can make a singature raw based on the two events IDs. // It is pretty unique. diff --git a/src/include/stir/recon_buildblock/GeneralisedObjectiveFunction.h b/src/include/stir/recon_buildblock/GeneralisedObjectiveFunction.h index 83b941df93..bd63c3ad05 100644 --- a/src/include/stir/recon_buildblock/GeneralisedObjectiveFunction.h +++ b/src/include/stir/recon_buildblock/GeneralisedObjectiveFunction.h @@ -2,6 +2,7 @@ // /* Copyright (C) 2003- 2009, Hammersmith Imanet Ltd + Copyright (C) 2016, University of Hull This file is part of STIR. This file is free software; you can redistribute it and/or modify @@ -21,6 +22,7 @@ \ingroup GeneralisedObjectiveFunction \brief Declaration of class stir::GeneralisedObjectiveFunction + \author Nikos Efthimiou \author Kris Thielemans \author Sanida Mustafovic @@ -228,6 +230,8 @@ class GeneralisedObjectiveFunction: //! Return the number of subsets in-use int get_num_subsets() const; + //! Return the status of TOF + bool get_tof_status() const; //! Attempts to change the number of subsets. /*! \return The number of subsets that will be used later, which is not @@ -295,6 +299,9 @@ class GeneralisedObjectiveFunction: protected: int num_subsets; + //! If set TOF information will be taken into account. + bool use_tof; + shared_ptr > prior_sptr; //! sets any default values diff --git a/src/include/stir/recon_buildblock/ProjMatrixByBin.h b/src/include/stir/recon_buildblock/ProjMatrixByBin.h index 684a03dbcc..5f80faca56 100644 --- a/src/include/stir/recon_buildblock/ProjMatrixByBin.h +++ b/src/include/stir/recon_buildblock/ProjMatrixByBin.h @@ -7,6 +7,7 @@ \ingroup projection \brief declaration of stir::ProjMatrixByBin and its helpers classes + \author Nikos Efthimiou \author Mustapha Sadki \author Kris Thielemans \author PARAPET project @@ -15,6 +16,7 @@ Copyright (C) 2000 PARAPET partners Copyright (C) 2000-2009, Hammersmith Imanet Ltd Copyright (C) 2013, 2015 University College London + Copyright (C) 2016, University of Hull This file is part of STIR. @@ -38,6 +40,7 @@ #include "stir/shared_ptr.h" #include "stir/VectorWithOffset.h" #include "stir/TimedObject.h" +#include "stir/VoxelsOnCartesianGrid.h" #include //#include #include @@ -123,6 +126,19 @@ class ProjMatrixByBin : get_proj_matrix_elems_for_one_bin( ProjMatrixElemsForOneBin&, const Bin&) STIR_MUTABLE_CONST; + + //! Returns a LOR with elements after application of the TOF + //! kernel. The central_point of the LOR is needed in order to + //! correlate the physical position of the LOR elements with the + //! timing bin dimentions which have as reference the center of the LOR. + //! \warning Currently, first it calculates a non-TOF LOR and then + //! kernel is applied. Which is slow. + inline void + get_proj_matrix_elems_for_one_bin_with_tof( + ProjMatrixElemsForOneBin&, + const Bin&, + const CartesianCoordinate3D& point1, + const CartesianCoordinate3D& point2) STIR_MUTABLE_CONST; #if 0 // TODO @@ -192,6 +208,8 @@ class ProjMatrixByBin : bool cache_disabled; bool cache_stores_only_basic_bins; + //! If activated TOF reconstruction will be performed. + bool tof_enabled; /*! \brief The method that tries to get data from the cache. @@ -232,7 +250,29 @@ class ProjMatrixByBin : // KT 15/05/2002 not static anymore as it uses cache_stores_only_basic_bins CacheKey cache_key(const Bin& bin) const; - + //! A local copy of the scanner's time resolution in mm. + float gauss_sigma_in_mm; + //! 1/(2*sigma_in_mm) + float r_sqrt2_gauss_sigma; + + //! We need a local copy of the discretised density in order to find the + //! cartesian coordinates of each voxel. + shared_ptr > image_info_sptr; + + //! We need a local copy of the proj_data_info to get the integration boundaries. + shared_ptr proj_data_info_sptr; + + //! The function which actually applies the TOF kernel on the LOR. + inline void apply_tof_kernel( ProjMatrixElemsForOneBin& nonTOF_probabilities, + ProjMatrixElemsForOneBin& tof_probabilities, + const CartesianCoordinate3D& point1, + const CartesianCoordinate3D& point2) STIR_MUTABLE_CONST; + + + + //! Get the interal value erf(m - v_j) - erf(m -v_j) + inline void get_tof_value(float& d1, float& d2, float& val) const; + }; diff --git a/src/include/stir/recon_buildblock/ProjMatrixByBin.inl b/src/include/stir/recon_buildblock/ProjMatrixByBin.inl index e288d8a5cf..7bf623390f 100644 --- a/src/include/stir/recon_buildblock/ProjMatrixByBin.inl +++ b/src/include/stir/recon_buildblock/ProjMatrixByBin.inl @@ -7,6 +7,7 @@ \brief Implementations of inline functions for class stir::ProjMatrixByBin + \author Nikos Efthimiou \author Mustapha Sadki \author Kris Thielemans \author PARAPET project @@ -15,6 +16,7 @@ /* Copyright (C) 2000 PARAPET partners Copyright (C) 2000- 2013, Hammersmith Imanet Ltd + Copyright (C) 2016, University of Hull This file is part of STIR. This file is free software; you can redistribute it and/or modify @@ -31,6 +33,8 @@ */ #include "stir/Succeeded.h" #include "stir/recon_buildblock/SymmetryOperation.h" +#include "stir/geometry/line_distances.h" +#include "stir/numerics/erf.h" START_NAMESPACE_STIR @@ -111,4 +115,111 @@ get_proj_matrix_elems_for_one_bin( // stop_timers(); TODO, can't do this in a const member } +inline void +ProjMatrixByBin:: +get_proj_matrix_elems_for_one_bin_with_tof( + ProjMatrixElemsForOneBin& probabilities, + const Bin& bin, + const CartesianCoordinate3D& point1, + const CartesianCoordinate3D& point2) STIR_MUTABLE_CONST +{ + // start_timers(); TODO, can't do this in a const member + + if (!tof_enabled) + error("The function get_proj_matrix_elems_for_one_bin_with_tof() needs proper timing " + "initialisation. Abort."); + // set to empty + probabilities.erase(); + ProjMatrixElemsForOneBin tmp_probabilities; + + if (cache_stores_only_basic_bins) + { + // find basic bin + Bin basic_bin = bin; + std::auto_ptr symm_ptr = + symmetries_sptr->find_symmetry_operation_from_basic_bin(basic_bin); + + tmp_probabilities.set_bin(basic_bin); + probabilities.set_bin(bin); + // check if basic bin is in cache + if (get_cached_proj_matrix_elems_for_one_bin(tmp_probabilities) == + Succeeded::no) + { + // call 'calculate' just for the basic bin + calculate_proj_matrix_elems_for_one_bin(tmp_probabilities); +#ifndef NDEBUG + tmp_probabilities.check_state(); +#endif + cache_proj_matrix_elems_for_one_bin(tmp_probabilities); + } +// else +// int nikos = 0; +// tmp_probabilities.set_bin(bin); + + // now transform to original bin + symm_ptr->transform_proj_matrix_elems_for_one_bin(tmp_probabilities); + apply_tof_kernel(tmp_probabilities, probabilities, point1, point2); + } + else // !cache_stores_only_basic_bins + { + error("This option has been deactivated as the amount of memory required is not realistic. Abort."); + } + // stop_timers(); TODO, can't do this in a const member +} + +void +ProjMatrixByBin::apply_tof_kernel(ProjMatrixElemsForOneBin& nonTOF_probabilities, + ProjMatrixElemsForOneBin& tof_probabilities, + const CartesianCoordinate3D& point1, + const CartesianCoordinate3D& point2) STIR_MUTABLE_CONST +{ + + CartesianCoordinate3D voxel_center; + float new_value = 0.f; + float low_dist = 0.f; + float high_dist = 0.f; + + float lor_length = std::sqrt((point1.x() - point2.x()) *(point1.x() - point2.x()) + + (point1.y() - point2.y()) *(point1.y() - point2.y()) + + (point1.z() - point2.z()) *(point1.z() - point2.z())); + + for (ProjMatrixElemsForOneBin::iterator element_ptr = nonTOF_probabilities.begin(); + element_ptr != nonTOF_probabilities.end(); ++element_ptr) + { + voxel_center = + image_info_sptr->get_physical_coordinates_for_indices (element_ptr->get_coords()); + + project_point_to_a_line_f(point1, point2, voxel_center ); + + float d1 = std::sqrt((point1.x() - voxel_center.x()) *(point1.x() - voxel_center.x()) + + (point1.y() - voxel_center.y()) *(point1.y() - voxel_center.y()) + + (point1.z() - voxel_center.z()) *(point1.z() - voxel_center.z())); + + // This might be risky. + // The advantage is significant speed up. + // float d2 = std::sqrt( (point2.x() - voxel_center.x()) *(point2.x() - voxel_center.x()) + + // (point2.y() - voxel_center.y()) *(point2.y() - voxel_center.y()) + + // (point2.z() - voxel_center.z()) *(point2.z() - voxel_center.z())); + + float m = (lor_length - d1 - d1) * 0.5f; + low_dist = (proj_data_info_sptr->timing_bin_boundaries[tof_probabilities.get_bin_ptr()->timing_pos_num()].low_lim - m) * r_sqrt2_gauss_sigma; + high_dist = (proj_data_info_sptr->timing_bin_boundaries[tof_probabilities.get_bin_ptr()->timing_pos_num()].high_lim - m) * r_sqrt2_gauss_sigma; + + get_tof_value(low_dist, high_dist, new_value); + new_value *= element_ptr->get_value(); + + if (new_value <= 0.0001f) + continue; + tof_probabilities.push_back(ProjMatrixElemsForOneBin::value_type(element_ptr->get_coords(), new_value)); + + } +} + +void +ProjMatrixByBin:: +get_tof_value(float& d1, float& d2, float& val) const +{ + val = ( erf(d2) - erf(d1)) * 0.5; +} + END_NAMESPACE_STIR diff --git a/src/listmode_buildblock/CListModeDataROOT.cxx b/src/listmode_buildblock/CListModeDataROOT.cxx index 73945cdecf..5fe8ead23a 100644 --- a/src/listmode_buildblock/CListModeDataROOT.cxx +++ b/src/listmode_buildblock/CListModeDataROOT.cxx @@ -1,6 +1,7 @@ /* Copyright (C) 2015, 2016 University of Leeds Copyright (C) 2016 University College London + Copyright (C) 2016, University of Hull This file is part of STIR. This file is free software; you can redistribute it and/or modify @@ -50,6 +51,11 @@ CListModeDataROOT(const std::string& listmode_filename) // number_of_views = -1; // number_of_segments = -1; + max_num_timing_bins = -1; + size_timing_bin = -1.f; + timing_resolution = -1.f; + tof_mash_factor = -1; + // Scanner related & Physical dimentions. this->parser.add_key("originating system", &this->originating_system); this->parser.add_key("Number of rings", &this->num_rings); @@ -68,6 +74,12 @@ CListModeDataROOT(const std::string& listmode_filename) // this->parser.add_key("%number_of_projections", &number_of_projections); // this->parser.add_key("%number_of_views", &number_of_views); // this->parser.add_key("%number_of_segments", &number_of_segments); + + this->parser.add_key("number of TOF time bins", &this->max_num_timing_bins); + this->parser.add_key("Size of timing bin (in picoseconds)", &this->size_timing_bin); + this->parser.add_key("Timing resolution (in picoseconds)", &this->timing_resolution); + + this->parser.add_key("%TOF mashing factor", &this->tof_mash_factor); // // ROOT related @@ -125,7 +137,10 @@ CListModeDataROOT(const std::string& listmode_filename) this->current_lm_data_ptr->get_num_axial_crystals_per_singles_unit(), /*num_transaxial_crystals_per_singles_unit_v*/ this->current_lm_data_ptr->get_num_trans_crystals_per_singles_unit(), - /*num_detector_layers_v*/ 1 )); + /*num_detector_layers_v*/ 1, + max_num_timing_bins, + size_timing_bin, + timing_resolution)); } if (this->open_lm_file() == Succeeded::no) @@ -146,6 +161,8 @@ CListModeDataROOT(const std::string& listmode_filename) num_detectors_per_ring/2, max_num_non_arccorrected_bins, /* arc_correction*/false)); + if (tof_mash_factor > 0) + this->proj_data_info_sptr->set_tof_mash_factor(tof_mash_factor); } std::string diff --git a/src/recon_buildblock/ForwardProjectorByBinUsingProjMatrixByBin.cxx b/src/recon_buildblock/ForwardProjectorByBinUsingProjMatrixByBin.cxx index 128eb2b2d7..3405dbcfe3 100644 --- a/src/recon_buildblock/ForwardProjectorByBinUsingProjMatrixByBin.cxx +++ b/src/recon_buildblock/ForwardProjectorByBinUsingProjMatrixByBin.cxx @@ -142,7 +142,7 @@ ForwardProjectorByBinUsingProjMatrixByBin:: for ( int tang_pos = min_tangential_pos_num ;tang_pos <= max_tangential_pos_num ;++tang_pos) for ( int ax_pos = min_axial_pos_num; ax_pos <= max_axial_pos_num ;++ax_pos) { - Bin bin(segment_num, view_num, ax_pos, tang_pos, 0); + Bin bin(segment_num, view_num, ax_pos, tang_pos, 0.f); proj_matrix_ptr->get_proj_matrix_elems_for_one_bin(proj_matrix_row, bin); proj_matrix_row.forward_project(bin,image); viewgram[ax_pos][tang_pos] = bin.get_bin_value(); diff --git a/src/recon_buildblock/GeneralisedObjectiveFunction.cxx b/src/recon_buildblock/GeneralisedObjectiveFunction.cxx index 689576d0c8..9d20ee1bfc 100644 --- a/src/recon_buildblock/GeneralisedObjectiveFunction.cxx +++ b/src/recon_buildblock/GeneralisedObjectiveFunction.cxx @@ -2,6 +2,7 @@ // /* Copyright (C) 2003- 2011, Hammersmith Imanet Ltd + Copyright (C) 2016, University of Hull This file is part of STIR. This file is free software; you can redistribute it and/or modify @@ -21,6 +22,7 @@ \ingroup GeneralisedObjectiveFunction \brief Declaration of class stir::GeneralisedObjectiveFunction + \author Nikos Efthimiou \author Kris Thielemans \author Sanida Mustafovic @@ -32,7 +34,7 @@ #include "stir/Succeeded.h" #include "stir/modelling/ParametricDiscretisedDensity.h" #include "stir/modelling/KineticParameters.h" - +#include "stir/info.h" using std::string; START_NAMESPACE_STIR @@ -45,6 +47,7 @@ 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; + use_tof = false; } template @@ -53,6 +56,7 @@ GeneralisedObjectiveFunction:: initialise_keymap() { this->parser.add_parsing_key("prior type", &prior_sptr); + this->parser.add_key("Use TOF information", &use_tof); } template @@ -76,6 +80,11 @@ set_up(shared_ptr const& target_data_ptr) return Succeeded::no; } + if (use_tof) + { + info("Time-Of-Flight reconstruction activated!"); + } + return Succeeded::yes; } @@ -169,6 +178,14 @@ get_num_subsets() const return this->num_subsets; } +template +bool +GeneralisedObjectiveFunction:: +get_tof_status() const +{ + return this->use_tof; +} + template double GeneralisedObjectiveFunction:: diff --git a/src/recon_buildblock/ProjMatrixByBin.cxx b/src/recon_buildblock/ProjMatrixByBin.cxx index 4aa5993fc2..951859650b 100644 --- a/src/recon_buildblock/ProjMatrixByBin.cxx +++ b/src/recon_buildblock/ProjMatrixByBin.cxx @@ -4,7 +4,8 @@ \ingroup projection \brief implementation of the stir::ProjMatrixByBin class - + + \author Nikos Efthimiou \author Mustapha Sadki \author Kris Thielemans \author PARAPET project @@ -13,6 +14,7 @@ Copyright (C) 2000 PARAPET partners Copyright (C) 2000-2009, Hammersmith Imanet Ltd Copyright (C) 2013, 2015 University College London + Copyright (C) 2016, University of Hull This file is part of STIR. @@ -46,6 +48,8 @@ void ProjMatrixByBin::set_defaults() { cache_disabled=false; cache_stores_only_basic_bins=true; + gauss_sigma_in_mm = 0.f; + r_sqrt2_gauss_sigma = 0.f; } void @@ -71,6 +75,19 @@ ProjMatrixByBin:: enable_cache(const bool v) { cache_disabled = !v;} +void +ProjMatrixByBin:: +enable_tof(const shared_ptr& _proj_data_info_sptr, const bool v) +{ + if (v) + { + tof_enabled = true; + proj_data_info_sptr = _proj_data_info_sptr; + gauss_sigma_in_mm = (proj_data_info_sptr->get_scanner_ptr()->get_timing_resolution() * 0.299792458f) / 2.355f; + r_sqrt2_gauss_sigma = 1.0f/ (gauss_sigma_in_mm * static_cast(sqrt(2.0))); + } +} + void ProjMatrixByBin:: store_only_basic_bins_in_cache(const bool v) From 0dd73eed5c0351ce22552145d99fe7b200b31082 Mon Sep 17 00:00:00 2001 From: NikEfth Date: Thu, 22 Dec 2016 11:45:58 +0000 Subject: [PATCH 033/509] compiles but new scanner is not ready --- src/buildblock/ProjDataInfo.cxx | 1 + src/buildblock/ProjDataInfoCylindrical.cxx | 9 ++- src/buildblock/Scanner.cxx | 67 +++++++++++++++++++ src/include/stir/Bin.h | 1 + src/include/stir/ProjDataInfoCylindrical.h | 7 +- src/include/stir/Scanner.h | 4 +- src/include/stir/listmode/CListRecordROOT.h | 19 ++---- .../stir/recon_buildblock/ProjMatrixByBin.h | 4 ++ .../stir/recon_buildblock/ProjMatrixByBin.inl | 2 +- .../ProjMatrixElemsForOneBin.h | 6 +- .../ProjMatrixElemsForOneBin.inl | 7 ++ 11 files changed, 106 insertions(+), 21 deletions(-) diff --git a/src/buildblock/ProjDataInfo.cxx b/src/buildblock/ProjDataInfo.cxx index 4e0b0eb1a2..162c6d0d5b 100644 --- a/src/buildblock/ProjDataInfo.cxx +++ b/src/buildblock/ProjDataInfo.cxx @@ -60,6 +60,7 @@ #include "stir/info.h" #include "boost/foreach.hpp" +#include "boost/format.hpp" #ifndef STIR_NO_NAMESPACES using std::string; diff --git a/src/buildblock/ProjDataInfoCylindrical.cxx b/src/buildblock/ProjDataInfoCylindrical.cxx index 808e861b34..0d54ea6587 100644 --- a/src/buildblock/ProjDataInfoCylindrical.cxx +++ b/src/buildblock/ProjDataInfoCylindrical.cxx @@ -584,7 +584,10 @@ void ProjDataInfoCylindrical:: get_LOR_as_two_points_alt(CartesianCoordinate3D& coord_1, CartesianCoordinate3D& coord_2, - const Bin& bin) const + const int& det1, + const int& det2, + const int& ring1, + const int& ring2) const { const int num_detectors_per_ring = get_scanner_ptr()->get_num_detectors_per_ring(); @@ -603,8 +606,8 @@ get_LOR_as_two_points_alt(CartesianCoordinate3D& coord_1, cyl_coords.p1().psi() = static_cast((2.*_PI/num_detectors_per_ring)*(det1)); cyl_coords.p2().psi() = static_cast((2.*_PI/num_detectors_per_ring)*(det2)); - cyl_coords.p1().z() = Ring_A*get_scanner_ptr()->get_ring_spacing() - h_scanner_height; - cyl_coords.p2().z() = Ring_B*get_scanner_ptr()->get_ring_spacing() - h_scanner_height; + cyl_coords.p1().z() = ring1*get_scanner_ptr()->get_ring_spacing() - h_scanner_height; + cyl_coords.p2().z() = ring2*get_scanner_ptr()->get_ring_spacing() - h_scanner_height; LORAs2Points lor(cyl_coords); coord_1 = lor.p1(); diff --git a/src/buildblock/Scanner.cxx b/src/buildblock/Scanner.cxx index eeccf6cc19..536eb1fc6a 100644 --- a/src/buildblock/Scanner.cxx +++ b/src/buildblock/Scanner.cxx @@ -486,6 +486,73 @@ Scanner::Scanner(Type type_v, const string& name, } +Scanner::Scanner(Type type_v, const list& list_of_names_v, + int num_detectors_per_ring_v, int num_rings_v, + int max_num_non_arccorrected_bins_v, + int default_num_arccorrected_bins_v, + float inner_ring_radius_v, float average_depth_of_interaction_v, + float ring_spacing_v, float bin_size_v, float intrinsic_tilt_v, + int num_axial_blocks_per_bucket_v, int num_transaxial_blocks_per_bucket_v, + int num_axial_crystals_per_block_v, int num_transaxial_crystals_per_block_v, + int num_axial_crystals_per_singles_unit_v, + int num_transaxial_crystals_per_singles_unit_v, + int num_detector_layers_v, + int max_num_of_timing_bins, + float size_timing_bin, + float timing_resolution, + float energy_resolution_v, + float reference_energy_v) +{ +// set_params(type_v, list_of_names_v, num_rings_v, +// max_num_non_arccorrected_bins_v, +// default_num_arccorrected_bins_v, +// num_detectors_per_ring_v, +// inner_ring_radius_v, +// average_depth_of_interaction_v, +// ring_spacing_v, bin_size_v, intrinsic_tilt_v, +// num_axial_blocks_per_bucket_v, num_transaxial_blocks_per_bucket_v, +// num_axial_crystals_per_block_v, num_transaxial_crystals_per_block_v, +// num_axial_crystals_per_singles_unit_v, +// num_transaxial_crystals_per_singles_unit_v, +// num_detector_layers_v, +// energy_resolution_v, +// reference_energy_v); +} + + + +Scanner::Scanner(Type type_v, const string& name, + int num_detectors_per_ring_v, int num_rings_v, + int max_num_non_arccorrected_bins_v, + int default_num_arccorrected_bins_v, + float inner_ring_radius_v, float average_depth_of_interaction_v, + float ring_spacing_v, float bin_size_v, float intrinsic_tilt_v, + int num_axial_blocks_per_bucket_v, int num_transaxial_blocks_per_bucket_v, + int num_axial_crystals_per_block_v, int num_transaxial_crystals_per_block_v, + int num_axial_crystals_per_singles_unit_v, + int num_transaxial_crystals_per_singles_unit_v, + int num_detector_layers_v, + int max_num_of_timing_bins, + float size_timing_bin, + float timing_resolution, + float energy_resolution_v, + float reference_energy_v) +{ +// set_params(type_v, string_list(name), num_rings_v, +// max_num_non_arccorrected_bins_v, +// default_num_arccorrected_bins_v, +// num_detectors_per_ring_v, +// inner_ring_radius_v, +// average_depth_of_interaction_v, +// ring_spacing_v, bin_size_v, intrinsic_tilt_v, +// num_axial_blocks_per_bucket_v, num_transaxial_blocks_per_bucket_v, +// num_axial_crystals_per_block_v, num_transaxial_crystals_per_block_v, +// num_axial_crystals_per_singles_unit_v, +// num_transaxial_crystals_per_singles_unit_v, +// num_detector_layers_v, +// energy_resolution_v, +// reference_energy_v); +} diff --git a/src/include/stir/Bin.h b/src/include/stir/Bin.h index 3b79c91429..1a9377ce93 100644 --- a/src/include/stir/Bin.h +++ b/src/include/stir/Bin.h @@ -91,6 +91,7 @@ class Bin inline int& segment_num(); inline int& tangential_pos_num(); inline int& view_num(); + inline int& timing_pos_num(); //! get an empty copy inline Bin get_empty_copy() const; diff --git a/src/include/stir/ProjDataInfoCylindrical.h b/src/include/stir/ProjDataInfoCylindrical.h index cdbae24267..8604973aa6 100644 --- a/src/include/stir/ProjDataInfoCylindrical.h +++ b/src/include/stir/ProjDataInfoCylindrical.h @@ -111,8 +111,11 @@ class ProjDataInfoCylindrical: public ProjDataInfo //! \warning More testing needed. void get_LOR_as_two_points_alt(CartesianCoordinate3D& coord_1, - CartesianCoordinate3D& coord_2, - const Bin& bin) const; + CartesianCoordinate3D& coord_2, + const int& det1, + const int& det2, + const int& ring1, + const int& ring2) const; void set_azimuthal_angle_sampling(const float angle); diff --git a/src/include/stir/Scanner.h b/src/include/stir/Scanner.h index 3cf517932e..565d6597be 100644 --- a/src/include/stir/Scanner.h +++ b/src/include/stir/Scanner.h @@ -395,7 +395,9 @@ class Scanner //! Get the transaxial singles bin coordinate from a singles bin. inline int get_transaxial_singles_unit(int singles_bin_index) const; - + + //! True if it is TOF compatible. + inline bool is_tof_ready() const; private: Type type; diff --git a/src/include/stir/listmode/CListRecordROOT.h b/src/include/stir/listmode/CListRecordROOT.h index 1c10bf2b25..3db20736af 100644 --- a/src/include/stir/listmode/CListRecordROOT.h +++ b/src/include/stir/listmode/CListRecordROOT.h @@ -106,9 +106,6 @@ class CListTimeROOT : public CListTime //! Get the delta Time between the two events inline unsigned long get_delta_time_in_millisecs() const { return delta_time * 1e3; } - //! Get delta time in picoseconds - inline unsigned long get_delta_time_in_picosecs() const - { return (timeB - timeA) * 1e12; } inline Succeeded set_time_in_millisecs(const unsigned long time_in_millisecs) { @@ -118,9 +115,9 @@ class CListTimeROOT : public CListTime virtual inline void get_bin(Bin& bin, const ProjDataInfo& proj_data_info) const { - delta_timing_bin > 0 ? - bin.timing_pos_num() = static_cast ( ( delta_timing_bin / proj_data_info.get_tof_mash_factor()) + 0.5) - : bin.timing_pos_num() = static_cast ( ( delta_timing_bin / proj_data_info.get_tof_mash_factor()) - 0.5); + delta_time > 0 ? + bin.timing_pos_num() = static_cast ( ( delta_time / proj_data_info.get_tof_mash_factor()) + 0.5) + : bin.timing_pos_num() = static_cast ( ( delta_time / proj_data_info.get_tof_mash_factor()) - 0.5); if (bin.timing_pos_num() < proj_data_info.get_min_timing_pos_num() || bin.timing_pos_num() > proj_data_info.get_max_timing_pos_num()) @@ -193,15 +190,11 @@ class CListRecordROOT : public CListRecord // currently no gating yet this->event_data.init_from_data(ring1, ring2, crystal1, crystal2); - if(!this->event_data.is_swapped()) + this->event_data.is_swapped() ? this->time_data.init_from_data( - time1, delta_timing_bin); - else - { -// delta_timing_bin = -delta_timing_bin; + time1, delta_time) : this->time_data.init_from_data( - time1, -delta_timing_bin); - } + time1, -delta_time); // We can make a singature raw based on the two events IDs. // It is pretty unique. diff --git a/src/include/stir/recon_buildblock/ProjMatrixByBin.h b/src/include/stir/recon_buildblock/ProjMatrixByBin.h index 5f80faca56..811167f522 100644 --- a/src/include/stir/recon_buildblock/ProjMatrixByBin.h +++ b/src/include/stir/recon_buildblock/ProjMatrixByBin.h @@ -170,6 +170,10 @@ class ProjMatrixByBin : //! Remove all elements from the cache void clear_cache() STIR_MUTABLE_CONST; + //! Activates the application of the timing kernel to the LOR + //! and performs initial set_up(). + //! \warning Must be called after set_up() + void enable_tof(const shared_ptr& proj_data_info_sptr,const bool v = true); protected: shared_ptr symmetries_sptr; diff --git a/src/include/stir/recon_buildblock/ProjMatrixByBin.inl b/src/include/stir/recon_buildblock/ProjMatrixByBin.inl index 7bf623390f..56e482d323 100644 --- a/src/include/stir/recon_buildblock/ProjMatrixByBin.inl +++ b/src/include/stir/recon_buildblock/ProjMatrixByBin.inl @@ -189,7 +189,7 @@ ProjMatrixByBin::apply_tof_kernel(ProjMatrixElemsForOneBin& nonTOF_probabilities voxel_center = image_info_sptr->get_physical_coordinates_for_indices (element_ptr->get_coords()); - project_point_to_a_line_f(point1, point2, voxel_center ); + project_point_on_a_line(point1, point2, voxel_center ); float d1 = std::sqrt((point1.x() - voxel_center.x()) *(point1.x() - voxel_center.x()) + (point1.y() - voxel_center.y()) *(point1.y() - voxel_center.y()) + diff --git a/src/include/stir/recon_buildblock/ProjMatrixElemsForOneBin.h b/src/include/stir/recon_buildblock/ProjMatrixElemsForOneBin.h index a869a966c9..d0b6e13eac 100644 --- a/src/include/stir/recon_buildblock/ProjMatrixElemsForOneBin.h +++ b/src/include/stir/recon_buildblock/ProjMatrixElemsForOneBin.h @@ -10,7 +10,8 @@ \ingroup projection \brief Declaration of class stir::ProjMatrixElemsForOneBin - + + \author Nikos Efthimiou \author Mustapha Sadki \author Kris Thielemans \author PARAPET project @@ -19,6 +20,7 @@ /* Copyright (C) 2000 PARAPET partners Copyright (C) 2000- 2009, Hammersmith Imanet Ltd + Copyright (C) 2016, University of Hull This file is part of STIR. This file is free software; you can redistribute it and/or modify @@ -121,6 +123,8 @@ class ProjMatrixElemsForOneBin inline Bin get_bin() const; //! and set the bin coordinates inline void set_bin(const Bin&); + //! get a ref to the bin + inline Bin* get_bin_ptr(); //! functions for allowing iterator access inline iterator begin() ; diff --git a/src/include/stir/recon_buildblock/ProjMatrixElemsForOneBin.inl b/src/include/stir/recon_buildblock/ProjMatrixElemsForOneBin.inl index 4fac67243c..4ab89b9a9e 100644 --- a/src/include/stir/recon_buildblock/ProjMatrixElemsForOneBin.inl +++ b/src/include/stir/recon_buildblock/ProjMatrixElemsForOneBin.inl @@ -40,6 +40,13 @@ get_bin() const return bin; } +Bin* +ProjMatrixElemsForOneBin:: +get_bin_ptr() +{ + return &bin; +} + void ProjMatrixElemsForOneBin:: set_bin(const Bin& new_bin) From 61e543274841a487933bb16deb31358c59033705 Mon Sep 17 00:00:00 2001 From: NikEfth Date: Fri, 23 Dec 2016 19:37:19 +0000 Subject: [PATCH 034/509] Experimenting on the integration boundaries --- src/buildblock/ProjDataInfo.cxx | 79 +-- src/buildblock/Scanner.cxx | 474 +++++++++++++++--- src/include/stir/Scanner.h | 253 +++++++--- .../stir/recon_buildblock/ProjMatrixByBin.h | 14 +- .../stir/recon_buildblock/ProjMatrixByBin.inl | 6 +- .../ProjMatrixByBinUsingRayTracing.h | 4 +- ...MeanAndListModeDataWithProjMatrixByBin.cxx | 35 +- .../ProjMatrixByBinUsingRayTracing.cxx | 26 +- 8 files changed, 669 insertions(+), 222 deletions(-) diff --git a/src/buildblock/ProjDataInfo.cxx b/src/buildblock/ProjDataInfo.cxx index 162c6d0d5b..702c7418b5 100644 --- a/src/buildblock/ProjDataInfo.cxx +++ b/src/buildblock/ProjDataInfo.cxx @@ -208,10 +208,15 @@ ProjDataInfo::set_tof_mash_factor(const int new_num) float lowest_boundary = lowest_t * k; float highest_boundary = highest_t * l; - int num_tof_positions_in_FOV = static_cast(((highest_boundary - lowest_boundary) / (2.f * timing_increament_in_mm))+0.5); + int num_tof_positions_in_FOV = static_cast(((highest_boundary - lowest_boundary) / ( timing_increament_in_mm))); - min_timing_pos_num = -num_tof_positions_in_FOV/2; - max_timing_pos_num = min_timing_pos_num + num_tof_positions_in_FOV; + min_timing_pos_num = - (scanner_ptr->get_num_max_of_timing_bins() / tof_mash_factor)/2; + max_timing_pos_num = min_timing_pos_num + (scanner_ptr->get_num_max_of_timing_bins() / tof_mash_factor); + //min_timing_pos_num = -num_tof_positions_in_FOV/2; + //max_timing_pos_num = min_timing_pos_num + num_tof_positions_in_FOV; + + info(boost::format("bound: %1%: %2% - %3% | %4%") %lowest_boundary % highest_boundary %num_tof_positions_in_FOV + %timing_increament_in_mm); // Upper and lower boundaries of the timing poss; timing_bin_boundaries.grow(min_timing_pos_num, max_timing_pos_num); @@ -224,32 +229,48 @@ ProjDataInfo::set_tof_mash_factor(const int new_num) float cur_low = get_k(bin); float cur_high = get_k(bin) + get_sampling_in_k(bin); - if (cur_low< 0 || cur_high < 0 ) - { - if (cur_low < lowest_boundary && cur_high > lowest_boundary) - { - timing_bin_boundaries[i].low_lim = lowest_boundary; - timing_bin_boundaries[i].high_lim = cur_high; - } - else if (cur_low >= lowest_boundary && cur_high >= lowest_boundary) - { - timing_bin_boundaries[i].low_lim = cur_low; - timing_bin_boundaries[i].high_lim = cur_high; - } - } - else - { - if (cur_high > highest_boundary && cur_low < highest_boundary) - { - timing_bin_boundaries[i].low_lim = cur_low; - timing_bin_boundaries[i].high_lim = highest_boundary; - } - else if (cur_low <= highest_boundary && cur_high <= highest_boundary) - { - timing_bin_boundaries[i].low_lim = cur_low; - timing_bin_boundaries[i].high_lim = cur_high; - } - } +// info(boost::format("tic: %1%: %2%") %cur_low % cur_high); + +// if (cur_low < lowest_boundary) +// cur_low = lowest_boundary; +// else if (cur_low > highest_boundary) +// cur_low = highest_boundary; + +// if (cur_high < lowest_boundary) +// cur_high = lowest_boundary; +// else if (cur_high > highest_boundary) +// cur_high = highest_boundary; + +// info(boost::format("tac: %1%: %2%") % cur_low % cur_high); +// if (cur_low< 0 || cur_high < 0 ) +// { +// if (cur_low < lowest_boundary && cur_high > lowest_boundary) +// { +// timing_bin_boundaries[i].low_lim = lowest_boundary; +// timing_bin_boundaries[i].high_lim = cur_high; +// } +// else if (cur_low >= lowest_boundary && cur_high >= lowest_boundary) +// { +// timing_bin_boundaries[i].low_lim = cur_low; +// timing_bin_boundaries[i].high_lim = cur_high; +// } +// } +// else +// { +// if (cur_high > highest_boundary && cur_low < highest_boundary) +// { +// timing_bin_boundaries[i].low_lim = cur_low; +// timing_bin_boundaries[i].high_lim = highest_boundary; +// } +// else if (cur_low <= highest_boundary && cur_high <= highest_boundary) +// { +// timing_bin_boundaries[i].low_lim = cur_low; +// timing_bin_boundaries[i].high_lim = cur_high; +// } +// } + + timing_bin_boundaries[i].low_lim = cur_low; + timing_bin_boundaries[i].high_lim = cur_high; float lowt = (timing_bin_boundaries[i].low_lim / 0.299792458f ) ; float hight = ( timing_bin_boundaries[i].high_lim/ 0.299792458f); info(boost::format("Tbin %1%: %2% - %3% mm (%4% - %5% ps) = %6%") %i % timing_bin_boundaries[i].low_lim % timing_bin_boundaries[i].high_lim diff --git a/src/buildblock/Scanner.cxx b/src/buildblock/Scanner.cxx index 536eb1fc6a..bbcc8348f0 100644 --- a/src/buildblock/Scanner.cxx +++ b/src/buildblock/Scanner.cxx @@ -206,6 +206,114 @@ Scanner::Scanner(Type scanner_type) 2, 1, 8, 9, 16, 9, 1 ); // TODO bucket/singles info incorrect? 224 buckets in total, but not sure how distributed break; + case Type_mCT: // dummy + // 8x8 blocks, 1 virtual "crystal", 56 blocks along the ring, 8 blocks in axial direction + // Transaxial blocks have 8 physical crystals and a gap at the + // 9th crystal where the counts are zero. + set_params(Type_mCT, string_list("Type mCT", "tmCT", "t2011"), + 52, 312, 624, + 424.5F, 7.0F, 4.16F, 2.17242F, 0.0F, + 1, 48, 52, 13, 13, 13, 1 ); // TODO bucket/singles info incorrect? 224 buckets in total, but not sure how distributed + break; + + case Type_mCT_TOF: // dummy + // 8x8 blocks, 1 virtual "crystal", 56 blocks along the ring, 8 blocks in axial direction + // Transaxial blocks have 8 physical crystals and a gap at the + // 9th crystal where the counts are zero. + // For TOF scanners the three last types have to be defined to avoid ambiguity. + set_params(Type_mCT_TOF, string_list("Type mCT_TOF", "tmCT_TOF", "t2011_tof"), + 52, 312, 624, + 424.5F, 7.0F, 4.16F, 2.17242F, 0.0F, + 1, 48, 52, 13, 13, 13, 1, + (short int)(315), + (float)(13.015F), + (float)(600.0F)); // TODO bucket/singles info incorrect? 224 buckets in total, but not sure how distributed + break; + + case Type_mCT_TOF_400: // dummy + // 8x8 blocks, 1 virtual "crystal", 56 blocks along the ring, 8 blocks in axial direction + // Transaxial blocks have 8 physical crystals and a gap at the + // 9th crystal where the counts are zero. + // For TOF scanners the three last types have to be defined to avoid ambiguity. + set_params(Type_mCT_TOF_400, string_list("Type mCT_TOF_400", "tmCT_TOF_400", "t2011_tof_400"), + 52, 312, 624, + 424.5F, 7.0F, 4.16F, 2.17242F, 0.0F, + 1, 48, 52, 13, 13, 13, 1, + (short int)(315), + (float)(13.015F), + (float)(400.0F)); // TODO bucket/singles info incorrect? 224 buckets in total, but not sure how distributed + break; + + case Type_mCT_TOF_200: // dummy + // 8x8 blocks, 1 virtual "crystal", 56 blocks along the ring, 8 blocks in axial direction + // Transaxial blocks have 8 physical crystals and a gap at the + // 9th crystal where the counts are zero. + // For TOF scanners the three last types have to be defined to avoid ambiguity. + set_params(Type_mCT_TOF_200, string_list("Type mCT_TOF_200", "tmCT_TOF_200", "t2011_tof_200"), + 52, 312, 624, + 424.5F, 7.0F, 4.16F, 2.17242F, 0.0F, + 1, 48, 52, 13, 13, 13, 1, + (short int)(315), + (float)(13.015F), + (float)(200.0F)); // TODO bucket/singles info incorrect? 224 buckets in total, but not sure how distributed + break; + + case Type_mCT_TOF_100: // dummy + // 8x8 blocks, 1 virtual "crystal", 56 blocks along the ring, 8 blocks in axial direction + // Transaxial blocks have 8 physical crystals and a gap at the + // 9th crystal where the counts are zero. + // For TOF scanners the three last types have to be defined to avoid ambiguity. + set_params(Type_mCT_TOF_100, string_list("Type mCT_TOF_100", "tmCT_TOF_100", "t2011_tof_100"), + 52, 312, 624, + 424.5F, 7.0F, 4.16F, 2.17242F, 0.0F, + 1, 48, 52, 13, 13, 13, 1, + (short int)(315), + (float)(13.015F), + (float)(100.0F)); // TODO bucket/singles info incorrect? 224 buckets in total, but not sure how distributed + break; + + case Type_mCT_TOF_50: // dummy + // 8x8 blocks, 1 virtual "crystal", 56 blocks along the ring, 8 blocks in axial direction + // Transaxial blocks have 8 physical crystals and a gap at the + // 9th crystal where the counts are zero. + // For TOF scanners the three last types have to be defined to avoid ambiguity. + set_params(Type_mCT_TOF_50, string_list("Type mCT_TOF_50", "tmCT_TOF_50", "t2011_tof_50"), + 52, 312, 624, + 424.5F, 7.0F, 4.16F, 2.17242F, 0.0F, + 1, 48, 52, 13, 13, 13, 1, + (short int)(315), + (float)(13.015F), + (float)(50.0F)); // TODO bucket/singles info incorrect? 224 buckets in total, but not sure how distributed + break; + + case Type_mCT_TOF_20: // dummy + // 8x8 blocks, 1 virtual "crystal", 56 blocks along the ring, 8 blocks in axial direction + // Transaxial blocks have 8 physical crystals and a gap at the + // 9th crystal where the counts are zero. + // For TOF scanners the three last types have to be defined to avoid ambiguity. + set_params(Type_mCT_TOF_20, string_list("Type mCT_TOF_20", "tmCT_TOF_20", "t2011_tof_20"), + 52, 312, 624, + 424.5F, 7.0F, 4.16F, 2.17242F, 0.0F, + 1, 48, 52, 13, 13, 13, 1, + (short int)(315), + (float)(13.015F), + (float)(20.0F)); // TODO bucket/singles info incorrect? 224 buckets in total, but not sure how distributed + break; + + case Type_mCT_TOF_10: // dummy + // 8x8 blocks, 1 virtual "crystal", 56 blocks along the ring, 8 blocks in axial direction + // Transaxial blocks have 8 physical crystals and a gap at the + // 9th crystal where the counts are zero. + // For TOF scanners the three last types have to be defined to avoid ambiguity. + set_params(Type_mCT_TOF_10, string_list("Type mCT_TOF_10", "tmCT_TOF_10", "t2011_tof_10"), + 52, 312, 624, + 424.5F, 7.0F, 4.16F, 2.17242F, 0.0F, + 1, 48, 52, 13, 13, 13, 1, + (short int)(315), + (float)(13.015F), + (float)(10.0F)); // TODO bucket/singles info incorrect? 224 buckets in total, but not sure how distributed + break; + case RPT: set_params(RPT, string_list("PRT-1", "RPT"), @@ -233,8 +341,8 @@ Scanner::Scanner(Type scanner_type) set_params(PANDA, string_list("PANDA"), 1 /*NumRings*/, 512 /*MaxBinsNonArcCor*/, 512 /*MaxBinsArcCor*/, 2048 /*NumDetPerRing*/, - /*MeanInnerRadius*/ 75.5/2.F, /*AverageDoI*/ 10.F, /*Ring Spacing*/ 3.F, /*BinSize*/ 0.1F, /*IntrinsicTilt*/ 0.F, - 1, 1, 1, 1, 0, 0, 1); + /*MeanInnerRadius*/ 75.5/2.F, /*AverageDoI*/ 10.F, /*Ring Spacing*/ 3.F, /*BinSize*/ 0.1F, /*IntrinsicTilt*/ 0.F, + 1, 1, 1, 1, 0, 0, 1); break; case nanoPET: @@ -424,14 +532,40 @@ Scanner::Scanner(Type scanner_type) Scanner::Scanner(Type type_v, const list& list_of_names_v, - int num_detectors_per_ring_v, int num_rings_v, + int num_detectors_per_ring_v, int num_rings_v, int max_num_non_arccorrected_bins_v, int default_num_arccorrected_bins_v, - float inner_ring_radius_v, float average_depth_of_interaction_v, + float inner_ring_radius_v, float average_depth_of_interaction_v, float ring_spacing_v, float bin_size_v, float intrinsic_tilt_v, int num_axial_blocks_per_bucket_v, int num_transaxial_blocks_per_bucket_v, int num_axial_crystals_per_block_v, int num_transaxial_crystals_per_block_v, - int num_axial_crystals_per_singles_unit_v, + int num_axial_crystals_per_singles_unit_v, + int num_transaxial_crystals_per_singles_unit_v, + int num_detector_layers_v) +{ + set_params(type_v, list_of_names_v, num_rings_v, + max_num_non_arccorrected_bins_v, + default_num_arccorrected_bins_v, + num_detectors_per_ring_v, + inner_ring_radius_v, + average_depth_of_interaction_v, + ring_spacing_v, bin_size_v, intrinsic_tilt_v, + num_axial_blocks_per_bucket_v, num_transaxial_blocks_per_bucket_v, + num_axial_crystals_per_block_v, num_transaxial_crystals_per_block_v, + num_axial_crystals_per_singles_unit_v, + num_transaxial_crystals_per_singles_unit_v, + num_detector_layers_v); +} + +Scanner::Scanner(Type type_v, const list& list_of_names_v, + int num_detectors_per_ring_v, int num_rings_v, + int max_num_non_arccorrected_bins_v, + int default_num_arccorrected_bins_v, + float inner_ring_radius_v, float average_depth_of_interaction_v, + float ring_spacing_v, float bin_size_v, float intrinsic_tilt_v, + int num_axial_blocks_per_bucket_v, int num_transaxial_blocks_per_bucket_v, + int num_axial_crystals_per_block_v, int num_transaxial_crystals_per_block_v, + int num_axial_crystals_per_singles_unit_v, int num_transaxial_crystals_per_singles_unit_v, int num_detector_layers_v, float energy_resolution_v, @@ -444,30 +578,59 @@ Scanner::Scanner(Type type_v, const list& list_of_names_v, inner_ring_radius_v, average_depth_of_interaction_v, ring_spacing_v, bin_size_v, intrinsic_tilt_v, + energy_resolution_v, + reference_energy_v, num_axial_blocks_per_bucket_v, num_transaxial_blocks_per_bucket_v, num_axial_crystals_per_block_v, num_transaxial_crystals_per_block_v, num_axial_crystals_per_singles_unit_v, num_transaxial_crystals_per_singles_unit_v, - num_detector_layers_v, - energy_resolution_v, - reference_energy_v); + num_detector_layers_v); } +Scanner::Scanner(Type type_v, const list& list_of_names_v, + int num_detectors_per_ring_v, int num_rings_v, + int max_num_non_arccorrected_bins_v, + int default_num_arccorrected_bins_v, + float inner_ring_radius_v, float average_depth_of_interaction_v, + float ring_spacing_v, float bin_size_v, float intrinsic_tilt_v, + int num_axial_blocks_per_bucket_v, int num_transaxial_blocks_per_bucket_v, + int num_axial_crystals_per_block_v, int num_transaxial_crystals_per_block_v, + int num_axial_crystals_per_singles_unit_v, + int num_transaxial_crystals_per_singles_unit_v, + int num_detector_layers_v, + short int max_num_of_timing_bins_v, + float size_timing_bin_v, + float timing_resolution_v) +{ + set_params(type_v, list_of_names_v, num_rings_v, + max_num_non_arccorrected_bins_v, + default_num_arccorrected_bins_v, + num_detectors_per_ring_v, + inner_ring_radius_v, + average_depth_of_interaction_v, + ring_spacing_v, bin_size_v, intrinsic_tilt_v, + num_axial_blocks_per_bucket_v, num_transaxial_blocks_per_bucket_v, + num_axial_crystals_per_block_v, num_transaxial_crystals_per_block_v, + num_axial_crystals_per_singles_unit_v, + num_transaxial_crystals_per_singles_unit_v, + num_detector_layers_v, + max_num_of_timing_bins_v, + size_timing_bin_v, + timing_resolution_v); +} Scanner::Scanner(Type type_v, const string& name, - int num_detectors_per_ring_v, int num_rings_v, + int num_detectors_per_ring_v, int num_rings_v, int max_num_non_arccorrected_bins_v, int default_num_arccorrected_bins_v, - float inner_ring_radius_v, float average_depth_of_interaction_v, + float inner_ring_radius_v, float average_depth_of_interaction_v, float ring_spacing_v, float bin_size_v, float intrinsic_tilt_v, int num_axial_blocks_per_bucket_v, int num_transaxial_blocks_per_bucket_v, int num_axial_crystals_per_block_v, int num_transaxial_crystals_per_block_v, - int num_axial_crystals_per_singles_unit_v, + int num_axial_crystals_per_singles_unit_v, int num_transaxial_crystals_per_singles_unit_v, - int num_detector_layers_v, - float energy_resolution_v, - float reference_energy_v) + int num_detector_layers_v) { set_params(type_v, string_list(name), num_rings_v, max_num_non_arccorrected_bins_v, @@ -480,13 +643,10 @@ Scanner::Scanner(Type type_v, const string& name, num_axial_crystals_per_block_v, num_transaxial_crystals_per_block_v, num_axial_crystals_per_singles_unit_v, num_transaxial_crystals_per_singles_unit_v, - num_detector_layers_v, - energy_resolution_v, - reference_energy_v); + num_detector_layers_v); } - -Scanner::Scanner(Type type_v, const list& list_of_names_v, +Scanner::Scanner(Type type_v, const string& name, int num_detectors_per_ring_v, int num_rings_v, int max_num_non_arccorrected_bins_v, int default_num_arccorrected_bins_v, @@ -497,30 +657,25 @@ Scanner::Scanner(Type type_v, const list& list_of_names_v, int num_axial_crystals_per_singles_unit_v, int num_transaxial_crystals_per_singles_unit_v, int num_detector_layers_v, - int max_num_of_timing_bins, - float size_timing_bin, - float timing_resolution, float energy_resolution_v, float reference_energy_v) { -// set_params(type_v, list_of_names_v, num_rings_v, -// max_num_non_arccorrected_bins_v, -// default_num_arccorrected_bins_v, -// num_detectors_per_ring_v, -// inner_ring_radius_v, -// average_depth_of_interaction_v, -// ring_spacing_v, bin_size_v, intrinsic_tilt_v, -// num_axial_blocks_per_bucket_v, num_transaxial_blocks_per_bucket_v, -// num_axial_crystals_per_block_v, num_transaxial_crystals_per_block_v, -// num_axial_crystals_per_singles_unit_v, -// num_transaxial_crystals_per_singles_unit_v, -// num_detector_layers_v, -// energy_resolution_v, -// reference_energy_v); + set_params(type_v, string_list(name), num_rings_v, + max_num_non_arccorrected_bins_v, + default_num_arccorrected_bins_v, + num_detectors_per_ring_v, + inner_ring_radius_v, + average_depth_of_interaction_v, + ring_spacing_v, bin_size_v, intrinsic_tilt_v, + energy_resolution_v, + reference_energy_v, + num_axial_blocks_per_bucket_v, num_transaxial_blocks_per_bucket_v, + num_axial_crystals_per_block_v, num_transaxial_crystals_per_block_v, + num_axial_crystals_per_singles_unit_v, + num_transaxial_crystals_per_singles_unit_v, + num_detector_layers_v); } - - Scanner::Scanner(Type type_v, const string& name, int num_detectors_per_ring_v, int num_rings_v, int max_num_non_arccorrected_bins_v, @@ -532,71 +687,132 @@ Scanner::Scanner(Type type_v, const string& name, int num_axial_crystals_per_singles_unit_v, int num_transaxial_crystals_per_singles_unit_v, int num_detector_layers_v, - int max_num_of_timing_bins, - float size_timing_bin, - float timing_resolution, - float energy_resolution_v, - float reference_energy_v) + short int max_num_of_timing_bins_v, + float size_timing_bin_v, + float timing_resolution_v + ) { -// set_params(type_v, string_list(name), num_rings_v, -// max_num_non_arccorrected_bins_v, -// default_num_arccorrected_bins_v, -// num_detectors_per_ring_v, -// inner_ring_radius_v, -// average_depth_of_interaction_v, -// ring_spacing_v, bin_size_v, intrinsic_tilt_v, -// num_axial_blocks_per_bucket_v, num_transaxial_blocks_per_bucket_v, -// num_axial_crystals_per_block_v, num_transaxial_crystals_per_block_v, -// num_axial_crystals_per_singles_unit_v, -// num_transaxial_crystals_per_singles_unit_v, -// num_detector_layers_v, -// energy_resolution_v, -// reference_energy_v); + set_params(type_v, string_list(name), num_rings_v, + max_num_non_arccorrected_bins_v, + default_num_arccorrected_bins_v, + num_detectors_per_ring_v, + inner_ring_radius_v, + average_depth_of_interaction_v, + ring_spacing_v, bin_size_v, intrinsic_tilt_v, + num_axial_blocks_per_bucket_v, num_transaxial_blocks_per_bucket_v, + num_axial_crystals_per_block_v, num_transaxial_crystals_per_block_v, + num_axial_crystals_per_singles_unit_v, + num_transaxial_crystals_per_singles_unit_v, + num_detector_layers_v, + max_num_of_timing_bins_v, + size_timing_bin_v, + timing_resolution_v); } - - - - void Scanner:: set_params(Type type_v,const list& list_of_names_v, - int num_rings_v, + int num_rings_v, int max_num_non_arccorrected_bins_v, int num_detectors_per_ring_v, float inner_ring_radius_v, float average_depth_of_interaction_v, float ring_spacing_v, float bin_size_v, float intrinsic_tilt_v, - int num_axial_blocks_per_bucket_v, int num_transaxial_blocks_per_bucket_v, + int num_axial_blocks_per_bucket_v, int num_transaxial_blocks_per_bucket_v, int num_axial_crystals_per_block_v, int num_transaxial_crystals_per_block_v, int num_axial_crystals_per_singles_unit_v, int num_transaxial_crystals_per_singles_unit_v, - int num_detector_layers_v, - float energy_resolution_v, - float reference_energy_v) + int num_detector_layers_v) { set_params(type_v, list_of_names_v, num_rings_v, max_num_non_arccorrected_bins_v, - max_num_non_arccorrected_bins_v, - num_detectors_per_ring_v, - inner_ring_radius_v, + max_num_non_arccorrected_bins_v, + num_detectors_per_ring_v, + inner_ring_radius_v, average_depth_of_interaction_v, ring_spacing_v, bin_size_v, intrinsic_tilt_v, - num_axial_blocks_per_bucket_v, num_transaxial_blocks_per_bucket_v, - num_axial_crystals_per_block_v, num_transaxial_crystals_per_block_v, - num_axial_crystals_per_singles_unit_v, + num_axial_blocks_per_bucket_v, num_transaxial_blocks_per_bucket_v, + num_axial_crystals_per_block_v, num_transaxial_crystals_per_block_v, + num_axial_crystals_per_singles_unit_v, num_transaxial_crystals_per_singles_unit_v, - num_detector_layers_v, + num_detector_layers_v); +} + +void +Scanner:: +set_params(Type type_v,const list& list_of_names_v, + int num_rings_v, + int max_num_non_arccorrected_bins_v, + int num_detectors_per_ring_v, + float inner_ring_radius_v, + float average_depth_of_interaction_v, + float ring_spacing_v, + float bin_size_v, float intrinsic_tilt_v, + float energy_resolution_v, + float reference_energy_v, + int num_axial_blocks_per_bucket_v, int num_transaxial_blocks_per_bucket_v, + int num_axial_crystals_per_block_v, int num_transaxial_crystals_per_block_v, + int num_axial_crystals_per_singles_unit_v, + int num_transaxial_crystals_per_singles_unit_v, + int num_detector_layers_v) +{ + set_params(type_v, list_of_names_v, num_rings_v, + max_num_non_arccorrected_bins_v, + max_num_non_arccorrected_bins_v, + num_detectors_per_ring_v, + inner_ring_radius_v, + average_depth_of_interaction_v, + ring_spacing_v, bin_size_v, intrinsic_tilt_v, energy_resolution_v, - reference_energy_v); + reference_energy_v, + num_axial_blocks_per_bucket_v, num_transaxial_blocks_per_bucket_v, + num_axial_crystals_per_block_v, num_transaxial_crystals_per_block_v, + num_axial_crystals_per_singles_unit_v, + num_transaxial_crystals_per_singles_unit_v, + num_detector_layers_v); } +void +Scanner:: +set_params(Type type_v,const list& list_of_names_v, + int num_rings_v, + int max_num_non_arccorrected_bins_v, + int num_detectors_per_ring_v, + float inner_ring_radius_v, + float average_depth_of_interaction_v, + float ring_spacing_v, + float bin_size_v, float intrinsic_tilt_v, + int num_axial_blocks_per_bucket_v, int num_transaxial_blocks_per_bucket_v, + int num_axial_crystals_per_block_v, int num_transaxial_crystals_per_block_v, + int num_axial_crystals_per_singles_unit_v, + int num_transaxial_crystals_per_singles_unit_v, + int num_detector_layers_v, + short int max_num_of_timing_bins_v, + float size_timing_bin_v, + float timing_resolution_v) +{ + set_params(type_v, list_of_names_v, num_rings_v, + max_num_non_arccorrected_bins_v, + max_num_non_arccorrected_bins_v, + num_detectors_per_ring_v, + inner_ring_radius_v, + average_depth_of_interaction_v, + ring_spacing_v, bin_size_v, intrinsic_tilt_v, + num_axial_blocks_per_bucket_v, num_transaxial_blocks_per_bucket_v, + num_axial_crystals_per_block_v, num_transaxial_crystals_per_block_v, + num_axial_crystals_per_singles_unit_v, + num_transaxial_crystals_per_singles_unit_v, + num_detector_layers_v, + max_num_of_timing_bins_v, + size_timing_bin_v, + timing_resolution_v); +} void Scanner:: -set_params(Type type_v,const list& list_of_names_v, - int num_rings_v, +set_params(Type type_v,const list& list_of_names_v, + int num_rings_v, int max_num_non_arccorrected_bins_v, int default_num_arccorrected_bins_v, int num_detectors_per_ring_v, @@ -604,25 +820,69 @@ set_params(Type type_v,const list& list_of_names_v, float average_depth_of_interaction_v, float ring_spacing_v, float bin_size_v, float intrinsic_tilt_v, - int num_axial_blocks_per_bucket_v, int num_transaxial_blocks_per_bucket_v, + int num_axial_blocks_per_bucket_v, int num_transaxial_blocks_per_bucket_v, int num_axial_crystals_per_block_v, int num_transaxial_crystals_per_block_v, int num_axial_crystals_per_singles_unit_v, int num_transaxial_crystals_per_singles_unit_v, - int num_detector_layers_v, + int num_detector_layers_v) +{ + type = type_v; + list_of_names = list_of_names_v; + num_rings = num_rings_v; + max_num_non_arccorrected_bins = max_num_non_arccorrected_bins_v; + default_num_arccorrected_bins = default_num_arccorrected_bins_v; + num_detectors_per_ring = num_detectors_per_ring_v; + inner_ring_radius = inner_ring_radius_v; + average_depth_of_interaction = average_depth_of_interaction_v; + ring_spacing = ring_spacing_v; + bin_size = bin_size_v; + intrinsic_tilt = intrinsic_tilt_v; + num_transaxial_blocks_per_bucket = num_transaxial_blocks_per_bucket_v; + num_axial_blocks_per_bucket = num_axial_blocks_per_bucket_v; + num_axial_crystals_per_block= num_axial_crystals_per_block_v; + num_transaxial_crystals_per_block= num_transaxial_crystals_per_block_v; + num_axial_crystals_per_singles_unit = num_axial_crystals_per_singles_unit_v; + num_transaxial_crystals_per_singles_unit = num_transaxial_crystals_per_singles_unit_v; + num_detector_layers = num_detector_layers_v; + + energy_resolution = -1.f; + reference_energy = -1.f; + max_num_of_timing_bins = -1; + size_timing_bin = -1.f; + timing_resolution = -1.f; + +} + +void +Scanner:: +set_params(Type type_v,const list& list_of_names_v, + int num_rings_v, + int max_num_non_arccorrected_bins_v, + int default_num_arccorrected_bins_v, + int num_detectors_per_ring_v, + float inner_ring_radius_v, + float average_depth_of_interaction_v, + float ring_spacing_v, + float bin_size_v, float intrinsic_tilt_v, float energy_resolution_v, - float reference_energy_v) + float reference_energy_v, + int num_axial_blocks_per_bucket_v, int num_transaxial_blocks_per_bucket_v, + int num_axial_crystals_per_block_v, int num_transaxial_crystals_per_block_v, + int num_axial_crystals_per_singles_unit_v, + int num_transaxial_crystals_per_singles_unit_v, + int num_detector_layers_v) { type = type_v; - list_of_names = list_of_names_v; + list_of_names = list_of_names_v; num_rings = num_rings_v; max_num_non_arccorrected_bins = max_num_non_arccorrected_bins_v; default_num_arccorrected_bins = default_num_arccorrected_bins_v; - num_detectors_per_ring = num_detectors_per_ring_v; + num_detectors_per_ring = num_detectors_per_ring_v; inner_ring_radius = inner_ring_radius_v; average_depth_of_interaction = average_depth_of_interaction_v; ring_spacing = ring_spacing_v; bin_size = bin_size_v; - intrinsic_tilt = intrinsic_tilt_v; + intrinsic_tilt = intrinsic_tilt_v; num_transaxial_blocks_per_bucket = num_transaxial_blocks_per_bucket_v; num_axial_blocks_per_bucket = num_axial_blocks_per_bucket_v; num_axial_crystals_per_block= num_axial_crystals_per_block_v; @@ -633,7 +893,57 @@ set_params(Type type_v,const list& list_of_names_v, energy_resolution = energy_resolution_v; reference_energy = reference_energy_v; + max_num_of_timing_bins = -1; + size_timing_bin = -1.f; + timing_resolution = -1.f; + +} + +void +Scanner:: +set_params(Type type_v,const list& list_of_names_v, + int num_rings_v, + int max_num_non_arccorrected_bins_v, + int default_num_arccorrected_bins_v, + int num_detectors_per_ring_v, + float inner_ring_radius_v, + float average_depth_of_interaction_v, + float ring_spacing_v, + float bin_size_v, float intrinsic_tilt_v, + int num_axial_blocks_per_bucket_v, int num_transaxial_blocks_per_bucket_v, + int num_axial_crystals_per_block_v, int num_transaxial_crystals_per_block_v, + int num_axial_crystals_per_singles_unit_v, + int num_transaxial_crystals_per_singles_unit_v, + int num_detector_layers_v, + short int max_num_of_timing_bins_v, + float size_timing_bin_v, + float timing_resolution_v) +{ + type = type_v; + list_of_names = list_of_names_v; + num_rings = num_rings_v; + max_num_non_arccorrected_bins = max_num_non_arccorrected_bins_v; + default_num_arccorrected_bins = default_num_arccorrected_bins_v; + num_detectors_per_ring = num_detectors_per_ring_v; + inner_ring_radius = inner_ring_radius_v; + average_depth_of_interaction = average_depth_of_interaction_v; + ring_spacing = ring_spacing_v; + bin_size = bin_size_v; + intrinsic_tilt = intrinsic_tilt_v; + num_transaxial_blocks_per_bucket = num_transaxial_blocks_per_bucket_v; + num_axial_blocks_per_bucket = num_axial_blocks_per_bucket_v; + num_axial_crystals_per_block= num_axial_crystals_per_block_v; + num_transaxial_crystals_per_block= num_transaxial_crystals_per_block_v; + num_axial_crystals_per_singles_unit = num_axial_crystals_per_singles_unit_v; + num_transaxial_crystals_per_singles_unit = num_transaxial_crystals_per_singles_unit_v; + num_detector_layers = num_detector_layers_v; + + max_num_of_timing_bins = max_num_of_timing_bins_v; + size_timing_bin = size_timing_bin_v; + timing_resolution = timing_resolution_v; + energy_resolution = -1.f; + reference_energy = -1.f; } diff --git a/src/include/stir/Scanner.h b/src/include/stir/Scanner.h index 565d6597be..d27c4a0530 100644 --- a/src/include/stir/Scanner.h +++ b/src/include/stir/Scanner.h @@ -113,7 +113,7 @@ class Scanner any given parameters. */ enum Type {E931, E951, E953, E921, E925, E961, E962, E966, E1080, Siemens_mMR, RPT,HiDAC, - /*Type_mCT, Type_mCT_TOF, Type_mCT_TOF_100, Type_mCT_TOF_200, Type_mCT_TOF_400,*/ + Type_mCT, Type_mCT_TOF, Type_mCT_TOF_10, Type_mCT_TOF_20, Type_mCT_TOF_50, Type_mCT_TOF_100, Type_mCT_TOF_200, Type_mCT_TOF_400, Advance, DiscoveryLS, DiscoveryST, DiscoverySTE, DiscoveryRX, Discovery600, HZLR, RATPET, PANDA, HYPERimage, nanoPET, HRRT, Allegro, GeminiTF, User_defined_scanner, Unknown_scanner}; @@ -123,79 +123,100 @@ class Scanner //! constructor -(list of names) - /*! size info is in mm - \param intrinsic_tilt_v value in radians, \see get_default_intrinsic_tilt() - \warning calls error() when block/bucket info are inconsistent - */ - Scanner(Type type_v, const std::list& list_of_names_v, - int num_detectors_per_ring_v, int num_rings_v, - int max_num_non_arccorrected_bins_v, - int default_num_arccorrected_bins_v, - float inner_ring_radius_v, float average_depth_of_interaction_v, - float ring_spacing_v, float bin_size_v, float intrinsic_tilt_v, - int num_axial_blocks_per_bucket_v, int num_transaxial_blocks_per_bucket_v, - int num_axial_crystals_per_block_v, int num_transaxial_crystals_per_block_v, - int num_axial_crystals_per_singles_unit_v, - int num_transaxial_crystals_per_singles_unit_v, - int num_detector_layers_v, - float energy_resolution_v = -1.0f, - float reference_energy_v = -1.0f); - - //! constructor ( a single name) - /*! size info is in mm - \param intrinsic_tilt value in radians, \see get_default_intrinsic_tilt() - \warning calls error() when block/bucket info are inconsistent - */ - Scanner(Type type_v, const std::string& name, - int num_detectors_per_ring_v, int num_rings_v, - int max_num_non_arccorrected_bins_v, - int default_num_arccorrected_bins_v, - float inner_ring_radius_v, float average_depth_of_interaction_v, - float ring_spacing_v, float bin_size_v, float intrinsic_tilt_v, - int num_axial_blocks_per_bucket_v, int num_transaxial_blocks_per_bucket_v, - int num_axial_crystals_per_block_v, int num_transaxial_crystals_per_block_v, - int num_axial_crystals_per_singles_unit_v, - int num_transaxial_crystals_per_singles_unit_v, - int num_detector_layers_v, - float energy_resolution_v = -1.0f, - float reference_energy_v = -1.0f); - - - //! TOF constructor -(list of names) - Scanner(Type type_v, const std::list& list_of_names_v, - int num_detectors_per_ring_v, int num_rings_v, - int max_num_non_arccorrected_bins_v, - int default_num_arccorrected_bins_v, - float inner_ring_radius_v, float average_depth_of_interaction_v, - float ring_spacing_v, float bin_size_v, float intrinsic_tilt_v, - int num_axial_blocks_per_bucket_v, int num_transaxial_blocks_per_bucket_v, - int num_axial_crystals_per_block_v, int num_transaxial_crystals_per_block_v, - int num_axial_crystals_per_singles_unit_v, - int num_transaxial_crystals_per_singles_unit_v, - int num_detector_layers_v, - int max_num_of_timing_bins, - float size_timing_bin, - float timing_resolution, - float energy_resolution_v = -1.0f, - float reference_energy_v = -1.0f); - - //! TOF constructor ( a single name) - Scanner(Type type_v, const std::string& name, - int num_detectors_per_ring_v, int num_rings_v, - int max_num_non_arccorrected_bins_v, - int default_num_arccorrected_bins_v, - float inner_ring_radius_v, float average_depth_of_interaction_v, - float ring_spacing_v, float bin_size_v, float intrinsic_tilt_v, - int num_axial_blocks_per_bucket_v, int num_transaxial_blocks_per_bucket_v, - int num_axial_crystals_per_block_v, int num_transaxial_crystals_per_block_v, - int num_axial_crystals_per_singles_unit_v, - int num_transaxial_crystals_per_singles_unit_v, - int num_detector_layers_v, - int max_num_of_timing_bins, - float size_timing_bin, - float timing_resolution, - float energy_resolution_v = -1.0f, - float reference_energy_v = -1.0f); + /*! size info is in mm + \param intrinsic_tilt_v value in radians, \see get_default_intrinsic_tilt() + \warning calls error() when block/bucket info are inconsistent + */ + Scanner(Type type_v, const std::list& list_of_names_v, + int num_detectors_per_ring_v, int num_rings_v, + int max_num_non_arccorrected_bins_v, + int default_num_arccorrected_bins_v, + float inner_ring_radius_v, float average_depth_of_interaction_v, + float ring_spacing_v, float bin_size_v, float intrinsic_tilt_v, + int num_axial_blocks_per_bucket_v, int num_transaxial_blocks_per_bucket_v, + int num_axial_crystals_per_block_v, int num_transaxial_crystals_per_block_v, + int num_axial_crystals_per_singles_unit_v, + int num_transaxial_crystals_per_singles_unit_v, + int num_detector_layers_v); + + //! Overloaded for energy resolution + Scanner(Type type_v, const std::list& list_of_names_v, + int num_detectors_per_ring_v, int num_rings_v, + int max_num_non_arccorrected_bins_v, + int default_num_arccorrected_bins_v, + float inner_ring_radius_v, float average_depth_of_interaction_v, + float ring_spacing_v, float bin_size_v, float intrinsic_tilt_v, + int num_axial_blocks_per_bucket_v, int num_transaxial_blocks_per_bucket_v, + int num_axial_crystals_per_block_v, int num_transaxial_crystals_per_block_v, + int num_axial_crystals_per_singles_unit_v, + int num_transaxial_crystals_per_singles_unit_v, + int num_detector_layers_v, + float energy_resolution_v, + float reference_energy_v); + + //! Overloaded constructed with TOF information + Scanner(Type type_v, const std::list& list_of_names_v, + int num_detectors_per_ring_v, int num_rings_v, + int max_num_non_arccorrected_bins_v, + int default_num_arccorrected_bins_v, + float inner_ring_radius_v, float average_depth_of_interaction_v, + float ring_spacing_v, float bin_size_v, float intrinsic_tilt_v, + int num_axial_blocks_per_bucket_v, int num_transaxial_blocks_per_bucket_v, + int num_axial_crystals_per_block_v, int num_transaxial_crystals_per_block_v, + int num_axial_crystals_per_singles_unit_v, + int num_transaxial_crystals_per_singles_unit_v, + int num_detector_layers_v, + short int max_num_of_timing_bins, + float size_timing_bin, + float timing_resolution); + + //! constructor ( a single name) + /*! size info is in mm + \param intrinsic_tilt value in radians, \see get_default_intrinsic_tilt() + \warning calls error() when block/bucket info are inconsistent + */ + Scanner(Type type_v, const std::string& name, + int num_detectors_per_ring_v, int num_rings_v, + int max_num_non_arccorrected_bins_v, + int default_num_arccorrected_bins_v, + float inner_ring_radius_v, float average_depth_of_interaction_v, + float ring_spacing_v, float bin_size_v, float intrinsic_tilt_v, + int num_axial_blocks_per_bucket_v, int num_transaxial_blocks_per_bucket_v, + int num_axial_crystals_per_block_v, int num_transaxial_crystals_per_block_v, + int num_axial_crystals_per_singles_unit_v, + int num_transaxial_crystals_per_singles_unit_v, + int num_detector_layers_v); + + //! Overloaded for energy resolution + Scanner(Type type_v, const std::string& name, + int num_detectors_per_ring_v, int num_rings_v, + int max_num_non_arccorrected_bins_v, + int default_num_arccorrected_bins_v, + float inner_ring_radius_v, float average_depth_of_interaction_v, + float ring_spacing_v, float bin_size_v, float intrinsic_tilt_v, + int num_axial_blocks_per_bucket_v, int num_transaxial_blocks_per_bucket_v, + int num_axial_crystals_per_block_v, int num_transaxial_crystals_per_block_v, + int num_axial_crystals_per_singles_unit_v, + int num_transaxial_crystals_per_singles_unit_v, + int num_detector_layers_v, + float energy_resolution_v, + float reference_energy_v); + + //! Overloaded constructor with TOF information ( a single name) + Scanner(Type type_v, const std::string& name, + int num_detectors_per_ring_v, int num_rings_v, + int max_num_non_arccorrected_bins_v, + int default_num_arccorrected_bins_v, + float inner_ring_radius_v, float average_depth_of_interaction_v, + float ring_spacing_v, float bin_size_v, float intrinsic_tilt_v, + int num_axial_blocks_per_bucket_v, int num_transaxial_blocks_per_bucket_v, + int num_axial_crystals_per_block_v, int num_transaxial_crystals_per_block_v, + int num_axial_crystals_per_singles_unit_v, + int num_transaxial_crystals_per_singles_unit_v, + int num_detector_layers_v, + short int max_num_of_timing_bins, + float size_timing_bin, + float timing_resolution); @@ -458,24 +479,89 @@ class Scanner //! set all parameters, case where default_num_arccorrected_bins==max_num_non_arccorrected_bins void set_params(Type type_v, const std::list& list_of_names_v, - int num_rings_v, + int num_rings_v, + int max_num_non_arccorrected_bins_v, + int num_detectors_per_ring_v, + float inner_ring_radius_v, + float average_depth_of_interaction_v, + float ring_spacing_v, + float bin_size_v, float intrinsic_tilt_v, + int num_axial_blocks_per_bucket_v, int num_transaxial_blocks_per_bucket_v, + int num_axial_crystals_per_block_v, int num_transaxial_crystals_per_block_v, + int num_axial_crystals_per_singles_unit_v, + int num_transaxial_crystals_per_singles_unit_v, + int num_detector_layers_v); + + void set_params(Type type_v, const std::list& list_of_names_v, + int num_rings_v, + int max_num_non_arccorrected_bins_v, + int num_detectors_per_ring_v, + float inner_ring_radius_v, + float average_depth_of_interaction_v, + float ring_spacing_v, + float bin_size_v, float intrinsic_tilt_v, + float energy_resolution_v, + float reference_energy, + int num_axial_blocks_per_bucket_v, int num_transaxial_blocks_per_bucket_v, + int num_axial_crystals_per_block_v, int num_transaxial_crystals_per_block_v, + int num_axial_crystals_per_singles_unit_v, + int num_transaxial_crystals_per_singles_unit_v, + int num_detector_layers_v); + + //! Overloaded with TOF stuff. + void set_params(Type type_v, const std::list& list_of_names_v, + int num_rings_v, int max_num_non_arccorrected_bins_v, int num_detectors_per_ring_v, float inner_ring_radius_v, float average_depth_of_interaction_v, float ring_spacing_v, float bin_size_v, float intrinsic_tilt_v, - int num_axial_blocks_per_bucket_v, int num_transaxial_blocks_per_bucket_v, + int num_axial_blocks_per_bucket_v, int num_transaxial_blocks_per_bucket_v, int num_axial_crystals_per_block_v, int num_transaxial_crystals_per_block_v, int num_axial_crystals_per_singles_unit_v, int num_transaxial_crystals_per_singles_unit_v, int num_detector_layers_v, - float energy_resolution_v = -1.0f, - float reference_energy = -1.0f); + short int max_num_of_timing_bins_v, + float size_timing_bin_v, + float timing_resolution_v); + + // ! set all parameters + void set_params(Type type_v, const std::list& list_of_names_v, + int num_rings_v, + int max_num_non_arccorrected_bins_v, + int default_num_arccorrected_bins_v, + int num_detectors_per_ring_v, + float inner_ring_radius_v, + float average_depth_of_interaction_v, + float ring_spacing_v, + float bin_size_v, float intrinsic_tilt_v, + int num_axial_blocks_per_bucket_v, int num_transaxial_blocks_per_bucket_v, + int num_axial_crystals_per_block_v, int num_transaxial_crystals_per_block_v, + int num_axial_crystals_per_singles_unit_v, + int num_transaxial_crystals_per_singles_unit_v, + int num_detector_layers_v); + + void set_params(Type type_v, const std::list& list_of_names_v, + int num_rings_v, + int max_num_non_arccorrected_bins_v, + int default_num_arccorrected_bins_v, + int num_detectors_per_ring_v, + float inner_ring_radius_v, + float average_depth_of_interaction_v, + float ring_spacing_v, + float bin_size_v, float intrinsic_tilt_v, + float energy_resolution_v, + float reference_energy, + int num_axial_blocks_per_bucket_v, int num_transaxial_blocks_per_bucket_v, + int num_axial_crystals_per_block_v, int num_transaxial_crystals_per_block_v, + int num_axial_crystals_per_singles_unit_v, + int num_transaxial_crystals_per_singles_unit_v, + int num_detector_layers_v); - //! set all parameters + //! Overloaded with TOF stuff. void set_params(Type type_v, const std::list& list_of_names_v, - int num_rings_v, + int num_rings_v, int max_num_non_arccorrected_bins_v, int default_num_arccorrected_bins_v, int num_detectors_per_ring_v, @@ -483,13 +569,14 @@ class Scanner float average_depth_of_interaction_v, float ring_spacing_v, float bin_size_v, float intrinsic_tilt_v, - int num_axial_blocks_per_bucket_v, int num_transaxial_blocks_per_bucket_v, + int num_axial_blocks_per_bucket_v, int num_transaxial_blocks_per_bucket_v, int num_axial_crystals_per_block_v, int num_transaxial_crystals_per_block_v, int num_axial_crystals_per_singles_unit_v, int num_transaxial_crystals_per_singles_unit_v, int num_detector_layers_v, - float energy_resolution_v = -1.0f, - float reference_energy = -1.0f); + short int max_num_of_timing_bins_v, + float size_timing_bin_v, + float timing_resolution_v); }; diff --git a/src/include/stir/recon_buildblock/ProjMatrixByBin.h b/src/include/stir/recon_buildblock/ProjMatrixByBin.h index 811167f522..a82f98a814 100644 --- a/src/include/stir/recon_buildblock/ProjMatrixByBin.h +++ b/src/include/stir/recon_buildblock/ProjMatrixByBin.h @@ -225,6 +225,13 @@ class ProjMatrixByBin : ProjMatrixElemsForOneBin& ) const; + //! We need a local copy of the discretised density in order to find the + //! cartesian coordinates of each voxel. + shared_ptr > image_info_sptr; + + //! We need a local copy of the proj_data_info to get the integration boundaries. + shared_ptr proj_data_info_sptr; + //! The method to store data in the cache. void cache_proj_matrix_elems_for_one_bin( const ProjMatrixElemsForOneBin&) STIR_MUTABLE_CONST; @@ -259,13 +266,6 @@ class ProjMatrixByBin : //! 1/(2*sigma_in_mm) float r_sqrt2_gauss_sigma; - //! We need a local copy of the discretised density in order to find the - //! cartesian coordinates of each voxel. - shared_ptr > image_info_sptr; - - //! We need a local copy of the proj_data_info to get the integration boundaries. - shared_ptr proj_data_info_sptr; - //! The function which actually applies the TOF kernel on the LOR. inline void apply_tof_kernel( ProjMatrixElemsForOneBin& nonTOF_probabilities, ProjMatrixElemsForOneBin& tof_probabilities, diff --git a/src/include/stir/recon_buildblock/ProjMatrixByBin.inl b/src/include/stir/recon_buildblock/ProjMatrixByBin.inl index 56e482d323..6c7590c1f3 100644 --- a/src/include/stir/recon_buildblock/ProjMatrixByBin.inl +++ b/src/include/stir/recon_buildblock/ProjMatrixByBin.inl @@ -208,8 +208,8 @@ ProjMatrixByBin::apply_tof_kernel(ProjMatrixElemsForOneBin& nonTOF_probabilities get_tof_value(low_dist, high_dist, new_value); new_value *= element_ptr->get_value(); - if (new_value <= 0.0001f) - continue; + //if (new_value <= 0.0001f) + // continue; tof_probabilities.push_back(ProjMatrixElemsForOneBin::value_type(element_ptr->get_coords(), new_value)); } @@ -219,7 +219,7 @@ void ProjMatrixByBin:: get_tof_value(float& d1, float& d2, float& val) const { - val = ( erf(d2) - erf(d1)) * 0.5; + val = ( erf(d2) - erf(d1)) * 0.5f; } END_NAMESPACE_STIR diff --git a/src/include/stir/recon_buildblock/ProjMatrixByBinUsingRayTracing.h b/src/include/stir/recon_buildblock/ProjMatrixByBinUsingRayTracing.h index dface8d895..a274c2e6bf 100644 --- a/src/include/stir/recon_buildblock/ProjMatrixByBinUsingRayTracing.h +++ b/src/include/stir/recon_buildblock/ProjMatrixByBinUsingRayTracing.h @@ -136,8 +136,8 @@ public : /*! Note that the density_info_ptr is not stored in this object. It's only used to get some info on sizes etc. */ virtual void set_up( - const shared_ptr& proj_data_info_ptr, - const shared_ptr >& density_info_ptr // TODO should be Info only + const shared_ptr& proj_data_info_sptr_v, + const shared_ptr >& density_info_sptr_v // TODO should be Info only ); //! \name If a cylindrical FOV or the whole image will be handled diff --git a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx index f79fb6c043..22522342f8 100644 --- a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx +++ b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx @@ -1,6 +1,7 @@ /* Copyright (C) 2003- 2011, Hammersmith Imanet Ltd Copyright (C) 2014, 2016, University College London + Copyright (C) 2016, University of Hull This file is part of STIR. This file is free software; you can redistribute it and/or modify @@ -29,6 +30,7 @@ #include "stir/recon_buildblock/ProjMatrixByBinUsingRayTracing.h" #include "stir/recon_buildblock/ProjMatrixElemsForOneBin.h" #include "stir/recon_buildblock/ProjectorByBinPairUsingProjMatrixByBin.h" +#include "stir/LORCoordinates.h" #include "stir/ProjDataInfoCylindricalNoArcCorr.h" #include "stir/ProjData.h" #include "stir/listmode/CListRecord.h" @@ -198,6 +200,8 @@ set_up_before_sensitivity(shared_ptr const& target_sptr) // set projector to be used for the calculations this->PM_sptr->set_up(proj_data_info_cyl_sptr->create_shared_clone(),target_sptr); + this->PM_sptr->enable_tof(proj_data_info_cyl_sptr->create_shared_clone(), this->use_tof); + shared_ptr forward_projector_ptr(new ForwardProjectorByBinUsingProjMatrixByBin(this->PM_sptr)); shared_ptr back_projector_ptr(new BackProjectorByBinUsingProjMatrixByBin(this->PM_sptr)); @@ -424,12 +428,19 @@ compute_sub_gradient_without_penalty_plus_sensitivity(TargetT& gradient, ProjDataInfoCylindricalNoArcCorr* proj_data_no_arc_ptr = dynamic_cast (proj_data_info_cyl_sptr.get()); + CartesianCoordinate3D lor_point_1, lor_point_2; + const double start_time = this->frame_defs.get_start_time(this->current_frame_num); const double end_time = this->frame_defs.get_end_time(this->current_frame_num); long num_stored_events = 0; const float max_quotient = 10000.F; + // Putting the Bins here I avoid rellocation. + Bin measured_bin; + Bin fwd_bin; + LORAs2Points lor_points; + //go to the beginning of this frame // list_mode_data_sptr->set_get_position(start_time); // TODO implement function that will do this for a random time @@ -463,17 +474,22 @@ compute_sub_gradient_without_penalty_plus_sensitivity(TargetT& gradient, if (record.is_event() && record.event().is_prompt()) { - Bin measured_bin; measured_bin.set_bin_value(1.0f); - record.event().get_bin(measured_bin, *proj_data_info_cyl_sptr); + this->use_tof ? record.full_event(measured_bin, *proj_data_info_cyl_sptr): + record.event().get_bin(measured_bin, *proj_data_info_cyl_sptr); + + // In theory we have already done all these checks so we can + // remove this if statement. if (measured_bin.get_bin_value() != 1.0f || measured_bin.segment_num() < proj_data_info_cyl_sptr->get_min_segment_num() || measured_bin.segment_num() > proj_data_info_cyl_sptr->get_max_segment_num() || measured_bin.tangential_pos_num() < proj_data_info_cyl_sptr->get_min_tangential_pos_num() || measured_bin.tangential_pos_num() > proj_data_info_cyl_sptr->get_max_tangential_pos_num() || measured_bin.axial_pos_num() < proj_data_info_cyl_sptr->get_min_axial_pos_num(measured_bin.segment_num()) - || measured_bin.axial_pos_num() > proj_data_info_cyl_sptr->get_max_axial_pos_num(measured_bin.segment_num())) + || measured_bin.axial_pos_num() > proj_data_info_cyl_sptr->get_max_axial_pos_num(measured_bin.segment_num()) + || measured_bin.timing_pos_num() < proj_data_info_cyl_sptr->get_min_timing_pos_num() + || measured_bin.timing_pos_num() > proj_data_info_cyl_sptr->get_max_timing_pos_num()) { continue; } @@ -493,9 +509,18 @@ compute_sub_gradient_without_penalty_plus_sensitivity(TargetT& gradient, } } - this->PM_sptr->get_proj_matrix_elems_for_one_bin(proj_matrix_row, measured_bin); + if(this->use_tof) + { + // proj_data_no_arc_ptr->get_LOR_as_two_points(lor_point_1, lor_point_2, measured_bin); + lor_points = record.event().get_LOR(); + this->PM_sptr->get_proj_matrix_elems_for_one_bin_with_tof(proj_matrix_row, + measured_bin, + lor_points.p1(), lor_points.p2()); + } + else + this->PM_sptr->get_proj_matrix_elems_for_one_bin(proj_matrix_row, measured_bin); + //in_the_range++; - Bin fwd_bin; fwd_bin.set_bin_value(0.0f); proj_matrix_row.forward_project(fwd_bin,current_estimate); // additive sinogram diff --git a/src/recon_buildblock/ProjMatrixByBinUsingRayTracing.cxx b/src/recon_buildblock/ProjMatrixByBinUsingRayTracing.cxx index 8560fc0f42..0702526c1f 100644 --- a/src/recon_buildblock/ProjMatrixByBinUsingRayTracing.cxx +++ b/src/recon_buildblock/ProjMatrixByBinUsingRayTracing.cxx @@ -2,6 +2,7 @@ Copyright (C) 2000 PARAPET partners Copyright (C) 2000-2011, Hammersmith Imanet Ltd Copyright (C) 2013-2014, University College London + Copyright (C) 2016, University of Hull This file is part of STIR. This file is free software; you can redistribute it and/or modify @@ -23,6 +24,7 @@ \brief non-inline implementations for stir::ProjMatrixByBinUsingRayTracing + \author Nikos Efthimiou \author Mustapha Sadki \author Kris Thielemans \author PARAPET project @@ -267,26 +269,28 @@ static bool is_multiple(const float a, const float b) void ProjMatrixByBinUsingRayTracing:: set_up( - const shared_ptr& proj_data_info_ptr_v, - const shared_ptr >& density_info_ptr // TODO should be Info only + const shared_ptr& proj_data_info_sptr_v, + const shared_ptr >& density_info_sptr_v // TODO should be Info only ) { - ProjMatrixByBin::set_up(proj_data_info_ptr_v, density_info_ptr); + ProjMatrixByBin::set_up(proj_data_info_sptr_v, density_info_sptr_v); - proj_data_info_ptr= proj_data_info_ptr_v; - const VoxelsOnCartesianGrid * image_info_ptr = - dynamic_cast*> (density_info_ptr.get()); + proj_data_info_ptr= proj_data_info_sptr_v; + image_info_sptr.reset( + dynamic_cast* > (density_info_sptr_v->clone() )); +// const VoxelsOnCartesianGrid * image_info_ptr = +// dynamic_cast*> (density_info_ptr.get()); - if (image_info_ptr == NULL) + if(is_null_ptr(image_info_sptr)) error("ProjMatrixByBinUsingRayTracing initialised with a wrong type of DiscretisedDensity\n"); - voxel_size = image_info_ptr->get_voxel_size(); - origin = image_info_ptr->get_origin(); - image_info_ptr->get_regular_range(min_index, max_index); + voxel_size = image_info_sptr->get_voxel_size(); + origin = image_info_sptr->get_origin(); + image_info_sptr->get_regular_range(min_index, max_index); symmetries_sptr.reset( new DataSymmetriesForBins_PET_CartesianGrid(proj_data_info_ptr, - density_info_ptr, + density_info_sptr_v, do_symmetry_90degrees_min_phi, do_symmetry_180degrees_min_phi, do_symmetry_swap_segment, From aae2d7b0861effedd92cc2501dded21f24803136 Mon Sep 17 00:00:00 2001 From: NikEfth Date: Fri, 23 Dec 2016 20:29:34 +0000 Subject: [PATCH 035/509] Forgot the function full_event() --- src/include/stir/listmode/CListRecordROOT.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/include/stir/listmode/CListRecordROOT.h b/src/include/stir/listmode/CListRecordROOT.h index 3db20736af..8164b523cd 100644 --- a/src/include/stir/listmode/CListRecordROOT.h +++ b/src/include/stir/listmode/CListRecordROOT.h @@ -167,6 +167,12 @@ class CListRecordROOT : public CListRecord // currently no gating yet return this->time_data; } + virtual void full_event(Bin&, const ProjDataInfo&) const + { + event_data.get_bin(bin, proj_data_info); + time_data.get_bin(bin, proj_data_info); + } + bool operator==(const CListRecord& e2) const { return dynamic_cast(&e2) != 0 && From b3f7047c636f2c78a4f8df4ff3b84a17108c22ce Mon Sep 17 00:00:00 2001 From: NikEfth Date: Fri, 23 Dec 2016 20:31:37 +0000 Subject: [PATCH 036/509] small bugfix --- src/include/stir/listmode/CListRecordROOT.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/include/stir/listmode/CListRecordROOT.h b/src/include/stir/listmode/CListRecordROOT.h index 8164b523cd..5a2cb84264 100644 --- a/src/include/stir/listmode/CListRecordROOT.h +++ b/src/include/stir/listmode/CListRecordROOT.h @@ -167,7 +167,7 @@ class CListRecordROOT : public CListRecord // currently no gating yet return this->time_data; } - virtual void full_event(Bin&, const ProjDataInfo&) const + virtual void full_event(Bin& bin, const ProjDataInfo& proj_data_info) const { event_data.get_bin(bin, proj_data_info); time_data.get_bin(bin, proj_data_info); From af479ce15c0b7c46f0bc0ec416da509075621601 Mon Sep 17 00:00:00 2001 From: NikEfth Date: Fri, 23 Dec 2016 21:51:27 +0000 Subject: [PATCH 037/509] keep forgetting code. --- src/IO/InputStreamFromROOTFileForCylindricalPET.cxx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/IO/InputStreamFromROOTFileForCylindricalPET.cxx b/src/IO/InputStreamFromROOTFileForCylindricalPET.cxx index 6027f8235c..0f5682f805 100644 --- a/src/IO/InputStreamFromROOTFileForCylindricalPET.cxx +++ b/src/IO/InputStreamFromROOTFileForCylindricalPET.cxx @@ -112,10 +112,18 @@ get_next_record(CListRecordROOT& record) crystal1 += offset_dets; crystal2 += offset_dets; + short int delta_timing_bin = 0; + double delta_time = time2 - time1; + + if (delta_time >=0) + delta_timing_bin = static_cast(delta_time * least_significant_clock_bit); + else + delta_timing_bin = static_cast(delta_time * least_significant_clock_bit); + return record.init_from_data(ring1, ring2, crystal1, crystal2, - time1, time2, + time1, delta_timing_bin, event1, event2); } From bbdf71838d98e14da90fb0b9ae15e937fbd3eeee Mon Sep 17 00:00:00 2001 From: NikEfth Date: Sat, 24 Dec 2016 09:44:49 +0000 Subject: [PATCH 038/509] floats --- src/IO/InputStreamFromROOTFileForCylindricalPET.cxx | 2 +- src/include/stir/recon_buildblock/ProjMatrixByBin.inl | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/IO/InputStreamFromROOTFileForCylindricalPET.cxx b/src/IO/InputStreamFromROOTFileForCylindricalPET.cxx index 0f5682f805..0f4d687987 100644 --- a/src/IO/InputStreamFromROOTFileForCylindricalPET.cxx +++ b/src/IO/InputStreamFromROOTFileForCylindricalPET.cxx @@ -112,7 +112,7 @@ get_next_record(CListRecordROOT& record) crystal1 += offset_dets; crystal2 += offset_dets; - short int delta_timing_bin = 0; + float delta_timing_bin = 0; double delta_time = time2 - time1; if (delta_time >=0) diff --git a/src/include/stir/recon_buildblock/ProjMatrixByBin.inl b/src/include/stir/recon_buildblock/ProjMatrixByBin.inl index 6c7590c1f3..3c372cf3c4 100644 --- a/src/include/stir/recon_buildblock/ProjMatrixByBin.inl +++ b/src/include/stir/recon_buildblock/ProjMatrixByBin.inl @@ -208,8 +208,6 @@ ProjMatrixByBin::apply_tof_kernel(ProjMatrixElemsForOneBin& nonTOF_probabilities get_tof_value(low_dist, high_dist, new_value); new_value *= element_ptr->get_value(); - //if (new_value <= 0.0001f) - // continue; tof_probabilities.push_back(ProjMatrixElemsForOneBin::value_type(element_ptr->get_coords(), new_value)); } From a22566fbde922a0c7423442ef2551ef4c9d99c7c Mon Sep 17 00:00:00 2001 From: NikEfth Date: Sat, 24 Dec 2016 09:50:53 +0000 Subject: [PATCH 039/509] initialisation of least_significant_clock_bit --- src/IO/InputStreamFromROOTFile.cxx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/IO/InputStreamFromROOTFile.cxx b/src/IO/InputStreamFromROOTFile.cxx index db182f597d..1e78e7293c 100644 --- a/src/IO/InputStreamFromROOTFile.cxx +++ b/src/IO/InputStreamFromROOTFile.cxx @@ -74,6 +74,8 @@ InputStreamFromROOTFile::post_processing() stream_ptr->SetBranchAddress("energy2", &energy2); stream_ptr->SetBranchAddress("comptonPhantom1", &comptonphantom1); stream_ptr->SetBranchAddress("comptonPhantom2", &comptonphantom2); + + least_significant_clock_bit = 5.0e+11 / least_significant_clock_bit; return false; } From f292a2d9bfa377045f11ebd7747dff81497cd4dc Mon Sep 17 00:00:00 2001 From: NikEfth Date: Sat, 24 Dec 2016 10:27:33 +0000 Subject: [PATCH 040/509] init least_significant bit --- src/IO/InputStreamFromROOTFile.cxx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/IO/InputStreamFromROOTFile.cxx b/src/IO/InputStreamFromROOTFile.cxx index 1e78e7293c..6198a1b428 100644 --- a/src/IO/InputStreamFromROOTFile.cxx +++ b/src/IO/InputStreamFromROOTFile.cxx @@ -44,7 +44,9 @@ InputStreamFromROOTFile(std::string filename, void InputStreamFromROOTFile::set_defaults() -{} +{ + least_significant_clock_bit = 1.0; +} void InputStreamFromROOTFile::initialise_keymap() @@ -57,6 +59,7 @@ InputStreamFromROOTFile::initialise_keymap() this->parser.add_key("offset (num of detectors)", &this->offset_dets); this->parser.add_key("low energy window (keV)", &this->low_energy_window); this->parser.add_key("upper energy window (keV)", &this->up_energy_window); + this->parser.add_key("minimum timing step (in picoseconds)", &this->least_significant_clock_bit); } bool From 17898cb15212d797afaacd33de0f89ce524ab15f Mon Sep 17 00:00:00 2001 From: NikEfth Date: Sat, 24 Dec 2016 10:59:12 +0000 Subject: [PATCH 041/509] invert bins --- src/include/stir/listmode/CListRecordROOT.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/include/stir/listmode/CListRecordROOT.h b/src/include/stir/listmode/CListRecordROOT.h index 5a2cb84264..93f4183d2e 100644 --- a/src/include/stir/listmode/CListRecordROOT.h +++ b/src/include/stir/listmode/CListRecordROOT.h @@ -167,7 +167,7 @@ class CListRecordROOT : public CListRecord // currently no gating yet return this->time_data; } - virtual void full_event(Bin& bin, const ProjDataInfo& proj_data_info) const + virtual void full_event(Bin& bin, const ProjDataInfo& proj_data_int) const { event_data.get_bin(bin, proj_data_info); time_data.get_bin(bin, proj_data_info); @@ -198,9 +198,9 @@ class CListRecordROOT : public CListRecord // currently no gating yet this->event_data.is_swapped() ? this->time_data.init_from_data( - time1, delta_time) : + time1, -delta_time) : this->time_data.init_from_data( - time1, -delta_time); + time1, delta_time); // We can make a singature raw based on the two events IDs. // It is pretty unique. From fd0cb5eb4d839bba8e515f1cf1596598fcab6c07 Mon Sep 17 00:00:00 2001 From: NikEfth Date: Sat, 24 Dec 2016 23:12:14 +0000 Subject: [PATCH 042/509] :) --- src/include/stir/listmode/CListRecordROOT.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/include/stir/listmode/CListRecordROOT.h b/src/include/stir/listmode/CListRecordROOT.h index 93f4183d2e..6fccff86a8 100644 --- a/src/include/stir/listmode/CListRecordROOT.h +++ b/src/include/stir/listmode/CListRecordROOT.h @@ -167,7 +167,7 @@ class CListRecordROOT : public CListRecord // currently no gating yet return this->time_data; } - virtual void full_event(Bin& bin, const ProjDataInfo& proj_data_int) const + virtual void full_event(Bin& bin, const ProjDataInfo& proj_data_info) const { event_data.get_bin(bin, proj_data_info); time_data.get_bin(bin, proj_data_info); From 62116f3bc499303b3eab8bae5ed583a29749fdc0 Mon Sep 17 00:00:00 2001 From: NikEfth Date: Sun, 25 Dec 2016 13:35:46 +0000 Subject: [PATCH 043/509] Faster scanner for 50, 20, 10 timing resolutions --- src/buildblock/Scanner.cxx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/buildblock/Scanner.cxx b/src/buildblock/Scanner.cxx index bbcc8348f0..2741a19b72 100644 --- a/src/buildblock/Scanner.cxx +++ b/src/buildblock/Scanner.cxx @@ -281,8 +281,8 @@ Scanner::Scanner(Type scanner_type) 52, 312, 624, 424.5F, 7.0F, 4.16F, 2.17242F, 0.0F, 1, 48, 52, 13, 13, 13, 1, - (short int)(315), - (float)(13.015F), + (short int)(630), + (float)(6.507), (float)(50.0F)); // TODO bucket/singles info incorrect? 224 buckets in total, but not sure how distributed break; @@ -295,8 +295,8 @@ Scanner::Scanner(Type scanner_type) 52, 312, 624, 424.5F, 7.0F, 4.16F, 2.17242F, 0.0F, 1, 48, 52, 13, 13, 13, 1, - (short int)(315), - (float)(13.015F), + (short int)(630), + (float)(6.507), (float)(20.0F)); // TODO bucket/singles info incorrect? 224 buckets in total, but not sure how distributed break; @@ -309,8 +309,8 @@ Scanner::Scanner(Type scanner_type) 52, 312, 624, 424.5F, 7.0F, 4.16F, 2.17242F, 0.0F, 1, 48, 52, 13, 13, 13, 1, - (short int)(315), - (float)(13.015F), + (short int)(630), + (float)(6.507), (float)(10.0F)); // TODO bucket/singles info incorrect? 224 buckets in total, but not sure how distributed break; From b7f1db95999ab8988a1eaa684c228a9c67580fb7 Mon Sep 17 00:00:00 2001 From: NikEfth Date: Tue, 27 Dec 2016 09:14:32 +0000 Subject: [PATCH 044/509] Faster 100 ps scanner --- src/buildblock/Scanner.cxx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/buildblock/Scanner.cxx b/src/buildblock/Scanner.cxx index 2741a19b72..074bd24c98 100644 --- a/src/buildblock/Scanner.cxx +++ b/src/buildblock/Scanner.cxx @@ -267,8 +267,8 @@ Scanner::Scanner(Type scanner_type) 52, 312, 624, 424.5F, 7.0F, 4.16F, 2.17242F, 0.0F, 1, 48, 52, 13, 13, 13, 1, - (short int)(315), - (float)(13.015F), + (short int)(630), + (float)(6.507F), (float)(100.0F)); // TODO bucket/singles info incorrect? 224 buckets in total, but not sure how distributed break; From 216a3cde5fc4a0f821bf7b62534d613cd3db1d80 Mon Sep 17 00:00:00 2001 From: NikEfth Date: Mon, 2 Jan 2017 23:35:34 +0000 Subject: [PATCH 045/509] TOF tests part 1 --- src/test/CMakeLists.txt | 2 + src/test/test_time_of_flight.cxx | 506 +++++++++++++++++++++++++++++++ 2 files changed, 508 insertions(+) create mode 100644 src/test/test_time_of_flight.cxx diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index 358d1d675c..d39da0b304 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -35,6 +35,7 @@ Set(${dir_INVOLVED_TEST_EXE_SOURCES} test_OutputFileFormat test_linear_regression test_stir_math + test_time_of_flight # the next 2 are interactive, so we don't add a test for it, but only compile them test_display test_interpolate @@ -82,6 +83,7 @@ ADD_TEST(NAME test_stir_math ) set_tests_properties(test_stir_math PROPERTIES DEPENDS stir_math) +ADD_TEST(NAME test_time_of_flight COMMAND test_time_of_flight) # Final note: we could use TARGET_FILE to avoid the use of ${CMAKE_CURRENT_BINARY_DIR} in the other tests, but both strategies work fine. ## add tests for OutputFileFormat diff --git a/src/test/test_time_of_flight.cxx b/src/test/test_time_of_flight.cxx new file mode 100644 index 0000000000..c9f16d8bdf --- /dev/null +++ b/src/test/test_time_of_flight.cxx @@ -0,0 +1,506 @@ +/* + Copyright (C) 2016, UCL + Copyright (C) 2016, University of Hull + 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 +*/ +/*! + \ingroup test + \brief Test class for Time-Of-Flight + \author Nikos Efthimiou +*/ +#include "stir/ProjDataInfoCylindricalNoArcCorr.h" +#include "stir/recon_buildblock/ProjMatrixByBin.h" +#include "stir/recon_buildblock/ProjMatrixByBinUsingRayTracing.h" +#include "stir/recon_buildblock/ForwardProjectorByBinUsingProjMatrixByBin.h" +#include "stir/recon_buildblock/BackProjectorByBinUsingProjMatrixByBin.h" +#include "stir/recon_buildblock/ProjMatrixElemsForOneBin.h" +#include "stir/recon_buildblock/ProjectorByBinPair.h" +#include "stir/recon_buildblock/ProjectorByBinPairUsingSeparateProjectors.h" +#include "stir/HighResWallClockTimer.h" +#include "stir/DiscretisedDensity.h" +#include "stir/VoxelsOnCartesianGrid.h" +#include "stir/recon_buildblock/ProjMatrixElemsForOneBin.h" +#include "stir/ViewSegmentNumbers.h" +#include "stir/RelatedViewgrams.h" +//#include "stir/geometry/line_distances.h" +#include "stir/Succeeded.h" +#include "stir/shared_ptr.h" +#include "stir/RunTests.h" +#include "stir/Scanner.h" + +#include "stir/info.h" +#include "stir/warning.h" + +START_NAMESPACE_STIR + +//! A helper class to keep the combination of a view, a segment and +//! a key tight. +//! \author Nikos Efthimiou +class cache_index{ +public: + cache_index() { + view_num = 0; + seg_num = 0; + key = 0; + } + + inline bool operator==(const cache_index& Y) const + { + return view_num == Y.view_num && + seg_num == Y.seg_num && + key == Y.key; + } + + inline bool operator!=(const cache_index& Y) const + { + return !(*this == Y); + } + + inline bool operator< (const cache_index& Y) const + { + return view_num < Y.view_num && + seg_num < Y.seg_num && + key < Y.key; + } + + int view_num; + int seg_num; + boost::uint32_t key; +}; + +// Helper class. +class FloatFloat{ +public: + FloatFloat() {} + float float1; + float float2; +}; + +class TOF_Tests : public RunTests +{ +public: + void run_tests(); + +private: + + void test_tof_proj_data_info(); + + void test_tof_geometry_1(); + + void test_tof_geometry_2(); + + //! This checks peaks a specific bin, finds the LOR and applies all the + //! kernels of all available timing positions. + void test_tof_kernel_application(); + + void + export_lor(ProjMatrixElemsForOneBin& probabilities, + const CartesianCoordinate3D& point1, + const CartesianCoordinate3D& point2,int current_id); + + void + export_lor(ProjMatrixElemsForOneBin& probabilities, + const CartesianCoordinate3D& point1, + const CartesianCoordinate3D& point2,int current_id, + ProjMatrixElemsForOneBin& template_probabilities); + + shared_ptr test_scanner_sptr; + shared_ptr test_proj_data_info_sptr; + shared_ptr > test_discretised_density_sptr; + shared_ptr test_proj_matrix_sptr; + shared_ptr projector_pair_sptr; + shared_ptr symmetries_used_sptr; +}; + +void +TOF_Tests::run_tests() +{ + // New Scanner + test_scanner_sptr.reset(new Scanner(Scanner::Type_mCT_TOF)); + + // New Proj_Data_Info + const int test_tof_mashing_factor = 6; + test_proj_data_info_sptr.reset(ProjDataInfo::ProjDataInfoCTI(test_scanner_sptr, + 1,test_scanner_sptr->get_num_rings() -1, + test_scanner_sptr->get_num_detectors_per_ring()/2, + test_scanner_sptr->get_max_num_non_arccorrected_bins(), + /* arc_correction*/false)); + test_proj_data_info_sptr->set_tof_mash_factor(test_tof_mashing_factor); + + test_tof_proj_data_info(); +// test_tof_geometry_1(); + + // New Discretised Density + test_discretised_density_sptr.reset( new VoxelsOnCartesianGrid (*test_proj_data_info_sptr, 1.f, + CartesianCoordinate3D(0.f, 0.f, 0.f), + CartesianCoordinate3D(-1, -1, -1))); + // New ProjMatrix + test_proj_matrix_sptr.reset(new ProjMatrixByBinUsingRayTracing()); + dynamic_cast(test_proj_matrix_sptr.get())->set_num_tangential_LORs(1); + dynamic_cast(test_proj_matrix_sptr.get())->set_up(test_proj_data_info_sptr, test_discretised_density_sptr); + test_proj_matrix_sptr->enable_tof(test_proj_data_info_sptr); + + shared_ptr forward_projector_ptr( + new ForwardProjectorByBinUsingProjMatrixByBin(test_proj_matrix_sptr)); + shared_ptr back_projector_ptr( + new BackProjectorByBinUsingProjMatrixByBin(test_proj_matrix_sptr)); + + projector_pair_sptr.reset( + new ProjectorByBinPairUsingSeparateProjectors(forward_projector_ptr, back_projector_ptr)); + projector_pair_sptr->set_up(test_proj_data_info_sptr, test_discretised_density_sptr); + + symmetries_used_sptr.reset(projector_pair_sptr->get_symmetries_used()->clone()); + + // Deactivated it now because it takes a long time to finish. + // test_cache(); + + test_tof_kernel_application(); +} + +void +TOF_Tests::test_tof_proj_data_info() +{ + const int correct_tof_mashing_factor = 13; + const int num_timing_positions = 27; + float correct_width_of_tof_bin = test_scanner_sptr->get_size_of_timing_bin() * + test_proj_data_info_sptr->get_tof_mash_factor() * 0.299792458f; + float correct_timing_locations[num_timing_positions] = {-683.077f, -632.334f, -581.591f, -530.849f, -480.106f, -429.363f, + -378.62f, -327.877f, -277.134f, -226.391f, -175.648f, + -124.906f, -74.1626f, -23.4197f, 27.3231f, 78.066f, 128.809f, + 179.552f, 230.295f, 281.038f, 331.78f, 382.523f, 433.266f, 484.009f, + 534.752f, 585.495f, 636.238f}; + + check_if_equal(correct_tof_mashing_factor, + test_proj_data_info_sptr->get_tof_mash_factor(), "Diffent tof mash factor."); + + check_if_equal(num_timing_positions, + test_proj_data_info_sptr->get_num_timing_poss(), "Diffent in number of timing positions."); + + for (int timing_num = test_proj_data_info_sptr->get_min_timing_pos_num(), counter = 0; + timing_num <= test_proj_data_info_sptr->get_max_timing_pos_num(); ++ timing_num, counter++) + { + Bin bin(0, 0, 0, 0, timing_num, 1.f); + + check_if_equal(static_cast(correct_width_of_tof_bin), + static_cast(test_proj_data_info_sptr->get_sampling_in_k(bin)), "Error in get_sampling_in_k()"); + check_if_equal(static_cast(correct_timing_locations[counter]), + static_cast(test_proj_data_info_sptr->get_k(bin)), "Error in get_sampling_in_k()"); + } + + float total_width = test_proj_data_info_sptr->get_k(Bin(0,0,0,0,test_proj_data_info_sptr->get_max_timing_pos_num(),1.f)) + - test_proj_data_info_sptr->get_k(Bin(0,0,0,0,test_proj_data_info_sptr->get_min_timing_pos_num(),1.f)) + + test_proj_data_info_sptr->get_sampling_in_k(Bin(0,0,0,0,0,1.f)); + + set_tolerance(static_cast(0.005)); + check_if_equal(static_cast(total_width), static_cast(test_proj_data_info_sptr->get_coincidence_window_width()), + "Coincidence widths don't match."); + + +} + +void +TOF_Tests::test_tof_geometry_1() +{ + + float correct_scanner_length = test_scanner_sptr->get_ring_spacing() * + test_scanner_sptr->get_num_rings() - test_proj_data_info_sptr->get_sampling_in_m(Bin(0,0,0,0,0)); + + CartesianCoordinate3D ez1_coord0, ez2_coord0, ez3_coord0, ez4_coord0, ez5_coord0; + ProjDataInfoCylindrical* proj_data_ptr = + dynamic_cast (test_proj_data_info_sptr.get()); + + int mid_seg = test_proj_data_info_sptr->get_num_segments()/2; + + int mid_axial_0 = (test_proj_data_info_sptr->get_min_axial_pos_num(0) + + test_proj_data_info_sptr->get_max_axial_pos_num(0)) /2; + + int mid_axial_mid_seg = (test_proj_data_info_sptr->get_min_axial_pos_num(mid_seg) + + test_proj_data_info_sptr->get_max_axial_pos_num(mid_seg)) /2; + + // Some easy to validate bins: + Bin ez1_bin(0,0,0,0,0,1.f); + Bin ez2_bin(0,0,mid_axial_0,0,0,1.f); + Bin ez3_bin(mid_seg,0,mid_axial_mid_seg,0,0,1.f); + Bin ez4_bin(0,0,test_proj_data_info_sptr->get_min_axial_pos_num(0),0,0,1.f); + Bin ez5_bin(0,0,test_proj_data_info_sptr->get_max_axial_pos_num(0),0,0,1.f); + + // Get middle points +// proj_data_ptr->get_LOR_middle_point(ez1_coord0, ez1_bin); +// proj_data_ptr->get_LOR_middle_point(ez2_coord0, ez2_bin); +// proj_data_ptr->get_LOR_middle_point(ez3_coord0, ez3_bin); +// proj_data_ptr->get_LOR_middle_point(ez4_coord0, ez4_bin); +// proj_data_ptr->get_LOR_middle_point(ez5_coord0, ez5_bin); + + // axial ez1 && ez4 should be -scanner_length/2.f + check_if_equal(static_cast(ez1_coord0.z()), + static_cast(-correct_scanner_length/2.f), + "Min axial possitions of mid-points don't look " + "resonable."); + + check_if_equal(static_cast(ez4_coord0.z()), + static_cast(-correct_scanner_length/2.f), + "Min axial possitions of mid-points don't look " + "resonable."); + + // axial ez2 should be -ring_spacing/2 + check_if_equal(static_cast(ez2_coord0.z()), + static_cast(-test_scanner_sptr->get_ring_spacing()/2.f), + "[1]Central axial possitions of mid-points don't look " + "resonable."); + + // axial ez3 should be 0 + check_if_equal(static_cast(ez3_coord0.z()), + static_cast(-test_proj_data_info_sptr->get_m(Bin(mid_seg,0,0,0,0))), + "[2]Central axial possitions of mid-points don't look " + "resonable."); + + // axial ez5 should be scanner_length/2.f + check_if_equal(static_cast(ez5_coord0.z()), + static_cast(correct_scanner_length/2.f), + "Max axial possitions of mid-points don't look " + "resonable."); + + //TODO: more tests for X and Y +} + +void +TOF_Tests::test_tof_geometry_2() +{ + float correct_scanner_length = test_scanner_sptr->get_ring_spacing() * + test_scanner_sptr->get_num_rings() - test_proj_data_info_sptr->get_sampling_in_m(Bin(0,0,0,0,0)); + + CartesianCoordinate3D ez1_coord1, ez2_coord1, ez3_coord1, ez4_coord1, ez5_coord1; + CartesianCoordinate3D ez1_coord2, ez2_coord2, ez3_coord2, ez4_coord2, ez5_coord2; + ProjDataInfoCylindrical* proj_data_ptr = + dynamic_cast (test_proj_data_info_sptr.get()); + + int mid_seg = test_proj_data_info_sptr->get_num_segments()/2; + + int mid_axial_0 = (test_proj_data_info_sptr->get_min_axial_pos_num(0) + + test_proj_data_info_sptr->get_max_axial_pos_num(0)) /2; + + int mid_axial_mid_seg = (test_proj_data_info_sptr->get_min_axial_pos_num(mid_seg) + + test_proj_data_info_sptr->get_max_axial_pos_num(mid_seg)) /2; + + // Some easy to validate bins: + Bin ez1_bin(0,0,0,0,0,1.f); + Bin ez2_bin(0,0,mid_axial_0,0,0,1.f); + Bin ez3_bin(mid_seg,0,mid_axial_mid_seg,0,0,1.f); + Bin ez4_bin(0,0,test_proj_data_info_sptr->get_min_axial_pos_num(0),0,0,1.f); + Bin ez5_bin(0,0,test_proj_data_info_sptr->get_max_axial_pos_num(0),0,0,1.f); + + // Get middle points +// proj_data_ptr->get_LOR_as_two_points(ez1_coord1,ez1_coord2, ez1_bin); +// proj_data_ptr->get_LOR_as_two_points(ez2_coord1,ez2_coord2, ez2_bin); +// proj_data_ptr->get_LOR_as_two_points(ez3_coord1,ez3_coord2, ez3_bin); +// proj_data_ptr->get_LOR_as_two_points(ez4_coord1,ez4_coord2, ez4_bin); +// proj_data_ptr->get_LOR_as_two_points(ez5_coord1,ez5_coord2, ez5_bin); + + // TESTS TO COME. + + // TEST IF THE FLIPING IS OK. +} + +void +TOF_Tests::test_tof_kernel_application() +{ + int seg_num = 0; + int view_num = 0; + int axial_num = 0; + int tang_num = 0; + CartesianCoordinate3D lor_point_1, lor_point_2; + ProjMatrixElemsForOneBin proj_matrix_row; + HighResWallClockTimer t; + std::vector times_of_tofing; + + ProjDataInfoCylindrical* proj_data_ptr = + dynamic_cast (test_proj_data_info_sptr.get()); + + Bin this_bin(seg_num, view_num, axial_num, tang_num, 1.f); + proj_data_ptr->get_LOR_as_two_points(lor_point_1, lor_point_2, this_bin); + + std::cerr<< lor_point_1.x() << " " << lor_point_1.y() << " " << lor_point_1.z() << " " << + lor_point_2.x() << " " << lor_point_2.y() << " " << lor_point_2.z() << std::endl; + + t.reset(); t.start(); + test_proj_matrix_sptr->get_proj_matrix_elems_for_one_bin(proj_matrix_row, this_bin); + t.stop(); + std::cerr<<"Execution time for nonTOF: "<get_min_timing_pos_num(); + timing_num <= test_proj_data_info_sptr->get_max_timing_pos_num(); ++ timing_num) + { + ProjMatrixElemsForOneBin new_proj_matrix_row; + Bin bin(seg_num, view_num, axial_num, tang_num, timing_num, 1.f); + + t.reset(); t.start(); + test_proj_matrix_sptr->get_proj_matrix_elems_for_one_bin_with_tof(new_proj_matrix_row, + bin, + lor_point_1, lor_point_2); + t.stop(); + times_of_tofing.push_back(t.value()); + export_lor(new_proj_matrix_row, + lor_point_1, lor_point_2, timing_num, + proj_matrix_row); + } + + double mean = 0.0; + for (unsigned i = 0; i < times_of_tofing.size(); i++) + mean += times_of_tofing.at(i); + + mean /= (times_of_tofing.size()); + + double s=0.0; + for (unsigned i = 0; i < times_of_tofing.size(); i++) + s += (times_of_tofing.at(i) - mean) * (times_of_tofing.at(i) - mean) / (times_of_tofing.size()-1); + + s = std::sqrt(s); + + std::cerr<<" Execution time for TOF: "<& point1, + const CartesianCoordinate3D& point2, int current_id) +{ + std::ofstream myfile; + std::string file_name = "glor_" + std::to_string(current_id) + ".txt"; + myfile.open (file_name.c_str()); + + CartesianCoordinate3D voxel_center; + + std::vector lor_to_export; + lor_to_export.reserve(probabilities.size()); + + ProjMatrixElemsForOneBin::iterator element_ptr = probabilities.begin(); + while (element_ptr != probabilities.end()) + { + voxel_center = + test_discretised_density_sptr->get_physical_coordinates_for_indices (element_ptr->get_coords()); + + if(voxel_center.z() == 0.f) + { + project_point_on_a_line(point1, point2, voxel_center ); + + float d1 = std::sqrt((point1.x() - voxel_center.x()) *(point1.x() - voxel_center.x()) + + (point1.y() - voxel_center.y()) *(point1.y() - voxel_center.y()) + + (point1.z() - voxel_center.z()) *(point1.z() - voxel_center.z())); + + float d2 = std::sqrt( (point2.x() - voxel_center.x()) *(point2.x() - voxel_center.x()) + + (point2.y() - voxel_center.y()) *(point2.y() - voxel_center.y()) + + (point2.z() - voxel_center.z()) *(point2.z() - voxel_center.z())); + + + float d12 = (d2 - d1) * 0.5f; + + std::cerr<< voxel_center.x() << " " << voxel_center.y() << " " << voxel_center.z() << " " << + d1 << " " << d2 << " " << d12 <get_value(); + lor_to_export.push_back(tmp); +} + ++element_ptr; + } + + for (unsigned int i = 0; i < lor_to_export.size(); i++) + myfile << lor_to_export.at(i).float1 << " " << lor_to_export.at(i).float2 << std::endl; + + myfile << std::endl; + myfile.close(); +} + +void +TOF_Tests:: +export_lor(ProjMatrixElemsForOneBin& probabilities, + const CartesianCoordinate3D& point1, + const CartesianCoordinate3D& point2, int current_id, + ProjMatrixElemsForOneBin& template_probabilities) +{ + std::ofstream myfile; + std::string file_name = "glor_" + std::to_string(current_id) + ".txt"; + myfile.open (file_name.c_str()); + + CartesianCoordinate3D voxel_center; + + std::vector lor_to_export; + lor_to_export.reserve(template_probabilities.size()); + + ProjMatrixElemsForOneBin::iterator tmpl_element_ptr = template_probabilities.begin(); + while (tmpl_element_ptr != template_probabilities.end()) + { + voxel_center = + test_discretised_density_sptr->get_physical_coordinates_for_indices (tmpl_element_ptr->get_coords()); + if(voxel_center.z() == 0.f) + { + project_point_on_a_line(point1, point2, voxel_center ); + + float d1 = std::sqrt((point1.x() - voxel_center.x()) *(point1.x() - voxel_center.x()) + + (point1.y() - voxel_center.y()) *(point1.y() - voxel_center.y()) + + (point1.z() - voxel_center.z()) *(point1.z() - voxel_center.z())); + + float d2 = std::sqrt( (point2.x() - voxel_center.x()) *(point2.x() - voxel_center.x()) + + (point2.y() - voxel_center.y()) *(point2.y() - voxel_center.y()) + + (point2.z() - voxel_center.z()) *(point2.z() - voxel_center.z())); + + float d12 = (d2 - d1) * 0.5f; + + FloatFloat tmp; + tmp.float1 = d12; + + ProjMatrixElemsForOneBin::iterator element_ptr = probabilities.begin(); + bool found = false; + + while (element_ptr != probabilities.end()) + { + if (element_ptr->get_coords() == tmpl_element_ptr->get_coords()) + { + tmp.float2 = element_ptr->get_value(); + found = true; + break; + } + ++element_ptr; + } + + if (!found) + tmp.float2 = 0.f; + + + lor_to_export.push_back(tmp); +} + ++tmpl_element_ptr; + } + + for (unsigned int i = 0; i < lor_to_export.size(); i++) + myfile << lor_to_export.at(i).float1 << " " << lor_to_export.at(i).float2 << std::endl; + + myfile << std::endl; + myfile.close(); +} + +END_NAMESPACE_STIR + +int main() +{ + USING_NAMESPACE_STIR + TOF_Tests tests; + tests.run_tests(); + return tests.main_return_value(); +} From 54fdbbae59b959dac8d7b6295e6fdc721be51578 Mon Sep 17 00:00:00 2001 From: Nikos Efthimiou Date: Mon, 16 Jan 2017 18:27:24 +0000 Subject: [PATCH 046/509] Correction on ROOT half_block rotation bug. When the default constructor was used, the half_block addtion wasn't applied. --- ...putStreamFromROOTFileForCylindricalPET.cxx | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/IO/InputStreamFromROOTFileForCylindricalPET.cxx b/src/IO/InputStreamFromROOTFileForCylindricalPET.cxx index 6027f8235c..b4bc67221e 100644 --- a/src/IO/InputStreamFromROOTFileForCylindricalPET.cxx +++ b/src/IO/InputStreamFromROOTFileForCylindricalPET.cxx @@ -25,7 +25,15 @@ InputStreamFromROOTFileForCylindricalPET::registered_name = InputStreamFromROOTFileForCylindricalPET:: InputStreamFromROOTFileForCylindricalPET(): base_type() -{} +{ + filename = ""; + chain_name = ""; + exclude_scattered = false; + exclude_randoms = false; + low_energy_window = 0; + up_energy_window = 1000; + offset_dets = 0; +} InputStreamFromROOTFileForCylindricalPET:: InputStreamFromROOTFileForCylindricalPET(std::string _filename, @@ -105,8 +113,9 @@ get_next_record(CListRecordROOT& record) // GATE counts crystal ID =0 the most negative. Therefore // ID = 0 should be negative, in Rsector 0 and the mid crystal ID be 0 . - crystal1 -= half_block; - crystal2 -= half_block; + // Moved to post_processings(). + //crystal1 -= half_block; + //crystal2 -= half_block; // Add offset crystal1 += offset_dets; @@ -170,6 +179,11 @@ post_processing() if (nentries == 0) error("The total number of entries in the ROOT file is zero. Abort."); + half_block = module_repeater_y * submodule_repeater_y * crystal_repeater_y / 2; + if (half_block < 0 ) + half_block = 0; + + offset_dets -= half_block; return false; } From 5be23f0f0cd5603672bf3d1e304f6cfed3091df5 Mon Sep 17 00:00:00 2001 From: Nikos Efthimiou Date: Tue, 17 Jan 2017 13:01:12 +0000 Subject: [PATCH 047/509] Simplification of the TOF algorithm LUT is used to get the TOF bin. --- src/IO/InputStreamFromROOTFile.cxx | 3 +- ...putStreamFromROOTFileForCylindricalPET.cxx | 12 +-- src/buildblock/ProjDataInfo.cxx | 83 +++---------------- src/include/stir/ProjDataInfo.h | 14 +++- src/include/stir/ProjDataInfo.inl | 15 ++++ src/include/stir/listmode/CListRecordROOT.h | 25 +++--- 6 files changed, 55 insertions(+), 97 deletions(-) diff --git a/src/IO/InputStreamFromROOTFile.cxx b/src/IO/InputStreamFromROOTFile.cxx index 6198a1b428..954229f1e6 100644 --- a/src/IO/InputStreamFromROOTFile.cxx +++ b/src/IO/InputStreamFromROOTFile.cxx @@ -1,6 +1,7 @@ /* * Copyright (C) 2015, 2016 University of Leeds Copyright (C) 2016, UCL + Copyright (C) 2016, 2017 University of Hull This file is part of STIR. This file is free software; you can redistribute it and/or modify @@ -78,7 +79,7 @@ InputStreamFromROOTFile::post_processing() stream_ptr->SetBranchAddress("comptonPhantom1", &comptonphantom1); stream_ptr->SetBranchAddress("comptonPhantom2", &comptonphantom2); - least_significant_clock_bit = 5.0e+11 / least_significant_clock_bit; + least_significant_clock_bit = 5.0e+11;// / least_significant_clock_bit; return false; } diff --git a/src/IO/InputStreamFromROOTFileForCylindricalPET.cxx b/src/IO/InputStreamFromROOTFileForCylindricalPET.cxx index ada155d6d2..d6cc9e15e5 100644 --- a/src/IO/InputStreamFromROOTFileForCylindricalPET.cxx +++ b/src/IO/InputStreamFromROOTFileForCylindricalPET.cxx @@ -59,7 +59,7 @@ InputStreamFromROOTFileForCylindricalPET(std::string _filename, up_energy_window = _up_energy_window; offset_dets = _offset_dets; - half_block = module_repeater_y * submodule_repeater_y * crystal_repeater_y / 2 - 1; + half_block = static_cast( (module_repeater_y * submodule_repeater_y * crystal_repeater_y) / 2); if (half_block < 0 ) half_block = 0; } @@ -121,13 +121,7 @@ get_next_record(CListRecordROOT& record) crystal1 += offset_dets; crystal2 += offset_dets; - float delta_timing_bin = 0; - double delta_time = time2 - time1; - - if (delta_time >=0) - delta_timing_bin = static_cast(delta_time * least_significant_clock_bit); - else - delta_timing_bin = static_cast(delta_time * least_significant_clock_bit); + double delta_timing_bin = (time2 - time1) * least_significant_clock_bit; return record.init_from_data(ring1, ring2, @@ -187,7 +181,7 @@ post_processing() if (nentries == 0) error("The total number of entries in the ROOT file is zero. Abort."); - half_block = module_repeater_y * submodule_repeater_y * crystal_repeater_y / 2; + half_block = static_cast( (module_repeater_y * submodule_repeater_y * crystal_repeater_y) / 2); if (half_block < 0 ) half_block = 0; diff --git a/src/buildblock/ProjDataInfo.cxx b/src/buildblock/ProjDataInfo.cxx index 702c7418b5..1603d74825 100644 --- a/src/buildblock/ProjDataInfo.cxx +++ b/src/buildblock/ProjDataInfo.cxx @@ -191,35 +191,17 @@ ProjDataInfo::set_tof_mash_factor(const int new_num) "the scanner's number of max timing bins. Abort."); tof_mash_factor = new_num; - timing_increament_in_mm = tof_mash_factor * scanner_ptr->get_size_of_timing_bin() * 0.299792458f; - - // limit to the size of the diameter. - - Bin b; - - float lowest_t = get_min_tangential_pos_num(); - float highest_t = get_max_tangential_pos_num(); - - b.tangential_pos_num() = lowest_t; - float k = get_sampling_in_t(b); - b.tangential_pos_num() = highest_t; - float l = get_sampling_in_t(b); - - float lowest_boundary = lowest_t * k; - float highest_boundary = highest_t * l; - - int num_tof_positions_in_FOV = static_cast(((highest_boundary - lowest_boundary) / ( timing_increament_in_mm))); + timing_increament_in_mm = (tof_mash_factor * scanner_ptr->get_size_of_timing_bin() * 0.299792458f); min_timing_pos_num = - (scanner_ptr->get_num_max_of_timing_bins() / tof_mash_factor)/2; max_timing_pos_num = min_timing_pos_num + (scanner_ptr->get_num_max_of_timing_bins() / tof_mash_factor); - //min_timing_pos_num = -num_tof_positions_in_FOV/2; - //max_timing_pos_num = min_timing_pos_num + num_tof_positions_in_FOV; - info(boost::format("bound: %1%: %2% - %3% | %4%") %lowest_boundary % highest_boundary %num_tof_positions_in_FOV - %timing_increament_in_mm); + num_tof_bins = max_timing_pos_num - min_timing_pos_num; // Upper and lower boundaries of the timing poss; - timing_bin_boundaries.grow(min_timing_pos_num, max_timing_pos_num); + timing_bin_boundaries_mm.grow(min_timing_pos_num, max_timing_pos_num); + + timing_bin_boundaries_ps.grow(min_timing_pos_num, max_timing_pos_num); for (int i = min_timing_pos_num; i <= max_timing_pos_num; ++i ) { @@ -229,55 +211,12 @@ ProjDataInfo::set_tof_mash_factor(const int new_num) float cur_low = get_k(bin); float cur_high = get_k(bin) + get_sampling_in_k(bin); -// info(boost::format("tic: %1%: %2%") %cur_low % cur_high); - -// if (cur_low < lowest_boundary) -// cur_low = lowest_boundary; -// else if (cur_low > highest_boundary) -// cur_low = highest_boundary; - -// if (cur_high < lowest_boundary) -// cur_high = lowest_boundary; -// else if (cur_high > highest_boundary) -// cur_high = highest_boundary; - -// info(boost::format("tac: %1%: %2%") % cur_low % cur_high); -// if (cur_low< 0 || cur_high < 0 ) -// { -// if (cur_low < lowest_boundary && cur_high > lowest_boundary) -// { -// timing_bin_boundaries[i].low_lim = lowest_boundary; -// timing_bin_boundaries[i].high_lim = cur_high; -// } -// else if (cur_low >= lowest_boundary && cur_high >= lowest_boundary) -// { -// timing_bin_boundaries[i].low_lim = cur_low; -// timing_bin_boundaries[i].high_lim = cur_high; -// } -// } -// else -// { -// if (cur_high > highest_boundary && cur_low < highest_boundary) -// { -// timing_bin_boundaries[i].low_lim = cur_low; -// timing_bin_boundaries[i].high_lim = highest_boundary; -// } -// else if (cur_low <= highest_boundary && cur_high <= highest_boundary) -// { -// timing_bin_boundaries[i].low_lim = cur_low; -// timing_bin_boundaries[i].high_lim = cur_high; -// } -// } - - timing_bin_boundaries[i].low_lim = cur_low; - timing_bin_boundaries[i].high_lim = cur_high; - float lowt = (timing_bin_boundaries[i].low_lim / 0.299792458f ) ; - float hight = ( timing_bin_boundaries[i].high_lim/ 0.299792458f); - info(boost::format("Tbin %1%: %2% - %3% mm (%4% - %5% ps) = %6%") %i % timing_bin_boundaries[i].low_lim % timing_bin_boundaries[i].high_lim - % lowt% hight % get_sampling_in_k(bin)); - // Go to natural coordinates - opted out. - // timing_bin_boundaries[i].low_lim *= r_sqrtdPI_gauss_sigma; - // timing_bin_boundaries[i].high_lim *= r_sqrtdPI_gauss_sigma; + timing_bin_boundaries_mm[i].low_lim = cur_low; + timing_bin_boundaries_mm[i].high_lim = cur_high; + timing_bin_boundaries_ps[i].low_lim = (timing_bin_boundaries_mm[i].low_lim * 3.33564095198f ) ; + timing_bin_boundaries_ps[i].high_lim = ( timing_bin_boundaries_mm[i].high_lim * 3.33564095198f); + info(boost::format("Tbin %1%: %2% - %3% mm (%4% - %5% ps) = %6%") %i % timing_bin_boundaries_mm[i].low_lim % timing_bin_boundaries_mm[i].high_lim + % timing_bin_boundaries_ps[i].low_lim % timing_bin_boundaries_ps[i].high_lim % get_sampling_in_k(bin)); } } else diff --git a/src/include/stir/ProjDataInfo.h b/src/include/stir/ProjDataInfo.h index 0d6af07307..6bb6c0ddeb 100644 --- a/src/include/stir/ProjDataInfo.h +++ b/src/include/stir/ProjDataInfo.h @@ -181,6 +181,10 @@ class ProjDataInfo inline int get_num_views() const; //! Get number of tangential positions inline int get_num_tangential_poss() const; + //! Get number of tof bins + inline int get_tof_bin(double delta) const; + //! Get number of TOF bins + inline int get_num_tof_poss() const; //! Get minimum segment number inline int get_min_segment_num() const; //! Get maximum segment number @@ -199,9 +203,9 @@ class ProjDataInfo inline int get_max_tangential_pos_num() const; //! Get number of TOF positions inline int get_num_timing_poss() const; - //! Get TOG mash factor + //! Get TOF mash factor inline int get_tof_mash_factor() const; - //! Get the index of the first timing position + //! Get the index of the first timing position inline int get_min_timing_pos_num() const; //! Get the index of the last timgin position. inline int get_max_timing_pos_num() const; @@ -363,7 +367,9 @@ class ProjDataInfo struct Float1Float2 { float low_lim; float high_lim; }; //! Vector which holds the lower and higher boundary for each timing position, for faster access. - mutable VectorWithOffset timing_bin_boundaries; + mutable VectorWithOffset timing_bin_boundaries_mm; + + mutable VectorWithOffset timing_bin_boundaries_ps; protected: virtual bool blindly_equals(const root_type * const) const = 0; @@ -382,6 +388,8 @@ class ProjDataInfo int tof_mash_factor; //! Finally (with any mashing factor) timing bin increament. float timing_increament_in_mm; + //! Number of tof bins (TOF mash factor applied) + int num_tof_bins; VectorWithOffset min_axial_pos_per_seg; VectorWithOffset max_axial_pos_per_seg; diff --git a/src/include/stir/ProjDataInfo.inl b/src/include/stir/ProjDataInfo.inl index d1d6fbe0c2..ca82f61fcd 100644 --- a/src/include/stir/ProjDataInfo.inl +++ b/src/include/stir/ProjDataInfo.inl @@ -61,6 +61,21 @@ int ProjDataInfo::get_num_timing_poss() const { return max_timing_pos_num - min_timing_pos_num +1; } +int +ProjDataInfo::get_num_tof_poss() const +{ return num_tof_bins; } + +int +ProjDataInfo::get_tof_bin(double delta) const +{ + for (int i = min_timing_pos_num; i < max_timing_pos_num; i++) + { + if ( delta > timing_bin_boundaries_ps[i].low_lim && + delta < timing_bin_boundaries_ps[i].high_lim) + return i; + } +} + int ProjDataInfo::get_tof_mash_factor() const { return tof_mash_factor; } diff --git a/src/include/stir/listmode/CListRecordROOT.h b/src/include/stir/listmode/CListRecordROOT.h index 6fccff86a8..658b56b915 100644 --- a/src/include/stir/listmode/CListRecordROOT.h +++ b/src/include/stir/listmode/CListRecordROOT.h @@ -115,15 +115,16 @@ class CListTimeROOT : public CListTime virtual inline void get_bin(Bin& bin, const ProjDataInfo& proj_data_info) const { - delta_time > 0 ? - bin.timing_pos_num() = static_cast ( ( delta_time / proj_data_info.get_tof_mash_factor()) + 0.5) - : bin.timing_pos_num() = static_cast ( ( delta_time / proj_data_info.get_tof_mash_factor()) - 0.5); - - if (bin.timing_pos_num() < proj_data_info.get_min_timing_pos_num() || - bin.timing_pos_num() > proj_data_info.get_max_timing_pos_num()) - { - bin.set_bin_value(-1.f); - } +// delta_time >= 0.0 ? +// bin.timing_pos_num() = static_cast ( ( delta_time / proj_data_info.get_num_tof_poss()) + 0.5) +// : bin.timing_pos_num() = static_cast ( ( delta_time / proj_data_info.get_num_tof_poss()) - 0.5); + +// if (bin.timing_pos_num() < proj_data_info.get_min_timing_pos_num() || +// bin.timing_pos_num() > proj_data_info.get_max_timing_pos_num()) +// { +// bin.set_bin_value(-1.f); +// } + bin.timing_pos_num() = proj_data_info.get_tof_bin(delta_time); } private: @@ -131,9 +132,9 @@ class CListTimeROOT : public CListTime //! //! \brief timeA //! \details The detection time of the first of the two photons, in seconds - float timeA; + double timeA; - float delta_time; + double delta_time; }; //! A class for a general element of a listmode file for a Siemens scanner using the ROOT files @@ -188,7 +189,7 @@ class CListRecordROOT : public CListRecord // currently no gating yet const int& ring2, const int& crystal1, const int& crystal2, - float time1, float delta_time, + const double& time1, const double& delta_time, const int& event1, const int& event2) { /// \warning ROOT data are time and event at the same time. From d257628e68fc5e5bcd7254338cb14d597a14b9eb Mon Sep 17 00:00:00 2001 From: Nikos Efthimiou Date: Tue, 17 Jan 2017 13:30:40 +0000 Subject: [PATCH 048/509] New scanners. Some refs as inputs. threshold at 5.5 stds --- src/buildblock/Scanner.cxx | 630 +++++++++--------- src/include/stir/ProjDataInfo.h | 2 +- src/include/stir/ProjDataInfo.inl | 2 +- .../stir/recon_buildblock/ProjMatrixByBin.inl | 9 +- 4 files changed, 323 insertions(+), 320 deletions(-) diff --git a/src/buildblock/Scanner.cxx b/src/buildblock/Scanner.cxx index 074bd24c98..5d3713b890 100644 --- a/src/buildblock/Scanner.cxx +++ b/src/buildblock/Scanner.cxx @@ -60,19 +60,19 @@ using std::list; START_NAMESPACE_STIR // local convenience functions to make a list of strings -static list +static list string_list(const string&); -static list +static list string_list(const string&, const string&); -static list +static list string_list(const string&, const string&, const string&); -static list +static list string_list(const string&, const string&, const string&, const string&); - - + + Scanner::Scanner(Type scanner_type) { @@ -81,47 +81,47 @@ Scanner::Scanner(Type scanner_type) // Type type_v, // const list& list_of_names_v, // - // int num_rings_v, - // int max_num_non_arccorrected_bins_v, - // (optional) int default_num_arccorrected_bins_v, + // int num_rings_v, + // int max_num_non_arccorrected_bins_v, + // (optional) int default_num_arccorrected_bins_v, // int num_detectors_per_ring_v, // // float inner_ring_radius_v, // float average_depth_of_interaction_v, // float ring_spacing_v, - // float bin_size_v, + // float bin_size_v, // float intrinsic_tilt_v, // - // int num_axial_blocks_per_bucket_v, - // int num_transaxial_blocks_per_bucket_v, - // int num_axial_crystals_per_block_v, + // int num_axial_blocks_per_bucket_v, + // int num_transaxial_blocks_per_bucket_v, + // int num_axial_crystals_per_block_v, // int num_transaxial_crystals_per_block_v, // int num_axial_crystals_per_singles_unit_v, // int num_transaxial_crystals_per_singles_unit_v, // int num_detector_layers_v // - + /* for CTI scanners (at least upto 966): - before arc-correction, central_bin_size ~= ring_radius* pi/num_detectors - num_transaxial_crystals_per_singles_unit= + before arc-correction, central_bin_size ~= ring_radius* pi/num_detectors + num_transaxial_crystals_per_singles_unit= transaxial_blocks_per_bucket*transaxial_crystals_per_block - num_axial_crystals_per_singles_unit= + num_axial_crystals_per_singles_unit= axial_crystals_per_block * x where x=1 except for the 966 where x=2 */ - + switch ( scanner_type ) { case E931: // KT 25/01/2002 corrected ring_spacing - set_params(E931, string_list("ECAT 931"), - 8, 192, 2 * 256, - 510.0F, 7.0F, 13.5F, 3.129F, 0.0F, + set_params(E931, string_list("ECAT 931"), + 8, 192, 2 * 256, + 510.0F, 7.0F, 13.5F, 3.129F, 0.0F, 2, 4, 4, 8, 4, 8 * 4, 1); // 16 BUCKETS per ring in TWO rings - i.e. 32 buckets in total @@ -129,60 +129,60 @@ Scanner::Scanner(Type scanner_type) case E951: - set_params(E951, string_list("ECAT 951"), - 16, 192, 2 * 256, - 510.0F, 7.0F, 6.75F, 3.12932F, 0.0F, + set_params(E951, string_list("ECAT 951"), + 16, 192, 2 * 256, + 510.0F, 7.0F, 6.75F, 3.12932F, 0.0F, 1, 4, 8, 8, 8, 8 * 4, 1); break; case E953: - set_params(E953, string_list("ECAT 953"), - 16, 160, 2 * 192, - 382.5F, 7.0F, 6.75F, 3.12932F, static_cast(15.*_PI/180), + set_params(E953, string_list("ECAT 953"), + 16, 160, 2 * 192, + 382.5F, 7.0F, 6.75F, 3.12932F, static_cast(15.*_PI/180), 1, 4, 8, 8, 8, 8 * 4, 1); break; case E921: - set_params(E921, string_list("ECAT 921", "ECAT EXACT", "EXACT"), - 24, 192, 2* 192, + set_params(E921, string_list("ECAT 921", "ECAT EXACT", "EXACT"), + 24, 192, 2* 192, 412.5F, 7.0F, 6.75F, 3.375F, static_cast(15.*_PI/180), 1, 4, 8, 8, 8, 8 * 4, 1); break; case E925: - - set_params(E925, string_list("ECAT 925", "ECAT ART"), - 24, 192, 2* 192, + + set_params(E925, string_list("ECAT 925", "ECAT ART"), + 24, 192, 2* 192, 412.5F, 7.0F, 6.75F, 3.375F, static_cast(15.*_PI/180), 3, 4, 8, 8, 8, 8 * 4, 1); break; - + case E961: - set_params(E961,string_list("ECAT 961", "ECAT HR"), - 24, 336, 2* 392, + set_params(E961,string_list("ECAT 961", "ECAT HR"), + 24, 336, 2* 392, 412.0F, 7.0F, 6.25F, 1.650F, static_cast(13.*_PI/180), 1, 8, 8, 7, 8, 7 * 8, 1); - break; + break; case E962: - set_params(E962,string_list("ECAT 962","ECAT HR+"), - 32, 288, 2* 288, - 412.0F, 7.0F, 4.85F, 2.25F, 0.0F, + set_params(E962,string_list("ECAT 962","ECAT HR+"), + 32, 288, 2* 288, + 412.0F, 7.0F, 4.85F, 2.25F, 0.0F, 4, 3, 8, 8, 8, 8 * 3, 1); break; case E966: - set_params(E966, string_list("ECAT EXACT 3D", "EXACT 3D", "ECAT HR++","ECAT 966"), - 48, 288, 2* 288, - 412.0F, 7.0F, 4.850F, 2.250F, 0.0, + set_params(E966, string_list("ECAT EXACT 3D", "EXACT 3D", "ECAT HR++","ECAT 966"), + 48, 288, 2* 288, + 412.0F, 7.0F, 4.850F, 2.250F, 0.0, 6, 2, 8, 8, 2 * 8, 8 * 2, 1); - break; + break; case E1080: // data added by Robert Barnett, Westmead Hospital, Sydney @@ -190,7 +190,7 @@ Scanner::Scanner(Type scanner_type) 41, 336, 2* 336, 412.0F, 7.0F, 4.0F, 2.000F, 0.0F, 1, 2, 41, 14, 41, 14, 1); - // Transaxial blocks have 13 physical crystals and a gap at the + // Transaxial blocks have 13 physical crystals and a gap at the // 140th crystal where the counts are zero. // There are 39 rings with 13 axial crystals per block. Two virtual // rings are added, but contain counts after applying axial compression. @@ -198,7 +198,7 @@ Scanner::Scanner(Type scanner_type) case Siemens_mMR: // 8x8 blocks, 1 virtual "crystal", 56 blocks along the ring, 8 blocks in axial direction - // Transaxial blocks have 8 physical crystals and a gap at the + // Transaxial blocks have 8 physical crystals and a gap at the // 9th crystal where the counts are zero. set_params(Siemens_mMR, string_list("Siemens mMR", "mMR", "2008"), 64, 344, 2* 252, @@ -225,8 +225,8 @@ Scanner::Scanner(Type scanner_type) 52, 312, 624, 424.5F, 7.0F, 4.16F, 2.17242F, 0.0F, 1, 48, 52, 13, 13, 13, 1, - (short int)(315), - (float)(13.015F), + (short int)(410), + (float)(10.0F), (float)(600.0F)); // TODO bucket/singles info incorrect? 224 buckets in total, but not sure how distributed break; @@ -239,8 +239,8 @@ Scanner::Scanner(Type scanner_type) 52, 312, 624, 424.5F, 7.0F, 4.16F, 2.17242F, 0.0F, 1, 48, 52, 13, 13, 13, 1, - (short int)(315), - (float)(13.015F), + (short int)(410), + (float)(10.0F), (float)(400.0F)); // TODO bucket/singles info incorrect? 224 buckets in total, but not sure how distributed break; @@ -253,8 +253,8 @@ Scanner::Scanner(Type scanner_type) 52, 312, 624, 424.5F, 7.0F, 4.16F, 2.17242F, 0.0F, 1, 48, 52, 13, 13, 13, 1, - (short int)(315), - (float)(13.015F), + (short int)(410), + (float)(10.0F), (float)(200.0F)); // TODO bucket/singles info incorrect? 224 buckets in total, but not sure how distributed break; @@ -267,8 +267,8 @@ Scanner::Scanner(Type scanner_type) 52, 312, 624, 424.5F, 7.0F, 4.16F, 2.17242F, 0.0F, 1, 48, 52, 13, 13, 13, 1, - (short int)(630), - (float)(6.507F), + (short int)(820), + (float)(5.00F), (float)(100.0F)); // TODO bucket/singles info incorrect? 224 buckets in total, but not sure how distributed break; @@ -281,8 +281,8 @@ Scanner::Scanner(Type scanner_type) 52, 312, 624, 424.5F, 7.0F, 4.16F, 2.17242F, 0.0F, 1, 48, 52, 13, 13, 13, 1, - (short int)(630), - (float)(6.507), + (short int)(820), + (float)(5.0F), (float)(50.0F)); // TODO bucket/singles info incorrect? 224 buckets in total, but not sure how distributed break; @@ -295,8 +295,8 @@ Scanner::Scanner(Type scanner_type) 52, 312, 624, 424.5F, 7.0F, 4.16F, 2.17242F, 0.0F, 1, 48, 52, 13, 13, 13, 1, - (short int)(630), - (float)(6.507), + (short int)(1025), + (float)(1.F), (float)(20.0F)); // TODO bucket/singles info incorrect? 224 buckets in total, but not sure how distributed break; @@ -309,137 +309,137 @@ Scanner::Scanner(Type scanner_type) 52, 312, 624, 424.5F, 7.0F, 4.16F, 2.17242F, 0.0F, 1, 48, 52, 13, 13, 13, 1, - (short int)(630), - (float)(6.507), + (short int)(1025), + (float)(1.F), (float)(10.0F)); // TODO bucket/singles info incorrect? 224 buckets in total, but not sure how distributed break; case RPT: - - set_params(RPT, string_list("PRT-1", "RPT"), - 16, 128, 2 * 192, - 380.0F - 7.0F, 7.0F, 6.75F, 3.1088F, 0.0F, + + set_params(RPT, string_list("PRT-1", "RPT"), + 16, 128, 2 * 192, + 380.0F - 7.0F, 7.0F, 6.75F, 3.1088F, 0.0F, 1, 4, 8, 8, 8, 32, 1); // Default 7.0mm average interaction depth. // This 7mm taken off the inner ring radius so that the effective radius remains 380mm - break; + break; case RATPET: - - set_params(RATPET, string_list("RATPET"), - 8, 56, 2 * 56, - 115 / 2.F, 7.0F, 6.25F, 1.65F, 0.0F, + + set_params(RATPET, string_list("RATPET"), + 8, 56, 2 * 56, + 115 / 2.F, 7.0F, 6.25F, 1.65F, 0.0F, 1, 16, 8, 7, 8, 0, 1); // HR block, 4 buckets per ring - + // Default 7.0mm average interaction depth. - // 8 x 0 crystals per singles unit because not known + // 8 x 0 crystals per singles unit because not known // although likely transaxial_blocks_per_bucket*transaxial_crystals_per_block break; case PANDA: - - set_params(PANDA, string_list("PANDA"), - 1 /*NumRings*/, 512 /*MaxBinsNonArcCor*/, 512 /*MaxBinsArcCor*/, 2048 /*NumDetPerRing*/, + + set_params(PANDA, string_list("PANDA"), + 1 /*NumRings*/, 512 /*MaxBinsNonArcCor*/, 512 /*MaxBinsArcCor*/, 2048 /*NumDetPerRing*/, /*MeanInnerRadius*/ 75.5/2.F, /*AverageDoI*/ 10.F, /*Ring Spacing*/ 3.F, /*BinSize*/ 0.1F, /*IntrinsicTilt*/ 0.F, 1, 1, 1, 1, 0, 0, 1); break; - + case nanoPET: - - set_params(nanoPET, string_list("nanoPET"), /*Modelling the gap with one fake crystal */ - 81, 39*3, /* We could also model gaps in the future as one detector so 39->39+1, while 1 (point source), 3 (mouse) or 5 (rats) */ - 39*3, /* Just put the same with NonArcCor for now*/ - 12 * 39, 174.F, 5.0F, 1.17F, 1.17F, /* Actual size is 1.12 and 0.05 is the thickness of the optical reflector */ 0.0F, /* not sure for this */ - 0,0,0,0,0,0, 1); - break; + + set_params(nanoPET, string_list("nanoPET"), /*Modelling the gap with one fake crystal */ + 81, 39*3, /* We could also model gaps in the future as one detector so 39->39+1, while 1 (point source), 3 (mouse) or 5 (rats) */ + 39*3, /* Just put the same with NonArcCor for now*/ + 12 * 39, 174.F, 5.0F, 1.17F, 1.17F, /* Actual size is 1.12 and 0.05 is the thickness of the optical reflector */ 0.0F, /* not sure for this */ + 0,0,0,0,0,0, 1); + break; case HYPERimage: - - set_params(HYPERimage, string_list("HYPERimage"), /*Modelling the gap with one fake crystal */ - 22, 239, 245, - 490, 103.97F, 3.0F, 1.4F, 1.4F, /* Actual size is 1.3667 and assume 0.0333 is the thickness of the optical reflector */ 0.F, - 0,0,0,0,0,0,1); - break; - - + + set_params(HYPERimage, string_list("HYPERimage"), /*Modelling the gap with one fake crystal */ + 22, 239, 245, + 490, 103.97F, 3.0F, 1.4F, 1.4F, /* Actual size is 1.3667 and assume 0.0333 is the thickness of the optical reflector */ 0.F, + 0,0,0,0,0,0,1); + break; + + case Advance: - - // 283 bins (non-uniform sampling) + + // 283 bins (non-uniform sampling) // 281 bins (uniform sampling) /* crystal size 4x8x30*/ - set_params(Advance, string_list("GE Advance", "Advance"), - 18, 283, 281, 2 * 336, + set_params(Advance, string_list("GE Advance", "Advance"), + 18, 283, 281, 2 * 336, 471.875F - 8.4F, 8.4F, 8.5F, 1.970177F, 0.0F, //TODO view offset shouldn't be zero 3, 2, 6, 6, 1, 1, 1); - break; + break; case DiscoveryLS: // identical to Advance - set_params(DiscoveryLS, string_list("GE Discovery LS", "Discovery LS"), - 18, 283, 281, 2 * 336, + set_params(DiscoveryLS, string_list("GE Discovery LS", "Discovery LS"), + 18, 283, 281, 2 * 336, 471.875F - 8.4F, 8.4F, 8.5F, 1.970177F, 0.0F, //TODO view offset shouldn't be zero 3, 2, 6, 6, 1, 1, 1); break; - case DiscoveryST: + case DiscoveryST: - // 249 bins (non-uniform sampling) + // 249 bins (non-uniform sampling) // 221 bins (uniform sampling) /* crystal size: 6.3 x 6.3 x 30 mm*/ - set_params(DiscoveryST, string_list("GE Discovery ST", "Discovery ST"), - 24, 249, 221, 2 * 210, - 886.2F/2.F, 8.4F, 6.54F, 3.195F, - static_cast(-4.54224*_PI/180),//sign? - 4, 2, 6, 6, 1, 1, 1);// TODO not sure about sign of view_offset + set_params(DiscoveryST, string_list("GE Discovery ST", "Discovery ST"), + 24, 249, 221, 2 * 210, + 886.2F/2.F, 8.4F, 6.54F, 3.195F, + static_cast(-4.54224*_PI/180),//sign? + 4, 2, 6, 6, 1, 1, 1);// TODO not sure about sign of view_offset break; - case DiscoverySTE: + case DiscoverySTE: - set_params(DiscoverySTE, string_list("GE Discovery STE", "Discovery STE"), + set_params(DiscoverySTE, string_list("GE Discovery STE", "Discovery STE"), 24, 329, 293, 2 * 280, 886.2F/2.F, 8.4F, 6.54F, 2.397F, - static_cast(-4.5490*_PI/180),//sign? + static_cast(-4.5490*_PI/180),//sign? 4, 2, 6, 8, 1, 1, 1);// TODO not sure about sign of view_offset break; - case DiscoveryRX: - - set_params(DiscoveryRX, string_list("GE Discovery RX", "Discovery RX"), - 24, - 367, - 331, - 2 * 315, - 886.2F/2.F, - 9.4F, - 6.54F, 2.13F, - static_cast(-4.5950*_PI/180),//sign? - 4, - 2, - 6, 9, 1, 1, 1);// TODO not sure about sign of view_offset + case DiscoveryRX: + + set_params(DiscoveryRX, string_list("GE Discovery RX", "Discovery RX"), + 24, + 367, + 331, + 2 * 315, + 886.2F/2.F, + 9.4F, + 6.54F, 2.13F, + static_cast(-4.5950*_PI/180),//sign? + 4, + 2, + 6, 9, 1, 1, 1);// TODO not sure about sign of view_offset break; - case Discovery600: - - set_params(Discovery600, string_list("GE Discovery 600", "Discovery 600"), - 24, - 339, - 293, // TODO - 2 * 256, - 826.70F/2.F - 8.4F, - 8.4F, - 6.54F, - 2.3974F, - static_cast(-4.5490*_PI/180),//sign? TODO value - 4, - 2, - 6, 8, 1, 1, 1); + case Discovery600: + + set_params(Discovery600, string_list("GE Discovery 600", "Discovery 600"), + 24, + 339, + 293, // TODO + 2 * 256, + 826.70F/2.F - 8.4F, + 8.4F, + 6.54F, + 2.3974F, + static_cast(-4.5490*_PI/180),//sign? TODO value + 4, + 2, + 6, 8, 1, 1, 1); break; - + case HZLR: - set_params(HZLR, string_list("Positron HZL/R"), - 32, 256, 2 * 192, - 780.0F, 7.0F, 5.1875F, 2.F, 0.0F, + set_params(HZLR, string_list("Positron HZL/R"), + 32, 256, 2 * 192, + 780.0F, 7.0F, 5.1875F, 2.F, 0.0F, 0, 0, 0, 0, 0,0, 1); // Default 7.0mm average interaction depth. // crystals per singles unit etc unknown @@ -447,9 +447,9 @@ Scanner::Scanner(Type scanner_type) case HRRT: - set_params(HRRT, string_list("HRRT"), - 104, 288, 2 * 288, - 234.765F, 7.0F, 2.4375F, 1.21875F, 0.0F, + set_params(HRRT, string_list("HRRT"), + 104, 288, 2 * 288, + 234.765F, 7.0F, 2.4375F, 1.21875F, 0.0F, 0, 0, 0, 0, 0, 0, 2); // added by Dylan Togane // warning: used 7.0mm average interaction depth. // crystals per singles unit etc unknown @@ -457,75 +457,75 @@ Scanner::Scanner(Type scanner_type) case Allegro: - /* + /* The following info is partially from - + Journal of Nuclear Medicine Vol. 45 No. 6 1040-1049 - Imaging Characteristics of a 3-Dimensional GSO Whole-Body PET Camera - Suleman Surti, PhD and Joel S. Karp, PhD + Imaging Characteristics of a 3-Dimensional GSO Whole-Body PET Camera + Suleman Surti, PhD and Joel S. Karp, PhD http://jnm.snmjournals.org/cgi/content/full/45/6/1040 Other info is from Ralph Brinks (Philips Research Lab, Aachen). - + The Allegro scanner is comprised of 28 flat modules of a 22 x 29 array of 4 x 6 x 20 mm3 GSO crystals. The output sinograms however consist - of 23 x 29 logical crystals per module. + of 23 x 29 logical crystals per module. This creates problems for the current version of STIR as the current - Scanner object does not support does. At present, KT put the + Scanner object does not support does. At present, KT put the transaxial info on crystals to 0. For 662keV photons the mean positron range in GSO is about 14 mm, so we put in 12mm for 511 keV, but we don't really know. Ralph Brinks things there is only one singles rate for the whole scanner. */ - set_params(Allegro,string_list("Allegro", "Philips Allegro"), - 29, 295, 28*23, - 430.05F, 12.F, - 6.3F, 4.3F, 0.0F, - 1, 0, - 29, 0 /* 23* or 22*/, - 29, 0 /* all detectors in a ring? */, - 1); + set_params(Allegro,string_list("Allegro", "Philips Allegro"), + 29, 295, 28*23, + 430.05F, 12.F, + 6.3F, 4.3F, 0.0F, + 1, 0, + 29, 0 /* 23* or 22*/, + 29, 0 /* all detectors in a ring? */, + 1); break; case GeminiTF: - set_params(GeminiTF,string_list("GeminiTF", "Philips GeminiTF"), + set_params(GeminiTF,string_list("GeminiTF", "Philips GeminiTF"), 44, 322, 287, // Based on GATE output - Normally it is 644 detectors at each of the 44 rings 322*2, // Actual number of crystals is 644 450.17F, 8.F, // DOI is from XXX et al 2008 MIC - 4.F, 4.F, 0.F, - 0, 0, - 0, 0, // Not considering any gap, but this is per module 28 flat modules in total, while 420 PMTs - 0, 0 /* Not sure about these, but shouldn't be important */, + 4.F, 4.F, 0.F, + 0, 0, + 0, 0, // Not considering any gap, but this is per module 28 flat modules in total, while 420 PMTs + 0, 0 /* Not sure about these, but shouldn't be important */, 1); break; case HiDAC: // all of these don't make any sense for the HiDAC - set_params(HiDAC, string_list("HiDAC"), - 0, 0, 0, - 0.F, 0.F, 0.F, 0.F, 0.F, + set_params(HiDAC, string_list("HiDAC"), + 0, 0, 0, + 0.F, 0.F, 0.F, 0.F, 0.F, 0, 0, 0, 0, 0, 0, 0); - + break; - + case User_defined_scanner: // zlong, 08-04-2004, Userdefined support - set_params(User_defined_scanner, string_list("Userdefined"), - 0, 0, 0, - 0.F, 0.F, 0.F, 0.F, 0.F, + set_params(User_defined_scanner, string_list("Userdefined"), + 0, 0, 0, + 0.F, 0.F, 0.F, 0.F, 0.F, 0, 0, 0, 0, 0, 0, 0); - + break; default: - // warning("Unknown scanner type used for initialisation of Scanner\n"); - set_params(Unknown_scanner, string_list("Unknown"), - 0, 0, 0, - 0.F, 0.F, 0.F, 0.F, 0.F, + // warning("Unknown scanner type used for initialisation of Scanner\n"); + set_params(Unknown_scanner, string_list("Unknown"), + 0, 0, 0, + 0.F, 0.F, 0.F, 0.F, 0.F, 0, 0, 0, 0, 0, 0, 0); - + break; - + } } @@ -948,133 +948,133 @@ set_params(Type type_v,const list& list_of_names_v, -Succeeded +Succeeded Scanner:: check_consistency() const { if (intrinsic_tilt<-_PI || intrinsic_tilt>_PI) warning("Scanner %s: intrinsic_tilt is very large. maybe it's in degrees (but should be in radians)", - this->get_name().c_str()); + this->get_name().c_str()); { if (get_num_transaxial_crystals_per_block() <= 0 || - get_num_transaxial_blocks() <= 0) + get_num_transaxial_blocks() <= 0) warning("Scanner %s: transaxial block info is not set (probably irrelevant unless you use a projector or normalisation that needs this block info)", - this->get_name().c_str()); + this->get_name().c_str()); else { - const int dets_per_ring = - get_num_transaxial_blocks() * - get_num_transaxial_crystals_per_block(); - if ( dets_per_ring != get_num_detectors_per_ring()) - { - warning("Scanner %s: inconsistent transaxial block info", - this->get_name().c_str()); - return Succeeded::no; - } + const int dets_per_ring = + get_num_transaxial_blocks() * + get_num_transaxial_crystals_per_block(); + if ( dets_per_ring != get_num_detectors_per_ring()) + { + warning("Scanner %s: inconsistent transaxial block info", + this->get_name().c_str()); + return Succeeded::no; + } } } { if (get_num_transaxial_blocks_per_bucket() <= 0 || - get_num_transaxial_buckets() <=0) + get_num_transaxial_buckets() <=0) warning("Scanner %s: transaxial bucket info is not set (probably irrelevant unless you use dead-time correction that needs this info)", - this->get_name().c_str()); + this->get_name().c_str()); else { - const int blocks_per_ring = - get_num_transaxial_buckets() * - get_num_transaxial_blocks_per_bucket(); - if ( blocks_per_ring != get_num_transaxial_blocks()) - { - warning("Scanner %s: inconsistent transaxial block/bucket info", - this->get_name().c_str()); - return Succeeded::no; - } + const int blocks_per_ring = + get_num_transaxial_buckets() * + get_num_transaxial_blocks_per_bucket(); + if ( blocks_per_ring != get_num_transaxial_blocks()) + { + warning("Scanner %s: inconsistent transaxial block/bucket info", + this->get_name().c_str()); + return Succeeded::no; + } } } { if (get_num_axial_crystals_per_block() <= 0 || - get_num_axial_blocks() <=0) + get_num_axial_blocks() <=0) warning("Scanner %s: axial block info is not set (probably irrelevant unless you use a projector or normalisation that needs this block info)", - this->get_name().c_str()); + this->get_name().c_str()); else { - const int dets_axial = - get_num_axial_blocks() * - get_num_axial_crystals_per_block(); - if ( dets_axial != get_num_rings()) - { - warning("Scanner %s: inconsistent axial block info", - this->get_name().c_str()); - return Succeeded::no; - } + const int dets_axial = + get_num_axial_blocks() * + get_num_axial_crystals_per_block(); + if ( dets_axial != get_num_rings()) + { + warning("Scanner %s: inconsistent axial block info", + this->get_name().c_str()); + return Succeeded::no; + } } } { if (get_num_axial_blocks_per_bucket() <= 0 || - get_num_axial_buckets() <=0) + get_num_axial_buckets() <=0) warning("Scanner %s: axial bucket info is not set (probably irrelevant unless you use dead-time correction that needs this info)", - this->get_name().c_str()); + this->get_name().c_str()); else { - const int blocks_axial = - get_num_axial_buckets() * - get_num_axial_blocks_per_bucket(); - if ( blocks_axial != get_num_axial_blocks()) - { - warning("Scanner %s: inconsistent axial block/bucket info", - this->get_name().c_str()); - return Succeeded::no; - } + const int blocks_axial = + get_num_axial_buckets() * + get_num_axial_blocks_per_bucket(); + if ( blocks_axial != get_num_axial_blocks()) + { + warning("Scanner %s: inconsistent axial block/bucket info", + this->get_name().c_str()); + return Succeeded::no; + } } } // checks on singles units { if (get_num_transaxial_crystals_per_singles_unit() <= 0) warning("Scanner %s: transaxial singles_unit info is not set (probably irrelevant unless you use dead-time correction that needs this info)", - this->get_name().c_str()); + this->get_name().c_str()); else { - if ( get_num_detectors_per_ring() % get_num_transaxial_crystals_per_singles_unit() != 0) - { - warning("Scanner %s: inconsistent transaxial singles unit info:\n" - "\tnum_detectors_per_ring %d should be a multiple of num_transaxial_crystals_per_singles_unit %d", - this->get_name().c_str(), - get_num_detectors_per_ring(), get_num_transaxial_crystals_per_singles_unit()); - return Succeeded::no; - } - if ( get_num_transaxial_crystals_per_bucket() % get_num_transaxial_crystals_per_singles_unit() != 0) - { - warning("Scanner %s: inconsistent transaxial singles unit info:\n" - "\tnum_transaxial_crystals_per_bucket %d should be a multiple of num_transaxial_crystals_per_singles_unit %d", - this->get_name().c_str(), - get_num_transaxial_crystals_per_bucket(), get_num_transaxial_crystals_per_singles_unit()); - return Succeeded::no; - } + if ( get_num_detectors_per_ring() % get_num_transaxial_crystals_per_singles_unit() != 0) + { + warning("Scanner %s: inconsistent transaxial singles unit info:\n" + "\tnum_detectors_per_ring %d should be a multiple of num_transaxial_crystals_per_singles_unit %d", + this->get_name().c_str(), + get_num_detectors_per_ring(), get_num_transaxial_crystals_per_singles_unit()); + return Succeeded::no; + } + if ( get_num_transaxial_crystals_per_bucket() % get_num_transaxial_crystals_per_singles_unit() != 0) + { + warning("Scanner %s: inconsistent transaxial singles unit info:\n" + "\tnum_transaxial_crystals_per_bucket %d should be a multiple of num_transaxial_crystals_per_singles_unit %d", + this->get_name().c_str(), + get_num_transaxial_crystals_per_bucket(), get_num_transaxial_crystals_per_singles_unit()); + return Succeeded::no; + } } } { if (get_num_axial_crystals_per_singles_unit() <= 0) warning("Scanner %s: axial singles_unit info is not set (probably irrelevant unless you use dead-time correction that needs this info)", - this->get_name().c_str()); + this->get_name().c_str()); else { - if ( get_num_rings() % get_num_axial_crystals_per_singles_unit() != 0) - { - warning("Scanner %s: inconsistent axial singles unit info:\n" - "\tnum_rings %d should be a multiple of num_axial_crystals_per_singles_unit %d", - this->get_name().c_str(), - get_num_rings(), get_num_axial_crystals_per_singles_unit()); - return Succeeded::no; - } - if ( get_num_axial_crystals_per_bucket() % get_num_axial_crystals_per_singles_unit() != 0) - { - warning("Scanner %s: inconsistent axial singles unit info:\n" - "\tnum_axial_crystals_per_bucket %d should be a multiple of num_axial_crystals_per_singles_unit %d", - this->get_name().c_str(), - get_num_axial_crystals_per_bucket(), get_num_axial_crystals_per_singles_unit()); - return Succeeded::no; - } + if ( get_num_rings() % get_num_axial_crystals_per_singles_unit() != 0) + { + warning("Scanner %s: inconsistent axial singles unit info:\n" + "\tnum_rings %d should be a multiple of num_axial_crystals_per_singles_unit %d", + this->get_name().c_str(), + get_num_rings(), get_num_axial_crystals_per_singles_unit()); + return Succeeded::no; + } + if ( get_num_axial_crystals_per_bucket() % get_num_axial_crystals_per_singles_unit() != 0) + { + warning("Scanner %s: inconsistent axial singles unit info:\n" + "\tnum_axial_crystals_per_bucket %d should be a multiple of num_axial_crystals_per_singles_unit %d", + this->get_name().c_str(), + get_num_axial_crystals_per_bucket(), get_num_axial_crystals_per_singles_unit()); + return Succeeded::no; + } } } @@ -1093,7 +1093,7 @@ bool static close_enough(const double a, const double b) return fabs(a-b) <= std::min(fabs(a), fabs(b)) * 10E-4; } -bool +bool Scanner::operator ==(const Scanner& scanner) const { if (!close_enough(energy_resolution, scanner.energy_resolution) && @@ -1123,7 +1123,7 @@ if (!close_enough(energy_resolution, scanner.energy_resolution) && } -const list& +const list& Scanner::get_all_names() const {return list_of_names;} @@ -1131,9 +1131,9 @@ Scanner::get_all_names() const const string& Scanner::get_name() const { - - return *(list_of_names.begin()); - + + return *(list_of_names.begin()); + } string @@ -1149,7 +1149,7 @@ Scanner::parameter_info() const #endif s << "Scanner parameters:= "<<'\n'; - s << "Scanner type := " << get_name() <<'\n'; + s << "Scanner type := " << get_name() <<'\n'; s << "Number of rings := " << num_rings << '\n'; s << "Number of detectors per ring := " << get_num_detectors_per_ring() << '\n'; @@ -1184,7 +1184,7 @@ Scanner::parameter_info() const << get_num_axial_crystals_per_singles_unit() << '\n' << "Number of crystals per singles unit in transaxial direction := " << get_num_transaxial_crystals_per_singles_unit() << '\n'; - + s << "end scanner parameters:=\n"; return s.str(); @@ -1204,7 +1204,7 @@ string Scanner::list_names() const // work-around VC bug std:: #endif - list::const_iterator iterator = list_of_names.begin(); + list::const_iterator iterator = list_of_names.begin(); s << *iterator; ++iterator; while(iterator!=list_of_names.end()) @@ -1219,7 +1219,7 @@ string Scanner::list_names() const /************************************************ static members *************************************************/ -Scanner* Scanner::ask_parameters() +Scanner* Scanner::ask_parameters() { cerr << list_all_names(); @@ -1227,7 +1227,7 @@ Scanner* Scanner::ask_parameters() const string name=ask_string("Enter the name of the scanner"); //get the type from the name itself - Scanner* scanner_ptr = + Scanner* scanner_ptr = get_scanner_from_name(name); // N.E: New optional parameters have been added, namely @@ -1246,44 +1246,44 @@ Scanner* Scanner::ask_parameters() if (scanner_ptr->type == Unknown_scanner) cerr << "I didn't recognise the scanner you entered."; cerr << "I'll ask lots of questions\n"; - + while (true) { - int num_detectors_per_ring = - ask_num("Enter number of detectors per ring:",0,2000,128); - - int NoRings = + int num_detectors_per_ring = + ask_num("Enter number of detectors per ring:",0,2000,128); + + int NoRings = ask_num("Enter number of rings :",0,1000,16); - - int NoBins = + + int NoBins = ask_num("Enter default number of tangential positions for this scanner: ",0,3000,128); - + float InnerRingRadius= - ask_num("Enter inner ring radius (in mm): ",0.F,600.F,256.F); - - float AverageDepthOfInteraction = + ask_num("Enter inner ring radius (in mm): ",0.F,600.F,256.F); + + float AverageDepthOfInteraction = ask_num("Enter average depth of interaction (in mm): ", 0.F, 100.F, 0.F); - - float RingSpacing= + + float RingSpacing= ask_num("Enter ring spacing (in mm): ",0.F,30.F,6.75F); - - float BinSize= + + float BinSize= ask_num("Enter default (tangential) bin size after arc-correction (in mm):",0.F,60.F,3.75F); float intrTilt= - ask_num("Enter intrinsic_tilt (in degrees):",-180.F,360.F,0.F); - int TransBlocksPerBucket = - ask_num("Enter number of transaxial blocks per bucket: ",0,10,2); - int AxialBlocksPerBucket = - ask_num("Enter number of axial blocks per bucket: ",0,10,6); - int AxialCrystalsPerBlock = - ask_num("Enter number of axial crystals per block: ",0,12,8); - int TransaxialCrystalsPerBlock = - ask_num("Enter number of transaxial crystals per block: ",0,12,8); - int AxialCrstalsPerSinglesUnit = + ask_num("Enter intrinsic_tilt (in degrees):",-180.F,360.F,0.F); + int TransBlocksPerBucket = + ask_num("Enter number of transaxial blocks per bucket: ",0,10,2); + int AxialBlocksPerBucket = + ask_num("Enter number of axial blocks per bucket: ",0,10,6); + int AxialCrystalsPerBlock = + ask_num("Enter number of axial crystals per block: ",0,12,8); + int TransaxialCrystalsPerBlock = + ask_num("Enter number of transaxial crystals per block: ",0,12,8); + int AxialCrstalsPerSinglesUnit = ask_num("Enter number of axial crystals per singles unit: ", 0, NoRings, 1); - int TransaxialCrystalsPerSinglesUnit = + int TransaxialCrystalsPerSinglesUnit = ask_num("Enter number of transaxial crystals per singles unit: ", 0, num_detectors_per_ring, 1); - + float EnergyResolution = ask_num("Enter the energy resolution of the scanner : ", 0.0f, 1000.0f, -1.0f); @@ -1293,7 +1293,7 @@ Scanner* Scanner::ask_parameters() int num_detector_layers = ask_num("Enter number of detector layers per block: ",1,100,1); Type type = User_defined_scanner; - + if (EnergyResolution > -1 && ReferenceEnergy > -1) Scanner* scanner_ptr = new Scanner(type, string_list(name), @@ -1318,11 +1318,11 @@ Scanner* Scanner::ask_parameters() AxialCrystalsPerBlock,TransaxialCrystalsPerBlock, AxialCrstalsPerSinglesUnit, TransaxialCrystalsPerSinglesUnit, num_detector_layers); - + if (scanner_ptr->check_consistency()==Succeeded::yes || - !ask("Ask questions again?",true)) - return scanner_ptr; - + !ask("Ask questions again?",true)) + return scanner_ptr; + delete scanner_ptr; } // infinite loop } @@ -1331,26 +1331,26 @@ Scanner* Scanner::ask_parameters() Scanner * Scanner::get_scanner_from_name(const string& name) -{ +{ Scanner * scanner_ptr; const string matching_name = standardise_interfile_keyword(name); - Type type= E931; + Type type= E931; while (type != Unknown_scanner) { scanner_ptr = new Scanner(type); const list& list_of_names = scanner_ptr->get_all_names(); for (std::list::const_iterator iter =list_of_names.begin(); - iter!=list_of_names.end(); - ++iter) + iter!=list_of_names.end(); + ++iter) { - const string matching_scanner_name = - standardise_interfile_keyword(*iter); - if (matching_scanner_name==matching_name) - return scanner_ptr; + const string matching_scanner_name = + standardise_interfile_keyword(*iter); + if (matching_scanner_name==matching_name) + return scanner_ptr; } - + // we didn't find it yet delete scanner_ptr; // tricky business to find next type @@ -1374,24 +1374,24 @@ string Scanner:: list_all_names() std::ostringstream s; #endif - Type type= E931; + Type type= E931; while (type != Unknown_scanner) { scanner_ptr = new Scanner(type); s << scanner_ptr->list_names() << '\n'; - + delete scanner_ptr; // tricky business to find next type int int_type = type; ++int_type; type = static_cast(int_type); } - + return s.str(); } -static list +static list string_list(const string& s) { list l; @@ -1399,7 +1399,7 @@ string_list(const string& s) return l; } -static list +static list string_list(const string& s1, const string& s2) { list l; @@ -1408,7 +1408,7 @@ string_list(const string& s1, const string& s2) return l; } -static list +static list string_list(const string& s1, const string& s2, const string& s3) { list l; @@ -1418,7 +1418,7 @@ string_list(const string& s1, const string& s2, const string& s3) return l; } -static list +static list string_list(const string& s1, const string& s2, const string& s3, const string& s4) { list l; diff --git a/src/include/stir/ProjDataInfo.h b/src/include/stir/ProjDataInfo.h index 6bb6c0ddeb..2d3f4851be 100644 --- a/src/include/stir/ProjDataInfo.h +++ b/src/include/stir/ProjDataInfo.h @@ -182,7 +182,7 @@ class ProjDataInfo //! Get number of tangential positions inline int get_num_tangential_poss() const; //! Get number of tof bins - inline int get_tof_bin(double delta) const; + inline int get_tof_bin(double& delta) const; //! Get number of TOF bins inline int get_num_tof_poss() const; //! Get minimum segment number diff --git a/src/include/stir/ProjDataInfo.inl b/src/include/stir/ProjDataInfo.inl index ca82f61fcd..ca82cd69c6 100644 --- a/src/include/stir/ProjDataInfo.inl +++ b/src/include/stir/ProjDataInfo.inl @@ -66,7 +66,7 @@ ProjDataInfo::get_num_tof_poss() const { return num_tof_bins; } int -ProjDataInfo::get_tof_bin(double delta) const +ProjDataInfo::get_tof_bin(double& delta) const { for (int i = min_timing_pos_num; i < max_timing_pos_num; i++) { diff --git a/src/include/stir/recon_buildblock/ProjMatrixByBin.inl b/src/include/stir/recon_buildblock/ProjMatrixByBin.inl index 3c372cf3c4..08a8120a92 100644 --- a/src/include/stir/recon_buildblock/ProjMatrixByBin.inl +++ b/src/include/stir/recon_buildblock/ProjMatrixByBin.inl @@ -202,12 +202,15 @@ ProjMatrixByBin::apply_tof_kernel(ProjMatrixElemsForOneBin& nonTOF_probabilities // (point2.z() - voxel_center.z()) *(point2.z() - voxel_center.z())); float m = (lor_length - d1 - d1) * 0.5f; - low_dist = (proj_data_info_sptr->timing_bin_boundaries[tof_probabilities.get_bin_ptr()->timing_pos_num()].low_lim - m) * r_sqrt2_gauss_sigma; - high_dist = (proj_data_info_sptr->timing_bin_boundaries[tof_probabilities.get_bin_ptr()->timing_pos_num()].high_lim - m) * r_sqrt2_gauss_sigma; + low_dist = (proj_data_info_sptr->timing_bin_boundaries_mm[tof_probabilities.get_bin_ptr()->timing_pos_num()].low_lim - m) * r_sqrt2_gauss_sigma; + high_dist = (proj_data_info_sptr->timing_bin_boundaries_mm[tof_probabilities.get_bin_ptr()->timing_pos_num()].high_lim - m) * r_sqrt2_gauss_sigma; + + // Cut-off really small values. + if (abs(low_dist) > 5.5 && abs(high_dist) > 5.5) + continue; get_tof_value(low_dist, high_dist, new_value); new_value *= element_ptr->get_value(); - tof_probabilities.push_back(ProjMatrixElemsForOneBin::value_type(element_ptr->get_coords(), new_value)); } From ed6b89c5975dc512d1e69890712b7494114ae0da Mon Sep 17 00:00:00 2001 From: Nikos Efthimiou Date: Tue, 17 Jan 2017 13:56:06 +0000 Subject: [PATCH 049/509] correction --- src/include/stir/ProjDataInfo.h | 2 +- src/include/stir/ProjDataInfo.inl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/include/stir/ProjDataInfo.h b/src/include/stir/ProjDataInfo.h index 2d3f4851be..43f64c3171 100644 --- a/src/include/stir/ProjDataInfo.h +++ b/src/include/stir/ProjDataInfo.h @@ -182,7 +182,7 @@ class ProjDataInfo //! Get number of tangential positions inline int get_num_tangential_poss() const; //! Get number of tof bins - inline int get_tof_bin(double& delta) const; + inline int get_tof_bin(const double& delta) const; //! Get number of TOF bins inline int get_num_tof_poss() const; //! Get minimum segment number diff --git a/src/include/stir/ProjDataInfo.inl b/src/include/stir/ProjDataInfo.inl index ca82cd69c6..b3487b6532 100644 --- a/src/include/stir/ProjDataInfo.inl +++ b/src/include/stir/ProjDataInfo.inl @@ -66,7 +66,7 @@ ProjDataInfo::get_num_tof_poss() const { return num_tof_bins; } int -ProjDataInfo::get_tof_bin(double& delta) const +ProjDataInfo::get_tof_bin(const double& delta) const { for (int i = min_timing_pos_num; i < max_timing_pos_num; i++) { From ca66bf192e4f6b2cc26ca6ed9a31bdd76b365992 Mon Sep 17 00:00:00 2001 From: Nikos Efthimiou Date: Sat, 21 Jan 2017 19:20:42 +0000 Subject: [PATCH 050/509] Mostly everything but ProjDataFromStream We can read/write interfile headers, read/write TOF scanners, LM_TO_PROJDATA is aware of timing information. But the low level work hasnot finish, yet. So we cannot actually read / write TOF data from the disk . --- src/IO/InterfileHeader.cxx | 219 +++++++++---- src/IO/interfile.cxx | 186 ++++++----- src/buildblock/ProjData.cxx | 6 +- src/buildblock/ProjDataFromStream.cxx | 16 + src/buildblock/ProjDataInfo.cxx | 42 ++- .../ProjDataInfoCylindricalArcCorr.cxx | 9 +- .../ProjDataInfoCylindricalNoArcCorr.cxx | 15 +- src/buildblock/Scanner.cxx | 65 +++- src/include/stir/Bin.inl | 2 +- src/include/stir/IO/InterfileHeader.h | 7 + src/include/stir/ProjData.h | 8 +- src/include/stir/ProjDataFromStream.h | 24 +- src/include/stir/ProjDataFromStream.inl | 4 + src/include/stir/ProjDataInfo.h | 20 +- .../stir/ProjDataInfoCylindricalArcCorr.h | 3 +- .../stir/ProjDataInfoCylindricalNoArcCorr.h | 6 +- src/include/stir/listmode/LmToProjData.h | 10 +- .../stir/listmode/LmToProjDataBootstrap.h | 2 +- src/listmode_buildblock/LmToProjData.cxx | 307 ++++++++++++++++-- .../LmToProjDataBootstrap.cxx | 6 +- src/listmode_utilities/lm_to_projdata.cxx | 2 +- ...MeanAndListModeDataWithProjMatrixByBin.cxx | 1 - src/test/test_proj_data_info.cxx | 22 +- src/utilities/create_projdata_template.cxx | 9 +- 24 files changed, 746 insertions(+), 245 deletions(-) diff --git a/src/IO/InterfileHeader.cxx b/src/IO/InterfileHeader.cxx index a6cd22f8bf..c556f75614 100644 --- a/src/IO/InterfileHeader.cxx +++ b/src/IO/InterfileHeader.cxx @@ -557,6 +557,19 @@ InterfilePDFSHeader::InterfilePDFSHeader() add_key("Reference energy (in keV)", &reference_energy); + tof_mash_factor=-1; + add_key("%TOF mashing factor", + &tof_mash_factor); + max_num_timing_poss = -1; + add_key("Number of TOF time bins", + &max_num_timing_poss); + size_of_timing_pos = -1.f; + add_key("Size of timing bin (ps)", + &size_of_timing_pos); + timing_resolution = -1.f; + add_key("Timing resolution (ps)", + &timing_resolution); + add_key("end scanner parameters", KeyArgument::NONE, &KeyParser::do_nothing); @@ -595,9 +608,10 @@ int InterfilePDFSHeader::find_storage_order() } */ - if (num_dimensions != 4) + if (num_dimensions != 4 && + num_dimensions != 5) { - warning("Interfile error: expecting 4D structure "); + warning("Interfile error: expecting 4D structure or 5D in case of TOF information "); stop_parsing(); return true; } @@ -614,16 +628,31 @@ int InterfilePDFSHeader::find_storage_order() if (matrix_labels[3] == "segment") { num_segments = matrix_size[3][0]; - + if (matrix_labels[1] == "axial coordinate" && matrix_labels[2] == "view") { - storage_order =ProjDataFromStream::Segment_View_AxialPos_TangPos; - num_views = matrix_size[2][0]; + // If TOF information is in there + if (matrix_labels.size() > 4) + { + num_timing_poss = matrix_size[4][0]; + storage_order =ProjDataFromStream::Timing_Segment_View_AxialPos_TangPos; + num_views = matrix_size[2][0]; #ifdef _MSC_VER - num_rings_per_segment.assign(matrix_size[1].begin(), matrix_size[1].end()); + num_rings_per_segment.assign(matrix_size[1].begin(), matrix_size[1].end()); #else - num_rings_per_segment = matrix_size[1]; + num_rings_per_segment = matrix_size[1]; +#endif + } + else + { + storage_order =ProjDataFromStream::Segment_View_AxialPos_TangPos; + num_views = matrix_size[2][0]; +#ifdef _MSC_VER + num_rings_per_segment.assign(matrix_size[1].begin(), matrix_size[1].end()); +#else + num_rings_per_segment = matrix_size[1]; #endif + } } else if (matrix_labels[1] == "view" && matrix_labels[2] == "axial coordinate") { @@ -845,6 +874,7 @@ find_segment_sequence(vector& segment_sequence, } // MJ 17/05/2000 made bool +// NE 28/12/2016 Accounts for TOF stuff. bool InterfilePDFSHeader::post_processing() { @@ -1171,7 +1201,7 @@ bool InterfilePDFSHeader::post_processing() { if (energy_resolution != guessed_scanner_ptr->get_energy_resolution()) { - warning("Interfile warning: 'energy resolution' (%d) is expected to be %d. " + warning("Interfile warning: 'energy resolution' (%f) is expected to be %d. " "Currently, the energy resolution and the reference energy, are used only in" " scatter correction.", energy_resolution, guessed_scanner_ptr->get_energy_resolution()); @@ -1179,7 +1209,7 @@ bool InterfilePDFSHeader::post_processing() } if (reference_energy != guessed_scanner_ptr->get_reference_energy()) { - warning("Interfile warning: 'reference energy' (%d) is expected to be %d." + warning("Interfile warning: 'reference energy' (%f) is expected to be %d." "Currently, the energy resolution and the reference energy, are used only in" " scatter correction.", reference_energy, guessed_scanner_ptr->get_reference_energy()); @@ -1187,6 +1217,28 @@ bool InterfilePDFSHeader::post_processing() } } + if (guessed_scanner_ptr->is_tof_ready()) + { + if (max_num_timing_poss != guessed_scanner_ptr->get_num_max_of_timing_bins()) + { + warning("Interfile warning: 'Number of TOF time bins' (%d) is expected to be %d.", + max_num_timing_poss, guessed_scanner_ptr->get_num_max_of_timing_bins()); + mismatch_between_header_and_guess = true; + } + if (size_of_timing_pos != guessed_scanner_ptr->get_size_of_timing_bin()) + { + warning("Interfile warning: 'Size of timing bin (ps)' (%f) is expected to be %f.", + size_of_timing_pos, guessed_scanner_ptr->get_size_of_timing_bin()); + mismatch_between_header_and_guess = true; + } + if (timing_resolution != guessed_scanner_ptr->get_timing_resolution()) + { + warning("Interfile warning: 'Timing resolution (ps)' (%f) is expected to be %f.", + timing_resolution, guessed_scanner_ptr->get_timing_resolution()); + mismatch_between_header_and_guess = true; + } + } + // end of checks. If they failed, we ignore the guess if (mismatch_between_header_and_guess) { @@ -1229,86 +1281,117 @@ bool InterfilePDFSHeader::post_processing() // finally, we construct a new scanner object with // data from the Interfile header (or the guessed scanner). - shared_ptr scanner_ptr_from_file( - new Scanner(guessed_scanner_ptr->get_type(), - get_exam_info_ptr()->originating_system, - num_detectors_per_ring, - num_rings, - max_num_non_arccorrected_bins, - default_num_arccorrected_bins, - static_cast(inner_ring_diameter_in_cm*10./2), - static_cast(average_depth_of_interaction_in_cm*10), - static_cast(distance_between_rings_in_cm*10.), - static_cast(default_bin_size_in_cm*10), - static_cast(view_offset_in_degrees*_PI/180), - num_axial_blocks_per_bucket, - num_transaxial_blocks_per_bucket, - num_axial_crystals_per_block, - num_transaxial_crystals_per_block, - num_axial_crystals_per_singles_unit, - num_transaxial_crystals_per_singles_unit, - num_detector_layers, - energy_resolution, - reference_energy)); + + shared_ptr scanner_sptr_from_file; + if (!guessed_scanner_ptr->is_tof_ready()) + { + scanner_sptr_from_file.reset( + new Scanner(guessed_scanner_ptr->get_type(), + get_exam_info_ptr()->originating_system, + num_detectors_per_ring, + num_rings, + max_num_non_arccorrected_bins, + default_num_arccorrected_bins, + static_cast(inner_ring_diameter_in_cm*10./2), + static_cast(average_depth_of_interaction_in_cm*10), + static_cast(distance_between_rings_in_cm*10.), + static_cast(default_bin_size_in_cm*10), + static_cast(view_offset_in_degrees*_PI/180), + num_axial_blocks_per_bucket, + num_transaxial_blocks_per_bucket, + num_axial_crystals_per_block, + num_transaxial_crystals_per_block, + num_axial_crystals_per_singles_unit, + num_transaxial_crystals_per_singles_unit, + num_detector_layers, + energy_resolution, + reference_energy)); + } + else + { + scanner_sptr_from_file.reset( + new Scanner(guessed_scanner_ptr->get_type(), + get_exam_info_ptr()->originating_system, + num_detectors_per_ring, + num_rings, + max_num_non_arccorrected_bins, + default_num_arccorrected_bins, + static_cast(inner_ring_diameter_in_cm*10./2), + static_cast(average_depth_of_interaction_in_cm*10), + static_cast(distance_between_rings_in_cm*10.), + static_cast(default_bin_size_in_cm*10), + static_cast(view_offset_in_degrees*_PI/180), + num_axial_blocks_per_bucket, + num_transaxial_blocks_per_bucket, + num_axial_crystals_per_block, + num_transaxial_crystals_per_block, + num_axial_crystals_per_singles_unit, + num_transaxial_crystals_per_singles_unit, + num_detector_layers, + max_num_timing_poss, + size_of_timing_pos, + timing_resolution)); + } + bool is_consistent = - scanner_ptr_from_file->check_consistency() == Succeeded::yes; - if (scanner_ptr_from_file->get_type() == Scanner::Unknown_scanner || - scanner_ptr_from_file->get_type() == Scanner::User_defined_scanner || + scanner_sptr_from_file->check_consistency() == Succeeded::yes; + if (scanner_sptr_from_file->get_type() == Scanner::Unknown_scanner || + scanner_sptr_from_file->get_type() == Scanner::User_defined_scanner || mismatch_between_header_and_guess || !is_consistent) { warning("Interfile parsing ended up with the following scanner:\n%s\n", - scanner_ptr_from_file->parameter_info().c_str()); + scanner_sptr_from_file->parameter_info().c_str()); } // float azimuthal_angle_sampling =_PI/num_views; - + //TODO: TOF ProjDataInfo if (is_arccorrected) { if (effective_central_bin_size_in_cm <= 0) - effective_central_bin_size_in_cm = - scanner_ptr_from_file->get_default_bin_size()/10; - else if (fabs(effective_central_bin_size_in_cm - - scanner_ptr_from_file->get_default_bin_size()/10)>.001) - warning("Interfile warning: unexpected effective_central_bin_size_in_cm\n" - "Value in header is %g while the default for the scanner is %g\n" - "Using value from header.", - effective_central_bin_size_in_cm, - scanner_ptr_from_file->get_default_bin_size()/10); + effective_central_bin_size_in_cm = + scanner_sptr_from_file->get_default_bin_size()/10; + else if (fabs(effective_central_bin_size_in_cm - + scanner_sptr_from_file->get_default_bin_size()/10)>.001) + warning("Interfile warning: unexpected effective_central_bin_size_in_cm\n" + "Value in header is %g while the default for the scanner is %g\n" + "Using value from header.", + effective_central_bin_size_in_cm, + scanner_sptr_from_file->get_default_bin_size()/10); - data_info_ptr = - new ProjDataInfoCylindricalArcCorr ( - scanner_ptr_from_file, - float(effective_central_bin_size_in_cm*10.), - sorted_num_rings_per_segment, - sorted_min_ring_diff, - sorted_max_ring_diff, - num_views,num_bins); + data_info_ptr = + new ProjDataInfoCylindricalArcCorr ( + scanner_sptr_from_file, + float(effective_central_bin_size_in_cm*10.), + sorted_num_rings_per_segment, + sorted_min_ring_diff, + sorted_max_ring_diff, + num_views,num_bins, tof_mash_factor); } else { - data_info_ptr = - new ProjDataInfoCylindricalNoArcCorr ( - scanner_ptr_from_file, - sorted_num_rings_per_segment, - sorted_min_ring_diff, - sorted_max_ring_diff, - num_views,num_bins); + data_info_ptr = + new ProjDataInfoCylindricalNoArcCorr ( + scanner_sptr_from_file, + sorted_num_rings_per_segment, + sorted_min_ring_diff, + sorted_max_ring_diff, + num_views,num_bins, tof_mash_factor); if (effective_central_bin_size_in_cm>0 && - fabs(effective_central_bin_size_in_cm - - data_info_ptr->get_sampling_in_s(Bin(0,0,0,0))/10.)>.01) - { - warning("Interfile warning: inconsistent effective_central_bin_size_in_cm\n" - "Value in header is %g while I expect %g from the inner ring radius etc\n" - "Ignoring value in header", - effective_central_bin_size_in_cm, - data_info_ptr->get_sampling_in_s(Bin(0,0,0,0))/10.); - } + fabs(effective_central_bin_size_in_cm - + data_info_ptr->get_sampling_in_s(Bin(0,0,0,0))/10.)>.01) + { + warning("Interfile warning: inconsistent effective_central_bin_size_in_cm\n" + "Value in header is %g while I expect %g from the inner ring radius etc\n" + "Ignoring value in header", + effective_central_bin_size_in_cm, + data_info_ptr->get_sampling_in_s(Bin(0,0,0,0))/10.); + } } //cerr << data_info_ptr->parameter_info() << endl; diff --git a/src/IO/interfile.cxx b/src/IO/interfile.cxx index 9fb86aa7f1..5e60d8f495 100644 --- a/src/IO/interfile.cxx +++ b/src/IO/interfile.cxx @@ -847,57 +847,76 @@ write_basic_interfile_PDFS_header(const string& header_file_name, } // it's PET data if we get here + // N.E. Added timing locations + pdfs.get_proj_data_info_ptr()->get_tof_mash_factor()>1 ? + output_header << "number of dimensions := 5\n" : + output_header << "number of dimensions := 4\n"; - output_header << "number of dimensions := 4\n"; - - // TODO support more ? + // TODO support more ? { // default to Segment_View_AxialPos_TangPos int order_of_segment = 4; int order_of_view = 3; int order_of_z = 2; int order_of_bin = 1; + int order_of_timing_poss = 0; switch(pdfs.get_storage_order()) /* - { + { case ProjDataFromStream::ViewSegmentRingBin: - { - order_of_segment = 2; - order_of_view = 1; - order_of_z = 3; - break; - } - */ + { + order_of_segment = 2; + order_of_view = 1; + order_of_z = 3; + break; + } + */ { case ProjDataFromStream::Segment_View_AxialPos_TangPos: - { - order_of_segment = 4; - order_of_view = 3; - order_of_z = 2; - break; - } + { + order_of_segment = 4; + order_of_view = 3; + order_of_z = 2; + break; + } case ProjDataFromStream::Segment_AxialPos_View_TangPos: - { - order_of_segment = 4; - order_of_view = 2; - order_of_z = 3; - break; - } + { + order_of_segment = 4; + order_of_view = 2; + order_of_z = 3; + break; + } + case ProjDataFromStream::Timing_Segment_View_AxialPos_TangPos: + { + order_of_timing_poss = 5; + order_of_segment = 4; + order_of_view = 3; + order_of_z = 2; + break; + } default: - { - error("write_interfile_PSOV_header: unsupported storage order, " + { + error("write_interfile_PSOV_header: unsupported storage order, " "defaulting to Segment_View_AxialPos_TangPos.\n Please correct by hand !"); - } + } } - - output_header << "matrix axis label [" << order_of_segment - << "] := segment\n"; - output_header << "!matrix size [" << order_of_segment << "] := " - << pdfs.get_segment_sequence_in_stream().size()<< "\n"; + + if (order_of_timing_poss > 0) + { + output_header << "matrix axis label [" << order_of_timing_poss + << "] := timing positions\n"; + output_header << "!matrix size [" << order_of_timing_poss << "] := " + << pdfs.get_timing_poss_sequence_in_stream().size()<< "\n"; + } + + output_header << "matrix axis label [" << order_of_segment + << "] := segment\n"; + output_header << "!matrix size [" << order_of_segment << "] := " + << pdfs.get_segment_sequence_in_stream().size()<< "\n"; output_header << "matrix axis label [" << order_of_view << "] := view\n"; output_header << "!matrix size [" << order_of_view << "] := " - << pdfs.get_proj_data_info_ptr()->get_num_views() << "\n"; - + << pdfs.get_proj_data_info_ptr()->get_num_views() << "\n"; + output_header << "matrix axis label [" << order_of_z << "] := axial coordinate\n"; output_header << "!matrix size [" << order_of_z << "] := "; // tedious way to print a list of numbers @@ -905,62 +924,73 @@ write_basic_interfile_PDFS_header(const string& header_file_name, std::vector::const_iterator seg = segment_sequence.begin(); output_header << "{ " <get_num_axial_poss(*seg); for (seg++; seg != segment_sequence.end(); seg++) - output_header << "," << pdfs.get_proj_data_info_ptr()->get_num_axial_poss(*seg); + output_header << "," << pdfs.get_proj_data_info_ptr()->get_num_axial_poss(*seg); output_header << "}\n"; } output_header << "matrix axis label [" << order_of_bin << "] := tangential coordinate\n"; output_header << "!matrix size [" << order_of_bin << "] := " - <get_num_tangential_poss() << "\n"; + <get_num_tangential_poss() << "\n"; + + // IF TOF is supported add this in the header. + if (pdfs.get_proj_data_info_ptr()->get_scanner_ptr()->is_tof_ready() && + pdfs.get_proj_data_info_ptr()->get_tof_mash_factor() > 1) + { + // Moved in scanner section +// output_header << "%number of TOF time bins :=" << +// pdfs.get_proj_data_info_ptr()->get_scanner_ptr()->get_num_max_of_timing_bins() << "\n"; + output_header << "%TOF mashing factor := " << + pdfs.get_proj_data_info_ptr()->get_tof_mash_factor() << "\n"; + } } - const ProjDataInfoCylindrical* proj_data_info_ptr = + const ProjDataInfoCylindrical* proj_data_info_ptr = dynamic_cast< const ProjDataInfoCylindrical*> (pdfs.get_proj_data_info_ptr()); if (proj_data_info_ptr!=NULL) { // cylindrical scanners - - output_header << "minimum ring difference per segment := "; + + output_header << "minimum ring difference per segment := "; { - std::vector::const_iterator seg = segment_sequence.begin(); - output_header << "{ " << proj_data_info_ptr->get_min_ring_difference(*seg); - for (seg++; seg != segment_sequence.end(); seg++) - output_header << "," <get_min_ring_difference(*seg); - output_header << "}\n"; + std::vector::const_iterator seg = segment_sequence.begin(); + output_header << "{ " << proj_data_info_ptr->get_min_ring_difference(*seg); + for (seg++; seg != segment_sequence.end(); seg++) + output_header << "," <get_min_ring_difference(*seg); + output_header << "}\n"; } output_header << "maximum ring difference per segment := "; { - std::vector::const_iterator seg = segment_sequence.begin(); - output_header << "{ " <get_max_ring_difference(*seg); - for (seg++; seg != segment_sequence.end(); seg++) - output_header << "," <get_max_ring_difference(*seg); - output_header << "}\n"; + std::vector::const_iterator seg = segment_sequence.begin(); + output_header << "{ " <get_max_ring_difference(*seg); + for (seg++; seg != segment_sequence.end(); seg++) + output_header << "," <get_max_ring_difference(*seg); + output_header << "}\n"; } - + const Scanner& scanner = *proj_data_info_ptr->get_scanner_ptr(); if (fabs(proj_data_info_ptr->get_ring_radius()- - scanner.get_effective_ring_radius()) > .1) - warning("write_basic_interfile_PDFS_header: inconsistent effective ring radius:\n" - "\tproj_data_info has %g, scanner has %g.\n" - "\tThis really should not happen and signifies a bug.\n" - "\tYou will have a problem reading this data back in.", - proj_data_info_ptr->get_ring_radius(), - scanner.get_effective_ring_radius()); + scanner.get_effective_ring_radius()) > .1) + warning("write_basic_interfile_PDFS_header: inconsistent effective ring radius:\n" + "\tproj_data_info has %g, scanner has %g.\n" + "\tThis really should not happen and signifies a bug.\n" + "\tYou will have a problem reading this data back in.", + proj_data_info_ptr->get_ring_radius(), + scanner.get_effective_ring_radius()); if (fabs(proj_data_info_ptr->get_ring_spacing()- - scanner.get_ring_spacing()) > .1) - warning("write_basic_interfile_PDFS_header: inconsistent ring spacing:\n" - "\tproj_data_info has %g, scanner has %g.\n" - "\tThis really should not happen and signifies a bug.\n" - "\tYou will have a problem reading this data back in.", - proj_data_info_ptr->get_ring_spacing(), - scanner.get_ring_spacing()); + scanner.get_ring_spacing()) > .1) + warning("write_basic_interfile_PDFS_header: inconsistent ring spacing:\n" + "\tproj_data_info has %g, scanner has %g.\n" + "\tThis really should not happen and signifies a bug.\n" + "\tYou will have a problem reading this data back in.", + proj_data_info_ptr->get_ring_spacing(), + scanner.get_ring_spacing()); output_header << scanner.parameter_info(); - output_header << "effective central bin size (cm) := " - << proj_data_info_ptr->get_sampling_in_s(Bin(0,0,0,0))/10. << endl; + output_header << "effective central bin size (cm) := " + << proj_data_info_ptr->get_sampling_in_s(Bin(0,0,0,0))/10. << endl; } // end of cylindrical scanner else @@ -975,22 +1005,22 @@ write_basic_interfile_PDFS_header(const string& header_file_name, const TimeFrameDefinitions& frame_defs(pdfs.get_exam_info_ptr()->time_frame_definitions); if (frame_defs.get_num_time_frames()>0) { - output_header << "number of time frames := " << frame_defs.get_num_time_frames() << '\n'; - for (unsigned int frame_num=1; frame_num<=frame_defs.get_num_time_frames(); ++frame_num) - { - if (frame_defs.get_duration(frame_num)>0) - { - output_header << "image duration (sec)[" << frame_num << "] := " - << frame_defs.get_duration(frame_num) << '\n'; - output_header << "image relative start time (sec)[" << frame_num << "] := " - << frame_defs.get_start_time(frame_num) << '\n'; - } - } + output_header << "number of time frames := " << frame_defs.get_num_time_frames() << '\n'; + for (unsigned int frame_num=1; frame_num<=frame_defs.get_num_time_frames(); ++frame_num) + { + if (frame_defs.get_duration(frame_num)>0) + { + output_header << "image duration (sec)[" << frame_num << "] := " + << frame_defs.get_duration(frame_num) << '\n'; + output_header << "image relative start time (sec)[" << frame_num << "] := " + << frame_defs.get_start_time(frame_num) << '\n'; + } + } } else { - // need to write this anyway to allow vectored keys below - output_header << "number of time frames := 1\n"; + // need to write this anyway to allow vectored keys below + output_header << "number of time frames := 1\n"; } } // Write energy window lower and upper thresholds, if they are not -1 diff --git a/src/buildblock/ProjData.cxx b/src/buildblock/ProjData.cxx index 1eea479fa7..416a62804e 100644 --- a/src/buildblock/ProjData.cxx +++ b/src/buildblock/ProjData.cxx @@ -353,7 +353,8 @@ SegmentByView ProjData::get_segment_by_view(const int segment_num) const } Succeeded -ProjData::set_segment(const SegmentBySinogram& segment) +ProjData::set_segment(const SegmentBySinogram& segment, + const int& timing_pos) { for (int view_num = get_min_view_num(); view_num <= get_max_view_num(); ++view_num) { @@ -365,7 +366,8 @@ ProjData::set_segment(const SegmentBySinogram& segment) } Succeeded -ProjData::set_segment(const SegmentByView& segment) +ProjData::set_segment(const SegmentByView& segment, + const int& timing_pos) { for (int view_num = get_min_view_num(); view_num <= get_max_view_num(); ++view_num) { diff --git a/src/buildblock/ProjDataFromStream.cxx b/src/buildblock/ProjDataFromStream.cxx index 0e19dee214..c768561476 100644 --- a/src/buildblock/ProjDataFromStream.cxx +++ b/src/buildblock/ProjDataFromStream.cxx @@ -119,6 +119,22 @@ ProjDataFromStream::ProjDataFromStream(shared_ptr const& exam_info_spt { segment_sequence[i] =segment_num; } + + // In this case lets start a TOF stream - Similar to segments + if (storage_order == Timing_Segment_View_AxialPos_TangPos && + proj_data_info_ptr->get_tof_mash_factor() > 1) + { + timing_poss_sequence.resize(proj_data_info_ptr->get_num_timing_poss()); + int timing_pos_num; + + for (i= 0, timing_pos_num = proj_data_info_ptr->get_min_timing_pos_num(); + timing_pos_num<=proj_data_info_ptr->get_max_timing_pos_num(); + ++i, ++timing_pos_num) + { + timing_poss_sequence[i] = timing_pos_num; + } + + } } Viewgram diff --git a/src/buildblock/ProjDataInfo.cxx b/src/buildblock/ProjDataInfo.cxx index 1603d74825..7bae650c39 100644 --- a/src/buildblock/ProjDataInfo.cxx +++ b/src/buildblock/ProjDataInfo.cxx @@ -194,9 +194,12 @@ ProjDataInfo::set_tof_mash_factor(const int new_num) timing_increament_in_mm = (tof_mash_factor * scanner_ptr->get_size_of_timing_bin() * 0.299792458f); min_timing_pos_num = - (scanner_ptr->get_num_max_of_timing_bins() / tof_mash_factor)/2; - max_timing_pos_num = min_timing_pos_num + (scanner_ptr->get_num_max_of_timing_bins() / tof_mash_factor); + max_timing_pos_num = min_timing_pos_num + (scanner_ptr->get_num_max_of_timing_bins() / tof_mash_factor) -1; - num_tof_bins = max_timing_pos_num - min_timing_pos_num; + num_tof_bins = max_timing_pos_num - min_timing_pos_num +1 ; + + if (num_tof_bins%2 == 0) + error("ProjDataInfo: Number of TOF bins should be an odd number. Abort."); // Upper and lower boundaries of the timing poss; timing_bin_boundaries_mm.grow(min_timing_pos_num, max_timing_pos_num); @@ -413,9 +416,10 @@ ProjDataInfo::get_empty_related_viewgrams(const ViewSegmentNumbers& view_segmnet ProjDataInfo* ProjDataInfo::ProjDataInfoCTI(const shared_ptr& scanner, - const int span, const int max_delta, - const int num_views, const int num_tangential_poss, - const bool arc_corrected) + const int span, const int max_delta, + const int num_views, const int num_tangential_poss, + const bool arc_corrected, + const int tof_mash_factor) { if (span < 1) error("ProjDataInfoCTI: span %d has to be larger than 0\n", span); @@ -499,23 +503,27 @@ ProjDataInfo::ProjDataInfoCTI(const shared_ptr& scanner, num_axial_pos_per_segment, min_ring_difference, max_ring_difference, - num_views,num_tangential_poss); + num_views,num_tangential_poss, + tof_mash_factor); else return new ProjDataInfoCylindricalNoArcCorr(scanner, num_axial_pos_per_segment, min_ring_difference, max_ring_difference, - num_views,num_tangential_poss); + num_views,num_tangential_poss, + tof_mash_factor); } // KT 28/06/2001 added arc_corrected flag +// NE 28/12/2016 added the tof_mash_factor ProjDataInfo* ProjDataInfo::ProjDataInfoGE(const shared_ptr& scanner, - const int max_delta, - const int num_views, const int num_tangential_poss, - const bool arc_corrected) + const int max_delta, + const int num_views, const int num_tangential_poss, + const bool arc_corrected, + const int tof_mash_factor) { @@ -562,14 +570,16 @@ ProjDataInfo::ProjDataInfoGE(const shared_ptr& scanner, num_axial_pos_per_segment, min_ring_difference, max_ring_difference, - num_views,num_tangential_poss); + num_views,num_tangential_poss, + tof_mash_factor); else return new ProjDataInfoCylindricalNoArcCorr(scanner, num_axial_pos_per_segment, min_ring_difference, max_ring_difference, - num_views,num_tangential_poss); + num_views,num_tangential_poss, + tof_mash_factor); } @@ -589,6 +599,10 @@ ProjDataInfo* ProjDataInfo::ask_parameters() const int num_views = scanner_ptr->get_max_num_views()/ ask_num("Mash factor for views",1,16,1); + const int tof_mash_factor = scanner_ptr->is_tof_ready() ? + ask_num("Time-of-flight mash factor (1: No TOF):", 1, + scanner_ptr->get_num_max_of_timing_bins(), 1) : 1; + const bool arc_corrected = ask("Is the data arc-corrected?",true); @@ -615,8 +629,8 @@ ProjDataInfo* ProjDataInfo::ask_parameters() ProjDataInfo * pdi_ptr = span==0 - ? ProjDataInfoGE(scanner_ptr,max_delta,num_views,num_tangential_poss,arc_corrected) - : ProjDataInfoCTI(scanner_ptr,span,max_delta,num_views,num_tangential_poss,arc_corrected); + ? ProjDataInfoGE(scanner_ptr,max_delta,num_views,num_tangential_poss,arc_corrected, tof_mash_factor) + : ProjDataInfoCTI(scanner_ptr,span,max_delta,num_views,num_tangential_poss,arc_corrected, tof_mash_factor); cout << pdi_ptr->parameter_info() <& num_axial_pos_per_segment, const VectorWithOffset& min_ring_diff_v, const VectorWithOffset& max_ring_diff_v, - const int num_views,const int num_tangential_poss) + const int num_views,const int num_tangential_poss, + const int tof_mash_factor) :ProjDataInfoCylindrical(scanner_ptr, num_axial_pos_per_segment, min_ring_diff_v, max_ring_diff_v, num_views, num_tangential_poss), bin_size(bin_size_v) -{} +{ + // If tof_mash_factor == 1 then there is only tof bin ... effectively no TOF + if (tof_mash_factor > 1) + set_tof_mash_factor(tof_mash_factor); +} void diff --git a/src/buildblock/ProjDataInfoCylindricalNoArcCorr.cxx b/src/buildblock/ProjDataInfoCylindricalNoArcCorr.cxx index 3a0ee2c858..8018bfc901 100644 --- a/src/buildblock/ProjDataInfoCylindricalNoArcCorr.cxx +++ b/src/buildblock/ProjDataInfoCylindricalNoArcCorr.cxx @@ -61,7 +61,8 @@ ProjDataInfoCylindricalNoArcCorr(const shared_ptr scanner_ptr, const VectorWithOffset& num_axial_pos_per_segment, const VectorWithOffset& min_ring_diff_v, const VectorWithOffset& max_ring_diff_v, - const int num_views,const int num_tangential_poss) + const int num_views,const int num_tangential_poss, + const int tof_mash_factor) : ProjDataInfoCylindrical(scanner_ptr, num_axial_pos_per_segment, min_ring_diff_v, max_ring_diff_v, @@ -71,6 +72,9 @@ ProjDataInfoCylindricalNoArcCorr(const shared_ptr scanner_ptr, { uncompressed_view_tangpos_to_det1det2_initialised = false; det1det2_to_uncompressed_view_tangpos_initialised = false; + // If tof_mash_factor == 1 then there is only tof bin ... effectively no TOF + if (tof_mash_factor > 1) + set_tof_mash_factor(tof_mash_factor); //this->initialise_uncompressed_view_tangpos_to_det1det2(); //this->initialise_det1det2_to_uncompressed_view_tangpos(); } @@ -78,9 +82,10 @@ ProjDataInfoCylindricalNoArcCorr(const shared_ptr scanner_ptr, ProjDataInfoCylindricalNoArcCorr:: ProjDataInfoCylindricalNoArcCorr(const shared_ptr scanner_ptr, const VectorWithOffset& num_axial_pos_per_segment, - const VectorWithOffset& min_ring_diff_v, + const VectorWithOffset& min_ring_diff_v, const VectorWithOffset& max_ring_diff_v, - const int num_views,const int num_tangential_poss) + const int num_views, const int num_tangential_poss, + const int tof_mash_factor) : ProjDataInfoCylindrical(scanner_ptr, num_axial_pos_per_segment, min_ring_diff_v, max_ring_diff_v, @@ -91,6 +96,10 @@ ProjDataInfoCylindricalNoArcCorr(const shared_ptr scanner_ptr, angular_increment = static_cast(_PI/scanner_ptr->get_num_detectors_per_ring()); uncompressed_view_tangpos_to_det1det2_initialised = false; det1det2_to_uncompressed_view_tangpos_initialised = false; + + // If tof_mash_factor == 1 then there is only tof bin ... effectively no TOF + if (tof_mash_factor > 1) + set_tof_mash_factor(tof_mash_factor); //this->initialise_uncompressed_view_tangpos_to_det1det2(); //this->initialise_det1det2_to_uncompressed_view_tangpos(); } diff --git a/src/buildblock/Scanner.cxx b/src/buildblock/Scanner.cxx index 5d3713b890..4a440aaacc 100644 --- a/src/buildblock/Scanner.cxx +++ b/src/buildblock/Scanner.cxx @@ -1169,6 +1169,13 @@ Scanner::parameter_info() const s << "Reference energy (in keV) := " << get_reference_energy() << '\n'; } + if (is_tof_ready()) + { + s << "Number of TOF time bins :=" << get_num_max_of_timing_bins() << "\n"; + s << "Size of timing bin (ps) :=" << get_size_of_timing_bin() << "\n"; + s << "Timing resolution (ps) :=" << get_timing_resolution() << "\n"; + } + // block/bucket description s << "Number of blocks per bucket in transaxial direction := " << get_num_transaxial_blocks_per_bucket() << '\n' @@ -1237,8 +1244,9 @@ Scanner* Scanner::ask_parameters() if (scanner_ptr->type != Unknown_scanner && scanner_ptr->type != User_defined_scanner) { info("Two new options are available: (a) Energy Resolution and (b) Reference energy (in keV)." - "They are used in Scatter Simulation. In case, you need them, please set them" - "manually in your file."); + "They are used in Scatter Simulation. In case, you need them, please set them " + "manually in your file. More over, the creation of a Time-Of-Flight scanner with energy" + "information is not supported. You have to do it manually."); return scanner_ptr; } @@ -1284,6 +1292,13 @@ Scanner* Scanner::ask_parameters() int TransaxialCrystalsPerSinglesUnit = ask_num("Enter number of transaxial crystals per singles unit: ", 0, num_detectors_per_ring, 1); + int Num_TOF_bins = + ask_num("Number of TOF time bins :", 0.0f, 800.0f, 0.0f); + float Size_TOF_bin = + ask_num("Size of timing bin (ps) :", 0.0f, 100.0f, 0.0f); + float TOF_resolution = + ask_num("Timing resolution (ps) :", 0.0f, 1000.0f, 0.0f); + float EnergyResolution = ask_num("Enter the energy resolution of the scanner : ", 0.0f, 1000.0f, -1.0f); @@ -1294,6 +1309,21 @@ Scanner* Scanner::ask_parameters() ask_num("Enter number of detector layers per block: ",1,100,1); Type type = User_defined_scanner; + bool make_tof_scanner = false; + if (Num_TOF_bins > 0 && Size_TOF_bin > 0 && TOF_resolution > 0) + make_tof_scanner = true; + + Scanner* scanner_ptr = + new Scanner(type, string_list(name), + num_detectors_per_ring, NoRings, + NoBins, NoBins, + InnerRingRadius, AverageDepthOfInteraction, + RingSpacing, BinSize,intrTilt*float(_PI)/180, + AxialBlocksPerBucket,TransBlocksPerBucket, + AxialCrystalsPerBlock,TransaxialCrystalsPerBlock, + AxialCrstalsPerSinglesUnit, TransaxialCrystalsPerSinglesUnit, + num_detector_layers); + if (EnergyResolution > -1 && ReferenceEnergy > -1) Scanner* scanner_ptr = new Scanner(type, string_list(name), @@ -1308,16 +1338,27 @@ Scanner* Scanner::ask_parameters() EnergyResolution, ReferenceEnergy ); else - Scanner* scanner_ptr = - new Scanner(type, string_list(name), - num_detectors_per_ring, NoRings, - NoBins, NoBins, - InnerRingRadius, AverageDepthOfInteraction, - RingSpacing, BinSize,intrTilt*float(_PI)/180, - AxialBlocksPerBucket,TransBlocksPerBucket, - AxialCrystalsPerBlock,TransaxialCrystalsPerBlock, - AxialCrstalsPerSinglesUnit, TransaxialCrystalsPerSinglesUnit, - num_detector_layers); + Scanner* scanner_ptr = make_tof_scanner ? new Scanner(type, string_list(name), + num_detectors_per_ring, NoRings, + NoBins, NoBins, + InnerRingRadius, AverageDepthOfInteraction, + RingSpacing, BinSize,intrTilt*float(_PI)/180, + AxialBlocksPerBucket,TransBlocksPerBucket, + AxialCrystalsPerBlock,TransaxialCrystalsPerBlock, + AxialCrstalsPerSinglesUnit, TransaxialCrystalsPerSinglesUnit, + num_detector_layers, + Num_TOF_bins, + Size_TOF_bin, + TOF_resolution) : + new Scanner(type, string_list(name), + num_detectors_per_ring, NoRings, + NoBins, NoBins, + InnerRingRadius, AverageDepthOfInteraction, + RingSpacing, BinSize,intrTilt*float(_PI)/180, + AxialBlocksPerBucket,TransBlocksPerBucket, + AxialCrystalsPerBlock,TransaxialCrystalsPerBlock, + AxialCrstalsPerSinglesUnit, TransaxialCrystalsPerSinglesUnit, + num_detector_layers); if (scanner_ptr->check_consistency()==Succeeded::yes || !ask("Ask questions again?",true)) diff --git a/src/include/stir/Bin.inl b/src/include/stir/Bin.inl index 4f0e37bd20..9b3872d145 100644 --- a/src/include/stir/Bin.inl +++ b/src/include/stir/Bin.inl @@ -41,7 +41,7 @@ Bin::Bin():segment(0),view(0), Bin::Bin(int segment_num,int view_num, int axial_pos_num,int tangential_pos_num, float bin_value) :segment(segment_num),view(view_num), - axial_pos(axial_pos_num),tangential_pos(tangential_pos_num), bin_value(bin_value), timing_pos(0) + axial_pos(axial_pos_num),tangential_pos(tangential_pos_num), timing_pos(0), bin_value(bin_value) {} Bin::Bin(int segment_num,int view_num, int axial_pos_num,int tangential_pos_num) diff --git a/src/include/stir/IO/InterfileHeader.h b/src/include/stir/IO/InterfileHeader.h index 29998e32f7..b716a6dc80 100644 --- a/src/include/stir/IO/InterfileHeader.h +++ b/src/include/stir/IO/InterfileHeader.h @@ -196,6 +196,7 @@ class InterfilePDFSHeader : public InterfileHeader std::vector applied_corrections; // derived values + int num_timing_poss; int num_segments; int num_views; int num_bins; @@ -233,6 +234,12 @@ class InterfilePDFSHeader : public InterfileHeader float energy_resolution; //! Reference energy. float reference_energy; + + int max_num_timing_poss; + float size_of_timing_pos; + float timing_resolution; + + int tof_mash_factor; // end scanner parameters double effective_central_bin_size_in_cm; diff --git a/src/include/stir/ProjData.h b/src/include/stir/ProjData.h index 1bf1227f84..159a32cda3 100644 --- a/src/include/stir/ProjData.h +++ b/src/include/stir/ProjData.h @@ -183,11 +183,15 @@ class ProjData : public ExamData virtual SegmentByView get_segment_by_view(const int segment_num) const; //! Set segment by sinogram + //! N.E. Extended to have timging positions. virtual Succeeded - set_segment(const SegmentBySinogram&); + set_segment(const SegmentBySinogram&, + const int& timing_pos = 1); //! Set segment by view + //! N.E. Extended to have timing positions. virtual Succeeded - set_segment(const SegmentByView&); + set_segment(const SegmentByView&, + const int& timing_pos = 1); //! Get related viewgrams virtual RelatedViewgrams diff --git a/src/include/stir/ProjDataFromStream.h b/src/include/stir/ProjDataFromStream.h index 1e374194ba..a0adfd2453 100644 --- a/src/include/stir/ProjDataFromStream.h +++ b/src/include/stir/ProjDataFromStream.h @@ -61,7 +61,9 @@ class ProjDataFromStream : public ProjData public: enum StorageOrder { - Segment_AxialPos_View_TangPos, Segment_View_AxialPos_TangPos, + Segment_AxialPos_View_TangPos, + Segment_View_AxialPos_TangPos, + Timing_Segment_View_AxialPos_TangPos, Unsupported }; #if 0 static ProjDataFromStream* ask_parameters(const bool on_disk = true); @@ -87,20 +89,20 @@ class ProjDataFromStream : public ProjData StorageOrder o = Segment_View_AxialPos_TangPos, NumericType data_type = NumericType::FLOAT, ByteOrder byte_order = ByteOrder::native, - float scale_factor = 1 ); + float scale_factor = 1.f ); //! as above, but with a default value for segment_sequence_in_stream /*! The default value for segment_sequence_in_stream is a vector with values min_segment_num, min_segment_num+1, ..., max_segment_num */ ProjDataFromStream (shared_ptr const& exam_info_sptr, - shared_ptr const& proj_data_info_ptr, - shared_ptr const& s, - const std::streamoff offs = 0, - StorageOrder o = Segment_View_AxialPos_TangPos, - NumericType data_type = NumericType::FLOAT, - ByteOrder byte_order = ByteOrder::native, - float scale_factor = 1 ); + shared_ptr const& proj_data_info_ptr, + shared_ptr const& s, + const std::streamoff offs = 0, + StorageOrder o = Segment_View_AxialPos_TangPos, + NumericType data_type = NumericType::FLOAT, + ByteOrder byte_order = ByteOrder::native, + float scale_factor = 1.f); //! Obtain the storage order inline StorageOrder get_storage_order() const; @@ -116,6 +118,8 @@ class ProjDataFromStream : public ProjData //! Get the segment sequence inline std::vector get_segment_sequence_in_stream() const; + //! Get the timing bins sequence + inline std::vector get_timing_poss_sequence_in_stream() const; //! Get & set viewgram Viewgram get_viewgram(const int view_num, const int segment_num,const bool make_num_tangential_poss_odd=false) const; @@ -153,6 +157,8 @@ class ProjDataFromStream : public ProjData //!the order in which the segments occur in the stream std::vector segment_sequence; + //!the order in which the timing bins occur in the stream + std::vector timing_poss_sequence; inline int find_segment_index_in_sequence(const int segment_num) const; diff --git a/src/include/stir/ProjDataFromStream.inl b/src/include/stir/ProjDataFromStream.inl index 83791557f8..93c6aa9e96 100644 --- a/src/include/stir/ProjDataFromStream.inl +++ b/src/include/stir/ProjDataFromStream.inl @@ -73,6 +73,10 @@ std::vector ProjDataFromStream::get_segment_sequence_in_stream() const { return segment_sequence; } +std::vector +ProjDataFromStream::get_timing_poss_sequence_in_stream() const +{ return timing_poss_sequence; } + #if 0 // this does not make a lot of sense. How to compare files etc. ? bool diff --git a/src/include/stir/ProjDataInfo.h b/src/include/stir/ProjDataInfo.h index 43f64c3171..0d1b9522fb 100644 --- a/src/include/stir/ProjDataInfo.h +++ b/src/include/stir/ProjDataInfo.h @@ -72,21 +72,25 @@ class ProjDataInfo ask_parameters(); //! Construct a ProjDataInfo suitable for GE Advance data + //! \warning N.E: TOF mash factor, means no TOF static ProjDataInfo* - ProjDataInfoGE(const shared_ptr& scanner_ptr, - const int max_delta, - const int num_views, const int num_tangential_poss, - const bool arc_corrected = true); + ProjDataInfoGE(const shared_ptr& scanner_ptr, + const int max_delta, + const int num_views, const int num_tangential_poss, + const bool arc_corrected = true, + const int tof_mash_factor = 1); //! Construct a ProjDataInfo suitable for CTI data /*! \c span is used to denote the amount of axial compression (see CTI doc). It has to be an odd number. */ + //! \warning N.E: TOF mash factor, means no TOF static ProjDataInfo* - ProjDataInfoCTI(const shared_ptr& scanner_ptr, - const int span, const int max_delta, - const int num_views, const int num_tangential_poss, - const bool arc_corrected = true); + ProjDataInfoCTI(const shared_ptr& scanner_ptr, + const int span, const int max_delta, + const int num_views, const int num_tangential_poss, + const bool arc_corrected = true, + const int tof_mash_factor = 1); /************ constructors ***********/ diff --git a/src/include/stir/ProjDataInfoCylindricalArcCorr.h b/src/include/stir/ProjDataInfoCylindricalArcCorr.h index 2375f75461..7c6bfc3fdf 100644 --- a/src/include/stir/ProjDataInfoCylindricalArcCorr.h +++ b/src/include/stir/ProjDataInfoCylindricalArcCorr.h @@ -62,7 +62,8 @@ class ProjDataInfoCylindricalArcCorr : public ProjDataInfoCylindrical const VectorWithOffset& num_axial_pos_per_segment, const VectorWithOffset& min_ring_diff_v, const VectorWithOffset& max_ring_diff_v, - const int num_views,const int num_tangential_poss); + const int num_views,const int num_tangential_poss, + const int tof_mash_factor = 1); ProjDataInfo* clone() const; diff --git a/src/include/stir/ProjDataInfoCylindricalNoArcCorr.h b/src/include/stir/ProjDataInfoCylindricalNoArcCorr.h index ce11e41793..4137d4d8d5 100644 --- a/src/include/stir/ProjDataInfoCylindricalNoArcCorr.h +++ b/src/include/stir/ProjDataInfoCylindricalNoArcCorr.h @@ -108,7 +108,8 @@ class ProjDataInfoCylindricalNoArcCorr : public ProjDataInfoCylindrical const VectorWithOffset& num_axial_pos_per_segment, const VectorWithOffset& min_ring_diff_v, const VectorWithOffset& max_ring_diff_v, - const int num_views,const int num_tangential_poss); + const int num_views,const int num_tangential_poss, + const int tof_mash_factor = 1); //! Constructor which gets \a ring_radius and \a angular_increment from the scanner /*! \a angular_increment is determined as Pi divided by the number of detectors in a ring. @@ -117,7 +118,8 @@ class ProjDataInfoCylindricalNoArcCorr : public ProjDataInfoCylindrical const VectorWithOffset& num_axial_pos_per_segment, const VectorWithOffset& min_ring_diff_v, const VectorWithOffset& max_ring_diff_v, - const int num_views,const int num_tangential_poss); + const int num_views,const int num_tangential_poss, + const int tof_mash_factor = 1); ProjDataInfo* clone() const; diff --git a/src/include/stir/listmode/LmToProjData.h b/src/include/stir/listmode/LmToProjData.h index 96b92ed210..7a97ea2c2e 100644 --- a/src/include/stir/listmode/LmToProjData.h +++ b/src/include/stir/listmode/LmToProjData.h @@ -174,10 +174,16 @@ class LmToProjData : public ParsingObject LmToProjData(); //! This function does the actual work + //! N.E: In order to keep the ToF functions separate from the non-TOF + //! STIR this function just call the appropriate actual_process_data_with(out)_tof(). virtual void process_data(); protected: + //! This function does the non-TOF work + virtual void actual_process_data_without_tof(); + //! This function does the TOF work + virtual void actual_process_data_with_tof(); //! will be called when a new time frame starts /*! The frame numbers start from 1. */ @@ -192,7 +198,7 @@ class LmToProjData : public ParsingObject (on top of anything done by normalisation_ptr). \todo Would need timing info or so for e.g. time dependent normalisation or angle info for a rotating scanner.*/ - virtual void get_bin_from_event(Bin& bin, const CListEvent&) const; + virtual void get_bin_from_record(Bin& bin, const CListRecord&) const; //! A function that should return the number of uncompressed bins in the current bin /*! \todo it is not compatiable with e.g. HiDAC doesn't belong here anyway @@ -224,6 +230,8 @@ class LmToProjData : public ParsingObject bool do_pre_normalisation; bool store_prompts; bool store_delayeds; + //! Use TOF information + bool use_tof; int num_segments_in_memory; // TODO make long (or even unsigned long) but can't do this yet because we can't parse longs yet unsigned long int num_events_to_store; diff --git a/src/include/stir/listmode/LmToProjDataBootstrap.h b/src/include/stir/listmode/LmToProjDataBootstrap.h index 4d98a813eb..ea7cd8c78c 100644 --- a/src/include/stir/listmode/LmToProjDataBootstrap.h +++ b/src/include/stir/listmode/LmToProjDataBootstrap.h @@ -87,7 +87,7 @@ class LmToProjDataBootstrap : public LmToProjDataT /*! Initialises a vector with the number of times each event has to be replicated */ virtual void start_new_time_frame(const unsigned int new_frame_num); - virtual void get_bin_from_event(Bin& bin, const CListEvent&) const; + virtual void get_bin_from_record(Bin& bin, const CListRecord&) const; // \name parsing variables diff --git a/src/listmode_buildblock/LmToProjData.cxx b/src/listmode_buildblock/LmToProjData.cxx index 2113b26737..73f412020f 100644 --- a/src/listmode_buildblock/LmToProjData.cxx +++ b/src/listmode_buildblock/LmToProjData.cxx @@ -129,10 +129,10 @@ allocate_segments(VectorWithOffset& segments, */ static void save_and_delete_segments(shared_ptr& output, - VectorWithOffset& segments, - const int start_segment_index, - const int end_segment_index, - ProjData& proj_data); + VectorWithOffset& segments, + const int start_segment_index, + const int end_segment_index, + ProjData& proj_data, const int timing_pos = 1); static shared_ptr construct_proj_data(shared_ptr& output, @@ -156,7 +156,7 @@ set_defaults() post_normalisation_ptr.reset(new TrivialBinNormalisation); do_pre_normalisation =0; num_events_to_store = 0; - + use_tof = false; } void @@ -379,15 +379,16 @@ LmToProjData(const char * const par_filename) Here follows the implementation of get_bin_from_event this function is complicated because of the normalisation stuff. sorry + N.E: Get_bin_from_event became Get_bin_from_record ***************************************************************/ void LmToProjData:: -get_bin_from_event(Bin& bin, const CListEvent& event) const +get_bin_from_record(Bin& bin, const CListRecord& record) const { if (do_pre_normalisation) { Bin uncompressed_bin; - event.get_bin(uncompressed_bin, *proj_data_info_cyl_uncompressed_ptr); + record.event().get_bin(uncompressed_bin, *proj_data_info_cyl_uncompressed_ptr); if (uncompressed_bin.get_bin_value()<=0) return; // rejected for some strange reason @@ -410,7 +411,7 @@ get_bin_from_event(Bin& bin, const CListEvent& event) const bin_efficiency, uncompressed_bin.segment_num(), uncompressed_bin.view_num(), uncompressed_bin.axial_pos_num(), uncompressed_bin.tangential_pos_num()); - bin.set_bin_value(-1); + bin.set_bin_value(-1.f); return; } @@ -418,20 +419,22 @@ get_bin_from_event(Bin& bin, const CListEvent& event) const // Also, adjust the normalisation factor according to the number of // uncompressed bins in a compressed bin - const float bin_value = 1/bin_efficiency; + const float bin_value = 1.f/bin_efficiency; // TODO wasteful: we decode the event twice. replace by something like // template_proj_data_info_ptr->get_bin_from_uncompressed(bin, uncompressed_bin); - event.get_bin(bin, *template_proj_data_info_ptr); + - if (bin.get_bin_value()>0) - { - bin.set_bin_value(bin_value); - } + if (use_tof) + record.full_event(bin, *template_proj_data_info_ptr); + else + record.event().get_bin(bin, *template_proj_data_info_ptr); + + bin.set_bin_value(bin_value); } else { - event.get_bin(bin, *template_proj_data_info_ptr); + record.event().get_bin(bin, *template_proj_data_info_ptr); } } @@ -510,9 +513,29 @@ start_new_time_frame(const unsigned int) It's essentially simple, but is in fact complicated because of the facility to store only part of the segments in memory. ***************************************************************/ + void LmToProjData:: process_data() +{ + assert(!is_null_ptr(template_proj_data_info_ptr)); + + if (template_proj_data_info_ptr->get_num_tof_poss() > 1) + { + use_tof = true; + actual_process_data_with_tof(); + } + else + { + use_tof = false; + actual_process_data_without_tof(); + } +} + + +void +LmToProjData:: +actual_process_data_without_tof() { CPUTimer timer; timer.start(); @@ -643,7 +666,7 @@ process_data() // set value in case the event decoder doesn't touch it // otherwise it would be 0 and all events will be ignored bin.set_bin_value(1); - get_bin_from_event(bin, record.event()); + get_bin_from_record(bin, record); // check if it's inside the range we want to store if (bin.get_bin_value()>0 @@ -727,6 +750,236 @@ process_data() } +void +LmToProjData:: +actual_process_data_with_tof() +{ + CPUTimer timer; + timer.start(); + + // assume list mode data starts at time 0 + // we have to do this because the first time tag might occur only after a + // few coincidence events (as happens with ECAT scanners) + current_time = 0; + + double time_of_last_stored_event = 0; + long num_stored_events = 0; + VectorWithOffset + segments (template_proj_data_info_ptr->get_min_segment_num(), + template_proj_data_info_ptr->get_max_segment_num()); + + VectorWithOffset + frame_start_positions(1, static_cast(frame_defs.get_num_frames())); + shared_ptr record_sptr = lm_data_ptr->get_empty_record_sptr(); + CListRecord& record = *record_sptr; + + /* Here starts the main loop which will store the listmode data. */ + for (current_frame_num = 1; + current_frame_num<=frame_defs.get_num_frames(); + ++current_frame_num) + { + start_new_time_frame(current_frame_num); + + // construct ExamInfo appropriate for a single projdata with this time frame + ExamInfo this_frame_exam_info(*lm_data_ptr->get_exam_info_ptr()); + { + TimeFrameDefinitions this_time_frame_defs(frame_defs, current_frame_num); + this_frame_exam_info.set_time_frame_definitions(this_time_frame_defs); + } + + // *********** open output file + shared_ptr output; + shared_ptr proj_data_ptr; + + { + char rest[50]; + sprintf(rest, "_f%dg1d0b0", current_frame_num); + const string output_filename = output_filename_prefix + rest; + + proj_data_ptr = + construct_proj_data(output, output_filename, this_frame_exam_info, template_proj_data_info_ptr); + } + + long num_prompts_in_frame = 0; + long num_delayeds_in_frame = 0; + + const double start_time = frame_defs.get_start_time(current_frame_num); + const double end_time = frame_defs.get_end_time(current_frame_num); + + for (int current_timing_pos_index = proj_data_ptr->get_min_timing_pos_num(); + current_timing_pos_index <= proj_data_ptr->get_max_timing_pos_num(); + current_timing_pos_index += 1) + { + /* + For each start_segment_index, we check which events occur in the + segments between start_segment_index and + start_segment_index+num_segments_in_memory. + */ + for (int start_segment_index = proj_data_ptr->get_min_segment_num(); + start_segment_index <= proj_data_ptr->get_max_segment_num(); + start_segment_index += num_segments_in_memory) + { + + const int end_segment_index = + min( proj_data_ptr->get_max_segment_num()+1, start_segment_index + num_segments_in_memory) - 1; + + if (!interactive) + allocate_segments(segments, start_segment_index, end_segment_index, proj_data_ptr->get_proj_data_info_ptr()); + + // the next variable is used to see if there are more events to store for the current segments + // num_events_to_store-more_events will be the number of allowed coincidence events currently seen in the file + // ('allowed' independent on the fact of we have its segment in memory or not) + // When do_time_frame=true, the number of events is irrelevant, so we + // just set more_events to 1, and never change it + unsigned long int more_events = + do_time_frame? 1 : num_events_to_store; + + if (start_segment_index != proj_data_ptr->get_min_segment_num()) + { + // we're going once more through the data (for the next batch of segments) + cerr << "\nProcessing next batch of segments\n"; + // go to the beginning of the listmode data for this frame + lm_data_ptr->set_get_position(frame_start_positions[current_frame_num]); + current_time = start_time; + } + else + { + cerr << "\nProcessing time frame " << current_frame_num << '\n'; + + // Note: we already have current_time from previous frame, so don't + // need to set it. In fact, setting it to start_time would be wrong + // as we first might have to skip some events before we get to start_time. + // So, let's do that now. + while (current_time < start_time && + lm_data_ptr->get_next_record(record) == Succeeded::yes) + { + if (record.is_time()) + current_time = record.time().get_time_in_secs(); + } + // now save position such that we can go back + frame_start_positions[current_frame_num] = + lm_data_ptr->save_get_position(); + } + { + // loop over all events in the listmode file + while (more_events) + { + if (lm_data_ptr->get_next_record(record) == Succeeded::no) + { + // no more events in file for some reason + break; //get out of while loop + } + if (record.is_time() && end_time > 0.01) // Direct comparison within doubles is unsafe. + { + current_time = record.time().get_time_in_secs(); + if (do_time_frame && current_time >= end_time) + break; // get out of while loop + assert(current_time>=start_time); + process_new_time_event(record.time()); + } + // note: could do "else if" here if we would be sure that + // a record can never be both timing and coincidence event + // and there might be a scanner around that has them both combined. + if (record.is_event()) + { + assert(start_time <= current_time); + Bin bin; + // set value in case the event decoder doesn't touch it + // otherwise it would be 0 and all events will be ignored + bin.set_bin_value(1.f); + + get_bin_from_record(bin, record); + + // check if it's inside the range we want to store + if (bin.get_bin_value()>0 + && bin.tangential_pos_num()>= proj_data_ptr->get_min_tangential_pos_num() + && bin.tangential_pos_num()<= proj_data_ptr->get_max_tangential_pos_num() + && bin.axial_pos_num()>=proj_data_ptr->get_min_axial_pos_num(bin.segment_num()) + && bin.axial_pos_num()<=proj_data_ptr->get_max_axial_pos_num(bin.segment_num()) + && bin.timing_pos_num()>=proj_data_ptr->get_min_timing_pos_num() + && bin.timing_pos_num()<=proj_data_ptr->get_max_timing_pos_num() + ) + { + assert(bin.view_num()>=proj_data_ptr->get_min_view_num()); + assert(bin.view_num()<=proj_data_ptr->get_max_view_num()); + + // see if we increment or decrement the value in the sinogram + const int event_increment = + record.event().is_prompt() + ? ( store_prompts ? 1 : 0 ) // it's a prompt + : delayed_increment;//it is a delayed-coincidence event + + if (event_increment==0) + continue; + + if (!do_time_frame) + more_events -= event_increment; + + if (bin.timing_pos_num() == current_timing_pos_index) + { + // now check if we have its segment in memory + if (bin.segment_num() >= start_segment_index && bin.segment_num()<=end_segment_index) + { + do_post_normalisation(bin); + + num_stored_events += event_increment; + if (record.event().is_prompt()) + ++num_prompts_in_frame; + else + ++num_delayeds_in_frame; + + if (num_stored_events%500000L==0) cout << "\r" << num_stored_events << " events stored" << flush; + + if (interactive) + printf("TOFbin %4d Seg %4d view %4d ax_pos %4d tang_pos %4d time %8g stored with incr %d \n", + bin.timing_pos_num(),bin.segment_num(), + bin.view_num(), bin.axial_pos_num(), bin.tangential_pos_num(), + current_time, event_increment); + else + (*segments[bin.segment_num()])[bin.view_num()][bin.axial_pos_num()][bin.tangential_pos_num()] += + bin.get_bin_value() * + event_increment; + } + } + } + else // event is rejected for some reason + { + if (interactive) + printf("TOFbin %4d Seg %4d view %4d ax_pos %4d tang_pos %4d time %8g ignored\n", + bin.timing_pos_num(), bin.segment_num(), bin.view_num(), + bin.axial_pos_num(), bin.tangential_pos_num(), current_time); + } + } // end of spatial event processing + } // end of while loop over all events + + time_of_last_stored_event = + max(time_of_last_stored_event,current_time); + } + + if (!interactive) + save_and_delete_segments(output, segments, + start_segment_index, end_segment_index, + *proj_data_ptr, current_timing_pos_index); + } // end of for loop for segment range + + } // end of for loop for timing positions + cerr << "\nNumber of prompts stored in this time period : " << num_prompts_in_frame + << "\nNumber of delayeds stored in this time period: " << num_delayeds_in_frame + << '\n'; + } // end of loop over frames + + timer.stop(); + + cerr << "Last stored event was recorded before time-tick at " << time_of_last_stored_event << " secs\n"; + if (!do_time_frame && + (num_stored_events<=0 || + /*static_cast*/(num_stored_events)& output, VectorWithOffset& segments, const int start_segment_index, const int end_segment_index, - ProjData& proj_data) + ProjData& proj_data, + const int timing_pos) { for (int seg=start_segment_index; seg<=end_segment_index; seg++) { { #ifdef USE_SegmentByView - proj_data.set_segment(*segments[seg]); + proj_data.set_segment(*segments[seg], timing_pos); #else (*segments[seg]).write_data(*output); #endif @@ -786,13 +1040,20 @@ construct_proj_data(shared_ptr& output, const shared_ptr& proj_data_info_ptr) { shared_ptr exam_info_sptr(new ExamInfo(exam_info)); - + shared_ptr proj_data_sptr; #ifdef USE_SegmentByView // don't need output stream in this case - shared_ptr proj_data_sptr(new ProjDataInterfile(exam_info_sptr, - proj_data_info_ptr, output_filename, ios::out, - ProjDataFromStream::Segment_View_AxialPos_TangPos, - OUTPUTNumericType)); + if (proj_data_info_ptr->get_tof_mash_factor() == 1) + proj_data_sptr.reset(new ProjDataInterfile(exam_info_sptr, + proj_data_info_ptr, output_filename, ios::out, + ProjDataFromStream::Segment_View_AxialPos_TangPos, + OUTPUTNumericType)); + else + proj_data_sptr.reset(new ProjDataInterfile(exam_info_sptr, + proj_data_info_ptr, output_filename, ios::out, + ProjDataFromStream::Timing_Segment_View_AxialPos_TangPos, + OUTPUTNumericType)); + return proj_data_sptr; #else // this code would work for USE_SegmentByView as well, but the above is far simpler... diff --git a/src/listmode_buildblock/LmToProjDataBootstrap.cxx b/src/listmode_buildblock/LmToProjDataBootstrap.cxx index d67de6db04..6327f8b193 100644 --- a/src/listmode_buildblock/LmToProjDataBootstrap.cxx +++ b/src/listmode_buildblock/LmToProjDataBootstrap.cxx @@ -166,7 +166,7 @@ start_new_time_frame(const unsigned int new_frame_num) // set value in case the event decoder doesn't touch it // otherwise it would be 0 and all events will be ignored bin.set_bin_value(1); - base_type::get_bin_from_event(bin, record.event()); + base_type::get_bin_from_record(bin, record); // check if it's inside the range we want to store if (bin.get_bin_value()>0 && bin.tangential_pos_num()>= this->template_proj_data_info_ptr->get_min_tangential_pos_num() @@ -232,12 +232,12 @@ start_new_time_frame(const unsigned int new_frame_num) template void LmToProjDataBootstrap:: -get_bin_from_event(Bin& bin, const CListEvent& event) const +get_bin_from_record(Bin& bin, const CListRecord& record) const { assert(num_times_to_replicate_iter != num_times_to_replicate.end()); if (*num_times_to_replicate_iter > 0) { - base_type::get_bin_from_event(bin, event); + base_type::get_bin_from_record(bin, record); bin.set_bin_value(bin.get_bin_value() * *num_times_to_replicate_iter); } else diff --git a/src/listmode_utilities/lm_to_projdata.cxx b/src/listmode_utilities/lm_to_projdata.cxx index 6c81789320..9fa37abaf0 100644 --- a/src/listmode_utilities/lm_to_projdata.cxx +++ b/src/listmode_utilities/lm_to_projdata.cxx @@ -50,7 +50,7 @@ int main(int argc, char * argv[]) "Run "<use_tof) { - // proj_data_no_arc_ptr->get_LOR_as_two_points(lor_point_1, lor_point_2, measured_bin); lor_points = record.event().get_LOR(); this->PM_sptr->get_proj_matrix_elems_for_one_bin_with_tof(proj_matrix_row, measured_bin, diff --git a/src/test/test_proj_data_info.cxx b/src/test/test_proj_data_info.cxx index 64c5f47f20..46ebe73813 100644 --- a/src/test/test_proj_data_info.cxx +++ b/src/test/test_proj_data_info.cxx @@ -143,7 +143,7 @@ test_generic_proj_data_info(ProjDataInfo& proj_data_info) tangential_pos_num<=proj_data_info.get_max_tangential_pos_num()-1; tangential_pos_num+=1) { - const Bin org_bin(segment_num,view_num,axial_pos_num,tangential_pos_num, /* value*/1); + const Bin org_bin(segment_num,view_num,axial_pos_num,tangential_pos_num, /* value*/1.f); LORInAxialAndNoArcCorrSinogramCoordinates lor; proj_data_info.get_LOR(lor, org_bin); { @@ -599,10 +599,10 @@ run_tests() cerr << "\nTests with proj_data_info with mashing and axial compression\n\n"; proj_data_info_ptr.reset( ProjDataInfo::ProjDataInfoCTI(scanner_ptr, - /*span*/5, scanner_ptr->get_num_rings()-1, - /*views*/ scanner_ptr->get_num_detectors_per_ring()/2/8, - /*tang_pos*/64, - /*arc_corrected*/ false)); + /*span*/5, scanner_ptr->get_num_rings()-1, + /*views*/ scanner_ptr->get_num_detectors_per_ring()/2/8, + /*tang_pos*/64, + /*arc_corrected*/ false)); test_proj_data_info(dynamic_cast(*proj_data_info_ptr)); } @@ -724,7 +724,7 @@ test_proj_data_info(ProjDataInfoCylindricalNoArcCorr& proj_data_info) // or an LOR parallel to the scanner axis if (det_pos_pair.pos1().tangential_coord() == det_pos_pair.pos2().tangential_coord()) continue; - Bin bin; + Bin bin(0,0,0,0,0,0.0f); DetectionPositionPair<> new_det_pos_pair; const bool there_is_a_bin = proj_data_info.get_bin_for_det_pos_pair(bin, det_pos_pair) == @@ -754,9 +754,9 @@ test_proj_data_info(ProjDataInfoCylindricalNoArcCorr& proj_data_info) cerr << "\n\tTest code for bin -> detector,ring and back conversions. (This might take a while...)"; { - Bin bin; + Bin bin(0,0,0,0,0,0.0f); // set value for comparison later on - bin.set_bin_value(0); + bin.set_bin_value(0.f); for (bin.segment_num() = max(-5,proj_data_info.get_min_segment_num()); bin.segment_num() <= min(5,proj_data_info.get_max_segment_num()); ++bin.segment_num()) @@ -776,7 +776,7 @@ test_proj_data_info(ProjDataInfoCylindricalNoArcCorr& proj_data_info) { // set from for-loop variable bin.tangential_pos_num() = tangential_pos_num; - Bin new_bin; + Bin new_bin(0,0,0,0,0,0.0f); // set value for comparison with bin new_bin.set_bin_value(0); DetectionPositionPair<> det_pos_pair; @@ -809,7 +809,7 @@ test_proj_data_info(ProjDataInfoCylindricalNoArcCorr& proj_data_info) { cerr << "\n\tTest code for bins <-> detectors routines that work with any mashing and axial compression"; - Bin bin; + Bin bin(0,0,0,0,0,0.0f); // set value for comparison later on bin.set_bin_value(0); std::vector > det_pos_pairs; @@ -830,7 +830,7 @@ test_proj_data_info(ProjDataInfoCylindricalNoArcCorr& proj_data_info) ++bin.tangential_pos_num()) { proj_data_info.get_all_det_pos_pairs_for_bin(det_pos_pairs, bin); - Bin new_bin; + Bin new_bin(0,0,0,0,0,0.0f); // set value for comparison with bin new_bin.set_bin_value(0); for (std::vector >::const_iterator det_pos_pair_iter = det_pos_pairs.begin(); diff --git a/src/utilities/create_projdata_template.cxx b/src/utilities/create_projdata_template.cxx index 0364d13682..e33eb399db 100644 --- a/src/utilities/create_projdata_template.cxx +++ b/src/utilities/create_projdata_template.cxx @@ -56,13 +56,18 @@ int main(int argc, char *argv[]) } - shared_ptr proj_data_info_ptr(ProjDataInfo::ask_parameters()); + shared_ptr proj_data_info_sptr(ProjDataInfo::ask_parameters()); const std::string output_file_name = argv[1]; shared_ptr exam_info_sptr(new ExamInfo); // TODO, Currently all stir::Scanner types are PET. exam_info_sptr->imaging_modality = ImagingModality::PT; - shared_ptr proj_data_ptr(new ProjDataInterfile(exam_info_sptr, proj_data_info_ptr, output_file_name)); + // If TOF activated -- No mashing factor will produce surrealistic sinograms + if ( proj_data_info_sptr->get_tof_mash_factor() >1) + shared_ptr proj_data_sptr(new ProjDataInterfile(exam_info_sptr, proj_data_info_sptr, output_file_name, std::ios::out, + ProjDataFromStream::Timing_Segment_View_AxialPos_TangPos)); + else + shared_ptr proj_data_sptr(new ProjDataInterfile(exam_info_sptr, proj_data_info_sptr, output_file_name)); return EXIT_SUCCESS; } From fe31466b608d375117ee9b1b9799deee13935481 Mon Sep 17 00:00:00 2001 From: Nikos Efthimiou Date: Mon, 6 Feb 2017 09:05:52 +0000 Subject: [PATCH 051/509] LmToProjData is TOF compatible list_projdata_info gives information about each tof bin And there is a test running to validate that the unlisting is performed properly. --- recon_test_pack/root_header.hroot | 5 + recon_test_pack/run_test_time_of_fligh.sh | 106 +++ src/buildblock/ProjData.cxx | 14 +- src/buildblock/ProjDataFromStream.cxx | 678 ++++++++++++------ src/buildblock/ProjDataGEAdvance.cxx | 9 +- src/buildblock/ProjDataInfo.cxx | 6 +- src/buildblock/Scanner.cxx | 117 +-- src/include/stir/Bin.h | 11 +- src/include/stir/IO/InputStreamFromROOTFile.h | 22 +- ...InputStreamFromROOTFileForCylindricalPET.h | 1 + src/include/stir/ProjData.h | 26 +- src/include/stir/ProjDataFromStream.h | 44 +- src/include/stir/ProjDataGEAdvance.h | 10 +- src/include/stir/ProjDataInfo.h | 10 +- src/include/stir/Scanner.h | 4 +- src/include/stir/listmode/CListRecordROOT.h | 22 +- src/include/stir/listmode/LmToProjData.h | 10 + .../stir/recon_buildblock/ProjMatrixByBin.inl | 5 +- src/listmode_buildblock/CListRecordROOT.cxx | 14 +- src/listmode_buildblock/LmToProjData.cxx | 516 +++++++------ src/listmode_utilities/lm_to_projdata.cxx | 13 + src/test/test_proj_data_in_memory.cxx | 52 +- src/test/test_proj_data_info.cxx | 2 + src/test/test_time_of_flight.cxx | 80 +-- src/utilities/list_projdata_info.cxx | 76 +- 25 files changed, 1110 insertions(+), 743 deletions(-) create mode 100755 recon_test_pack/run_test_time_of_fligh.sh diff --git a/recon_test_pack/root_header.hroot b/recon_test_pack/root_header.hroot index 3bada70ad8..f8af3f7559 100644 --- a/recon_test_pack/root_header.hroot +++ b/recon_test_pack/root_header.hroot @@ -8,6 +8,11 @@ Average depth of interaction (cm) := 0.7 Distance between rings (cm) := 0.40625 Default bin size (cm) := 0.208626 Maximum number of non-arc-corrected bins := 344 +Number of TOF time bins := 410 +Size of timing bin (in picoseconds) := 10.00 +Timing resolution (in picoseconds) := 400.0 + +%TOF mashing factor:= 82 GATE scanner type := GATE_Cylindrical_PET GATE_Cylindrical_PET Parameters := diff --git a/recon_test_pack/run_test_time_of_fligh.sh b/recon_test_pack/run_test_time_of_fligh.sh new file mode 100755 index 0000000000..9d2d5ecd4d --- /dev/null +++ b/recon_test_pack/run_test_time_of_fligh.sh @@ -0,0 +1,106 @@ +#! /bin/sh +# A script to check to see if Time Of Flight data are binned and used properly +# +# Copyright (C) 2016, University of Leeds +# Copyright (C) 2017, University of Hull +# 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 +# +# Author Nikos Efthimiou +# + +# Scripts should exit with error code when a test fails: +if [ -n "$TRAVIS" ]; then + # The code runs inside Travis + set -e +fi + +echo This script should work with STIR version '>'3.0. If you have +echo a later version, you might have to update your test pack. +echo Please check the web site. +echo + +if [ $# -eq 1 ]; then + echo "Prepending $1 to your PATH for the duration of this script." + PATH=$1:$PATH +fi + +# first need to set this to the C locale, as this is what the STIR utilities use +# otherwise, awk might interpret floating point numbers incorrectly +LC_ALL=C +export LC_ALL + +echo "=== create template sinogram. We'll use a test_scanner which is small and +has TOF info" +template_sino=my_test_scanner_template.hs +cat > my_input.txt < my_create_${template_sino}.log 2>&1 +if [ $? -ne 0 ]; then + echo "ERROR running create_projdata_template. Check my_create_${template_sino}.log"; exit 1; +fi + +export INPUT_ROOT_FILE=test_PET_GATE.root +export EXCLUDE_RANDOM=1 +export EXCLUDE_SCATTERED=1 + +INPUT=root_header.hroot TEMPLATE=$template_sino OUT_PROJDATA_FILE=my_tof_sinogram lm_to_projdata --test_timing_positions lm_to_projdata.par > my_write_TOF_values_${template_sino}.log 2>&1 + +if [ $? -ne 0 ]; then + echo "ERROR running lm_to_projdata --test_timing_positions. Check my_write_TOF_values_${template_sino}.log"; exit 1; +fi + +echo "Comparing values in TOF sinogram ..." +list_projdata_info --all my_tof_sinogram_f179g1d0b0.hs > my_sino_values_$template_sino.log 2>&1 +if [ $? -ne 0 ]; then + echo "ERROR running list_projdata_info. Check my_sino_values_$template_sino.log"; + exit 1; +fi + + +TOF_bins=$(grep 'Total number of timing positions' my_sino_values_$template_sino.log | awk -F ':' '{ print $2 }') +echo "Total number of TOF bins:" $TOF_bins + +Timming_Locations=$(grep 'Timing location' my_sino_values_$template_sino.log | awk -F ':' '{ print $2 }') +echo "Timming_Locations:" $Timming_Locations + +Data_mins=$(grep 'Data min' my_sino_values_$template_sino.log | awk -F ':' '{ print $2 }') +echo "Data mins:" $Data_mins + +Data_maxs=$(grep 'Data max' my_sino_values_$template_sino.log | awk -F ':' '{ print $2 }') +echo "Data maxs:" $Data_maxs + +for i in $(seq 5) +do + if [ $(( $(($(($i-1)) - $((TOF_bins/2)))) - $((Data_mins[$i])))) -ne 0 ]; then + echo "Wrong values in TOF sinogram. Error. $(( $(($(($i-1)) - $((TOF_bins/2)))) - $((Data_mins[$i]))))" + exit 1 + fi +done + + +echo +echo '--------------- End of Time-Of-Flight tests -------------' +echo +echo "Everything seems to be fine !" +echo 'You could remove all generated files using "rm -f my_* *.log"' +exit 0 + diff --git a/src/buildblock/ProjData.cxx b/src/buildblock/ProjData.cxx index 416a62804e..c5b1e77e16 100644 --- a/src/buildblock/ProjData.cxx +++ b/src/buildblock/ProjData.cxx @@ -3,6 +3,7 @@ Copyright (C) 2000 - 2010-10-15, Hammersmith Imanet Ltd Copyright (C) 2011-07-01 -2013, Kris Thielemans Copyright (C) 2015, University College London + Copyright (C) 2016, University of Hull This file is part of STIR. This file is free software; you can redistribute it and/or modify @@ -23,6 +24,7 @@ \brief Implementations for non-inline functions of class stir::ProjData + \author Nikos Efthimiou \author Kris Thielemans \author PARAPET project */ @@ -330,24 +332,24 @@ ProjData::set_related_viewgrams( const RelatedViewgrams& viewgrams) } #endif -SegmentBySinogram ProjData::get_segment_by_sinogram(const int segment_num) const +SegmentBySinogram ProjData::get_segment_by_sinogram(const int segment_num, const int timing_num) const { SegmentBySinogram segment = proj_data_info_ptr->get_empty_segment_by_sinogram(segment_num,false); // TODO optimise to get shared proj_data_info_ptr for (int view_num = get_min_view_num(); view_num <= get_max_view_num(); ++view_num) - segment.set_viewgram(get_viewgram(view_num, segment_num, false)); + segment.set_viewgram(get_viewgram(view_num, segment_num, false, timing_num)); return segment; } -SegmentByView ProjData::get_segment_by_view(const int segment_num) const +SegmentByView ProjData::get_segment_by_view(const int segment_num, const int timing_num) const { SegmentByView segment = proj_data_info_ptr->get_empty_segment_by_view(segment_num,false); // TODO optimise to get shared proj_data_info_ptr for (int view_num = get_min_view_num(); view_num <= get_max_view_num(); ++view_num) - segment.set_viewgram(get_viewgram(view_num, segment_num, false)); + segment.set_viewgram(get_viewgram(view_num, segment_num, false, timing_num)); return segment; } @@ -358,7 +360,7 @@ ProjData::set_segment(const SegmentBySinogram& segment, { for (int view_num = get_min_view_num(); view_num <= get_max_view_num(); ++view_num) { - if(set_viewgram(segment.get_viewgram(view_num)) + if(set_viewgram(segment.get_viewgram(view_num), timing_pos) == Succeeded::no) return Succeeded::no; } @@ -371,7 +373,7 @@ ProjData::set_segment(const SegmentByView& segment, { for (int view_num = get_min_view_num(); view_num <= get_max_view_num(); ++view_num) { - if(set_viewgram(segment.get_viewgram(view_num)) + if(set_viewgram(segment.get_viewgram(view_num), timing_pos) == Succeeded::no) return Succeeded::no; } diff --git a/src/buildblock/ProjDataFromStream.cxx b/src/buildblock/ProjDataFromStream.cxx index c768561476..0639af379f 100644 --- a/src/buildblock/ProjDataFromStream.cxx +++ b/src/buildblock/ProjDataFromStream.cxx @@ -3,6 +3,7 @@ \ingroup projdata \brief Implementations for non-inline functions of class stir::ProjDataFromStream + \author Nikos Efthimiou \author Sanida Mustafovic \author Kris Thielemans \author Claire Labbe @@ -13,6 +14,7 @@ Copyright (C) 2000 - 2011-12-21, Hammersmith Imanet Ltd Copyright (C) 2011-2012, Kris Thielemans Copyright (C) 2013, University College London + Copyright (C) 2016, University of Hull This file is part of STIR. @@ -71,14 +73,14 @@ START_NAMESPACE_STIR //--------------------------------------------------------- ProjDataFromStream::ProjDataFromStream(shared_ptr const& exam_info_sptr, - shared_ptr const& proj_data_info_ptr, - shared_ptr const& s, const streamoff offs, + shared_ptr const& proj_data_info_ptr, + shared_ptr const& s, const streamoff offs, const vector& segment_sequence_in_stream_v, - StorageOrder o, + StorageOrder o, NumericType data_type, - ByteOrder byte_order, + ByteOrder byte_order, float scale_factor) - + : ProjData(exam_info_sptr, proj_data_info_ptr), sino_stream(s), offset(offs), @@ -90,15 +92,40 @@ ProjDataFromStream::ProjDataFromStream(shared_ptr const& exam_info_spt { assert(storage_order != Unsupported); assert(!(data_type == NumericType::UNKNOWN_TYPE)); + + int sum = 0; + for (int segment_num = proj_data_info_ptr->get_min_segment_num(); + segment_num<=proj_data_info_ptr->get_max_segment_num(); + ++segment_num) + { + sum += get_num_axial_poss(segment_num) * get_num_views() * get_num_tangential_poss(); + } + + offset_3d_data = static_cast (sum * on_disk_data_type.size_in_bytes()); + + // Now, lets initialise a TOF stream - Similarly to segments + if (storage_order == Timing_Segment_View_AxialPos_TangPos && + proj_data_info_ptr->get_num_timing_poss() > 1) + { + timing_poss_sequence.resize(proj_data_info_ptr->get_num_timing_poss()); + int timing_pos_num; + + for (int i= 0, timing_pos_num = proj_data_info_ptr->get_min_timing_pos_num(); + timing_pos_num<=proj_data_info_ptr->get_max_timing_pos_num(); + ++i, ++timing_pos_num) + { + timing_poss_sequence[i] = timing_pos_num; + } + } } ProjDataFromStream::ProjDataFromStream(shared_ptr const& exam_info_sptr, - shared_ptr const& proj_data_info_ptr, - shared_ptr const& s, const streamoff offs, - StorageOrder o, + shared_ptr const& proj_data_info_ptr, + shared_ptr const& s, const streamoff offs, + StorageOrder o, NumericType data_type, - ByteOrder byte_order, - float scale_factor) + ByteOrder byte_order, + float scale_factor) : ProjData(exam_info_sptr, proj_data_info_ptr), sino_stream(s), offset(offs), @@ -112,17 +139,22 @@ ProjDataFromStream::ProjDataFromStream(shared_ptr const& exam_info_spt segment_sequence.resize(proj_data_info_ptr->get_num_segments()); - int segment_num, i; + //N.E. Take this opportunity to calculate the size of the complete -full- 3D sinogram. + // We will need that to skip timing positions + int segment_num, i, tmp_add = 0; for (i= 0, segment_num = proj_data_info_ptr->get_min_segment_num(); - segment_num<=proj_data_info_ptr->get_max_segment_num(); + segment_num<=proj_data_info_ptr->get_max_segment_num(); ++i, ++segment_num) { segment_sequence[i] =segment_num; + tmp_add += get_num_axial_poss(segment_num) * get_num_views() * get_num_tangential_poss(); } - // In this case lets start a TOF stream - Similar to segments + offset_3d_data = static_cast (tmp_add * on_disk_data_type.size_in_bytes()); + + // Now, lets initialise a TOF stream - Similarly to segments if (storage_order == Timing_Segment_View_AxialPos_TangPos && - proj_data_info_ptr->get_tof_mash_factor() > 1) + proj_data_info_ptr->get_num_timing_poss() > 1) { timing_poss_sequence.resize(proj_data_info_ptr->get_num_timing_poss()); int timing_pos_num; @@ -133,13 +165,12 @@ ProjDataFromStream::ProjDataFromStream(shared_ptr const& exam_info_spt { timing_poss_sequence[i] = timing_pos_num; } - } } -Viewgram +Viewgram ProjDataFromStream::get_viewgram(const int view_num, const int segment_num, - const bool make_num_tangential_poss_odd) const + const bool make_num_tangential_poss_odd, const int timing_pos) const { if (sino_stream == 0) { @@ -149,16 +180,16 @@ ProjDataFromStream::get_viewgram(const int view_num, const int segment_num, { error("ProjDataFromStream::get_viewgram: error in stream state before reading\n"); } - - vector offsets = get_offsets(view_num,segment_num); - + + vector offsets = get_offsets(view_num,segment_num, timing_pos); + const streamoff segment_offset = offsets[0]; const streamoff beg_view_offset = offsets[1]; const streamoff intra_views_offset = offsets[2]; - + sino_stream->seekg(segment_offset, ios::beg); // start of segment sino_stream->seekg(beg_view_offset, ios::cur); // start of view within segment - + if (! *sino_stream) { error("ProjDataFromStream::get_viewgram: error after seekg\n"); @@ -166,12 +197,12 @@ ProjDataFromStream::get_viewgram(const int view_num, const int segment_num, Viewgram viewgram(proj_data_info_ptr, view_num, segment_num); float scale = float(1); - + if (get_storage_order() == Segment_AxialPos_View_TangPos) - { + { for (int ax_pos_num = get_min_axial_pos_num(segment_num); ax_pos_num <= get_max_axial_pos_num(segment_num); ax_pos_num++) { - + if (read_data(*sino_stream, viewgram[ax_pos_num], on_disk_data_type, scale, on_disk_byte_order) == Succeeded::no) error("ProjDataFromStream: error reading data\n"); @@ -182,8 +213,8 @@ ProjDataFromStream::get_viewgram(const int view_num, const int segment_num, sino_stream->seekg(intra_views_offset, ios::cur); } } - - + + else if (get_storage_order() == Segment_View_AxialPos_TangPos) { if(read_data(*sino_stream, viewgram, on_disk_data_type, scale, on_disk_byte_order) @@ -196,18 +227,18 @@ ProjDataFromStream::get_viewgram(const int view_num, const int segment_num, viewgram *= scale_factor; if (make_num_tangential_poss_odd &&(get_num_tangential_poss()%2==0)) - { + { const int new_max_tangential_pos = get_max_tangential_pos_num() + 1; viewgram.grow( IndexRange2D(get_min_axial_pos_num(segment_num), get_max_axial_pos_num(segment_num), - + get_min_tangential_pos_num(), - new_max_tangential_pos)); - } + new_max_tangential_pos)); + } - return viewgram; + return viewgram; } float @@ -223,7 +254,8 @@ ProjDataFromStream::get_bin_value(const Bin& this_bin) const } vector offsets = get_offsets_bin(this_bin.segment_num(), this_bin.axial_pos_num(), - this_bin.view_num(), this_bin.tangential_pos_num()); + this_bin.view_num(), this_bin.tangential_pos_num(), + this_bin.timing_pos_num()); const streamoff total_offset = offsets[0]; @@ -252,7 +284,8 @@ ProjDataFromStream::get_bin_value(const Bin& this_bin) const vector -ProjDataFromStream::get_offsets(const int view_num, const int segment_num) const +ProjDataFromStream::get_offsets(const int view_num, const int segment_num, + const int timing_num) const { if (!(segment_num >= get_min_segment_num() && @@ -260,9 +293,8 @@ ProjDataFromStream::get_offsets(const int view_num, const int segment_num) const error("ProjDataFromStream::get_offsets: segment_num out of range : %d", segment_num); if (!(view_num >= get_min_view_num() && - view_num <= get_max_view_num())) - error("ProjDataFromStream::get_offsets: view_num out of range : %d", view_num); - + view_num <= get_max_view_num())) + error("ProjDataFromStream::get_offsets: view_num out of range : %d", view_num); // cout<<"get_offsets"<(FIND(segment_sequence.begin(), segment_sequence.end(), segment_num) - + const int index = + static_cast(FIND(segment_sequence.begin(), segment_sequence.end(), segment_num) - segment_sequence.begin()); - + streamoff num_axial_pos_offset = 0; for (int i=0; i(num_axial_pos_offset* get_num_tangential_poss() * get_num_views() * on_disk_data_type.size_in_bytes()); - + if (get_storage_order() == Segment_AxialPos_View_TangPos) { - - + + const streamoff beg_view_offset = (view_num - get_min_view_num()) *get_num_tangential_poss() * on_disk_data_type.size_in_bytes(); - - const streamoff intra_views_offset = + + const streamoff intra_views_offset = (get_num_views() -1) *get_num_tangential_poss() * on_disk_data_type.size_in_bytes(); vector temp(3); temp[0] = segment_offset; temp[1] = beg_view_offset; temp[2] = intra_views_offset; - + return temp; - } - else //if (get_storage_order() == Segment_View_AxialPos_TangPos) + } + else if (get_storage_order() == Segment_View_AxialPos_TangPos) { const streamoff beg_view_offset = - (view_num - get_min_view_num()) - * get_num_axial_poss(segment_num) + (view_num - get_min_view_num()) + * get_num_axial_poss(segment_num) * get_num_tangential_poss() * on_disk_data_type.size_in_bytes(); - - + + vector temp(3); temp[0] =segment_offset; temp[1]= beg_view_offset; temp[2] = 0; return temp; - + } + else if (get_storage_order() == Timing_Segment_View_AxialPos_TangPos) + { + // The timing offset will be added to the segment offset. This approach we minimise the + // changes + if (!(timing_num >= get_min_timing_pos_num() && + timing_num <= get_max_timing_pos_num())) + error("ProjDataFromStream::get_offsets: timing_num out of range : %d", timing_num); + + const int timing_index = static_cast(FIND(timing_poss_sequence.begin(), timing_poss_sequence.end(), timing_num) - + timing_poss_sequence.begin()); + + assert(offset_3d_data > 0); + segment_offset += static_cast(timing_index) * offset_3d_data; + + const streamoff beg_view_offset = + (view_num - get_min_view_num()) + * get_num_axial_poss(segment_num) + * get_num_tangential_poss() + * on_disk_data_type.size_in_bytes(); + + + vector temp(3); + temp[0] = segment_offset; + temp[1] = beg_view_offset; + temp[2] = 0; + return temp; } } Succeeded -ProjDataFromStream::set_viewgram(const Viewgram& v) +ProjDataFromStream::set_viewgram(const Viewgram& v, const int &timing_pos) { if (sino_stream == 0) { @@ -340,23 +398,23 @@ ProjDataFromStream::set_viewgram(const Viewgram& v) { warning("ProjDataFromStream::set_viewgram: non-float output uses original " "scale factor %g which might not be appropriate for the current data\n", - scale_factor); + scale_factor); } if (get_num_tangential_poss() != v.get_proj_data_info_ptr()->get_num_tangential_poss()) { - warning("ProjDataFromStream::set_viewgram: num_bins is not correct\n"); + warning("ProjDataFromStream::set_viewgram: num_bins is not correct\n"); return Succeeded::no; } if (get_num_axial_poss(v.get_segment_num()) != v.get_num_axial_poss()) { - warning("ProjDataFromStream::set_viewgram: number of axial positions is not correct\n"); + warning("ProjDataFromStream::set_viewgram: number of axial positions is not correct\n"); return Succeeded::no; } - + if (*get_proj_data_info_ptr() != *(v.get_proj_data_info_ptr())) { warning("ProjDataFromStream::set_viewgram: viewgram has incompatible ProjDataInfo member\n" @@ -368,31 +426,31 @@ ProjDataFromStream::set_viewgram(const Viewgram& v) return Succeeded::no; } - int segment_num = v.get_segment_num(); + int segment_num = v.get_segment_num(); int view_num = v.get_view_num(); - - vector offsets = get_offsets(view_num,segment_num); + + vector offsets = get_offsets(view_num,segment_num, timing_pos); const streamoff segment_offset = offsets[0]; const streamoff beg_view_offset = offsets[1]; const streamoff intra_views_offset = offsets[2]; sino_stream->seekp(segment_offset, ios::beg); // start of segment sino_stream->seekp(beg_view_offset, ios::cur); // start of view within segment - + if (! *sino_stream) { warning("ProjDataFromStream::set_viewgram: error after seekg\n"); return Succeeded::no; - } + } float scale = scale_factor; - + if (get_storage_order() == Segment_AxialPos_View_TangPos) { for (int ax_pos_num = get_min_axial_pos_num(segment_num); ax_pos_num <= get_max_axial_pos_num(segment_num); ax_pos_num++) { - - if (write_data(*sino_stream, v[ax_pos_num], on_disk_data_type, scale, on_disk_byte_order) + + if (write_data(*sino_stream, v[ax_pos_num], on_disk_data_type, scale, on_disk_byte_order) == Succeeded::no || scale != scale_factor) { @@ -420,9 +478,22 @@ ProjDataFromStream::set_viewgram(const Viewgram& v) } return Succeeded::yes; } + else if (get_storage_order() == Timing_Segment_View_AxialPos_TangPos) + { + if (write_data(*sino_stream, v, on_disk_data_type, scale, on_disk_byte_order) + == Succeeded::no + || scale != scale_factor) + { + warning("ProjDataFromStream::set_viewgram: viewgram (view=%d, segment=%d)" + " corrupted due to problems with writing or the scale factor \n", + view_num, segment_num); + return Succeeded::no; + } + return Succeeded::yes; + } else { - warning("ProjDataFromStream::set_viewgram: unsupported storage order\n"); + warning("ProjDataFromStream::set_viewgram: unsupported storage order\n"); return Succeeded::no; } } @@ -432,7 +503,7 @@ std::vector ProjDataFromStream::get_offsets_bin(const int segment_num, const int ax_pos_num, const int view_num, - const int tang_pos_num) const + const int tang_pos_num, const int timing_pos_num) const { if (!(segment_num >= get_min_segment_num() && @@ -454,7 +525,7 @@ ProjDataFromStream::get_offsets_bin(const int segment_num, num_axial_pos_offset += get_num_axial_poss(segment_sequence[i]); - const streamoff segment_offset = + streamoff segment_offset = offset + static_cast(num_axial_pos_offset* get_num_tangential_poss() * @@ -488,8 +559,45 @@ ProjDataFromStream::get_offsets_bin(const int segment_num, return temp; } - else //if (get_storage_order() == Segment_View_AxialPos_TangPos) + else if (get_storage_order() == Segment_View_AxialPos_TangPos) + { + + // Skip views + const streamoff view_offset = + (view_num - get_min_view_num())* + get_num_axial_poss(segment_num) * + get_num_tangential_poss()* + on_disk_data_type.size_in_bytes(); + + + // find axial pos + const streamoff ax_pos_offset = + (ax_pos_num - get_min_axial_pos_num(segment_num)) * + get_num_tangential_poss()* + on_disk_data_type.size_in_bytes(); + + // find tang pos + const streamoff tang_offset = + (tang_pos_num - get_min_tangential_pos_num()) * on_disk_data_type.size_in_bytes(); + + vector temp(1); + temp[0] = segment_offset + ax_pos_offset +view_offset + tang_offset; + + return temp; + } + else if (get_storage_order() == Timing_Segment_View_AxialPos_TangPos) { + // The timing offset will be added to the segment offset. This approach we minimise the + // changes + if (!(timing_pos_num >= get_min_timing_pos_num() && + timing_pos_num <= get_max_timing_pos_num())) + error("ProjDataFromStream::get_offsets_bin: timing_num out of range : %d", timing_pos_num); + + const int timing_index = static_cast(FIND(timing_poss_sequence.begin(), timing_poss_sequence.end(), timing_pos_num) - + timing_poss_sequence.begin()); + + assert(offset_3d_data > 0); + segment_offset += static_cast(timing_index) * offset_3d_data; // Skip views const streamoff view_offset = @@ -514,45 +622,49 @@ ProjDataFromStream::get_offsets_bin(const int segment_num, return temp; } + else + { + error("ProjDataFromStream::get_offsets_bin: unsupported storage order\n"); + } } // get offsets for the sino data vector -ProjDataFromStream::get_offsets_sino(const int ax_pos_num, const int segment_num) const +ProjDataFromStream::get_offsets_sino(const int ax_pos_num, const int segment_num, const int timing_num) const { if (!(segment_num >= get_min_segment_num() && segment_num <= get_max_segment_num())) error("ProjDataFromStream::get_offsets: segment_num out of range : %d", segment_num); if (!(ax_pos_num >= get_min_axial_pos_num(segment_num) && - ax_pos_num <= get_max_axial_pos_num(segment_num))) + ax_pos_num <= get_max_axial_pos_num(segment_num))) error("ProjDataFromStream::get_offsets: axial_pos_num out of range : %d", ax_pos_num); - const int index = - static_cast(FIND(segment_sequence.begin(), segment_sequence.end(), segment_num) - + const int index = + static_cast(FIND(segment_sequence.begin(), segment_sequence.end(), segment_num) - segment_sequence.begin()); - - + + streamoff num_axial_pos_offset = 0; for (int i=0; i(num_axial_pos_offset* get_num_tangential_poss() * get_num_views() * on_disk_data_type.size_in_bytes()); - + if (get_storage_order() == Segment_AxialPos_View_TangPos) { - + const streamoff beg_ax_pos_offset = (ax_pos_num - get_min_axial_pos_num(segment_num))* - get_num_views() * + get_num_views() * get_num_tangential_poss()* on_disk_data_type.size_in_bytes(); @@ -560,32 +672,62 @@ ProjDataFromStream::get_offsets_sino(const int ax_pos_num, const int segment_num temp[0] = segment_offset; temp[1] = beg_ax_pos_offset; temp[2] = 0; - + return temp; - } - else //if (get_storage_order() == Segment_View_AxialPos_TangPos) + } + else if (get_storage_order() == Segment_View_AxialPos_TangPos) { const streamoff beg_ax_pos_offset = (ax_pos_num - get_min_axial_pos_num(segment_num)) *get_num_tangential_poss() * on_disk_data_type.size_in_bytes(); - - const streamoff intra_ax_pos_offset = + + const streamoff intra_ax_pos_offset = (get_num_axial_poss(segment_num) -1) *get_num_tangential_poss() * on_disk_data_type.size_in_bytes(); - - + + vector temp(3); temp[0] =segment_offset; temp[1]= beg_ax_pos_offset; temp[2] =intra_ax_pos_offset; return temp; - + } + else if (get_storage_order() == Timing_Segment_View_AxialPos_TangPos) + { + // The timing offset will be added to the segment offset. This approach we minimise the + // changes + if (!(timing_num >= get_min_timing_pos_num() && + timing_num <= get_max_timing_pos_num())) + error("ProjDataFromStream::get_offsets: timing_num out of range : %d", timing_num); + + const int timing_index = static_cast(FIND(timing_poss_sequence.begin(), timing_poss_sequence.end(), timing_num) - + timing_poss_sequence.begin()); + + assert(offset_3d_data > 0); + segment_offset += static_cast(timing_index) * offset_3d_data; + + const streamoff beg_ax_pos_offset = + (ax_pos_num - get_min_axial_pos_num(segment_num)) *get_num_tangential_poss() * on_disk_data_type.size_in_bytes(); + + const streamoff intra_ax_pos_offset = + (get_num_axial_poss(segment_num) -1) *get_num_tangential_poss() * on_disk_data_type.size_in_bytes(); + + + vector temp(3); + temp[0] =segment_offset; + temp[1]= beg_ax_pos_offset; + temp[2] =intra_ax_pos_offset; + return temp; + } + else + { + error("ProjDataFromStream::get_offsets_sino: unsupported storage order\n"); } } Sinogram ProjDataFromStream::get_sinogram(const int ax_pos_num, const int segment_num, - const bool make_num_tangential_poss_odd) const + const bool make_num_tangential_poss_odd, const int timing_pos) const { if (sino_stream == 0) { @@ -595,18 +737,18 @@ ProjDataFromStream::get_sinogram(const int ax_pos_num, const int segment_num, { error("ProjDataFromStream::get_sinogram: error in stream state before reading\n"); } - + // Call the get_offset to calculate the offsets, e.g // segment offset + view_offset + intra_view_offsets - vector offsets = get_offsets_sino(ax_pos_num,segment_num); - + vector offsets = get_offsets_sino(ax_pos_num,segment_num, timing_pos); + const streamoff segment_offset = offsets[0]; const streamoff beg_ax_pos_offset = offsets[1]; const streamoff intra_ax_pos_offset = offsets[2]; - + sino_stream->seekg(segment_offset, ios::beg); // start of segment sino_stream->seekg(beg_ax_pos_offset, ios::cur); // start of view within segment - + if (! *sino_stream) { error("ProjDataFromStream::get_sinogram: error after seekg\n"); @@ -614,17 +756,17 @@ ProjDataFromStream::get_sinogram(const int ax_pos_num, const int segment_num, Sinogram sinogram(proj_data_info_ptr, ax_pos_num, segment_num); float scale = float(1); - + if (get_storage_order() == Segment_AxialPos_View_TangPos) - { + { if(read_data(*sino_stream, sinogram, on_disk_data_type, scale, on_disk_byte_order) == Succeeded::no) error("ProjDataFromStream: error reading data\n"); if(scale != 1) error("ProjDataFromStream: error reading data: scale factor returned by read_data should be 1\n"); } - - + + else if (get_storage_order() == Segment_View_AxialPos_TangPos) { for (int view = get_min_view_num(); view <= get_max_view_num(); view++) @@ -637,7 +779,7 @@ ProjDataFromStream::get_sinogram(const int ax_pos_num, const int segment_num, // seek to next line unless it was the last we need to read if(view != get_max_view_num()) sino_stream->seekg(intra_ax_pos_offset, ios::cur); - } + } } sinogram *= scale_factor; @@ -648,16 +790,16 @@ ProjDataFromStream::get_sinogram(const int ax_pos_num, const int segment_num, sinogram.grow(IndexRange2D(get_min_view_num(), get_max_view_num(), get_min_tangential_pos_num(), - new_max_tangential_pos)); + new_max_tangential_pos)); } - + return sinogram; - - + + } Succeeded -ProjDataFromStream::set_sinogram(const Sinogram& s) +ProjDataFromStream::set_sinogram(const Sinogram& s, const int &timing_pos) { if (sino_stream == 0) { @@ -674,9 +816,9 @@ ProjDataFromStream::set_sinogram(const Sinogram& s) { warning("ProjDataFromStream::set_sinogram: non-float output uses original " "scale factor %g which might not be appropriate for the current data\n", - scale_factor); + scale_factor); } - + if (*get_proj_data_info_ptr() != *(s.get_proj_data_info_ptr())) { warning("ProjDataFromStream::set_sinogram: Sinogram has incompatible ProjDataInfo member.\n" @@ -688,28 +830,28 @@ ProjDataFromStream::set_sinogram(const Sinogram& s) return Succeeded::no; } - int segment_num = s.get_segment_num(); + int segment_num = s.get_segment_num(); int ax_pos_num = s.get_axial_pos_num(); - - - vector offsets = get_offsets_sino(ax_pos_num,segment_num); + + + vector offsets = get_offsets_sino(ax_pos_num,segment_num, timing_pos); const streamoff segment_offset = offsets[0]; const streamoff beg_ax_pos_offset = offsets[1]; const streamoff intra_ax_pos_offset = offsets[2]; - + sino_stream->seekp(segment_offset, ios::beg); // start of segment sino_stream->seekp(beg_ax_pos_offset, ios::cur); // start of view within segment - + if (! *sino_stream) { warning("ProjDataFromStream::set_sinogram: error after seekg\n"); return Succeeded::no; - } + } float scale = scale_factor; - - + + if (get_storage_order() == Segment_AxialPos_View_TangPos) - + { if (write_data(*sino_stream, s, on_disk_data_type, scale, on_disk_byte_order) == Succeeded::no @@ -723,7 +865,7 @@ ProjDataFromStream::set_sinogram(const Sinogram& s) return Succeeded::yes; } - + else if (get_storage_order() == Segment_View_AxialPos_TangPos) { for (int view = get_min_view_num();view <= get_max_view_num(); view++) @@ -745,7 +887,7 @@ ProjDataFromStream::set_sinogram(const Sinogram& s) } else { - warning("ProjDataFromStream::set_sinogram: unsupported storage order\n"); + warning("ProjDataFromStream::set_sinogram: unsupported storage order\n"); return Succeeded::no; } } @@ -755,25 +897,53 @@ ProjDataFromStream::get_offset_segment(const int segment_num) const { assert(segment_num >= get_min_segment_num() && segment_num <= get_max_segment_num()); - { - const int index = - static_cast(FIND(segment_sequence.begin(), segment_sequence.end(), segment_num) - + { + const int index = + static_cast(FIND(segment_sequence.begin(), segment_sequence.end(), segment_num) - segment_sequence.begin()); streamoff num_axial_pos_offset = 0; for (int i=0; i(FIND(timing_poss_sequence.begin(), timing_poss_sequence.end(), timing_num) - + timing_poss_sequence.begin()); + + assert (timing_num >= get_min_timing_pos_num() && + timing_num <= get_max_timing_pos_num()); + { + if(offset_3d_data < 0 ) // Calculate the full 3D sinogram size - Very slow + { + long long sum = 0; + + for (int segment_num = proj_data_info_ptr->get_min_segment_num(); + segment_num<=proj_data_info_ptr->get_max_segment_num(); + ++segment_num) + { + sum += get_num_axial_poss(segment_num) * get_num_views() * get_num_tangential_poss(); + } + return static_cast (sum * index * on_disk_data_type.size_in_bytes()); + } + else + return static_cast(offset_3d_data * index); + } } @@ -781,7 +951,7 @@ ProjDataFromStream::get_offset_segment(const int segment_num) const // -> No need for get_offset_segment SegmentBySinogram -ProjDataFromStream::get_segment_by_sinogram(const int segment_num) const +ProjDataFromStream::get_segment_by_sinogram(const int segment_num, const int timing_num) const { if(sino_stream == 0) { @@ -791,44 +961,44 @@ ProjDataFromStream::get_segment_by_sinogram(const int segment_num) const { error("ProjDataFromStream::get_segment_by_sinogram: error in stream state before reading\n"); } - + streamoff segment_offset = get_offset_segment(segment_num); sino_stream->seekg(segment_offset, ios::beg); if (! *sino_stream) { error("ProjDataFromStream::get_segment_by_sinogram: error after seekg\n"); } - + if (get_storage_order() == Segment_AxialPos_View_TangPos) { SegmentBySinogram segment(proj_data_info_ptr,segment_num); { float scale = float(1); - if(read_data(*sino_stream, segment, on_disk_data_type, scale, on_disk_byte_order) + if(read_data(*sino_stream, segment, on_disk_data_type, scale, on_disk_byte_order) == Succeeded::no) error("ProjDataFromStream: error reading data\n"); if(scale != 1) - error("ProjDataFromStream: error reading data: scale factor returned by read_data should be 1\n"); + error("ProjDataFromStream: error reading data: scale factor returned by read_data should be 1\n"); } - + segment *= scale_factor; - + return segment; - + } else { // TODO rewrite in terms of get_viewgram return SegmentBySinogram (get_segment_by_view(segment_num)); } - - + + } SegmentByView -ProjDataFromStream::get_segment_by_view(const int segment_num) const +ProjDataFromStream::get_segment_by_view(const int segment_num, const int timing_pos) const { - + if(sino_stream == 0) { error("ProjDataFromStream::get_segment_by_view: stream ptr is 0\n"); @@ -837,34 +1007,64 @@ ProjDataFromStream::get_segment_by_view(const int segment_num) const { error("ProjDataFromStream::get_segment_by_view: error in stream state before reading\n"); } - + if (get_storage_order() == Segment_View_AxialPos_TangPos) { - + streamoff segment_offset = get_offset_segment(segment_num); sino_stream->seekg(segment_offset, ios::beg); - + if (! *sino_stream) { error("ProjDataFromStream::get_segment_by_sinogram: error after seekg\n"); } - + SegmentByView segment(proj_data_info_ptr,segment_num); - + { - float scale = float(1); + float scale = float(1.f); if(read_data(*sino_stream, segment, on_disk_data_type, scale, on_disk_byte_order) == Succeeded::no) error("ProjDataFromStream: error reading data\n"); if(scale != 1) error("ProjDataFromStream: error reading data: scale factor returned by read_data should be 1\n"); } - + segment *= scale_factor; - + return segment; } - else + else if (get_storage_order() == Timing_Segment_View_AxialPos_TangPos) + { + //Get the right segment offset + streamoff segment_offset = get_offset_segment(segment_num); + // Go to the right timing full 3D sinogram + segment_offset += get_offset_timing(timing_pos) ; + + sino_stream->seekg(segment_offset, ios::beg); + + if (! *sino_stream) + { + error("ProjDataFromStream::get_segment_by_sinogram: error after seekg\n"); + } + + SegmentByView segment(proj_data_info_ptr,segment_num); + + { + float scale = float(1.f); + if(read_data(*sino_stream, segment, on_disk_data_type, scale, on_disk_byte_order) + == Succeeded::no) + error("ProjDataFromStream: error reading data\n"); + if(scale != 1) + error("ProjDataFromStream: error reading data: scale factor returned by read_data should be 1\n"); + } + + segment *= scale_factor; + + return segment; + + } + else // TODO rewrite in terms of get_sinogram as this doubles memory temporarily return SegmentByView (get_segment_by_sinogram(segment_num)); } @@ -880,37 +1080,37 @@ ProjDataFromStream::set_segment(const SegmentBySinogram& segmentbysinogra { error("ProjDataFromStream::set_segment: error in stream state before writing\n"); } - + if (get_num_tangential_poss() != segmentbysinogram_v.get_num_tangential_poss()) { - warning("ProjDataFromStream::set_segmen: num_bins is not correct\n"); + warning("ProjDataFromStream::set_segmen: num_bins is not correct\n"); return Succeeded::no; } if (get_num_views() != segmentbysinogram_v.get_num_views()) { - warning("ProjDataFromStream::set_segment: num_views is not correct\n"); + warning("ProjDataFromStream::set_segment: num_views is not correct\n"); return Succeeded::no; } - + int segment_num = segmentbysinogram_v.get_segment_num(); streamoff segment_offset = get_offset_segment(segment_num); - + sino_stream->seekp(segment_offset,ios::beg); - + if (! *sino_stream) { warning("ProjDataFromStream::set_segment: error after seekp\n"); return Succeeded::no; - } - - if (get_storage_order() == Segment_AxialPos_View_TangPos) + } + + if (get_storage_order() == Segment_AxialPos_View_TangPos) { // KT 03/07/2001 handle scale_factor appropriately if (on_disk_data_type.id != NumericType::FLOAT) { warning("ProjDataFromStream::set_segment: non-float output uses original " "scale factor %g which might not be appropriate for the current data\n", - scale_factor); + scale_factor); } float scale = scale_factor; if (write_data(*sino_stream, segmentbysinogram_v, on_disk_data_type, scale, on_disk_byte_order) @@ -925,16 +1125,16 @@ ProjDataFromStream::set_segment(const SegmentBySinogram& segmentbysinogra return Succeeded::yes; } - else + else { // TODO rewrite in terms of set_viewgram const SegmentByView segmentbyview= SegmentByView(segmentbysinogram_v); - set_segment(segmentbyview); + set_segment(segmentbyview); return Succeeded::yes; } - + } Succeeded @@ -945,41 +1145,41 @@ ProjDataFromStream::set_segment(const SegmentByView& segmentbyview_v) error("ProjDataFromStream::set_segment: stream ptr is 0\n"); } if (! *sino_stream) - { + { error("ProjDataFromStream::set_segment: error in stream state before writing\n"); } - - + + if (get_num_tangential_poss() != segmentbyview_v.get_num_tangential_poss()) { - warning("ProjDataFromStream::set_segment: num_bins is not correct\n"); + warning("ProjDataFromStream::set_segment: num_bins is not correct\n"); return Succeeded::no; } if (get_num_views() != segmentbyview_v.get_num_views()) { - warning("ProjDataFromStream::set_segment: num_views is not correct\n"); + warning("ProjDataFromStream::set_segment: num_views is not correct\n"); return Succeeded::no; } - + int segment_num = segmentbyview_v.get_segment_num(); streamoff segment_offset = get_offset_segment(segment_num); - + sino_stream->seekp(segment_offset,ios::beg); - + if (! *sino_stream) { warning("ProjDataFromStream::set_segment: error after seekp"); return Succeeded::no; - } - - if (get_storage_order() == Segment_View_AxialPos_TangPos) + } + + if (get_storage_order() == Segment_View_AxialPos_TangPos) { // KT 03/07/2001 handle scale_factor appropriately if (on_disk_data_type.id != NumericType::FLOAT) { warning("ProjDataFromStream::set_segment: non-float output uses original " "scale factor %g which might not be appropriate for the current data\n", - scale_factor); + scale_factor); } float scale = scale_factor; if (write_data(*sino_stream, segmentbyview_v, on_disk_data_type, scale, on_disk_byte_order) @@ -994,44 +1194,44 @@ ProjDataFromStream::set_segment(const SegmentByView& segmentbyview_v) return Succeeded::yes; } - else + else { - // TODO rewrite in terms of set_sinogram - const SegmentBySinogram segmentbysinogram = + // TODO rewrite in terms of set_sinogram + const SegmentBySinogram segmentbysinogram = SegmentBySinogram(segmentbyview_v); set_segment(segmentbysinogram); return Succeeded::yes; } - + } #if 0 ProjDataFromStream* ProjDataFromStream::ask_parameters(const bool on_disk) { - + shared_ptr p_in_stream; - - + + char filename[256]; - + ask_filename_with_extension( - filename, + filename, "Enter file name of 3D sinogram data : ", ".scn"); // KT 03/07/2001 initialise to avoid compiler warnings - ios::openmode open_mode=ios::in; + ios::openmode open_mode=ios::in; switch(ask_num("Read (1), Create and write(2), Read/Write (3) : ", 1,3,1)) { case 1: open_mode=ios::in; break; case 2: open_mode=ios::out; break; case 3: open_mode=ios::in | ios::out; break; } - + if (on_disk) { - + //fstream * p_fstream = new fstream; p_in_stream.reset(new fstream (filename, open_mode | ios::binary)); if (!p_in_stream->good()) @@ -1042,48 +1242,48 @@ ProjDataFromStream* ProjDataFromStream::ask_parameters(const bool on_disk) //p_in_stream = p_fstream; } else - { + { streamsize file_size = 0; char *memory = 0; - { + { fstream input; open_read_binary(input, filename); memory = (char *)read_stream_in_memory(input, file_size); } - + #ifdef BOOST_NO_STRINGSTREAM // This is the old implementation of the strstream class. // The next constructor should work according to the doc, but it doesn't in gcc 2.8.1. //strstream in_stream(memory, file_size, ios::in | ios::binary); - // Reason: in_stream contains an internal strstreambuf which is + // Reason: in_stream contains an internal strstreambuf which is // initialised as buffer(memory, file_size, memory), which prevents // reading from it. - + strstreambuf * buffer = new strstreambuf(memory, file_size, memory+file_size); p_in_stream.reset(new iostream(buffer)); #else // TODO this does allocate and copy 2 times // TODO file_size could be longer than what size_t allows, but string doesn't take anything longer - p_in_stream.reset(new std::stringstream (string(memory, std::size_t(file_size)), + p_in_stream.reset(new std::stringstream (string(memory, std::size_t(file_size)), open_mode | ios::binary)); - + delete[] memory; #endif - - } // else 'on_disk' - - // KT 03/07/2001 initialise to avoid compiler warnings + } // else 'on_disk' + + + // KT 03/07/2001 initialise to avoid compiler warnings ProjDataFromStream::StorageOrder storage_order = Segment_AxialPos_View_TangPos; { int data_org = ask_num("Type of data organisation:\n\ - 0: Segment_AxialPos_View_TangPos, 1: Segment_View_AxialPos_TangPos", + 0: Segment_AxialPos_View_TangPos, 1: Segment_View_AxialPos_TangPos", 0, 1,0); - + switch (data_org) - { + { case 0: storage_order = ProjDataFromStream::Segment_AxialPos_View_TangPos; break; @@ -1092,13 +1292,13 @@ ProjDataFromStream* ProjDataFromStream::ask_parameters(const bool on_disk) break; } } - + NumericType data_type; { int data_type_sel = ask_num("Type of data :\n\ 0: signed 16bit int, 1: unsigned 16bit int, 2: 4bit float ", 0,2,2); switch (data_type_sel) - { + { case 0: data_type = NumericType::SHORT; break; @@ -1110,80 +1310,80 @@ ProjDataFromStream* ProjDataFromStream::ask_parameters(const bool on_disk) break; } } - - + + ByteOrder byte_order; - { - byte_order = + { + byte_order = ask("Little endian byte order ?", ByteOrder::get_native_order() == ByteOrder::little_endian) ? ByteOrder::little_endian : ByteOrder::big_endian; } - + std::streamoff offset_in_file ; { // find file size - p_in_stream->seekg(static_cast(0), ios::beg); + p_in_stream->seekg(static_cast(0), ios::beg); streamsize file_size = find_remaining_size(*p_in_stream); - - offset_in_file = ask_num("Offset in file (in bytes)", + + offset_in_file = ask_num("Offset in file (in bytes)", static_cast(0), - static_cast(file_size), + static_cast(file_size), static_cast(0)); } float scale_factor =1; - + shared_ptr data_info_ptr(ProjDataInfo::ask_parameters()); - - vector segment_sequence_in_stream; - segment_sequence_in_stream = vector(data_info_ptr->get_num_segments()); - segment_sequence_in_stream[0] = 0; - + + vector segment_sequence_in_stream; + segment_sequence_in_stream = vector(data_info_ptr->get_num_segments()); + segment_sequence_in_stream[0] = 0; + for (int i=1; i<= data_info_ptr->get_num_segments()/2; i++) - { + { segment_sequence_in_stream[2*i-1] = i; segment_sequence_in_stream[2*i] = -i; } - - + + cerr << "Segment sequence :"; for (unsigned int i=0; i ProjDataGEAdvance:: get_viewgram(const int view_num, const int segment_num, - const bool make_num_tangential_poss_odd) const + const bool make_num_tangential_poss_odd, const int timing_pos) const { // -------------------------------------------------------- // -------------------------------------------------------- @@ -304,7 +304,7 @@ get_viewgram(const int view_num, const int segment_num, } -Succeeded ProjDataGEAdvance::set_viewgram(const Viewgram& v) +Succeeded ProjDataGEAdvance::set_viewgram(const Viewgram& v, const int &timing_pos) { // TODO // but this is difficult: how to adjust the scale factors when writing only 1 viewgram ? @@ -312,13 +312,14 @@ Succeeded ProjDataGEAdvance::set_viewgram(const Viewgram& v) return Succeeded::no; } -Sinogram ProjDataGEAdvance::get_sinogram(const int ax_pos_num, const int segment_num,const bool make_num_tangential_poss_odd) const +Sinogram ProjDataGEAdvance::get_sinogram(const int ax_pos_num, const int segment_num, + const bool make_num_tangential_poss_odd, const int timing_pos) const { // TODO error("ProjDataGEAdvance::get_sinogram not implemented yet\n"); return get_empty_sinogram(ax_pos_num, segment_num);} -Succeeded ProjDataGEAdvance::set_sinogram(const Sinogram& s) +Succeeded ProjDataGEAdvance::set_sinogram(const Sinogram& s, const int &timing_pos) { // TODO warning("ProjDataGEAdvance::set_sinogram not implemented yet\n"); diff --git a/src/buildblock/ProjDataInfo.cxx b/src/buildblock/ProjDataInfo.cxx index 7bae650c39..03fa0e32a7 100644 --- a/src/buildblock/ProjDataInfo.cxx +++ b/src/buildblock/ProjDataInfo.cxx @@ -76,6 +76,8 @@ START_NAMESPACE_STIR float ProjDataInfo::get_k(const Bin& bin) const { + // Probably, This condition should be removed, since I have the check odd number in the + // set_tof_mash_factor(). if (!get_num_timing_poss()%2) return bin.timing_pos_num() * timing_increament_in_mm; else @@ -198,6 +200,7 @@ ProjDataInfo::set_tof_mash_factor(const int new_num) num_tof_bins = max_timing_pos_num - min_timing_pos_num +1 ; + // Ensure that we have a central tof bin. if (num_tof_bins%2 == 0) error("ProjDataInfo: Number of TOF bins should be an odd number. Abort."); @@ -218,6 +221,7 @@ ProjDataInfo::set_tof_mash_factor(const int new_num) timing_bin_boundaries_mm[i].high_lim = cur_high; timing_bin_boundaries_ps[i].low_lim = (timing_bin_boundaries_mm[i].low_lim * 3.33564095198f ) ; timing_bin_boundaries_ps[i].high_lim = ( timing_bin_boundaries_mm[i].high_lim * 3.33564095198f); + // I could imagine a better printing. info(boost::format("Tbin %1%: %2% - %3% mm (%4% - %5% ps) = %6%") %i % timing_bin_boundaries_mm[i].low_lim % timing_bin_boundaries_mm[i].high_lim % timing_bin_boundaries_ps[i].low_lim % timing_bin_boundaries_ps[i].high_lim % get_sampling_in_k(bin)); } @@ -247,7 +251,7 @@ ProjDataInfo::ProjDataInfo(const shared_ptr& scanner_ptr_v, min_timing_pos_num = 0; max_timing_pos_num = 0; timing_increament_in_mm = 0.f; - tof_mash_factor = 1; // zero? + tof_mash_factor = 1; } // TOF version. diff --git a/src/buildblock/Scanner.cxx b/src/buildblock/Scanner.cxx index 4a440aaacc..2c51219d35 100644 --- a/src/buildblock/Scanner.cxx +++ b/src/buildblock/Scanner.cxx @@ -206,113 +206,16 @@ Scanner::Scanner(Type scanner_type) 2, 1, 8, 9, 16, 9, 1 ); // TODO bucket/singles info incorrect? 224 buckets in total, but not sure how distributed break; - case Type_mCT: // dummy - // 8x8 blocks, 1 virtual "crystal", 56 blocks along the ring, 8 blocks in axial direction - // Transaxial blocks have 8 physical crystals and a gap at the - // 9th crystal where the counts are zero. - set_params(Type_mCT, string_list("Type mCT", "tmCT", "t2011"), - 52, 312, 624, - 424.5F, 7.0F, 4.16F, 2.17242F, 0.0F, - 1, 48, 52, 13, 13, 13, 1 ); // TODO bucket/singles info incorrect? 224 buckets in total, but not sure how distributed - break; - - case Type_mCT_TOF: // dummy - // 8x8 blocks, 1 virtual "crystal", 56 blocks along the ring, 8 blocks in axial direction - // Transaxial blocks have 8 physical crystals and a gap at the - // 9th crystal where the counts are zero. - // For TOF scanners the three last types have to be defined to avoid ambiguity. - set_params(Type_mCT_TOF, string_list("Type mCT_TOF", "tmCT_TOF", "t2011_tof"), - 52, 312, 624, - 424.5F, 7.0F, 4.16F, 2.17242F, 0.0F, - 1, 48, 52, 13, 13, 13, 1, - (short int)(410), - (float)(10.0F), - (float)(600.0F)); // TODO bucket/singles info incorrect? 224 buckets in total, but not sure how distributed - break; - - case Type_mCT_TOF_400: // dummy - // 8x8 blocks, 1 virtual "crystal", 56 blocks along the ring, 8 blocks in axial direction - // Transaxial blocks have 8 physical crystals and a gap at the - // 9th crystal where the counts are zero. - // For TOF scanners the three last types have to be defined to avoid ambiguity. - set_params(Type_mCT_TOF_400, string_list("Type mCT_TOF_400", "tmCT_TOF_400", "t2011_tof_400"), - 52, 312, 624, - 424.5F, 7.0F, 4.16F, 2.17242F, 0.0F, - 1, 48, 52, 13, 13, 13, 1, - (short int)(410), - (float)(10.0F), - (float)(400.0F)); // TODO bucket/singles info incorrect? 224 buckets in total, but not sure how distributed - break; - - case Type_mCT_TOF_200: // dummy - // 8x8 blocks, 1 virtual "crystal", 56 blocks along the ring, 8 blocks in axial direction - // Transaxial blocks have 8 physical crystals and a gap at the - // 9th crystal where the counts are zero. - // For TOF scanners the three last types have to be defined to avoid ambiguity. - set_params(Type_mCT_TOF_200, string_list("Type mCT_TOF_200", "tmCT_TOF_200", "t2011_tof_200"), - 52, 312, 624, - 424.5F, 7.0F, 4.16F, 2.17242F, 0.0F, - 1, 48, 52, 13, 13, 13, 1, - (short int)(410), - (float)(10.0F), - (float)(200.0F)); // TODO bucket/singles info incorrect? 224 buckets in total, but not sure how distributed - break; - - case Type_mCT_TOF_100: // dummy - // 8x8 blocks, 1 virtual "crystal", 56 blocks along the ring, 8 blocks in axial direction - // Transaxial blocks have 8 physical crystals and a gap at the - // 9th crystal where the counts are zero. - // For TOF scanners the three last types have to be defined to avoid ambiguity. - set_params(Type_mCT_TOF_100, string_list("Type mCT_TOF_100", "tmCT_TOF_100", "t2011_tof_100"), - 52, 312, 624, - 424.5F, 7.0F, 4.16F, 2.17242F, 0.0F, - 1, 48, 52, 13, 13, 13, 1, - (short int)(820), - (float)(5.00F), - (float)(100.0F)); // TODO bucket/singles info incorrect? 224 buckets in total, but not sure how distributed - break; - - case Type_mCT_TOF_50: // dummy - // 8x8 blocks, 1 virtual "crystal", 56 blocks along the ring, 8 blocks in axial direction - // Transaxial blocks have 8 physical crystals and a gap at the - // 9th crystal where the counts are zero. - // For TOF scanners the three last types have to be defined to avoid ambiguity. - set_params(Type_mCT_TOF_50, string_list("Type mCT_TOF_50", "tmCT_TOF_50", "t2011_tof_50"), - 52, 312, 624, - 424.5F, 7.0F, 4.16F, 2.17242F, 0.0F, - 1, 48, 52, 13, 13, 13, 1, - (short int)(820), - (float)(5.0F), - (float)(50.0F)); // TODO bucket/singles info incorrect? 224 buckets in total, but not sure how distributed - break; - - case Type_mCT_TOF_20: // dummy - // 8x8 blocks, 1 virtual "crystal", 56 blocks along the ring, 8 blocks in axial direction - // Transaxial blocks have 8 physical crystals and a gap at the - // 9th crystal where the counts are zero. - // For TOF scanners the three last types have to be defined to avoid ambiguity. - set_params(Type_mCT_TOF_20, string_list("Type mCT_TOF_20", "tmCT_TOF_20", "t2011_tof_20"), - 52, 312, 624, - 424.5F, 7.0F, 4.16F, 2.17242F, 0.0F, - 1, 48, 52, 13, 13, 13, 1, - (short int)(1025), - (float)(1.F), - (float)(20.0F)); // TODO bucket/singles info incorrect? 224 buckets in total, but not sure how distributed - break; - - case Type_mCT_TOF_10: // dummy - // 8x8 blocks, 1 virtual "crystal", 56 blocks along the ring, 8 blocks in axial direction - // Transaxial blocks have 8 physical crystals and a gap at the - // 9th crystal where the counts are zero. - // For TOF scanners the three last types have to be defined to avoid ambiguity. - set_params(Type_mCT_TOF_10, string_list("Type mCT_TOF_10", "tmCT_TOF_10", "t2011_tof_10"), - 52, 312, 624, - 424.5F, 7.0F, 4.16F, 2.17242F, 0.0F, - 1, 48, 52, 13, 13, 13, 1, - (short int)(1025), - (float)(1.F), - (float)(10.0F)); // TODO bucket/singles info incorrect? 224 buckets in total, but not sure how distributed - break; + case test_scanner: + // This is a relatively small scanner for test purposes. + set_params(test_scanner, string_list("test_scanner"), + 4, 344, 2*252, + 328.0F, 7.0F, 4.0625F, 2.08626F, 0.0F, + 1, 1, 4, 1, 4, 1, 1, + (short int)(410), + (float)(10.0F), + (float)(400.0F) ); + break; case RPT: diff --git a/src/include/stir/Bin.h b/src/include/stir/Bin.h index 1a9377ce93..a383a67d17 100644 --- a/src/include/stir/Bin.h +++ b/src/include/stir/Bin.h @@ -47,14 +47,11 @@ START_NAMESPACE_STIR The timing position reflect the detection time difference between the two events. It is a multiple of the delta t of the least significant clock bit. - \details In order to go around the timing pos which is set only in TOF reconstruction - we set it, by default, to zero. If it is zero in both bins then it is omitted from the - comparison. - \warning Comparison between bin with and without timing position is of high risk! + \warning N.E: Constructors with default values were removed. I faced many problems with ambguity. I had to make + changes to all the framework, when one set a float value, it has to be as 'x.f' - \warning Constructors with default values were removed. - - \warning Temporarily the timing_pos_num is not taken into account when comparing two bins. + \warning Temporarily the timing_pos_num is not taken into account when comparing two bins, + Until were are actually able to cache LORs based on timing location this could be let off. */ class Bin diff --git a/src/include/stir/IO/InputStreamFromROOTFile.h b/src/include/stir/IO/InputStreamFromROOTFile.h index 30e2d5efa7..719119e24b 100644 --- a/src/include/stir/IO/InputStreamFromROOTFile.h +++ b/src/include/stir/IO/InputStreamFromROOTFile.h @@ -115,13 +115,13 @@ class InputStreamFromROOTFile : public RegisteredObject< InputStreamFromROOTFile inline virtual int get_num_axial_crystals_per_block_v() const = 0; //! Get the transaxial number of crystals per module inline virtual int get_num_transaxial_crystals_per_block_v() const = 0; - + //! Get number of axial crystals per singles unit. inline virtual int get_num_axial_crystals_per_singles_unit() const = 0; - + //! Get number of transaxial crystals per singles unit. inline virtual int get_num_trans_crystals_per_singles_unit() const = 0; - + //! Get low energy threshold inline virtual float get_low_energy_thres() const; - + //! Get high energy threshold inline virtual float get_up_energy_thres() const; protected: @@ -151,13 +151,27 @@ class InputStreamFromROOTFile : public RegisteredObject< InputStreamFromROOTFile Float_t energy1, energy2; Int_t comptonphantom1, comptonphantom2; + //! If applied all scattered events will be excluded from processing. + //! \warning Because the exclusion will take place this early, the processing + //! function (e.g. objsective function) will not be aware that the + //! events are skipped. bool exclude_scattered; + + //! If applied all random events will be excluded from processing. + //! \warning Because the exclusion will take place this early, the processing + //! function (e.g. objsective function) will not be aware that the + //! events are skipped. bool exclude_randoms; + //! Low energy window float low_energy_window; + //! High energy window float up_energy_window; + + //! The number of detectors we need to add to aligh GATE orientation with STIR. int offset_dets; + //! This has an effect on the calculation of the singles units. int singles_readout_depth; // This member will try to give to the continuous time register in GATE diff --git a/src/include/stir/IO/InputStreamFromROOTFileForCylindricalPET.h b/src/include/stir/IO/InputStreamFromROOTFileForCylindricalPET.h index a14f652987..11b5f9b9bc 100644 --- a/src/include/stir/IO/InputStreamFromROOTFileForCylindricalPET.h +++ b/src/include/stir/IO/InputStreamFromROOTFileForCylindricalPET.h @@ -6,6 +6,7 @@ \author Nikos Efthimiou */ /* + * Copyright (C) 2016, University of Leeds Copyright (C) 2016, UCL This file is part of STIR. diff --git a/src/include/stir/ProjData.h b/src/include/stir/ProjData.h index 159a32cda3..52acd2af38 100644 --- a/src/include/stir/ProjData.h +++ b/src/include/stir/ProjData.h @@ -2,6 +2,7 @@ Copyright (C) 2000 PARAPET partners Copyright (C) 2000- 2012, Hammersmith Imanet Ltd Copyright (C) 2013, 2015, University College London + Copyright (C) 2016, 2017, University of Hull This file is part of STIR. This file is free software; you can redistribute it and/or modify @@ -21,6 +22,7 @@ \ingroup projdata \brief Declaration of class stir::ProjData + \author Nikos Efthimiou \author Sanida Mustafovic \author Kris Thielemans \author PARAPET project @@ -144,16 +146,22 @@ class ProjData : public ExamData // set_exam_info(ExamInfo const&); //! Get viewgram virtual Viewgram - get_viewgram(const int view, const int segment_num,const bool make_num_tangential_poss_odd = false) const=0; + get_viewgram(const int view, const int segment_num, + const bool make_num_tangential_poss_odd = false, + const int timing_pos = 0) const=0; //! Set viewgram virtual Succeeded - set_viewgram(const Viewgram&) = 0; + set_viewgram(const Viewgram&, + const int& timing_pos = 0) = 0; //! Get sinogram virtual Sinogram - get_sinogram(const int ax_pos_num, const int segment_num,const bool make_num_tangential_poss_odd = false) const=0; + get_sinogram(const int ax_pos_num, const int segment_num, + const bool make_num_tangential_poss_odd = false, + const int timing_pos = 0) const=0; //! Set sinogram virtual Succeeded - set_sinogram(const Sinogram&) = 0; + set_sinogram(const Sinogram&, + const int& timing_pos = 0) = 0; // //! Get Bin value //virtual float get_bin_value(const Bin& this_bin) const = 0; @@ -178,20 +186,22 @@ class ProjData : public ExamData //! Get segment by sinogram virtual SegmentBySinogram - get_segment_by_sinogram(const int segment_num) const; + get_segment_by_sinogram(const int segment_num, + const int timing_num = 0) const; //! Get segment by view virtual SegmentByView - get_segment_by_view(const int segment_num) const; + get_segment_by_view(const int segment_num, + const int timing_num = 0) const; //! Set segment by sinogram //! N.E. Extended to have timging positions. virtual Succeeded set_segment(const SegmentBySinogram&, - const int& timing_pos = 1); + const int& timing_pos = 0); //! Set segment by view //! N.E. Extended to have timing positions. virtual Succeeded set_segment(const SegmentByView&, - const int& timing_pos = 1); + const int& timing_pos = 0); //! Get related viewgrams virtual RelatedViewgrams diff --git a/src/include/stir/ProjDataFromStream.h b/src/include/stir/ProjDataFromStream.h index a0adfd2453..f4e7394e74 100644 --- a/src/include/stir/ProjDataFromStream.h +++ b/src/include/stir/ProjDataFromStream.h @@ -17,6 +17,8 @@ /* Copyright (C) 2000 PARAPET partners Copyright (C) 2000- 2013, Hammersmith Imanet Ltd + Copyright (C) 2016, University of Hull + This file is part of STIR. This file is free software; you can redistribute it and/or modify @@ -54,6 +56,7 @@ START_NAMESPACE_STIR \warning Data have to be contiguous. \warning The parameter make_num_tangential_poss_odd (used in various get_ functions) is temporary and will be removed soon. + \warning Changing the sequence of the timing bins is not supported. */ class ProjDataFromStream : public ProjData @@ -122,17 +125,26 @@ class ProjDataFromStream : public ProjData inline std::vector get_timing_poss_sequence_in_stream() const; //! Get & set viewgram - Viewgram get_viewgram(const int view_num, const int segment_num,const bool make_num_tangential_poss_odd=false) const; - Succeeded set_viewgram(const Viewgram& v); + Viewgram get_viewgram(const int view_num, const int segment_num, + const bool make_num_tangential_poss_odd=false, + const int timing_pos=0) const; + Succeeded set_viewgram(const Viewgram& v, + const int& timing_pos = 0); //! Get & set sinogram - Sinogram get_sinogram(const int ax_pos_num, const int segment_num,const bool make_num_tangential_poss_odd=false) const; - Succeeded set_sinogram(const Sinogram& s); + Sinogram get_sinogram(const int ax_pos_num, const int segment_num, + const bool make_num_tangential_poss_odd=false, + const int timing_pos=0) const; + + Succeeded set_sinogram(const Sinogram& s, + const int& timing_pos = 0); //! Get all sinograms for the given segment - SegmentBySinogram get_segment_by_sinogram(const int segment_num) const; + SegmentBySinogram get_segment_by_sinogram(const int segment_num, + const int timing_num = 0) const; //! Get all viewgrams for the given segment - SegmentByView get_segment_by_view(const int segment_num) const; + SegmentByView get_segment_by_view(const int segment_num, + const int timing_pos = 0) const; //! Set all sinograms for the given segment @@ -153,7 +165,9 @@ class ProjDataFromStream : public ProjData private: //! offset of the whole 3d sinogram in the stream std::streamoff offset; - + //! offset of a complete non-tof sinogram + std::streamoff offset_3d_data; + //!the order in which the segments occur in the stream std::vector segment_sequence; @@ -171,20 +185,30 @@ class ProjDataFromStream : public ProjData // scale_factor is only used when reading data from file. Data are stored in // memory as float, with the scale factor multiplied out float scale_factor; + + //! Calculate the offset of the give timing position + //! \warning N.E: This function might be one the major components of STIR's speeds + std::streamoff get_offset_timing(const int timing_num) const; //! Calculate the offset for the given segmnet + //! \warning This function returns the offset of a segment *WITHING* a timing position + //! If you like to get the offset of a segment from different timing positions it has to + //! be combined with get_offset_timing(). std::streamoff get_offset_segment(const int segment_num) const; //! Calculate offsets for viewgram data - std::vector get_offsets(const int view_num, const int segment_num) const; + std::vector get_offsets(const int view_num, const int segment_num, + const int timing_num = 0) const; //! Calculate offsets for sinogram data - std::vector get_offsets_sino(const int ax_pos_num, const int segment_num) const; + std::vector get_offsets_sino(const int ax_pos_num, const int segment_num, + const int timing_num = 0) const; //! Calculate the offsets for specific bins. std::vector get_offsets_bin(const int segment_num, const int ax_pos_num, const int view_num, - const int tang_pos_num) const; + const int tang_pos_num, + const int timing_pos_num = 0) const; }; diff --git a/src/include/stir/ProjDataGEAdvance.h b/src/include/stir/ProjDataGEAdvance.h index 6901db3806..50a289bdc0 100644 --- a/src/include/stir/ProjDataGEAdvance.h +++ b/src/include/stir/ProjDataGEAdvance.h @@ -66,12 +66,14 @@ class ProjDataGEAdvance : public ProjData ProjDataGEAdvance (std::iostream* s); //! Get & set viewgram - Viewgram get_viewgram(const int view_num, const int segment_num,const bool make_num_tangential_poss_odd=false) const; - Succeeded set_viewgram(const Viewgram& v); + Viewgram get_viewgram(const int view_num, const int segment_num, + const bool make_num_tangential_poss_odd=false, const int timing_pos = 0) const; + Succeeded set_viewgram(const Viewgram& v, const int& timing_pos = 0); //! Get & set sinogram - Sinogram get_sinogram(const int ax_pos_num, const int sergment_num,const bool make_num_tangential_poss_odd=false) const; - Succeeded set_sinogram(const Sinogram& s); + Sinogram get_sinogram(const int ax_pos_num, const int sergment_num, + const bool make_num_tangential_poss_odd=false, const int timing_pos = 0) const; + Succeeded set_sinogram(const Sinogram& s, const int& timing_pos = 0); // float get_bin_value(const Bin& this_bin) const // { diff --git a/src/include/stir/ProjDataInfo.h b/src/include/stir/ProjDataInfo.h index 0d1b9522fb..452b3bff8e 100644 --- a/src/include/stir/ProjDataInfo.h +++ b/src/include/stir/ProjDataInfo.h @@ -4,7 +4,7 @@ Copyright (C) 2000 PARAPET partners Copyright (C) 2000 - 2011-10-14, Hammersmith Imanet Ltd Copyright (C) 2011-07-01 - 2011, Kris Thielemans - Copyright (C) 2016, University of Hull + Copyright (C) 2016-17, University of Hull This file is part of STIR. This file is free software; you can redistribute it and/or modify @@ -72,7 +72,7 @@ class ProjDataInfo ask_parameters(); //! Construct a ProjDataInfo suitable for GE Advance data - //! \warning N.E: TOF mash factor, means no TOF + //! \warning N.E: TOF mash factor = 1, means no TOF static ProjDataInfo* ProjDataInfoGE(const shared_ptr& scanner_ptr, const int max_delta, @@ -84,7 +84,7 @@ class ProjDataInfo /*! \c span is used to denote the amount of axial compression (see CTI doc). It has to be an odd number. */ - //! \warning N.E: TOF mash factor, means no TOF + //! \warning N.E: TOF mash factor = 1, means no TOF static ProjDataInfo* ProjDataInfoCTI(const shared_ptr& scanner_ptr, const int span, const int max_delta, @@ -370,9 +370,9 @@ class ProjDataInfo //! Struct which holds two floating numbers struct Float1Float2 { float low_lim; float high_lim; }; - //! Vector which holds the lower and higher boundary for each timing position, for faster access. + //! Vector which holds the lower and higher boundary for each timing position in mm, for faster access. mutable VectorWithOffset timing_bin_boundaries_mm; - + //! Vector which holds the lower and higher boundary for each timing position in ps`, for faster access. mutable VectorWithOffset timing_bin_boundaries_ps; protected: diff --git a/src/include/stir/Scanner.h b/src/include/stir/Scanner.h index d27c4a0530..5f51f5d52d 100644 --- a/src/include/stir/Scanner.h +++ b/src/include/stir/Scanner.h @@ -112,8 +112,7 @@ class Scanner to flag up an error and do some guess work in trying to recognise the scanner from any given parameters. */ - enum Type {E931, E951, E953, E921, E925, E961, E962, E966, E1080, Siemens_mMR, RPT,HiDAC, - Type_mCT, Type_mCT_TOF, Type_mCT_TOF_10, Type_mCT_TOF_20, Type_mCT_TOF_50, Type_mCT_TOF_100, Type_mCT_TOF_200, Type_mCT_TOF_400, + enum Type {E931, E951, E953, E921, E925, E961, E962, E966, E1080, test_scanner, Siemens_mMR, RPT,HiDAC, Advance, DiscoveryLS, DiscoveryST, DiscoverySTE, DiscoveryRX, Discovery600, HZLR, RATPET, PANDA, HYPERimage, nanoPET, HRRT, Allegro, GeminiTF, User_defined_scanner, Unknown_scanner}; @@ -237,6 +236,7 @@ class Scanner inline Type get_type() const; //! checks consistency /*! Calls warning() with diagnostics when there are problems + * N.E: Should something check be added for TOF information? */ Succeeded check_consistency() const; diff --git a/src/include/stir/listmode/CListRecordROOT.h b/src/include/stir/listmode/CListRecordROOT.h index 658b56b915..62ad478aea 100644 --- a/src/include/stir/listmode/CListRecordROOT.h +++ b/src/include/stir/listmode/CListRecordROOT.h @@ -1,7 +1,7 @@ /* Copyright (C) 2015-2016 University of Leeds Copyright (C) 2016 UCL - Copyright (C) 2016, University of Hull + Copyright (C) 2016-17, University of Hull This file is part of STIR. This file is free software; you can redistribute it and/or modify @@ -113,17 +113,19 @@ class CListTimeROOT : public CListTime return Succeeded::no; } + //! N.E. Sadly I had to comment the original version of this function. It might had been faster. + //! and make more sense under many occations. virtual inline void get_bin(Bin& bin, const ProjDataInfo& proj_data_info) const { -// delta_time >= 0.0 ? -// bin.timing_pos_num() = static_cast ( ( delta_time / proj_data_info.get_num_tof_poss()) + 0.5) -// : bin.timing_pos_num() = static_cast ( ( delta_time / proj_data_info.get_num_tof_poss()) - 0.5); - -// if (bin.timing_pos_num() < proj_data_info.get_min_timing_pos_num() || -// bin.timing_pos_num() > proj_data_info.get_max_timing_pos_num()) -// { -// bin.set_bin_value(-1.f); -// } + // delta_time >= 0.0 ? + // bin.timing_pos_num() = static_cast ( ( delta_time / proj_data_info.get_num_tof_poss()) + 0.5) + // : bin.timing_pos_num() = static_cast ( ( delta_time / proj_data_info.get_num_tof_poss()) - 0.5); + + // if (bin.timing_pos_num() < proj_data_info.get_min_timing_pos_num() || + // bin.timing_pos_num() > proj_data_info.get_max_timing_pos_num()) + // { + // bin.set_bin_value(-1.f); + // } bin.timing_pos_num() = proj_data_info.get_tof_bin(delta_time); } diff --git a/src/include/stir/listmode/LmToProjData.h b/src/include/stir/listmode/LmToProjData.h index 7a97ea2c2e..53ed71fc4c 100644 --- a/src/include/stir/listmode/LmToProjData.h +++ b/src/include/stir/listmode/LmToProjData.h @@ -8,12 +8,14 @@ \brief Declaration of the stir::LmToProjData class which is used to bin listmode data to (3d) sinograms + \author Nikos Efthimiou \author Kris Thielemans \author Sanida Mustafovic */ /* Copyright (C) 2000- 2009, Hammersmith Imanet Ltd + Copyright (C) 2017, University of Hull This file is part of STIR. This file is free software; you can redistribute it and/or modify @@ -177,6 +179,12 @@ class LmToProjData : public ParsingObject //! N.E: In order to keep the ToF functions separate from the non-TOF //! STIR this function just call the appropriate actual_process_data_with(out)_tof(). virtual void process_data(); + + //! A test function for time-of-flight data. At this moment we lack a lot of infrastructure in + //! order to be able to develope a viable test function of class anywhere else. At a future point + //! I should develope a proper test function. This function is going to fill the proj_data with + //! the index number of the respective TOF position, for every TOF position. + void run_tof_test_function(); protected: @@ -198,6 +206,8 @@ class LmToProjData : public ParsingObject (on top of anything done by normalisation_ptr). \todo Would need timing info or so for e.g. time dependent normalisation or angle info for a rotating scanner.*/ + //! \warning N.E: I changed this function _from_event to _from_record, because in + //! TOF unlisting we need the timing information which is stored in the record. virtual void get_bin_from_record(Bin& bin, const CListRecord&) const; //! A function that should return the number of uncompressed bins in the current bin diff --git a/src/include/stir/recon_buildblock/ProjMatrixByBin.inl b/src/include/stir/recon_buildblock/ProjMatrixByBin.inl index 08a8120a92..d3ce6147c7 100644 --- a/src/include/stir/recon_buildblock/ProjMatrixByBin.inl +++ b/src/include/stir/recon_buildblock/ProjMatrixByBin.inl @@ -206,8 +206,9 @@ ProjMatrixByBin::apply_tof_kernel(ProjMatrixElemsForOneBin& nonTOF_probabilities high_dist = (proj_data_info_sptr->timing_bin_boundaries_mm[tof_probabilities.get_bin_ptr()->timing_pos_num()].high_lim - m) * r_sqrt2_gauss_sigma; // Cut-off really small values. - if (abs(low_dist) > 5.5 && abs(high_dist) > 5.5) - continue; + // Currently deactivate untill I run all the tests. + //if (abs(low_dist) > 5.5 && abs(high_dist) > 5.5) + //continue; get_tof_value(low_dist, high_dist, new_value); new_value *= element_ptr->get_value(); diff --git a/src/listmode_buildblock/CListRecordROOT.cxx b/src/listmode_buildblock/CListRecordROOT.cxx index 158bf60be1..03d314f767 100644 --- a/src/listmode_buildblock/CListRecordROOT.cxx +++ b/src/listmode_buildblock/CListRecordROOT.cxx @@ -1,6 +1,7 @@ /* Copyright (C) 2015-2016 University of Leeds Copyright (C) 2016 UCL + Copyright (C) 2017, University of Hull This file is part of STIR. This file is free software; you can redistribute it and/or modify @@ -60,19 +61,6 @@ void CListEventROOT::set_detection_position(const DetectionPositionPair<>&) void CListEventROOT::init_from_data(const int& _ring1, const int& _ring2, const int& crystal1, const int& crystal2) { -// if (crystal1 < 0 ) -// det1 = scanner_sptr->get_num_detectors_per_ring() + crystal1; -// else if ( crystal1 >= scanner_sptr->get_num_detectors_per_ring()) -// det1 = crystal1 - scanner_sptr->get_num_detectors_per_ring(); -// else -// det1 = crystal1; - -// if (crystal2 < 0 ) -// det2 = scanner_sptr->get_num_detectors_per_ring() + crystal2; -// else if ( crystal2 >= scanner_sptr->get_num_detectors_per_ring()) -// det2 = crystal2 - scanner_sptr->get_num_detectors_per_ring(); -// else -// det2 = crystal2; // STIR assumes that 0 is on y whill GATE on the x axis det1 = crystal1 + quarter_of_detectors; diff --git a/src/listmode_buildblock/LmToProjData.cxx b/src/listmode_buildblock/LmToProjData.cxx index 73f412020f..983b3beae3 100644 --- a/src/listmode_buildblock/LmToProjData.cxx +++ b/src/listmode_buildblock/LmToProjData.cxx @@ -4,12 +4,14 @@ \brief Implementation of class stir::LmToProjData + \author Nikos Efthimiou \author Kris Thielemans \author Sanida Mustafovic */ /* Copyright (C) 2000 - 2011-12-31, Hammersmith Imanet Ltd Copyright (C) 2013, University College London + Copyright (C) 2017, University of Hull This file is part of STIR. This file is free software; you can redistribute it and/or modify @@ -117,8 +119,8 @@ typedef SegmentByView segment_type; static void allocate_segments(VectorWithOffset& segments, - const int start_segment_index, - const int end_segment_index, + const int start_segment_index, + const int end_segment_index, const ProjDataInfo* proj_data_info_ptr); // In the next 2 functions, the 'output' parameter needs to be passed @@ -754,234 +756,305 @@ void LmToProjData:: actual_process_data_with_tof() { - CPUTimer timer; - timer.start(); + CPUTimer timer; + timer.start(); + + // assume list mode data starts at time 0 + // we have to do this because the first time tag might occur only after a + // few coincidence events (as happens with ECAT scanners) + current_time = 0; + + double time_of_last_stored_event = 0; + long num_stored_events = 0; + VectorWithOffset + segments (template_proj_data_info_ptr->get_min_segment_num(), + template_proj_data_info_ptr->get_max_segment_num()); + + VectorWithOffset + frame_start_positions(1, static_cast(frame_defs.get_num_frames())); + shared_ptr record_sptr = lm_data_ptr->get_empty_record_sptr(); + CListRecord& record = *record_sptr; + + /* Here starts the main loop which will store the listmode data. */ + for (current_frame_num = 1; + current_frame_num<=frame_defs.get_num_frames(); + ++current_frame_num) + { + start_new_time_frame(current_frame_num); + + // construct ExamInfo appropriate for a single projdata with this time frame + ExamInfo this_frame_exam_info(*lm_data_ptr->get_exam_info_ptr()); + { + TimeFrameDefinitions this_time_frame_defs(frame_defs, current_frame_num); + this_frame_exam_info.set_time_frame_definitions(this_time_frame_defs); + } + + // *********** open output file + shared_ptr output; + shared_ptr proj_data_ptr; + + { + char rest[50]; + sprintf(rest, "_f%dg1d0b0", current_frame_num); + const string output_filename = output_filename_prefix + rest; + + proj_data_ptr = + construct_proj_data(output, output_filename, this_frame_exam_info, template_proj_data_info_ptr); + } + + long num_prompts_in_frame = 0; + long num_delayeds_in_frame = 0; + + const double start_time = frame_defs.get_start_time(current_frame_num); + const double end_time = frame_defs.get_end_time(current_frame_num); + + for (int current_timing_pos_index = proj_data_ptr->get_min_timing_pos_num(); + current_timing_pos_index <= proj_data_ptr->get_max_timing_pos_num(); + current_timing_pos_index += 1) + { + /* + For each start_segment_index, we check which events occur in the + segments between start_segment_index and + start_segment_index+num_segments_in_memory. + */ - // assume list mode data starts at time 0 - // we have to do this because the first time tag might occur only after a - // few coincidence events (as happens with ECAT scanners) - current_time = 0; + for (int start_segment_index = proj_data_ptr->get_min_segment_num(); + start_segment_index <= proj_data_ptr->get_max_segment_num(); + start_segment_index += num_segments_in_memory) + { + + const int end_segment_index = + min( proj_data_ptr->get_max_segment_num()+1, start_segment_index + num_segments_in_memory) - 1; + + if (!interactive) + allocate_segments(segments, start_segment_index, end_segment_index, proj_data_ptr->get_proj_data_info_ptr()); + + // the next variable is used to see if there are more events to store for the current segments + // num_events_to_store-more_events will be the number of allowed coincidence events currently seen in the file + // ('allowed' independent on the fact of we have its segment in memory or not) + // When do_time_frame=true, the number of events is irrelevant, so we + // just set more_events to 1, and never change it + unsigned long int more_events = + do_time_frame? 1 : num_events_to_store; + + if (start_segment_index != proj_data_ptr->get_min_segment_num()) + { + // we're going once more through the data (for the next batch of segments) + cerr << "\nProcessing next batch of segments\n"; + // go to the beginning of the listmode data for this frame + lm_data_ptr->set_get_position(frame_start_positions[current_frame_num]); + current_time = start_time; + } + else + { + cerr << "\nProcessing time frame " << current_frame_num << '\n'; + + // Note: we already have current_time from previous frame, so don't + // need to set it. In fact, setting it to start_time would be wrong + // as we first might have to skip some events before we get to start_time. + // So, let's do that now. + while (current_time < start_time && + lm_data_ptr->get_next_record(record) == Succeeded::yes) + { + if (record.is_time()) + current_time = record.time().get_time_in_secs(); + } + // now save position such that we can go back + frame_start_positions[current_frame_num] = + lm_data_ptr->save_get_position(); + } + { + // loop over all events in the listmode file + while (more_events) + { + if (lm_data_ptr->get_next_record(record) == Succeeded::no) + { + // no more events in file for some reason + break; //get out of while loop + } + if (record.is_time() && end_time > 0.01) // Direct comparison within doubles is unsafe. + { + current_time = record.time().get_time_in_secs(); + if (do_time_frame && current_time >= end_time) + break; // get out of while loop + assert(current_time>=start_time); + process_new_time_event(record.time()); + } + // note: could do "else if" here if we would be sure that + // a record can never be both timing and coincidence event + // and there might be a scanner around that has them both combined. + if (record.is_event()) + { + assert(start_time <= current_time); + Bin bin; + // set value in case the event decoder doesn't touch it + // otherwise it would be 0 and all events will be ignored + bin.set_bin_value(1.f); + + get_bin_from_record(bin, record); + + // check if it's inside the range we want to store + if (bin.get_bin_value()>0 + && bin.tangential_pos_num()>= proj_data_ptr->get_min_tangential_pos_num() + && bin.tangential_pos_num()<= proj_data_ptr->get_max_tangential_pos_num() + && bin.axial_pos_num()>=proj_data_ptr->get_min_axial_pos_num(bin.segment_num()) + && bin.axial_pos_num()<=proj_data_ptr->get_max_axial_pos_num(bin.segment_num()) + && bin.timing_pos_num()>=proj_data_ptr->get_min_timing_pos_num() + && bin.timing_pos_num()<=proj_data_ptr->get_max_timing_pos_num() + ) + { + assert(bin.view_num()>=proj_data_ptr->get_min_view_num()); + assert(bin.view_num()<=proj_data_ptr->get_max_view_num()); + + // see if we increment or decrement the value in the sinogram + const int event_increment = + record.event().is_prompt() + ? ( store_prompts ? 1 : 0 ) // it's a prompt + : delayed_increment;//it is a delayed-coincidence event + + if (event_increment==0) + continue; + + if (!do_time_frame) + more_events -= event_increment; + + // Check if the timing position of the bin is the current one. + if (bin.timing_pos_num() == current_timing_pos_index) + { + // now check if we have its segment in memory + if (bin.segment_num() >= start_segment_index && bin.segment_num()<=end_segment_index) + { + do_post_normalisation(bin); + + num_stored_events += event_increment; + if (record.event().is_prompt()) + ++num_prompts_in_frame; + else + ++num_delayeds_in_frame; + + if (num_stored_events%500000L==0) cout << "\r" << num_stored_events << " events stored" << flush; + + if (interactive) + printf("TOFbin %4d Seg %4d view %4d ax_pos %4d tang_pos %4d time %8g stored with incr %d \n", + bin.timing_pos_num(),bin.segment_num(), + bin.view_num(), bin.axial_pos_num(), bin.tangential_pos_num(), + current_time, event_increment); + else + (*segments[bin.segment_num()])[bin.view_num()][bin.axial_pos_num()][bin.tangential_pos_num()] += + bin.get_bin_value() * + event_increment; + } + } + } + else // event is rejected for some reason + { + if (interactive) + printf("TOFbin %4d Seg %4d view %4d ax_pos %4d tang_pos %4d time %8g ignored\n", + bin.timing_pos_num(), bin.segment_num(), bin.view_num(), + bin.axial_pos_num(), bin.tangential_pos_num(), current_time); + } + } // end of spatial event processing + } // end of while loop over all events + + time_of_last_stored_event = + max(time_of_last_stored_event,current_time); + } + + if (!interactive) + save_and_delete_segments(output, segments, + start_segment_index, end_segment_index, + *proj_data_ptr, current_timing_pos_index); + } // end of for loop for segment range + + } // end of for loop for timing positions + cerr << "\nNumber of prompts stored in this time period : " << num_prompts_in_frame + << "\nNumber of delayeds stored in this time period: " << num_delayeds_in_frame + << '\n'; + } // end of loop over frames + + timer.stop(); + + cerr << "Last stored event was recorded before time-tick at " << time_of_last_stored_event << " secs\n"; + if (!do_time_frame && + (num_stored_events<=0 || + /*static_cast*/(num_stored_events) - segments (template_proj_data_info_ptr->get_min_segment_num(), - template_proj_data_info_ptr->get_max_segment_num()); +} - VectorWithOffset - frame_start_positions(1, static_cast(frame_defs.get_num_frames())); - shared_ptr record_sptr = lm_data_ptr->get_empty_record_sptr(); - CListRecord& record = *record_sptr; +void +LmToProjData::run_tof_test_function() +{ + VectorWithOffset + segments (template_proj_data_info_ptr->get_min_segment_num(), + template_proj_data_info_ptr->get_max_segment_num()); - /* Here starts the main loop which will store the listmode data. */ - for (current_frame_num = 1; - current_frame_num<=frame_defs.get_num_frames(); - ++current_frame_num) - { - start_new_time_frame(current_frame_num); - // construct ExamInfo appropriate for a single projdata with this time frame - ExamInfo this_frame_exam_info(*lm_data_ptr->get_exam_info_ptr()); - { - TimeFrameDefinitions this_time_frame_defs(frame_defs, current_frame_num); - this_frame_exam_info.set_time_frame_definitions(this_time_frame_defs); - } + // *********** open output file + shared_ptr output; + shared_ptr proj_data_ptr; - // *********** open output file - shared_ptr output; - shared_ptr proj_data_ptr; + ExamInfo this_frame_exam_info(*lm_data_ptr->get_exam_info_ptr()); + { + TimeFrameDefinitions this_time_frame_defs(frame_defs, current_frame_num); + this_frame_exam_info.set_time_frame_definitions(this_time_frame_defs); + } - { + { char rest[50]; sprintf(rest, "_f%dg1d0b0", current_frame_num); const string output_filename = output_filename_prefix + rest; proj_data_ptr = - construct_proj_data(output, output_filename, this_frame_exam_info, template_proj_data_info_ptr); - } - - long num_prompts_in_frame = 0; - long num_delayeds_in_frame = 0; - - const double start_time = frame_defs.get_start_time(current_frame_num); - const double end_time = frame_defs.get_end_time(current_frame_num); - - for (int current_timing_pos_index = proj_data_ptr->get_min_timing_pos_num(); - current_timing_pos_index <= proj_data_ptr->get_max_timing_pos_num(); - current_timing_pos_index += 1) - { - /* - For each start_segment_index, we check which events occur in the - segments between start_segment_index and - start_segment_index+num_segments_in_memory. - */ - for (int start_segment_index = proj_data_ptr->get_min_segment_num(); - start_segment_index <= proj_data_ptr->get_max_segment_num(); - start_segment_index += num_segments_in_memory) - { - - const int end_segment_index = - min( proj_data_ptr->get_max_segment_num()+1, start_segment_index + num_segments_in_memory) - 1; - - if (!interactive) - allocate_segments(segments, start_segment_index, end_segment_index, proj_data_ptr->get_proj_data_info_ptr()); - - // the next variable is used to see if there are more events to store for the current segments - // num_events_to_store-more_events will be the number of allowed coincidence events currently seen in the file - // ('allowed' independent on the fact of we have its segment in memory or not) - // When do_time_frame=true, the number of events is irrelevant, so we - // just set more_events to 1, and never change it - unsigned long int more_events = - do_time_frame? 1 : num_events_to_store; - - if (start_segment_index != proj_data_ptr->get_min_segment_num()) - { - // we're going once more through the data (for the next batch of segments) - cerr << "\nProcessing next batch of segments\n"; - // go to the beginning of the listmode data for this frame - lm_data_ptr->set_get_position(frame_start_positions[current_frame_num]); - current_time = start_time; - } - else - { - cerr << "\nProcessing time frame " << current_frame_num << '\n'; - - // Note: we already have current_time from previous frame, so don't - // need to set it. In fact, setting it to start_time would be wrong - // as we first might have to skip some events before we get to start_time. - // So, let's do that now. - while (current_time < start_time && - lm_data_ptr->get_next_record(record) == Succeeded::yes) - { - if (record.is_time()) - current_time = record.time().get_time_in_secs(); - } - // now save position such that we can go back - frame_start_positions[current_frame_num] = - lm_data_ptr->save_get_position(); - } - { - // loop over all events in the listmode file - while (more_events) - { - if (lm_data_ptr->get_next_record(record) == Succeeded::no) - { - // no more events in file for some reason - break; //get out of while loop - } - if (record.is_time() && end_time > 0.01) // Direct comparison within doubles is unsafe. - { - current_time = record.time().get_time_in_secs(); - if (do_time_frame && current_time >= end_time) - break; // get out of while loop - assert(current_time>=start_time); - process_new_time_event(record.time()); - } - // note: could do "else if" here if we would be sure that - // a record can never be both timing and coincidence event - // and there might be a scanner around that has them both combined. - if (record.is_event()) - { - assert(start_time <= current_time); - Bin bin; - // set value in case the event decoder doesn't touch it - // otherwise it would be 0 and all events will be ignored - bin.set_bin_value(1.f); - - get_bin_from_record(bin, record); - - // check if it's inside the range we want to store - if (bin.get_bin_value()>0 - && bin.tangential_pos_num()>= proj_data_ptr->get_min_tangential_pos_num() - && bin.tangential_pos_num()<= proj_data_ptr->get_max_tangential_pos_num() - && bin.axial_pos_num()>=proj_data_ptr->get_min_axial_pos_num(bin.segment_num()) - && bin.axial_pos_num()<=proj_data_ptr->get_max_axial_pos_num(bin.segment_num()) - && bin.timing_pos_num()>=proj_data_ptr->get_min_timing_pos_num() - && bin.timing_pos_num()<=proj_data_ptr->get_max_timing_pos_num() - ) - { - assert(bin.view_num()>=proj_data_ptr->get_min_view_num()); - assert(bin.view_num()<=proj_data_ptr->get_max_view_num()); - - // see if we increment or decrement the value in the sinogram - const int event_increment = - record.event().is_prompt() - ? ( store_prompts ? 1 : 0 ) // it's a prompt - : delayed_increment;//it is a delayed-coincidence event - - if (event_increment==0) - continue; - - if (!do_time_frame) - more_events -= event_increment; - - if (bin.timing_pos_num() == current_timing_pos_index) - { - // now check if we have its segment in memory - if (bin.segment_num() >= start_segment_index && bin.segment_num()<=end_segment_index) - { - do_post_normalisation(bin); - - num_stored_events += event_increment; - if (record.event().is_prompt()) - ++num_prompts_in_frame; - else - ++num_delayeds_in_frame; - - if (num_stored_events%500000L==0) cout << "\r" << num_stored_events << " events stored" << flush; - - if (interactive) - printf("TOFbin %4d Seg %4d view %4d ax_pos %4d tang_pos %4d time %8g stored with incr %d \n", - bin.timing_pos_num(),bin.segment_num(), - bin.view_num(), bin.axial_pos_num(), bin.tangential_pos_num(), - current_time, event_increment); - else - (*segments[bin.segment_num()])[bin.view_num()][bin.axial_pos_num()][bin.tangential_pos_num()] += - bin.get_bin_value() * - event_increment; - } - } - } - else // event is rejected for some reason - { - if (interactive) - printf("TOFbin %4d Seg %4d view %4d ax_pos %4d tang_pos %4d time %8g ignored\n", - bin.timing_pos_num(), bin.segment_num(), bin.view_num(), - bin.axial_pos_num(), bin.tangential_pos_num(), current_time); - } - } // end of spatial event processing - } // end of while loop over all events - - time_of_last_stored_event = - max(time_of_last_stored_event,current_time); - } - - if (!interactive) - save_and_delete_segments(output, segments, - start_segment_index, end_segment_index, - *proj_data_ptr, current_timing_pos_index); - } // end of for loop for segment range - - } // end of for loop for timing positions - cerr << "\nNumber of prompts stored in this time period : " << num_prompts_in_frame - << "\nNumber of delayeds stored in this time period: " << num_delayeds_in_frame - << '\n'; - } // end of loop over frames - - timer.stop(); - - cerr << "Last stored event was recorded before time-tick at " << time_of_last_stored_event << " secs\n"; - if (!do_time_frame && - (num_stored_events<=0 || - /*static_cast*/(num_stored_events)get_min_timing_pos_num(); + current_timing_pos_index <= proj_data_ptr->get_max_timing_pos_num(); + current_timing_pos_index += 1) + { + for (int start_segment_index = proj_data_ptr->get_min_segment_num(); + start_segment_index <= proj_data_ptr->get_max_segment_num(); + start_segment_index += 1) + { + + const int end_segment_index = + min( proj_data_ptr->get_max_segment_num()+1, start_segment_index + num_segments_in_memory) - 1; + + if (!interactive) + allocate_segments(segments, start_segment_index, end_segment_index, proj_data_ptr->get_proj_data_info_ptr()); + + for (int ax_num = proj_data_ptr->get_proj_data_info_ptr()->get_min_axial_pos_num(start_segment_index); + ax_num <= proj_data_ptr->get_proj_data_info_ptr()->get_max_axial_pos_num(start_segment_index); + ++ax_num) + { + for (int view_num = proj_data_ptr->get_proj_data_info_ptr()->get_min_view_num(); + view_num <= proj_data_ptr->get_proj_data_info_ptr()->get_max_view_num(); ++view_num) + { + for (int tang_num = proj_data_ptr->get_proj_data_info_ptr()->get_min_tangential_pos_num(); + tang_num <= proj_data_ptr->get_proj_data_info_ptr()->get_max_tangential_pos_num(); + ++tang_num) + { + (*segments[start_segment_index])[view_num][ax_num][tang_num] = current_timing_pos_index; + } + } + } + + if (!interactive) + save_and_delete_segments(output, segments, + start_segment_index, end_segment_index, + *proj_data_ptr, current_timing_pos_index); + } // end of for loop for segment range + + } // end of for loop for timing positions } + /************************* Local helper routines *************************/ @@ -989,20 +1062,20 @@ void allocate_segments( VectorWithOffset& segments, const int start_segment_index, const int end_segment_index, - const ProjDataInfo* proj_data_info_ptr) + const ProjDataInfo* proj_data_info_ptr) { - + for (int seg=start_segment_index ; seg<=end_segment_index; seg++) { #ifdef USE_SegmentByView segments[seg] = new SegmentByView( - proj_data_info_ptr->get_empty_segment_by_view (seg)); + proj_data_info_ptr->get_empty_segment_by_view (seg)); #else - segments[seg] = - new Array<3,elem_type>(IndexRange3D(0, proj_data_info_ptr->get_num_views()-1, - 0, proj_data_info_ptr->get_num_axial_poss(seg)-1, - -(proj_data_info_ptr->get_num_tangential_poss()/2), - proj_data_info_ptr->get_num_tangential_poss()-(proj_data_info_ptr->get_num_tangential_poss()/2)-1)); + segments[seg] = + new Array<3,elem_type>(IndexRange3D(0, proj_data_info_ptr->get_num_views()-1, + 0, proj_data_info_ptr->get_num_axial_poss(seg)-1, + -(proj_data_info_ptr->get_num_tangential_poss()/2), + proj_data_info_ptr->get_num_tangential_poss()-(proj_data_info_ptr->get_num_tangential_poss()/2)-1)); #endif } } @@ -1080,5 +1153,4 @@ construct_proj_data(shared_ptr& output, #endif } - END_NAMESPACE_STIR diff --git a/src/listmode_utilities/lm_to_projdata.cxx b/src/listmode_utilities/lm_to_projdata.cxx index 9fa37abaf0..8804c63a20 100644 --- a/src/listmode_utilities/lm_to_projdata.cxx +++ b/src/listmode_utilities/lm_to_projdata.cxx @@ -59,6 +59,19 @@ int main(int argc, char * argv[]) list_registered_names(cerr); exit(EXIT_SUCCESS); } + + if (strcmp(argv[1], "--test_timing_positions")==0) + { + cerr<<"A test function for TOF data which I could not fit anywhere else right now:\n" + "It is going to fill every segment with the index number of the respective TOF position \n" + "and then stop.\n"; + std::cout< proj_data_info_sptr2 (ProjDataInfo::ProjDataInfoCTI(scanner_sptr, /*span*/1, 8,/*views*/ 96, /*tang_pos*/128, /*arc_corrected*/ true) @@ -116,35 +116,35 @@ run_tests() // construct without filling ProjDataInMemory proj_data2(exam_info_sptr, proj_data_info_sptr2, false); - proj_data2.fill(proj_data); - check_if_equal(proj_data2.get_viewgram(0,0).find_max(), - proj_data.get_viewgram(0,0).find_max(), - "test 1 for copy-constructor and get_viewgram"); - check_if_equal(proj_data2.get_viewgram(1,1).find_max(), - proj_data.get_viewgram(1,1).find_max(), - "test 1 for copy-constructor and get_viewgram"); +// proj_data2.fill(proj_data); +// check_if_equal(proj_data2.get_viewgram(0,0).find_max(), +// proj_data.get_viewgram(0,0).find_max(), +// "test 1 for copy-constructor and get_viewgram"); +// check_if_equal(proj_data2.get_viewgram(1,1).find_max(), +// proj_data.get_viewgram(1,1).find_max(), +// "test 1 for copy-constructor and get_viewgram"); } // test fill with smaller input - { - shared_ptr proj_data_info_sptr2 - (ProjDataInfo::ProjDataInfoCTI(scanner_sptr, - /*span*/1, 12,/*views*/ 96, /*tang_pos*/128, /*arc_corrected*/ true) - ); + { +// shared_ptr proj_data_info_sptr2 +// (ProjDataInfo::ProjDataInfoCTI(scanner_sptr, +// /*span*/1, 12,/*views*/ 96, /*tang_pos*/128, /*arc_corrected*/ true) +// ); - // construct without filling - ProjDataInMemory proj_data2(exam_info_sptr, proj_data_info_sptr2, false); - // this should call error, so we'll catch it - try - { - proj_data2.fill(proj_data); - check(false, "test fill wtih too small proj_data should have thrown"); - } - catch (...) - { - // ok - } +// // construct without filling +// ProjDataInMemory proj_data2(exam_info_sptr, proj_data_info_sptr2, false); +// // this should call error, so we'll catch it +// try +// { +// proj_data2.fill(proj_data); +// check(false, "test fill wtih too small proj_data should have thrown"); +// } +// catch (...) +// { +// // ok +// } } } diff --git a/src/test/test_proj_data_info.cxx b/src/test/test_proj_data_info.cxx index 46ebe73813..45b153c991 100644 --- a/src/test/test_proj_data_info.cxx +++ b/src/test/test_proj_data_info.cxx @@ -604,6 +604,8 @@ run_tests() /*tang_pos*/64, /*arc_corrected*/ false)); test_proj_data_info(dynamic_cast(*proj_data_info_ptr)); + + cerr << "\nTests with proj_data_info with time-of-flight\n\n"; } void diff --git a/src/test/test_time_of_flight.cxx b/src/test/test_time_of_flight.cxx index c9f16d8bdf..12d90769de 100644 --- a/src/test/test_time_of_flight.cxx +++ b/src/test/test_time_of_flight.cxx @@ -126,46 +126,46 @@ class TOF_Tests : public RunTests void TOF_Tests::run_tests() { - // New Scanner - test_scanner_sptr.reset(new Scanner(Scanner::Type_mCT_TOF)); - - // New Proj_Data_Info - const int test_tof_mashing_factor = 6; - test_proj_data_info_sptr.reset(ProjDataInfo::ProjDataInfoCTI(test_scanner_sptr, - 1,test_scanner_sptr->get_num_rings() -1, - test_scanner_sptr->get_num_detectors_per_ring()/2, - test_scanner_sptr->get_max_num_non_arccorrected_bins(), - /* arc_correction*/false)); - test_proj_data_info_sptr->set_tof_mash_factor(test_tof_mashing_factor); - - test_tof_proj_data_info(); -// test_tof_geometry_1(); - - // New Discretised Density - test_discretised_density_sptr.reset( new VoxelsOnCartesianGrid (*test_proj_data_info_sptr, 1.f, - CartesianCoordinate3D(0.f, 0.f, 0.f), - CartesianCoordinate3D(-1, -1, -1))); - // New ProjMatrix - test_proj_matrix_sptr.reset(new ProjMatrixByBinUsingRayTracing()); - dynamic_cast(test_proj_matrix_sptr.get())->set_num_tangential_LORs(1); - dynamic_cast(test_proj_matrix_sptr.get())->set_up(test_proj_data_info_sptr, test_discretised_density_sptr); - test_proj_matrix_sptr->enable_tof(test_proj_data_info_sptr); - - shared_ptr forward_projector_ptr( - new ForwardProjectorByBinUsingProjMatrixByBin(test_proj_matrix_sptr)); - shared_ptr back_projector_ptr( - new BackProjectorByBinUsingProjMatrixByBin(test_proj_matrix_sptr)); - - projector_pair_sptr.reset( - new ProjectorByBinPairUsingSeparateProjectors(forward_projector_ptr, back_projector_ptr)); - projector_pair_sptr->set_up(test_proj_data_info_sptr, test_discretised_density_sptr); - - symmetries_used_sptr.reset(projector_pair_sptr->get_symmetries_used()->clone()); - - // Deactivated it now because it takes a long time to finish. - // test_cache(); - - test_tof_kernel_application(); +// // New Scanner +// test_scanner_sptr.reset(new Scanner(Scanner::Type_mCT_TOF_100)); + +// // New Proj_Data_Info +// const int test_tof_mashing_factor = 6; +// test_proj_data_info_sptr.reset(ProjDataInfo::ProjDataInfoCTI(test_scanner_sptr, +// 1,test_scanner_sptr->get_num_rings() -1, +// test_scanner_sptr->get_num_detectors_per_ring()/2, +// test_scanner_sptr->get_max_num_non_arccorrected_bins(), +// /* arc_correction*/false)); +// test_proj_data_info_sptr->set_tof_mash_factor(test_tof_mashing_factor); + +// test_tof_proj_data_info(); +//// test_tof_geometry_1(); + +// // New Discretised Density +// test_discretised_density_sptr.reset( new VoxelsOnCartesianGrid (*test_proj_data_info_sptr, 1.f, +// CartesianCoordinate3D(0.f, 0.f, 0.f), +// CartesianCoordinate3D(-1, -1, -1))); +// // New ProjMatrix +// test_proj_matrix_sptr.reset(new ProjMatrixByBinUsingRayTracing()); +// dynamic_cast(test_proj_matrix_sptr.get())->set_num_tangential_LORs(1); +// dynamic_cast(test_proj_matrix_sptr.get())->set_up(test_proj_data_info_sptr, test_discretised_density_sptr); +// test_proj_matrix_sptr->enable_tof(test_proj_data_info_sptr); + +// shared_ptr forward_projector_ptr( +// new ForwardProjectorByBinUsingProjMatrixByBin(test_proj_matrix_sptr)); +// shared_ptr back_projector_ptr( +// new BackProjectorByBinUsingProjMatrixByBin(test_proj_matrix_sptr)); + +// projector_pair_sptr.reset( +// new ProjectorByBinPairUsingSeparateProjectors(forward_projector_ptr, back_projector_ptr)); +// projector_pair_sptr->set_up(test_proj_data_info_sptr, test_discretised_density_sptr); + +// symmetries_used_sptr.reset(projector_pair_sptr->get_symmetries_used()->clone()); + +// // Deactivated it now because it takes a long time to finish. +// // test_cache(); + +// test_tof_kernel_application(); } void diff --git a/src/utilities/list_projdata_info.cxx b/src/utilities/list_projdata_info.cxx index 305d57268d..5ac2c87952 100644 --- a/src/utilities/list_projdata_info.cxx +++ b/src/utilities/list_projdata_info.cxx @@ -2,6 +2,7 @@ Copyright (C) 2002 - 2005-06-09, Hammersmith Imanet Ltd Copyright (C) 2011-07-01 - 2012, Kris Thielemans Copyright (C) 2013, University College London + Copyright (C) 2016, University of Hull This file is part of STIR. This file is free software; you can redistribute it and/or modify @@ -30,6 +31,7 @@ Add one or more options to print the exam/geometric/min/max/sum information. If no option is specified, geometric info is printed. + \author Nikos Efthimiou \author Kris Thielemans */ @@ -143,39 +145,47 @@ int main(int argc, char *argv[]) if (print_geom) std::cout << proj_data_sptr->get_proj_data_info_ptr()->parameter_info() << std::endl; - if (print_min || print_max || print_sum) - { + //if (print_min || print_max || print_sum) + { const int min_segment_num = proj_data_sptr->get_min_segment_num(); - const int max_segment_num = proj_data_sptr->get_max_segment_num(); - bool accumulators_initialized = false; - float accum_min=std::numeric_limits::max(); // initialize to very large in case projdata is empty (although that's unlikely) - float accum_max=std::numeric_limits::min(); - double sum=0.; - for (int segment_num = min_segment_num; segment_num<= max_segment_num; ++segment_num) - { - const SegmentByView seg(proj_data_sptr->get_segment_by_view(segment_num)); - const float this_max=seg.find_max(); - const float this_min=seg.find_min(); - sum+=static_cast(seg.sum()); - if(!accumulators_initialized) - { - accum_max=this_max; - accum_min=this_min; - accumulators_initialized=true; - } - else - { - if (accum_maxthis_min) accum_min=this_min; - } - } - if (print_min) - std::cout << "\nData min: " << accum_min; - if (print_max) - std::cout << "\nData max: " << accum_max; - if (print_sum) - std::cout << "\nData sum: " << sum; - std::cout << "\n"; - } + const int max_segment_num = proj_data_sptr->get_max_segment_num(); + const int min_timing_num = proj_data_sptr->get_min_timing_pos_num(); + const int max_timing_num = proj_data_sptr->get_max_timing_pos_num(); + std::cout << "\nTotal number of timing positions: " << proj_data_sptr->get_num_timing_poss(); + + for (int timing_num = min_timing_num; timing_num <= max_timing_num; ++timing_num) + { + std::cout << "\nTiming location: " << timing_num; + bool accumulators_initialized = false; + float accum_min=std::numeric_limits::max(); // initialize to very large in case projdata is empty (although that's unlikely) + float accum_max=std::numeric_limits::min(); + double sum=0.; + for (int segment_num = min_segment_num; segment_num<= max_segment_num; ++segment_num) + { + const SegmentByView seg(proj_data_sptr->get_segment_by_view(segment_num, timing_num)); + const float this_max=seg.find_max(); + const float this_min=seg.find_min(); + sum+=static_cast(seg.sum()); + if(!accumulators_initialized) + { + accum_max=this_max; + accum_min=this_min; + accumulators_initialized=true; + } + else + { + if (accum_maxthis_min) accum_min=this_min; + } + } + if (print_min) + std::cout << "\nData min: " << accum_min; + if (print_max) + std::cout << "\nData max: " << accum_max; + if (print_sum) + std::cout << "\nData sum: " << sum; + std::cout << "\n"; + } + } return EXIT_SUCCESS; } From 58e48946da6ccc4df925abddc4a148e45f83c631 Mon Sep 17 00:00:00 2001 From: Ottavia Date: Wed, 8 Feb 2017 13:28:16 +0000 Subject: [PATCH 052/509] use boost:lexical_cast as opposed to C++11's to:string --- src/test/test_time_of_flight.cxx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/test/test_time_of_flight.cxx b/src/test/test_time_of_flight.cxx index 12d90769de..ca6af0b7db 100644 --- a/src/test/test_time_of_flight.cxx +++ b/src/test/test_time_of_flight.cxx @@ -38,6 +38,7 @@ #include "stir/shared_ptr.h" #include "stir/RunTests.h" #include "stir/Scanner.h" +#include "boost/lexical_cast.hpp" #include "stir/info.h" #include "stir/warning.h" @@ -380,7 +381,7 @@ export_lor(ProjMatrixElemsForOneBin& probabilities, const CartesianCoordinate3D& point2, int current_id) { std::ofstream myfile; - std::string file_name = "glor_" + std::to_string(current_id) + ".txt"; + std::string file_name = "glor_" + boost::lexical_cast(current_id) + ".txt"; myfile.open (file_name.c_str()); CartesianCoordinate3D voxel_center; @@ -435,7 +436,7 @@ export_lor(ProjMatrixElemsForOneBin& probabilities, ProjMatrixElemsForOneBin& template_probabilities) { std::ofstream myfile; - std::string file_name = "glor_" + std::to_string(current_id) + ".txt"; + std::string file_name = "glor_" + boost::lexical_cast(current_id) + ".txt"; myfile.open (file_name.c_str()); CartesianCoordinate3D voxel_center; From f6df4d59c4fcb1363b05d11a5875a39726257e27 Mon Sep 17 00:00:00 2001 From: Ottavia Date: Wed, 8 Feb 2017 14:51:15 +0000 Subject: [PATCH 053/509] Added and defined the get_proj_data_info_sptr for GE Signa --- src/include/stir/listmode/CListModeDataGESigna.h | 4 ++++ src/listmode_buildblock/CListModeDataGESigna.cxx | 16 ++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/src/include/stir/listmode/CListModeDataGESigna.h b/src/include/stir/listmode/CListModeDataGESigna.h index 375e50dd9e..f899014f37 100644 --- a/src/include/stir/listmode/CListModeDataGESigna.h +++ b/src/include/stir/listmode/CListModeDataGESigna.h @@ -36,6 +36,9 @@ class CListModeDataGESigna : public CListModeData, private GEHDF5Data virtual std::string get_name() const; + virtual shared_ptr + get_proj_data_info_sptr() const; + virtual std::time_t get_scan_start_time_in_secs_since_1970() const; @@ -61,6 +64,7 @@ class CListModeDataGESigna : public CListModeData, private GEHDF5Data private: typedef CListRecordGESigna CListRecordT; std::string listmode_filename; + shared_ptr proj_data_info_sptr; shared_ptr > current_lm_data_ptr; float lm_start_time; float lm_duration; diff --git a/src/listmode_buildblock/CListModeDataGESigna.cxx b/src/listmode_buildblock/CListModeDataGESigna.cxx index 5dfb4b1538..3ba7e3ab35 100644 --- a/src/listmode_buildblock/CListModeDataGESigna.cxx +++ b/src/listmode_buildblock/CListModeDataGESigna.cxx @@ -13,6 +13,7 @@ #include "stir/listmode/CListModeDataGESigna.h" #include "stir/Succeeded.h" #include "stir/ExamInfo.h" +#include "stir/ProjDataInfo.h" #include "stir/info.h" #include #include @@ -37,6 +38,13 @@ get_name() const return listmode_filename; } +shared_ptr +CListModeDataGESigna:: +get_proj_data_info_sptr() const +{ + return this->proj_data_info_sptr; +} + std::time_t CListModeDataGESigna:: get_scan_start_time_in_secs_since_1970() const @@ -89,6 +97,14 @@ std::cout << "\n Manufacturer : " << read_str_manufacturer << "\n\n"; #endif CListModeData::scanner_sptr = GEHDF5Data::scanner_sptr; + this->proj_data_info_sptr.reset( + ProjDataInfo::ProjDataInfoCTI(GEHDF5Data::scanner_sptr, + /*span=*/ 1, + GEHDF5Data::scanner_sptr->get_num_rings()-1, + GEHDF5Data::scanner_sptr->get_num_detectors_per_ring()/2, + GEHDF5Data::scanner_sptr->get_max_num_non_arccorrected_bins(), + /*arc_corrected =*/ false, + /*tof_mash_factor = TODO*/ 1)); shared_ptr dataset_list_sptr(new H5::DataSet(this->file.openDataSet("/ListData/listData"))); current_lm_data_ptr. From dcbc02255d0bc9ee3a85f25554ca57282612a149 Mon Sep 17 00:00:00 2001 From: Nikos Efthimiou Date: Sun, 12 Feb 2017 22:20:30 +0000 Subject: [PATCH 054/509] Revered CListTime, changede the get_tof_mash_factor with get_num_tof_pos Amended the following comments: > You have changed the CListRecord stuff to use get_bin_from_record, apparently because you put the TOF timing information in CListTime. > the meaning of the TOF mashing factor. > The potential overlap of meaning of the TOF keywords --- src/IO/InputStreamFromROOTFileForECATPET.cxx | 4 +- src/IO/interfile.cxx | 2 +- src/buildblock/ProjDataFromStream.cxx | 32 +++++----- src/buildblock/ProjDataInfo.cxx | 55 ++++++++++------- .../ProjDataInfoCylindricalNoArcCorr.cxx | 8 +-- src/include/stir/ProjData.h | 6 +- src/include/stir/ProjData.inl | 12 ++-- src/include/stir/ProjDataInfo.h | 42 ++++++------- src/include/stir/ProjDataInfo.inl | 18 +++--- .../stir/ProjDataInfoCylindricalArcCorr.h | 2 +- .../stir/ProjDataInfoCylindricalNoArcCorr.h | 4 +- ...ylindricalScannerWithDiscreteDetectors.inl | 4 ++ ...calScannerWithViewTangRingRingEncoding.inl | 2 + src/include/stir/listmode/CListRecord.h | 28 +++------ src/include/stir/listmode/CListRecordROOT.h | 61 ++++--------------- src/include/stir/listmode/CListRecordROOT.inl | 3 - .../stir/recon_buildblock/ProjMatrixByBin.inl | 4 +- src/listmode_buildblock/CListEvent.cxx | 2 + src/listmode_buildblock/CListRecordROOT.cxx | 5 +- src/listmode_buildblock/LmToProjData.cxx | 18 +++--- ...MeanAndListModeDataWithProjMatrixByBin.cxx | 8 +-- src/test/test_time_of_flight.cxx | 14 ++--- src/utilities/create_projdata_template.cxx | 8 +-- src/utilities/list_projdata_info.cxx | 8 +-- 24 files changed, 155 insertions(+), 195 deletions(-) diff --git a/src/IO/InputStreamFromROOTFileForECATPET.cxx b/src/IO/InputStreamFromROOTFileForECATPET.cxx index 87efb85d8c..b330346e00 100644 --- a/src/IO/InputStreamFromROOTFileForECATPET.cxx +++ b/src/IO/InputStreamFromROOTFileForECATPET.cxx @@ -102,10 +102,12 @@ get_next_record(CListRecordROOT& record) crystal1 += offset_dets; crystal2 += offset_dets; + double delta_timing_bin = (time2 - time1) * least_significant_clock_bit; + return record.init_from_data(ring1, ring2, crystal1, crystal2, - time1, time2, + time1, delta_timing_bin, event1, event2); } diff --git a/src/IO/interfile.cxx b/src/IO/interfile.cxx index 5e60d8f495..4666f23bf8 100644 --- a/src/IO/interfile.cxx +++ b/src/IO/interfile.cxx @@ -934,7 +934,7 @@ write_basic_interfile_PDFS_header(const string& header_file_name, // IF TOF is supported add this in the header. if (pdfs.get_proj_data_info_ptr()->get_scanner_ptr()->is_tof_ready() && - pdfs.get_proj_data_info_ptr()->get_tof_mash_factor() > 1) + pdfs.get_proj_data_info_ptr()->get_num_tof_poss() > 1) { // Moved in scanner section // output_header << "%number of TOF time bins :=" << diff --git a/src/buildblock/ProjDataFromStream.cxx b/src/buildblock/ProjDataFromStream.cxx index 0639af379f..13190f4362 100644 --- a/src/buildblock/ProjDataFromStream.cxx +++ b/src/buildblock/ProjDataFromStream.cxx @@ -105,13 +105,13 @@ ProjDataFromStream::ProjDataFromStream(shared_ptr const& exam_info_spt // Now, lets initialise a TOF stream - Similarly to segments if (storage_order == Timing_Segment_View_AxialPos_TangPos && - proj_data_info_ptr->get_num_timing_poss() > 1) + proj_data_info_ptr->get_num_tof_poss() > 1) { - timing_poss_sequence.resize(proj_data_info_ptr->get_num_timing_poss()); + timing_poss_sequence.resize(proj_data_info_ptr->get_num_tof_poss()); int timing_pos_num; - for (int i= 0, timing_pos_num = proj_data_info_ptr->get_min_timing_pos_num(); - timing_pos_num<=proj_data_info_ptr->get_max_timing_pos_num(); + for (int i= 0, timing_pos_num = proj_data_info_ptr->get_min_tof_pos_num(); + timing_pos_num<=proj_data_info_ptr->get_max_tof_pos_num(); ++i, ++timing_pos_num) { timing_poss_sequence[i] = timing_pos_num; @@ -154,13 +154,13 @@ ProjDataFromStream::ProjDataFromStream(shared_ptr const& exam_info_spt // Now, lets initialise a TOF stream - Similarly to segments if (storage_order == Timing_Segment_View_AxialPos_TangPos && - proj_data_info_ptr->get_num_timing_poss() > 1) + proj_data_info_ptr->get_num_tof_poss() > 1) { - timing_poss_sequence.resize(proj_data_info_ptr->get_num_timing_poss()); + timing_poss_sequence.resize(proj_data_info_ptr->get_num_tof_poss()); int timing_pos_num; - for (i= 0, timing_pos_num = proj_data_info_ptr->get_min_timing_pos_num(); - timing_pos_num<=proj_data_info_ptr->get_max_timing_pos_num(); + for (i= 0, timing_pos_num = proj_data_info_ptr->get_min_tof_pos_num(); + timing_pos_num<=proj_data_info_ptr->get_max_tof_pos_num(); ++i, ++timing_pos_num) { timing_poss_sequence[i] = timing_pos_num; @@ -354,8 +354,8 @@ ProjDataFromStream::get_offsets(const int view_num, const int segment_num, { // The timing offset will be added to the segment offset. This approach we minimise the // changes - if (!(timing_num >= get_min_timing_pos_num() && - timing_num <= get_max_timing_pos_num())) + if (!(timing_num >= get_min_tof_pos_num() && + timing_num <= get_max_tof_pos_num())) error("ProjDataFromStream::get_offsets: timing_num out of range : %d", timing_num); const int timing_index = static_cast(FIND(timing_poss_sequence.begin(), timing_poss_sequence.end(), timing_num) - @@ -589,8 +589,8 @@ ProjDataFromStream::get_offsets_bin(const int segment_num, { // The timing offset will be added to the segment offset. This approach we minimise the // changes - if (!(timing_pos_num >= get_min_timing_pos_num() && - timing_pos_num <= get_max_timing_pos_num())) + if (!(timing_pos_num >= get_min_tof_pos_num() && + timing_pos_num <= get_max_tof_pos_num())) error("ProjDataFromStream::get_offsets_bin: timing_num out of range : %d", timing_pos_num); const int timing_index = static_cast(FIND(timing_poss_sequence.begin(), timing_poss_sequence.end(), timing_pos_num) - @@ -696,8 +696,8 @@ ProjDataFromStream::get_offsets_sino(const int ax_pos_num, const int segment_num { // The timing offset will be added to the segment offset. This approach we minimise the // changes - if (!(timing_num >= get_min_timing_pos_num() && - timing_num <= get_max_timing_pos_num())) + if (!(timing_num >= get_min_tof_pos_num() && + timing_num <= get_max_tof_pos_num())) error("ProjDataFromStream::get_offsets: timing_num out of range : %d", timing_num); const int timing_index = static_cast(FIND(timing_poss_sequence.begin(), timing_poss_sequence.end(), timing_num) - @@ -926,8 +926,8 @@ ProjDataFromStream::get_offset_timing(const int timing_num) const static_cast(FIND(timing_poss_sequence.begin(), timing_poss_sequence.end(), timing_num) - timing_poss_sequence.begin()); - assert (timing_num >= get_min_timing_pos_num() && - timing_num <= get_max_timing_pos_num()); + assert (timing_num >= get_min_tof_pos_num() && + timing_num <= get_max_tof_pos_num()); { if(offset_3d_data < 0 ) // Calculate the full 3D sinogram size - Very slow { diff --git a/src/buildblock/ProjDataInfo.cxx b/src/buildblock/ProjDataInfo.cxx index 03fa0e32a7..5ea9cd50bd 100644 --- a/src/buildblock/ProjDataInfo.cxx +++ b/src/buildblock/ProjDataInfo.cxx @@ -78,10 +78,10 @@ ProjDataInfo::get_k(const Bin& bin) const { // Probably, This condition should be removed, since I have the check odd number in the // set_tof_mash_factor(). - if (!get_num_timing_poss()%2) - return bin.timing_pos_num() * timing_increament_in_mm; + if (!num_tof_bins%2) + return bin.timing_pos_num() * tof_increament_in_mm; else - return (bin.timing_pos_num() * timing_increament_in_mm) - timing_increament_in_mm/2.f; + return (bin.timing_pos_num() * tof_increament_in_mm) - tof_increament_in_mm/2.f; } float @@ -186,30 +186,30 @@ ProjDataInfo::set_max_tangential_pos_num(const int max_tang_poss) void ProjDataInfo::set_tof_mash_factor(const int new_num) { - if (scanner_ptr->is_tof_ready()) + if (scanner_ptr->is_tof_ready() && new_num > 0 ) { if(tof_mash_factor < 0 || tof_mash_factor > scanner_ptr->get_num_max_of_timing_bins()) error("ProjDataInfo: TOF mashing factor must be positive and smaller or equal than" "the scanner's number of max timing bins. Abort."); tof_mash_factor = new_num; - timing_increament_in_mm = (tof_mash_factor * scanner_ptr->get_size_of_timing_bin() * 0.299792458f); + tof_increament_in_mm = (tof_mash_factor * scanner_ptr->get_size_of_timing_bin() * 0.299792458f); - min_timing_pos_num = - (scanner_ptr->get_num_max_of_timing_bins() / tof_mash_factor)/2; - max_timing_pos_num = min_timing_pos_num + (scanner_ptr->get_num_max_of_timing_bins() / tof_mash_factor) -1; + min_tof_pos_num = - (scanner_ptr->get_num_max_of_timing_bins() / tof_mash_factor)/2; + max_tof_pos_num = min_tof_pos_num + (scanner_ptr->get_num_max_of_timing_bins() / tof_mash_factor) -1; - num_tof_bins = max_timing_pos_num - min_timing_pos_num +1 ; + num_tof_bins = max_tof_pos_num - min_tof_pos_num +1 ; // Ensure that we have a central tof bin. if (num_tof_bins%2 == 0) error("ProjDataInfo: Number of TOF bins should be an odd number. Abort."); // Upper and lower boundaries of the timing poss; - timing_bin_boundaries_mm.grow(min_timing_pos_num, max_timing_pos_num); + tof_bin_boundaries_mm.grow(min_tof_pos_num, max_tof_pos_num); - timing_bin_boundaries_ps.grow(min_timing_pos_num, max_timing_pos_num); + tof_bin_boundaries_ps.grow(min_tof_pos_num, max_tof_pos_num); - for (int i = min_timing_pos_num; i <= max_timing_pos_num; ++i ) + for (int i = min_tof_pos_num; i <= max_tof_pos_num; ++i ) { Bin bin; bin.timing_pos_num() = i; @@ -217,15 +217,24 @@ ProjDataInfo::set_tof_mash_factor(const int new_num) float cur_low = get_k(bin); float cur_high = get_k(bin) + get_sampling_in_k(bin); - timing_bin_boundaries_mm[i].low_lim = cur_low; - timing_bin_boundaries_mm[i].high_lim = cur_high; - timing_bin_boundaries_ps[i].low_lim = (timing_bin_boundaries_mm[i].low_lim * 3.33564095198f ) ; - timing_bin_boundaries_ps[i].high_lim = ( timing_bin_boundaries_mm[i].high_lim * 3.33564095198f); + tof_bin_boundaries_mm[i].low_lim = cur_low; + tof_bin_boundaries_mm[i].high_lim = cur_high; + tof_bin_boundaries_ps[i].low_lim = (tof_bin_boundaries_mm[i].low_lim * 3.33564095198f ) ; + tof_bin_boundaries_ps[i].high_lim = ( tof_bin_boundaries_mm[i].high_lim * 3.33564095198f); // I could imagine a better printing. - info(boost::format("Tbin %1%: %2% - %3% mm (%4% - %5% ps) = %6%") %i % timing_bin_boundaries_mm[i].low_lim % timing_bin_boundaries_mm[i].high_lim - % timing_bin_boundaries_ps[i].low_lim % timing_bin_boundaries_ps[i].high_lim % get_sampling_in_k(bin)); + info(boost::format("Tbin %1%: %2% - %3% mm (%4% - %5% ps) = %6%") %i % tof_bin_boundaries_mm[i].low_lim % tof_bin_boundaries_mm[i].high_lim + % tof_bin_boundaries_ps[i].low_lim % tof_bin_boundaries_ps[i].high_lim % get_sampling_in_k(bin)); } } + else if (new_num == 0) // Case new_num = 0, will produce non-TOF data for a TOF compatible scanner. + { + num_tof_bins = 1; + tof_mash_factor = 0; + min_tof_pos_num = 0; + max_tof_pos_num = 0; + + // Should I initialise here and the boundaries? + } else error("Not TOF compatible scanner template. Abort."); } @@ -248,10 +257,10 @@ ProjDataInfo::ProjDataInfo(const shared_ptr& scanner_ptr_v, set_num_tangential_poss(num_tangential_poss_v); set_num_axial_poss_per_segment(num_axial_pos_per_segment_v); // Initialise the TOF elements to non-used. - min_timing_pos_num = 0; - max_timing_pos_num = 0; - timing_increament_in_mm = 0.f; - tof_mash_factor = 1; + min_tof_pos_num = 0; + max_tof_pos_num = 0; + tof_increament_in_mm = 0.f; + tof_mash_factor = 0; } // TOF version. @@ -604,8 +613,8 @@ ProjDataInfo* ProjDataInfo::ask_parameters() ask_num("Mash factor for views",1,16,1); const int tof_mash_factor = scanner_ptr->is_tof_ready() ? - ask_num("Time-of-flight mash factor (1: No TOF):", 1, - scanner_ptr->get_num_max_of_timing_bins(), 1) : 1; + ask_num("Time-of-flight mash factor:", 0, + scanner_ptr->get_num_max_of_timing_bins(), 25) : 0; const bool arc_corrected = ask("Is the data arc-corrected?",true); diff --git a/src/buildblock/ProjDataInfoCylindricalNoArcCorr.cxx b/src/buildblock/ProjDataInfoCylindricalNoArcCorr.cxx index 8018bfc901..83a498ae8a 100644 --- a/src/buildblock/ProjDataInfoCylindricalNoArcCorr.cxx +++ b/src/buildblock/ProjDataInfoCylindricalNoArcCorr.cxx @@ -72,8 +72,7 @@ ProjDataInfoCylindricalNoArcCorr(const shared_ptr scanner_ptr, { uncompressed_view_tangpos_to_det1det2_initialised = false; det1det2_to_uncompressed_view_tangpos_initialised = false; - // If tof_mash_factor == 1 then there is only tof bin ... effectively no TOF - if (tof_mash_factor > 1) + if(scanner_ptr->is_tof_ready()) set_tof_mash_factor(tof_mash_factor); //this->initialise_uncompressed_view_tangpos_to_det1det2(); //this->initialise_det1det2_to_uncompressed_view_tangpos(); @@ -97,9 +96,8 @@ ProjDataInfoCylindricalNoArcCorr(const shared_ptr scanner_ptr, uncompressed_view_tangpos_to_det1det2_initialised = false; det1det2_to_uncompressed_view_tangpos_initialised = false; - // If tof_mash_factor == 1 then there is only tof bin ... effectively no TOF - if (tof_mash_factor > 1) - set_tof_mash_factor(tof_mash_factor); + if(scanner_ptr->is_tof_ready()) + set_tof_mash_factor(tof_mash_factor); //this->initialise_uncompressed_view_tangpos_to_det1det2(); //this->initialise_det1det2_to_uncompressed_view_tangpos(); } diff --git a/src/include/stir/ProjData.h b/src/include/stir/ProjData.h index 52acd2af38..ac9db5f5d3 100644 --- a/src/include/stir/ProjData.h +++ b/src/include/stir/ProjData.h @@ -293,11 +293,11 @@ class ProjData : public ExamData //! Get number of tangential positions inline int get_num_tangential_poss() const; //! Get number of TOF positions - inline int get_num_timing_poss() const; + inline int get_num_tof_poss() const; //! Get the index of the first timing position - inline int get_min_timing_pos_num() const; + inline int get_min_tof_pos_num() const; //! Get the index of the last timgin position. - inline int get_max_timing_pos_num() const; + inline int get_max_tof_pos_num() const; //! Get TOG mash factor inline int get_tof_mash_factor() const; //! Get minimum segment number diff --git a/src/include/stir/ProjData.inl b/src/include/stir/ProjData.inl index c48e200f8a..37f55eb13c 100644 --- a/src/include/stir/ProjData.inl +++ b/src/include/stir/ProjData.inl @@ -68,8 +68,8 @@ int ProjData::get_num_views() const int ProjData::get_num_tangential_poss() const { return proj_data_info_ptr->get_num_tangential_poss(); } -int ProjData::get_num_timing_poss() const -{ return proj_data_info_ptr->get_num_timing_poss(); } +int ProjData::get_num_tof_poss() const +{ return proj_data_info_ptr->get_num_tof_poss(); } int ProjData::get_tof_mash_factor() const { return proj_data_info_ptr->get_tof_mash_factor(); } @@ -98,11 +98,11 @@ int ProjData::get_min_tangential_pos_num() const int ProjData::get_max_tangential_pos_num() const { return proj_data_info_ptr->get_max_tangential_pos_num(); } -int ProjData::get_min_timing_pos_num() const -{ return proj_data_info_ptr->get_min_timing_pos_num(); } +int ProjData::get_min_tof_pos_num() const +{ return proj_data_info_ptr->get_min_tof_pos_num(); } -int ProjData::get_max_timing_pos_num() const -{ return proj_data_info_ptr->get_max_timing_pos_num(); } +int ProjData::get_max_tof_pos_num() const +{ return proj_data_info_ptr->get_max_tof_pos_num(); } int ProjData::get_num_sinograms() const { diff --git a/src/include/stir/ProjDataInfo.h b/src/include/stir/ProjDataInfo.h index 452b3bff8e..1f99ba631e 100644 --- a/src/include/stir/ProjDataInfo.h +++ b/src/include/stir/ProjDataInfo.h @@ -72,25 +72,27 @@ class ProjDataInfo ask_parameters(); //! Construct a ProjDataInfo suitable for GE Advance data - //! \warning N.E: TOF mash factor = 1, means no TOF + //! \warning N.E: TOF mash factor = 1, means possible many TOF bins + //! \warning N.E: TOF mash factor = 0 will produce nonTOF data static ProjDataInfo* ProjDataInfoGE(const shared_ptr& scanner_ptr, const int max_delta, const int num_views, const int num_tangential_poss, const bool arc_corrected = true, - const int tof_mash_factor = 1); + const int tof_mash_factor = 0); //! Construct a ProjDataInfo suitable for CTI data /*! \c span is used to denote the amount of axial compression (see CTI doc). It has to be an odd number. */ - //! \warning N.E: TOF mash factor = 1, means no TOF + //! \warning N.E: TOF mash factor = 1, means possible many TOF bins + //! \warning N.E: TOF mash factor = 0 will produce nonTOF data static ProjDataInfo* ProjDataInfoCTI(const shared_ptr& scanner_ptr, const int span, const int max_delta, const int num_views, const int num_tangential_poss, const bool arc_corrected = true, - const int tof_mash_factor = 1); + const int tof_mash_factor = 0); /************ constructors ***********/ @@ -205,17 +207,15 @@ class ProjDataInfo inline int get_min_tangential_pos_num() const; //! Get maximum tangential position number inline int get_max_tangential_pos_num() const; - //! Get number of TOF positions - inline int get_num_timing_poss() const; //! Get TOF mash factor inline int get_tof_mash_factor() const; - //! Get the index of the first timing position - inline int get_min_timing_pos_num() const; + //! Get the index of the first TOF position + inline int get_min_tof_pos_num() const; //! Get the index of the last timgin position. - inline int get_max_timing_pos_num() const; + inline int get_max_tof_pos_num() const; //! Get the coincide window in pico seconds //! \warning Proposed convension: If the scanner is not TOF ready then - //! the coincidence windowis in the timing bin size. + //! the coincidence windowis in the TOF bin size. inline float get_coincidence_window_in_pico_sec() const; //! Get the total width of the coincide window in mm inline float get_coincidence_window_width() const; @@ -260,7 +260,7 @@ class ProjDataInfo normal to the projection plane */ virtual float get_s(const Bin&) const =0; - //! Get value ot the timing location along the LOR (in mm) + //! Get value ot the TOF location along the LOR (in mm) //! k is a line segment connecting the centers of the two detectors. float get_k(const Bin&) const; @@ -370,10 +370,10 @@ class ProjDataInfo //! Struct which holds two floating numbers struct Float1Float2 { float low_lim; float high_lim; }; - //! Vector which holds the lower and higher boundary for each timing position in mm, for faster access. - mutable VectorWithOffset timing_bin_boundaries_mm; - //! Vector which holds the lower and higher boundary for each timing position in ps`, for faster access. - mutable VectorWithOffset timing_bin_boundaries_ps; + //! Vector which holds the lower and higher boundary for each TOF position in mm, for faster access. + mutable VectorWithOffset tof_bin_boundaries_mm; + //! Vector which holds the lower and higher boundary for each TOF position in ps`, for faster access. + mutable VectorWithOffset tof_bin_boundaries_ps; protected: virtual bool blindly_equals(const root_type * const) const = 0; @@ -384,14 +384,14 @@ class ProjDataInfo int max_view_num; int min_tangential_pos_num; int max_tangential_pos_num; - //! Minimum timing pos - int min_timing_pos_num; - //! Maximum timing pos - int max_timing_pos_num; + //! Minimum TOF pos + int min_tof_pos_num; + //! Maximum TOF pos + int max_tof_pos_num; //! TOF mash factor. int tof_mash_factor; - //! Finally (with any mashing factor) timing bin increament. - float timing_increament_in_mm; + //! Finally (with any mashing factor) TOF bin increament. + float tof_increament_in_mm; //! Number of tof bins (TOF mash factor applied) int num_tof_bins; VectorWithOffset min_axial_pos_per_seg; diff --git a/src/include/stir/ProjDataInfo.inl b/src/include/stir/ProjDataInfo.inl index b3487b6532..588d73b186 100644 --- a/src/include/stir/ProjDataInfo.inl +++ b/src/include/stir/ProjDataInfo.inl @@ -57,10 +57,6 @@ int ProjDataInfo::get_num_tangential_poss() const { return max_tangential_pos_num - min_tangential_pos_num + 1; } -int -ProjDataInfo::get_num_timing_poss() const -{ return max_timing_pos_num - min_timing_pos_num +1; } - int ProjDataInfo::get_num_tof_poss() const { return num_tof_bins; } @@ -68,10 +64,10 @@ ProjDataInfo::get_num_tof_poss() const int ProjDataInfo::get_tof_bin(const double& delta) const { - for (int i = min_timing_pos_num; i < max_timing_pos_num; i++) + for (int i = min_tof_pos_num; i < max_tof_pos_num; i++) { - if ( delta > timing_bin_boundaries_ps[i].low_lim && - delta < timing_bin_boundaries_ps[i].high_lim) + if ( delta > tof_bin_boundaries_ps[i].low_lim && + delta < tof_bin_boundaries_ps[i].high_lim) return i; } } @@ -116,15 +112,15 @@ ProjDataInfo::get_max_tangential_pos_num()const { return max_tangential_pos_num; } int -ProjDataInfo::get_min_timing_pos_num() const +ProjDataInfo::get_min_tof_pos_num() const { - return min_timing_pos_num; + return min_tof_pos_num; } int -ProjDataInfo::get_max_timing_pos_num() const +ProjDataInfo::get_max_tof_pos_num() const { - return max_timing_pos_num; + return max_tof_pos_num; } float diff --git a/src/include/stir/ProjDataInfoCylindricalArcCorr.h b/src/include/stir/ProjDataInfoCylindricalArcCorr.h index 7c6bfc3fdf..be7fb6a14c 100644 --- a/src/include/stir/ProjDataInfoCylindricalArcCorr.h +++ b/src/include/stir/ProjDataInfoCylindricalArcCorr.h @@ -63,7 +63,7 @@ class ProjDataInfoCylindricalArcCorr : public ProjDataInfoCylindrical const VectorWithOffset& min_ring_diff_v, const VectorWithOffset& max_ring_diff_v, const int num_views,const int num_tangential_poss, - const int tof_mash_factor = 1); + const int tof_mash_factor = 0); ProjDataInfo* clone() const; diff --git a/src/include/stir/ProjDataInfoCylindricalNoArcCorr.h b/src/include/stir/ProjDataInfoCylindricalNoArcCorr.h index 4137d4d8d5..99a6ab3c26 100644 --- a/src/include/stir/ProjDataInfoCylindricalNoArcCorr.h +++ b/src/include/stir/ProjDataInfoCylindricalNoArcCorr.h @@ -109,7 +109,7 @@ class ProjDataInfoCylindricalNoArcCorr : public ProjDataInfoCylindrical const VectorWithOffset& min_ring_diff_v, const VectorWithOffset& max_ring_diff_v, const int num_views,const int num_tangential_poss, - const int tof_mash_factor = 1); + const int tof_mash_factor = 0); //! Constructor which gets \a ring_radius and \a angular_increment from the scanner /*! \a angular_increment is determined as Pi divided by the number of detectors in a ring. @@ -119,7 +119,7 @@ class ProjDataInfoCylindricalNoArcCorr : public ProjDataInfoCylindrical const VectorWithOffset& min_ring_diff_v, const VectorWithOffset& max_ring_diff_v, const int num_views,const int num_tangential_poss, - const int tof_mash_factor = 1); + const int tof_mash_factor = 0); ProjDataInfo* clone() const; diff --git a/src/include/stir/listmode/CListEventCylindricalScannerWithDiscreteDetectors.inl b/src/include/stir/listmode/CListEventCylindricalScannerWithDiscreteDetectors.inl index 7baa19f36b..f8a87c416a 100644 --- a/src/include/stir/listmode/CListEventCylindricalScannerWithDiscreteDetectors.inl +++ b/src/include/stir/listmode/CListEventCylindricalScannerWithDiscreteDetectors.inl @@ -84,7 +84,11 @@ get_bin(Bin& bin, const ProjDataInfo& proj_data_info) const get_bin_for_det_pos_pair(bin, det_pos) == Succeeded::no) bin.set_bin_value(0); else + { bin.set_bin_value(1); + if (proj_data_info.get_num_tof_poss() > 1) + bin.timing_pos_num() = proj_data_info.get_tof_bin(delta_time); + } } END_NAMESPACE_STIR diff --git a/src/include/stir/listmode/CListEventCylindricalScannerWithViewTangRingRingEncoding.inl b/src/include/stir/listmode/CListEventCylindricalScannerWithViewTangRingRingEncoding.inl index be0db7deaa..2630ba82f8 100644 --- a/src/include/stir/listmode/CListEventCylindricalScannerWithViewTangRingRingEncoding.inl +++ b/src/include/stir/listmode/CListEventCylindricalScannerWithViewTangRingRingEncoding.inl @@ -110,6 +110,8 @@ get_bin(Bin& bin, const ProjDataInfo& proj_data_info) const get_sinogram_and_ring_coordinates(view_num, tangential_pos_num, ring_a, ring_b); sinogram_coordinates_to_bin(bin, view_num, tangential_pos_num, ring_a, ring_b, static_cast(proj_data_info)); + if (proj_data_info.get_num_tof_poss() > 1) + bin.timing_pos_num() = proj_data_info.get_tof_bin(delta_time); } template diff --git a/src/include/stir/listmode/CListRecord.h b/src/include/stir/listmode/CListRecord.h index 85ed55ef16..2eeb9fb7b5 100644 --- a/src/include/stir/listmode/CListRecord.h +++ b/src/include/stir/listmode/CListRecord.h @@ -109,6 +109,11 @@ class CListEvent void get_bin(Bin& bin, const ProjDataInfo&) const; +protected: + //! The detection time difference, between the two photons. + //! This will work for ROOT files, but not so sure about acquired data. + double delta_time; + }; /*-coincidence event*/ @@ -135,25 +140,12 @@ class CListTime virtual Succeeded set_time_in_millisecs(const unsigned long time_in_millisecs) = 0; inline Succeeded set_time_in_secs(const double time_in_secs) - { + { unsigned long time_in_millisecs; round_to(time_in_millisecs, time_in_secs/1000.); - return set_time_in_millisecs(time_in_millisecs); + return set_time_in_millisecs(time_in_millisecs); } - virtual inline int get_timing_bin() const - { - error("Function CListTime::get_timing_bin() currently is implemented only " - "ROOT data. Abort."); - } - - //! Get the timing component of the bin. - virtual inline void get_bin(Bin&, const ProjDataInfo&) const - { - error("CListTime::get_bin() currently is implemented only " - "ROOT data. Abort."); - } - }; //! A class recording external input to the scanner (normally used for gating) @@ -204,12 +196,6 @@ class CListRecord virtual bool operator==(const CListRecord& e2) const = 0; bool operator!=(const CListRecord& e2) const { return !(*this == e2); } - //! Used in TOF reconstruction to get both the geometric and the timing - //! component of the event - virtual void full_event(Bin&, const ProjDataInfo&) const - {error("CListRecord::full_event() is implemented only for records which " - "hold timing and spatial information.");} - }; class CListRecordWithGatingInput : public CListRecord diff --git a/src/include/stir/listmode/CListRecordROOT.h b/src/include/stir/listmode/CListRecordROOT.h index 62ad478aea..3cee3e0aaa 100644 --- a/src/include/stir/listmode/CListRecordROOT.h +++ b/src/include/stir/listmode/CListRecordROOT.h @@ -52,7 +52,8 @@ class CListEventROOT : public CListEventCylindricalScannerWithDiscreteDetectors //! \details This is the main function which transform GATE coordinates to STIR void init_from_data(const int &_ring1, const int &_ring2, - const int &crystal1, const int &crystal2); + const int &crystal1, const int &crystal2, + const double& _delta_time); inline bool is_prompt() const { return true; } @@ -74,6 +75,7 @@ class CListEventROOT : public CListEventCylindricalScannerWithDiscreteDetectors //! This is the number of detector we have to rotate in order to //! align GATE and STIR. int quarter_of_detectors; + }; //! A class for storing and using a timing 'event' from a listmode file from the ECAT 8_32bit scanner @@ -82,10 +84,9 @@ class CListEventROOT : public CListEventCylindricalScannerWithDiscreteDetectors class CListTimeROOT : public CListTime { public: - void init_from_data(float _timeA, float _delta_time) + void init_from_data(double time1) { - timeA = _timeA; - delta_time = _delta_time; + timeA = time1; } //! Returns always true @@ -94,49 +95,19 @@ class CListTimeROOT : public CListTime //! Returns the detection time of the first photon //! in milliseconds. inline unsigned long get_time_in_millisecs() const - { return timeA * 1e3; } - //! Get the detection time of the first photon - //! in milliseconds - inline unsigned long get_timeA_in_millisecs() const - { return timeA * 1e3; } - //! Get the detection time of the second photon - //! in milliseconds - inline unsigned long get_timeB_in_millisecs() const - { return (delta_time - timeA) * 1e3; } - //! Get the delta Time between the two events - inline unsigned long get_delta_time_in_millisecs() const - { return delta_time * 1e3; } - + { return static_cast (timeA * 1e3); } inline Succeeded set_time_in_millisecs(const unsigned long time_in_millisecs) { warning("set_time_in_millisecs: Not implemented for ROOT files. Aborting."); return Succeeded::no; } - //! N.E. Sadly I had to comment the original version of this function. It might had been faster. - //! and make more sense under many occations. - virtual inline void get_bin(Bin& bin, const ProjDataInfo& proj_data_info) const - { - // delta_time >= 0.0 ? - // bin.timing_pos_num() = static_cast ( ( delta_time / proj_data_info.get_num_tof_poss()) + 0.5) - // : bin.timing_pos_num() = static_cast ( ( delta_time / proj_data_info.get_num_tof_poss()) - 0.5); - - // if (bin.timing_pos_num() < proj_data_info.get_min_timing_pos_num() || - // bin.timing_pos_num() > proj_data_info.get_max_timing_pos_num()) - // { - // bin.set_bin_value(-1.f); - // } - bin.timing_pos_num() = proj_data_info.get_tof_bin(delta_time); - } - private: //! //! \brief timeA //! \details The detection time of the first of the two photons, in seconds double timeA; - - double delta_time; }; //! A class for a general element of a listmode file for a Siemens scanner using the ROOT files @@ -147,8 +118,6 @@ class CListRecordROOT : public CListRecord // currently no gating yet bool inline is_time() const; //! Returns always true bool inline is_event() const; - //! Returns always true - bool inline is_full_event() const; virtual CListEventROOT& event() { @@ -170,12 +139,6 @@ class CListRecordROOT : public CListRecord // currently no gating yet return this->time_data; } - virtual void full_event(Bin& bin, const ProjDataInfo& proj_data_info) const - { - event_data.get_bin(bin, proj_data_info); - time_data.get_bin(bin, proj_data_info); - } - bool operator==(const CListRecord& e2) const { return dynamic_cast(&e2) != 0 && @@ -191,19 +154,17 @@ class CListRecordROOT : public CListRecord // currently no gating yet const int& ring2, const int& crystal1, const int& crystal2, - const double& time1, const double& delta_time, + const double& time1, + const double& delta_time, const int& event1, const int& event2) { /// \warning ROOT data are time and event at the same time. this->event_data.init_from_data(ring1, ring2, - crystal1, crystal2); + crystal1, crystal2, + delta_time); - this->event_data.is_swapped() ? - this->time_data.init_from_data( - time1, -delta_time) : - this->time_data.init_from_data( - time1, delta_time); + this->time_data.init_from_data(time1); // We can make a singature raw based on the two events IDs. // It is pretty unique. diff --git a/src/include/stir/listmode/CListRecordROOT.inl b/src/include/stir/listmode/CListRecordROOT.inl index 26270593c4..d1c0d7de3c 100644 --- a/src/include/stir/listmode/CListRecordROOT.inl +++ b/src/include/stir/listmode/CListRecordROOT.inl @@ -33,7 +33,4 @@ bool CListRecordROOT::is_time() const bool CListRecordROOT::is_event() const { return true; } -bool CListRecordROOT::is_full_event() const -{ return true; } - END_NAMESPACE_STIR diff --git a/src/include/stir/recon_buildblock/ProjMatrixByBin.inl b/src/include/stir/recon_buildblock/ProjMatrixByBin.inl index d3ce6147c7..d691e2e4df 100644 --- a/src/include/stir/recon_buildblock/ProjMatrixByBin.inl +++ b/src/include/stir/recon_buildblock/ProjMatrixByBin.inl @@ -202,8 +202,8 @@ ProjMatrixByBin::apply_tof_kernel(ProjMatrixElemsForOneBin& nonTOF_probabilities // (point2.z() - voxel_center.z()) *(point2.z() - voxel_center.z())); float m = (lor_length - d1 - d1) * 0.5f; - low_dist = (proj_data_info_sptr->timing_bin_boundaries_mm[tof_probabilities.get_bin_ptr()->timing_pos_num()].low_lim - m) * r_sqrt2_gauss_sigma; - high_dist = (proj_data_info_sptr->timing_bin_boundaries_mm[tof_probabilities.get_bin_ptr()->timing_pos_num()].high_lim - m) * r_sqrt2_gauss_sigma; + low_dist = (proj_data_info_sptr->tof_bin_boundaries_mm[tof_probabilities.get_bin_ptr()->timing_pos_num()].low_lim - m) * r_sqrt2_gauss_sigma; + high_dist = (proj_data_info_sptr->tof_bin_boundaries_mm[tof_probabilities.get_bin_ptr()->timing_pos_num()].high_lim - m) * r_sqrt2_gauss_sigma; // Cut-off really small values. // Currently deactivate untill I run all the tests. diff --git a/src/listmode_buildblock/CListEvent.cxx b/src/listmode_buildblock/CListEvent.cxx index ad801a2685..3f5f92bbd0 100644 --- a/src/listmode_buildblock/CListEvent.cxx +++ b/src/listmode_buildblock/CListEvent.cxx @@ -46,6 +46,8 @@ CListEvent:: get_bin(Bin& bin, const ProjDataInfo& proj_data_info) const { bin = proj_data_info.get_bin(get_LOR()); + if (proj_data_info.get_num_tof_poss() > 1) + bin.timing_pos_num() = proj_data_info.get_tof_bin(delta_time); } END_NAMESPACE_STIR diff --git a/src/listmode_buildblock/CListRecordROOT.cxx b/src/listmode_buildblock/CListRecordROOT.cxx index 03d314f767..4aa4051dc6 100644 --- a/src/listmode_buildblock/CListRecordROOT.cxx +++ b/src/listmode_buildblock/CListRecordROOT.cxx @@ -59,7 +59,8 @@ void CListEventROOT::set_detection_position(const DetectionPositionPair<>&) } void CListEventROOT::init_from_data(const int& _ring1, const int& _ring2, - const int& crystal1, const int& crystal2) + const int& crystal1, const int& crystal2, + const double& _delta_time) { // STIR assumes that 0 is on y whill GATE on the x axis @@ -84,12 +85,14 @@ void CListEventROOT::init_from_data(const int& _ring1, const int& _ring2, ring1 = _ring2; ring2 = _ring1; + delta_time = -_delta_time; swapped = true; } else { ring1 = _ring1; ring2 = _ring2; + delta_time = _delta_time; swapped = false; } } diff --git a/src/listmode_buildblock/LmToProjData.cxx b/src/listmode_buildblock/LmToProjData.cxx index 983b3beae3..a92dddffef 100644 --- a/src/listmode_buildblock/LmToProjData.cxx +++ b/src/listmode_buildblock/LmToProjData.cxx @@ -426,9 +426,9 @@ get_bin_from_record(Bin& bin, const CListRecord& record) const // template_proj_data_info_ptr->get_bin_from_uncompressed(bin, uncompressed_bin); - if (use_tof) - record.full_event(bin, *template_proj_data_info_ptr); - else +// if (use_tof) +// record.full_event(bin, *template_proj_data_info_ptr); +// else record.event().get_bin(bin, *template_proj_data_info_ptr); bin.set_bin_value(bin_value); @@ -808,8 +808,8 @@ actual_process_data_with_tof() const double start_time = frame_defs.get_start_time(current_frame_num); const double end_time = frame_defs.get_end_time(current_frame_num); - for (int current_timing_pos_index = proj_data_ptr->get_min_timing_pos_num(); - current_timing_pos_index <= proj_data_ptr->get_max_timing_pos_num(); + for (int current_timing_pos_index = proj_data_ptr->get_min_tof_pos_num(); + current_timing_pos_index <= proj_data_ptr->get_max_tof_pos_num(); current_timing_pos_index += 1) { /* @@ -899,8 +899,8 @@ actual_process_data_with_tof() && bin.tangential_pos_num()<= proj_data_ptr->get_max_tangential_pos_num() && bin.axial_pos_num()>=proj_data_ptr->get_min_axial_pos_num(bin.segment_num()) && bin.axial_pos_num()<=proj_data_ptr->get_max_axial_pos_num(bin.segment_num()) - && bin.timing_pos_num()>=proj_data_ptr->get_min_timing_pos_num() - && bin.timing_pos_num()<=proj_data_ptr->get_max_timing_pos_num() + && bin.timing_pos_num()>=proj_data_ptr->get_min_tof_pos_num() + && bin.timing_pos_num()<=proj_data_ptr->get_max_tof_pos_num() ) { assert(bin.view_num()>=proj_data_ptr->get_min_view_num()); @@ -1012,8 +1012,8 @@ LmToProjData::run_tof_test_function() construct_proj_data(output, output_filename, this_frame_exam_info, template_proj_data_info_ptr); } - for (int current_timing_pos_index = proj_data_ptr->get_min_timing_pos_num(); - current_timing_pos_index <= proj_data_ptr->get_max_timing_pos_num(); + for (int current_timing_pos_index = proj_data_ptr->get_min_tof_pos_num(); + current_timing_pos_index <= proj_data_ptr->get_max_tof_pos_num(); current_timing_pos_index += 1) { for (int start_segment_index = proj_data_ptr->get_min_segment_num(); diff --git a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx index 67951cf6a3..5a49aca198 100644 --- a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx +++ b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx @@ -476,8 +476,8 @@ compute_sub_gradient_without_penalty_plus_sensitivity(TargetT& gradient, { measured_bin.set_bin_value(1.0f); - this->use_tof ? record.full_event(measured_bin, *proj_data_info_cyl_sptr): - record.event().get_bin(measured_bin, *proj_data_info_cyl_sptr); +// this->use_tof ? record.full_event(measured_bin, *proj_data_info_cyl_sptr): + record.event().get_bin(measured_bin, *proj_data_info_cyl_sptr); // In theory we have already done all these checks so we can // remove this if statement. @@ -488,8 +488,8 @@ compute_sub_gradient_without_penalty_plus_sensitivity(TargetT& gradient, || measured_bin.tangential_pos_num() > proj_data_info_cyl_sptr->get_max_tangential_pos_num() || measured_bin.axial_pos_num() < proj_data_info_cyl_sptr->get_min_axial_pos_num(measured_bin.segment_num()) || measured_bin.axial_pos_num() > proj_data_info_cyl_sptr->get_max_axial_pos_num(measured_bin.segment_num()) - || measured_bin.timing_pos_num() < proj_data_info_cyl_sptr->get_min_timing_pos_num() - || measured_bin.timing_pos_num() > proj_data_info_cyl_sptr->get_max_timing_pos_num()) + || measured_bin.timing_pos_num() < proj_data_info_cyl_sptr->get_min_tof_pos_num() + || measured_bin.timing_pos_num() > proj_data_info_cyl_sptr->get_max_tof_pos_num()) { continue; } diff --git a/src/test/test_time_of_flight.cxx b/src/test/test_time_of_flight.cxx index 12d90769de..9edc0c587b 100644 --- a/src/test/test_time_of_flight.cxx +++ b/src/test/test_time_of_flight.cxx @@ -185,10 +185,10 @@ TOF_Tests::test_tof_proj_data_info() test_proj_data_info_sptr->get_tof_mash_factor(), "Diffent tof mash factor."); check_if_equal(num_timing_positions, - test_proj_data_info_sptr->get_num_timing_poss(), "Diffent in number of timing positions."); + test_proj_data_info_sptr->get_num_tof_poss(), "Diffent in number of timing positions."); - for (int timing_num = test_proj_data_info_sptr->get_min_timing_pos_num(), counter = 0; - timing_num <= test_proj_data_info_sptr->get_max_timing_pos_num(); ++ timing_num, counter++) + for (int timing_num = test_proj_data_info_sptr->get_min_tof_pos_num(), counter = 0; + timing_num <= test_proj_data_info_sptr->get_max_tof_pos_num(); ++ timing_num, counter++) { Bin bin(0, 0, 0, 0, timing_num, 1.f); @@ -198,8 +198,8 @@ TOF_Tests::test_tof_proj_data_info() static_cast(test_proj_data_info_sptr->get_k(bin)), "Error in get_sampling_in_k()"); } - float total_width = test_proj_data_info_sptr->get_k(Bin(0,0,0,0,test_proj_data_info_sptr->get_max_timing_pos_num(),1.f)) - - test_proj_data_info_sptr->get_k(Bin(0,0,0,0,test_proj_data_info_sptr->get_min_timing_pos_num(),1.f)) + float total_width = test_proj_data_info_sptr->get_k(Bin(0,0,0,0,test_proj_data_info_sptr->get_max_tof_pos_num(),1.f)) + - test_proj_data_info_sptr->get_k(Bin(0,0,0,0,test_proj_data_info_sptr->get_min_tof_pos_num(),1.f)) + test_proj_data_info_sptr->get_sampling_in_k(Bin(0,0,0,0,0,1.f)); set_tolerance(static_cast(0.005)); @@ -340,8 +340,8 @@ TOF_Tests::test_tof_kernel_application() export_lor(proj_matrix_row, lor_point_1, lor_point_2, 5000); - for (int timing_num = test_proj_data_info_sptr->get_min_timing_pos_num(); - timing_num <= test_proj_data_info_sptr->get_max_timing_pos_num(); ++ timing_num) + for (int timing_num = test_proj_data_info_sptr->get_min_tof_pos_num(); + timing_num <= test_proj_data_info_sptr->get_max_tof_pos_num(); ++ timing_num) { ProjMatrixElemsForOneBin new_proj_matrix_row; Bin bin(seg_num, view_num, axial_num, tang_num, timing_num, 1.f); diff --git a/src/utilities/create_projdata_template.cxx b/src/utilities/create_projdata_template.cxx index e33eb399db..bb85ad5575 100644 --- a/src/utilities/create_projdata_template.cxx +++ b/src/utilities/create_projdata_template.cxx @@ -63,10 +63,10 @@ int main(int argc, char *argv[]) // TODO, Currently all stir::Scanner types are PET. exam_info_sptr->imaging_modality = ImagingModality::PT; // If TOF activated -- No mashing factor will produce surrealistic sinograms - if ( proj_data_info_sptr->get_tof_mash_factor() >1) - shared_ptr proj_data_sptr(new ProjDataInterfile(exam_info_sptr, proj_data_info_sptr, output_file_name, std::ios::out, - ProjDataFromStream::Timing_Segment_View_AxialPos_TangPos)); - else + //if ( proj_data_info_sptr->get_num_tof_poss() >1) + // shared_ptr proj_data_sptr(new ProjDataInterfile(exam_info_sptr, proj_data_info_sptr, output_file_name, std::ios::out, + // ProjDataFromStream::Timing_Segment_View_AxialPos_TangPos)); + // else shared_ptr proj_data_sptr(new ProjDataInterfile(exam_info_sptr, proj_data_info_sptr, output_file_name)); return EXIT_SUCCESS; diff --git a/src/utilities/list_projdata_info.cxx b/src/utilities/list_projdata_info.cxx index 5ac2c87952..0b1bdd9e6c 100644 --- a/src/utilities/list_projdata_info.cxx +++ b/src/utilities/list_projdata_info.cxx @@ -145,13 +145,13 @@ int main(int argc, char *argv[]) if (print_geom) std::cout << proj_data_sptr->get_proj_data_info_ptr()->parameter_info() << std::endl; - //if (print_min || print_max || print_sum) + if (print_min || print_max || print_sum) { const int min_segment_num = proj_data_sptr->get_min_segment_num(); const int max_segment_num = proj_data_sptr->get_max_segment_num(); - const int min_timing_num = proj_data_sptr->get_min_timing_pos_num(); - const int max_timing_num = proj_data_sptr->get_max_timing_pos_num(); - std::cout << "\nTotal number of timing positions: " << proj_data_sptr->get_num_timing_poss(); + const int min_timing_num = proj_data_sptr->get_min_tof_pos_num(); + const int max_timing_num = proj_data_sptr->get_max_tof_pos_num(); + std::cout << "\nTotal number of TOF positions: " << proj_data_sptr->get_num_tof_poss(); for (int timing_num = min_timing_num; timing_num <= max_timing_num; ++timing_num) { From c35c701a3ea81a22354e9ee1809855765ce9b667 Mon Sep 17 00:00:00 2001 From: Nikos Efthimiou Date: Sun, 12 Feb 2017 22:26:24 +0000 Subject: [PATCH 055/509] Amended CListEvent at LmToProjData --- src/include/stir/listmode/LmToProjData.h | 4 +--- src/include/stir/listmode/LmToProjDataBootstrap.h | 2 +- src/listmode_buildblock/LmToProjData.cxx | 15 ++++++--------- src/listmode_buildblock/LmToProjDataBootstrap.cxx | 6 +++--- 4 files changed, 11 insertions(+), 16 deletions(-) diff --git a/src/include/stir/listmode/LmToProjData.h b/src/include/stir/listmode/LmToProjData.h index 53ed71fc4c..d6e4b9f00e 100644 --- a/src/include/stir/listmode/LmToProjData.h +++ b/src/include/stir/listmode/LmToProjData.h @@ -206,9 +206,7 @@ class LmToProjData : public ParsingObject (on top of anything done by normalisation_ptr). \todo Would need timing info or so for e.g. time dependent normalisation or angle info for a rotating scanner.*/ - //! \warning N.E: I changed this function _from_event to _from_record, because in - //! TOF unlisting we need the timing information which is stored in the record. - virtual void get_bin_from_record(Bin& bin, const CListRecord&) const; + virtual void get_bin_from_event(Bin& bin, const CListEvent&) const; //! A function that should return the number of uncompressed bins in the current bin /*! \todo it is not compatiable with e.g. HiDAC doesn't belong here anyway diff --git a/src/include/stir/listmode/LmToProjDataBootstrap.h b/src/include/stir/listmode/LmToProjDataBootstrap.h index ea7cd8c78c..4d98a813eb 100644 --- a/src/include/stir/listmode/LmToProjDataBootstrap.h +++ b/src/include/stir/listmode/LmToProjDataBootstrap.h @@ -87,7 +87,7 @@ class LmToProjDataBootstrap : public LmToProjDataT /*! Initialises a vector with the number of times each event has to be replicated */ virtual void start_new_time_frame(const unsigned int new_frame_num); - virtual void get_bin_from_record(Bin& bin, const CListRecord&) const; + virtual void get_bin_from_event(Bin& bin, const CListEvent&) const; // \name parsing variables diff --git a/src/listmode_buildblock/LmToProjData.cxx b/src/listmode_buildblock/LmToProjData.cxx index a92dddffef..13ac7c7755 100644 --- a/src/listmode_buildblock/LmToProjData.cxx +++ b/src/listmode_buildblock/LmToProjData.cxx @@ -385,12 +385,12 @@ LmToProjData(const char * const par_filename) ***************************************************************/ void LmToProjData:: -get_bin_from_record(Bin& bin, const CListRecord& record) const +get_bin_from_event(Bin& bin, const CListEvent& event) const { if (do_pre_normalisation) { Bin uncompressed_bin; - record.event().get_bin(uncompressed_bin, *proj_data_info_cyl_uncompressed_ptr); + event.get_bin(uncompressed_bin, *proj_data_info_cyl_uncompressed_ptr); if (uncompressed_bin.get_bin_value()<=0) return; // rejected for some strange reason @@ -426,17 +426,14 @@ get_bin_from_record(Bin& bin, const CListRecord& record) const // template_proj_data_info_ptr->get_bin_from_uncompressed(bin, uncompressed_bin); -// if (use_tof) -// record.full_event(bin, *template_proj_data_info_ptr); -// else - record.event().get_bin(bin, *template_proj_data_info_ptr); + event.get_bin(bin, *template_proj_data_info_ptr); bin.set_bin_value(bin_value); } else { - record.event().get_bin(bin, *template_proj_data_info_ptr); + event.get_bin(bin, *template_proj_data_info_ptr); } } @@ -668,7 +665,7 @@ actual_process_data_without_tof() // set value in case the event decoder doesn't touch it // otherwise it would be 0 and all events will be ignored bin.set_bin_value(1); - get_bin_from_record(bin, record); + get_bin_from_event(bin, record.event()); // check if it's inside the range we want to store if (bin.get_bin_value()>0 @@ -891,7 +888,7 @@ actual_process_data_with_tof() // otherwise it would be 0 and all events will be ignored bin.set_bin_value(1.f); - get_bin_from_record(bin, record); + get_bin_from_event(bin, record.event()); // check if it's inside the range we want to store if (bin.get_bin_value()>0 diff --git a/src/listmode_buildblock/LmToProjDataBootstrap.cxx b/src/listmode_buildblock/LmToProjDataBootstrap.cxx index 6327f8b193..6b2752c7e3 100644 --- a/src/listmode_buildblock/LmToProjDataBootstrap.cxx +++ b/src/listmode_buildblock/LmToProjDataBootstrap.cxx @@ -166,7 +166,7 @@ start_new_time_frame(const unsigned int new_frame_num) // set value in case the event decoder doesn't touch it // otherwise it would be 0 and all events will be ignored bin.set_bin_value(1); - base_type::get_bin_from_record(bin, record); + base_type::get_bin_from_event(bin, record.event()); // check if it's inside the range we want to store if (bin.get_bin_value()>0 && bin.tangential_pos_num()>= this->template_proj_data_info_ptr->get_min_tangential_pos_num() @@ -232,12 +232,12 @@ start_new_time_frame(const unsigned int new_frame_num) template void LmToProjDataBootstrap:: -get_bin_from_record(Bin& bin, const CListRecord& record) const +get_bin_from_event(Bin& bin, const CListEvent& event) const { assert(num_times_to_replicate_iter != num_times_to_replicate.end()); if (*num_times_to_replicate_iter > 0) { - base_type::get_bin_from_record(bin, record); + base_type::get_bin_from_event(bin, event); bin.set_bin_value(bin.get_bin_value() * *num_times_to_replicate_iter); } else From 4407f03eb5baa2ea816f53565eba9377dc81cffc Mon Sep 17 00:00:00 2001 From: Nikos Efthimiou Date: Thu, 23 Feb 2017 14:43:52 +0000 Subject: [PATCH 056/509] cleaned the hdf5 --- CMakeLists.txt | 5 +++++ src/CMakeLists.txt | 4 ---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a735814efc..aa1d471d73 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -57,6 +57,7 @@ option(DISABLE_AVW "disable use of AVW library" OFF) option(DISABLE_RDF "disable use of GE RDF library" OFF) option(DISABLE_STIR_LOCAL "disable use of LOCAL extensions to STIR" OFF) option(DISABLE_CERN_ROOT_SUPPORT "disable use of Cern ROOT libraries" OFF) +option(DISABLE_HDF5_SUPPORT "disable use of HDF5 libraries" OFF) if(NOT DISABLE_ITK) # See if we can find a compiled version of ITK (http://www.itk.org/) @@ -71,6 +72,10 @@ if(NOT DISABLE_CERN_ROOT_SUPPORT) find_package(CERN_ROOT) endif() +if(NOT DISABLE_HDF5_SUPPORT) + find_package(HDF5 COMPONENTS CXX) +endif() + if(NOT DISABLE_AVW) find_package(AVW) endif() diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6c4decee14..f08f1381bb 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -34,10 +34,6 @@ option(BUILD_EXECUTABLES option(BUILD_SHARED_LIBS "Use shared libraries" OFF) -### Settings for external libraries - -find_package(HDF5 COMPONENTS CXX) - if (LLN_FOUND) set(HAVE_ECAT ON) message(STATUS "ECAT support enabled.") From 1f14f44fbf54e59473a7510a715a3135e7d04768 Mon Sep 17 00:00:00 2001 From: Ottavia Date: Thu, 23 Feb 2017 15:29:52 +0000 Subject: [PATCH 057/509] Added get_tof_bin and evaluation of delta time in picoseconds in CListRecordGESigna.h --- src/include/stir/listmode/CListRecordGESigna.h | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/include/stir/listmode/CListRecordGESigna.h b/src/include/stir/listmode/CListRecordGESigna.h index abb4f2fa84..e8e19f4153 100644 --- a/src/include/stir/listmode/CListRecordGESigna.h +++ b/src/include/stir/listmode/CListRecordGESigna.h @@ -76,7 +76,10 @@ class CListEventDataGESigna { return (eventType==COINC_EVT)/* && eventTypeExt==COINC_COUNT_EVT)*/; } // TODO need to find out how to see if it's a coincidence event - + inline int get_tof_bin() const + { + return static_cast(deltaTime); + } private: #if STIRIsNativeByteOrderBigEndian @@ -315,6 +318,16 @@ dynamic_cast(&e2) != 0 && error("don't know how to byteswap"); ByteOrder::swap_order(this->raw[1]); } + + if (this->is_event()) + { + // set TOF info in ps + this->delta_time = this->event_data.get_tof_bin() * + this-> get_scanner_ptr()->get_size_of_timing_bin(); + } + + + return Succeeded::yes; } From a6b96ed569e672bd9d07944410d096776d2c2f37 Mon Sep 17 00:00:00 2001 From: Nikos Efthimiou Date: Thu, 23 Feb 2017 15:37:28 +0000 Subject: [PATCH 058/509] Moved TOF initialisation in ProjDataStream new support function --- src/buildblock/ProjDataFromStream.cxx | 73 +++++++++++++-------------- src/include/stir/ProjDataFromStream.h | 2 + 2 files changed, 36 insertions(+), 39 deletions(-) diff --git a/src/buildblock/ProjDataFromStream.cxx b/src/buildblock/ProjDataFromStream.cxx index 13190f4362..2c369ae792 100644 --- a/src/buildblock/ProjDataFromStream.cxx +++ b/src/buildblock/ProjDataFromStream.cxx @@ -93,30 +93,9 @@ ProjDataFromStream::ProjDataFromStream(shared_ptr const& exam_info_spt assert(storage_order != Unsupported); assert(!(data_type == NumericType::UNKNOWN_TYPE)); - int sum = 0; - for (int segment_num = proj_data_info_ptr->get_min_segment_num(); - segment_num<=proj_data_info_ptr->get_max_segment_num(); - ++segment_num) - { - sum += get_num_axial_poss(segment_num) * get_num_views() * get_num_tangential_poss(); - } - - offset_3d_data = static_cast (sum * on_disk_data_type.size_in_bytes()); + if (proj_data_info_ptr->get_num_tof_poss() > 1) + activate_TOF(); - // Now, lets initialise a TOF stream - Similarly to segments - if (storage_order == Timing_Segment_View_AxialPos_TangPos && - proj_data_info_ptr->get_num_tof_poss() > 1) - { - timing_poss_sequence.resize(proj_data_info_ptr->get_num_tof_poss()); - int timing_pos_num; - - for (int i= 0, timing_pos_num = proj_data_info_ptr->get_min_tof_pos_num(); - timing_pos_num<=proj_data_info_ptr->get_max_tof_pos_num(); - ++i, ++timing_pos_num) - { - timing_poss_sequence[i] = timing_pos_num; - } - } } ProjDataFromStream::ProjDataFromStream(shared_ptr const& exam_info_sptr, @@ -141,31 +120,47 @@ ProjDataFromStream::ProjDataFromStream(shared_ptr const& exam_info_spt //N.E. Take this opportunity to calculate the size of the complete -full- 3D sinogram. // We will need that to skip timing positions - int segment_num, i, tmp_add = 0; + int segment_num, i; + for (i= 0, segment_num = proj_data_info_ptr->get_min_segment_num(); segment_num<=proj_data_info_ptr->get_max_segment_num(); ++i, ++segment_num) { segment_sequence[i] =segment_num; - tmp_add += get_num_axial_poss(segment_num) * get_num_views() * get_num_tangential_poss(); } - offset_3d_data = static_cast (tmp_add * on_disk_data_type.size_in_bytes()); + if (proj_data_info_ptr->get_num_tof_poss() > 1) + activate_TOF(); - // Now, lets initialise a TOF stream - Similarly to segments - if (storage_order == Timing_Segment_View_AxialPos_TangPos && - proj_data_info_ptr->get_num_tof_poss() > 1) - { - timing_poss_sequence.resize(proj_data_info_ptr->get_num_tof_poss()); - int timing_pos_num; +} - for (i= 0, timing_pos_num = proj_data_info_ptr->get_min_tof_pos_num(); - timing_pos_num<=proj_data_info_ptr->get_max_tof_pos_num(); - ++i, ++timing_pos_num) - { - timing_poss_sequence[i] = timing_pos_num; - } - } +void +ProjDataFromStream::activate_TOF() +{ + int sum = 0; + for (int segment_num = proj_data_info_ptr->get_min_segment_num(); + segment_num<=proj_data_info_ptr->get_max_segment_num(); + ++segment_num) + { + sum += get_num_axial_poss(segment_num) * get_num_views() * get_num_tangential_poss(); + } + + offset_3d_data = static_cast (sum * on_disk_data_type.size_in_bytes()); + + // Now, lets initialise a TOF stream - Similarly to segments + if (storage_order == Timing_Segment_View_AxialPos_TangPos && + proj_data_info_ptr->get_num_tof_poss() > 1) + { + timing_poss_sequence.resize(proj_data_info_ptr->get_num_tof_poss()); + int timing_pos_num; + + for (int i= 0, timing_pos_num = proj_data_info_ptr->get_min_tof_pos_num(); + timing_pos_num<=proj_data_info_ptr->get_max_tof_pos_num(); + ++i, ++timing_pos_num) + { + timing_poss_sequence[i] = timing_pos_num; + } + } } Viewgram diff --git a/src/include/stir/ProjDataFromStream.h b/src/include/stir/ProjDataFromStream.h index f4e7394e74..40b160a71e 100644 --- a/src/include/stir/ProjDataFromStream.h +++ b/src/include/stir/ProjDataFromStream.h @@ -163,6 +163,8 @@ class ProjDataFromStream : public ProjData shared_ptr sino_stream; private: + + void activate_TOF(); //! offset of the whole 3d sinogram in the stream std::streamoff offset; //! offset of a complete non-tof sinogram From ac79a3eff916990c44a8d833397d9b138f5f41f0 Mon Sep 17 00:00:00 2001 From: Nikos Efthimiou Date: Thu, 23 Feb 2017 15:44:39 +0000 Subject: [PATCH 059/509] fix in activate_TOF --- src/buildblock/ProjDataFromStream.cxx | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/buildblock/ProjDataFromStream.cxx b/src/buildblock/ProjDataFromStream.cxx index 2c369ae792..543b79338d 100644 --- a/src/buildblock/ProjDataFromStream.cxx +++ b/src/buildblock/ProjDataFromStream.cxx @@ -148,19 +148,17 @@ ProjDataFromStream::activate_TOF() offset_3d_data = static_cast (sum * on_disk_data_type.size_in_bytes()); // Now, lets initialise a TOF stream - Similarly to segments - if (storage_order == Timing_Segment_View_AxialPos_TangPos && - proj_data_info_ptr->get_num_tof_poss() > 1) - { - timing_poss_sequence.resize(proj_data_info_ptr->get_num_tof_poss()); - int timing_pos_num; + storage_order = Timing_Segment_View_AxialPos_TangPos; - for (int i= 0, timing_pos_num = proj_data_info_ptr->get_min_tof_pos_num(); - timing_pos_num<=proj_data_info_ptr->get_max_tof_pos_num(); - ++i, ++timing_pos_num) - { - timing_poss_sequence[i] = timing_pos_num; - } + timing_poss_sequence.resize(proj_data_info_ptr->get_num_tof_poss()); + + for (int i= 0, timing_pos_num = proj_data_info_ptr->get_min_tof_pos_num(); + timing_pos_num<=proj_data_info_ptr->get_max_tof_pos_num(); + ++i, ++timing_pos_num) + { + timing_poss_sequence[i] = timing_pos_num; } + } Viewgram From d8553d5676bc9d8ecefd624a8fc0af376f5ae626 Mon Sep 17 00:00:00 2001 From: Nikos Efthimiou Date: Thu, 23 Feb 2017 16:02:56 +0000 Subject: [PATCH 060/509] swapped has been moved to CListEvent --- .../CListEventCylindricalScannerWithDiscreteDetectors.h | 1 + src/include/stir/listmode/CListRecord.h | 8 ++++++++ src/include/stir/listmode/CListRecordROOT.h | 3 +-- src/utilities/create_projdata_template.cxx | 2 +- 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/include/stir/listmode/CListEventCylindricalScannerWithDiscreteDetectors.h b/src/include/stir/listmode/CListEventCylindricalScannerWithDiscreteDetectors.h index caf533641f..2337be4d87 100644 --- a/src/include/stir/listmode/CListEventCylindricalScannerWithDiscreteDetectors.h +++ b/src/include/stir/listmode/CListEventCylindricalScannerWithDiscreteDetectors.h @@ -78,6 +78,7 @@ class CListEventCylindricalScannerWithDiscreteDetectors : public CListEvent } shared_ptr scanner_sptr; + private: shared_ptr uncompressed_proj_data_info_sptr; diff --git a/src/include/stir/listmode/CListRecord.h b/src/include/stir/listmode/CListRecord.h index 2eeb9fb7b5..0497ff549f 100644 --- a/src/include/stir/listmode/CListRecord.h +++ b/src/include/stir/listmode/CListRecord.h @@ -109,11 +109,19 @@ class CListEvent void get_bin(Bin& bin, const ProjDataInfo&) const; + //! Returns true is the delta_time has been swapped. + bool + get_swapped() const + {return swapped;} + protected: //! The detection time difference, between the two photons. //! This will work for ROOT files, but not so sure about acquired data. double delta_time; + //! Indicates if the detectors' order has been swapped. + bool swapped; + }; /*-coincidence event*/ diff --git a/src/include/stir/listmode/CListRecordROOT.h b/src/include/stir/listmode/CListRecordROOT.h index 3cee3e0aaa..6c2b10177e 100644 --- a/src/include/stir/listmode/CListRecordROOT.h +++ b/src/include/stir/listmode/CListRecordROOT.h @@ -70,8 +70,7 @@ class CListEventROOT : public CListEventCylindricalScannerWithDiscreteDetectors int det1; //! Second detector, in order to detector tangestial index int det2; - //! Indicates if swap segments - bool swapped; + //! This is the number of detector we have to rotate in order to //! align GATE and STIR. int quarter_of_detectors; diff --git a/src/utilities/create_projdata_template.cxx b/src/utilities/create_projdata_template.cxx index bb85ad5575..877995452c 100644 --- a/src/utilities/create_projdata_template.cxx +++ b/src/utilities/create_projdata_template.cxx @@ -66,7 +66,7 @@ int main(int argc, char *argv[]) //if ( proj_data_info_sptr->get_num_tof_poss() >1) // shared_ptr proj_data_sptr(new ProjDataInterfile(exam_info_sptr, proj_data_info_sptr, output_file_name, std::ios::out, // ProjDataFromStream::Timing_Segment_View_AxialPos_TangPos)); - // else + //else shared_ptr proj_data_sptr(new ProjDataInterfile(exam_info_sptr, proj_data_info_sptr, output_file_name)); return EXIT_SUCCESS; From 44f317e4593e4afa3482e3bc170ef9b591b6cbdd Mon Sep 17 00:00:00 2001 From: Elise Date: Thu, 23 Feb 2017 17:13:03 +0000 Subject: [PATCH 061/509] Additional fix for HDF5 library before merge with UCL-STIR/tof_sino repo --- src/IO/IO_registries.cxx | 5 +++++ src/listmode_buildblock/CMakeLists.txt | 7 ++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/IO/IO_registries.cxx b/src/IO/IO_registries.cxx index 6cef3d8603..df02a03c1c 100644 --- a/src/IO/IO_registries.cxx +++ b/src/IO/IO_registries.cxx @@ -55,7 +55,10 @@ #endif #include "stir/IO/ECAT8_32bitListmodeInputFileFormat.h" +#ifdef HAVE_HDF5 #include "stir/IO/GESignaListmodeInputFileFormat.h" +#endif + //! Addition for ROOT support - Nikos Efthimiou #ifdef HAVE_CERN_ROOT #include "stir/IO/ROOTListmodeInputFileFormat.h" @@ -123,6 +126,8 @@ static RegisterInputFileFormat LMdu static RegisterInputFileFormat LMdummyECAT962(5); #endif static RegisterInputFileFormat LMdummyECAT8(6); +#ifdef HAVE_HDF5 static RegisterInputFileFormat LMdummyGESigna(7); +#endif END_NAMESPACE_STIR diff --git a/src/listmode_buildblock/CMakeLists.txt b/src/listmode_buildblock/CMakeLists.txt index 9e705fcee8..1f2afd6186 100644 --- a/src/listmode_buildblock/CMakeLists.txt +++ b/src/listmode_buildblock/CMakeLists.txt @@ -11,9 +11,14 @@ set(${dir_LIB_SOURCES} LmToProjDataBootstrap CListModeDataECAT8_32bit CListRecordECAT8_32bit +) + +if (HAVE_HDF5) +list(APPEND ${dir_LIB_SOURCES} CListModeDataGESigna -# CListRecordGESigna + # CListRecordGESigna ) +endif() if (HAVE_ECAT) list(APPEND ${dir_LIB_SOURCES} From 3a26c731252b1493d6bd448dbf840946ed2027d0 Mon Sep 17 00:00:00 2001 From: Nikos Efthimiou Date: Thu, 23 Feb 2017 17:20:23 +0000 Subject: [PATCH 062/509] Corrected num_events to store in lm_to_proj_data --- src/include/stir/listmode/LmToProjData.h | 2 +- src/listmode_buildblock/LmToProjData.cxx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/include/stir/listmode/LmToProjData.h b/src/include/stir/listmode/LmToProjData.h index d6e4b9f00e..2cd25a678e 100644 --- a/src/include/stir/listmode/LmToProjData.h +++ b/src/include/stir/listmode/LmToProjData.h @@ -242,7 +242,7 @@ class LmToProjData : public ParsingObject bool use_tof; int num_segments_in_memory; // TODO make long (or even unsigned long) but can't do this yet because we can't parse longs yet - unsigned long int num_events_to_store; + int num_events_to_store; int max_segment_num_to_process; //! Toggle readable output on stdout or actual projdata diff --git a/src/listmode_buildblock/LmToProjData.cxx b/src/listmode_buildblock/LmToProjData.cxx index 13ac7c7755..a062a9e89f 100644 --- a/src/listmode_buildblock/LmToProjData.cxx +++ b/src/listmode_buildblock/LmToProjData.cxx @@ -609,7 +609,7 @@ actual_process_data_without_tof() // ('allowed' independent on the fact of we have its segment in memory or not) // When do_time_frame=true, the number of events is irrelevant, so we // just set more_events to 1, and never change it - unsigned long int more_events = + long int more_events = do_time_frame? 1 : num_events_to_store; if (start_segment_index != proj_data_ptr->get_min_segment_num()) From d5f9e50f1baf3d64237f19a5c5c7b10256ced220 Mon Sep 17 00:00:00 2001 From: Nikos Efthimiou Date: Thu, 23 Feb 2017 17:56:59 +0000 Subject: [PATCH 063/509] Removed get_rof_mash_factor() as trigger, leftovers --- src/IO/interfile.cxx | 2 +- src/listmode_buildblock/LmToProjData.cxx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/IO/interfile.cxx b/src/IO/interfile.cxx index 4666f23bf8..633e11e0f0 100644 --- a/src/IO/interfile.cxx +++ b/src/IO/interfile.cxx @@ -848,7 +848,7 @@ write_basic_interfile_PDFS_header(const string& header_file_name, // it's PET data if we get here // N.E. Added timing locations - pdfs.get_proj_data_info_ptr()->get_tof_mash_factor()>1 ? + pdfs.get_proj_data_info_ptr()->get_num_tof_poss()>1 ? output_header << "number of dimensions := 5\n" : output_header << "number of dimensions := 4\n"; diff --git a/src/listmode_buildblock/LmToProjData.cxx b/src/listmode_buildblock/LmToProjData.cxx index a062a9e89f..0dd9a0a262 100644 --- a/src/listmode_buildblock/LmToProjData.cxx +++ b/src/listmode_buildblock/LmToProjData.cxx @@ -1113,7 +1113,7 @@ construct_proj_data(shared_ptr& output, shared_ptr proj_data_sptr; #ifdef USE_SegmentByView // don't need output stream in this case - if (proj_data_info_ptr->get_tof_mash_factor() == 1) + if (proj_data_info_ptr->get_num_tof_poss() == 1) proj_data_sptr.reset(new ProjDataInterfile(exam_info_sptr, proj_data_info_ptr, output_filename, ios::out, ProjDataFromStream::Segment_View_AxialPos_TangPos, From 502743ac9183b0d3fab3c06fe7ef670f21b87950 Mon Sep 17 00:00:00 2001 From: Elise Date: Fri, 3 Mar 2017 10:55:01 +0000 Subject: [PATCH 064/509] Fix to read template header in boundary conditions --- src/buildblock/ProjDataInfo.cxx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/buildblock/ProjDataInfo.cxx b/src/buildblock/ProjDataInfo.cxx index 5ea9cd50bd..cf2f2e0b22 100644 --- a/src/buildblock/ProjDataInfo.cxx +++ b/src/buildblock/ProjDataInfo.cxx @@ -226,17 +226,15 @@ ProjDataInfo::set_tof_mash_factor(const int new_num) % tof_bin_boundaries_ps[i].low_lim % tof_bin_boundaries_ps[i].high_lim % get_sampling_in_k(bin)); } } - else if (new_num == 0) // Case new_num = 0, will produce non-TOF data for a TOF compatible scanner. + else if ((scanner_ptr->is_tof_ready() && new_num <= 0) + || !scanner_ptr->is_tof_ready()) // Case new_num <=, will produce non-TOF data for a TOF compatible scanner { num_tof_bins = 1; tof_mash_factor = 0; min_tof_pos_num = 0; max_tof_pos_num = 0; - - // Should I initialise here and the boundaries? + // we assume TOF mashing factor = 0 means non-TOF and the projecter won't use any boundary conditions } - else - error("Not TOF compatible scanner template. Abort."); } @@ -261,6 +259,7 @@ ProjDataInfo::ProjDataInfo(const shared_ptr& scanner_ptr_v, max_tof_pos_num = 0; tof_increament_in_mm = 0.f; tof_mash_factor = 0; + num_tof_bins = 1; } // TOF version. From f6a19ff57f6ea101f894fbc4029c6e59a277509e Mon Sep 17 00:00:00 2001 From: Elise Date: Wed, 22 Mar 2017 13:04:10 +0000 Subject: [PATCH 065/509] timing position index added to Segment + forward/back projection for TOF sinograms + TOF recontructions --- src/buildblock/ML_norm.cxx | 123 +++--- src/buildblock/ProjData.cxx | 78 ++-- src/buildblock/ProjDataFromStream.cxx | 77 ++-- src/buildblock/ProjDataGEAdvance.cxx | 4 +- src/buildblock/ProjDataInMemory.cxx | 31 +- src/buildblock/ProjDataInfo.cxx | 35 +- src/buildblock/RelatedViewgrams.cxx | 10 + src/buildblock/Scanner.cxx | 30 +- src/buildblock/Segment.cxx | 10 + src/buildblock/SegmentBySinogram.cxx | 16 +- src/buildblock/SegmentByView.cxx | 14 +- src/buildblock/Sinogram.cxx | 10 + src/buildblock/Viewgram.cxx | 10 + src/buildblock/interpolate_projdata.cxx | 3 +- src/include/stir/DynamicProjData.h | 42 +- .../IO/InputStreamWithRecordsFromHDF5.inl | 6 +- src/include/stir/ProjData.h | 94 +++-- src/include/stir/ProjDataFromStream.h | 6 +- src/include/stir/ProjDataGEAdvance.h | 4 +- src/include/stir/ProjDataInfo.h | 16 +- src/include/stir/ProjDataInfo.inl | 21 + src/include/stir/RelatedViewgrams.h | 3 + src/include/stir/RelatedViewgrams.inl | 8 + src/include/stir/Scanner.h | 2 +- src/include/stir/Segment.h | 5 +- src/include/stir/Segment.inl | 9 +- src/include/stir/SegmentBySinogram.h | 10 +- src/include/stir/SegmentBySinogram.inl | 3 +- src/include/stir/SegmentByView.h | 6 +- src/include/stir/SegmentByView.inl | 3 +- src/include/stir/Sinogram.h | 7 +- src/include/stir/Sinogram.inl | 17 +- src/include/stir/ViewSegmentNumbers.h | 4 +- src/include/stir/ViewSegmentNumbers.inl | 2 - src/include/stir/Viewgram.h | 9 +- src/include/stir/Viewgram.inl | 16 +- src/include/stir/listmode/CListRecord.h | 14 + .../stir/listmode/CListRecordGESigna.h | 3 +- .../recon_buildblock/BackProjectorByBin.h | 15 +- .../BackProjectorByBinUsingInterpolation.h | 3 + .../BackProjectorByBinUsingProjMatrixByBin.h | 11 +- ...ProjectorByBinUsingSquareProjMatrixByBin.h | 3 + .../recon_buildblock/DataSymmetriesForBins.h | 3 +- .../recon_buildblock/ForwardProjectorByBin.h | 28 ++ ...orwardProjectorByBinUsingProjMatrixByBin.h | 3 + .../ForwardProjectorByBinUsingRayTracing.h | 3 + ...orMeanAndListModeDataWithProjMatrixByBin.h | 13 +- .../PostsmoothingBackProjectorByBin.h | 9 + .../PresmoothingForwardProjectorByBin.h | 8 +- .../stir/recon_buildblock/ProjMatrixByBin.inl | 14 + .../recon_buildblock/ProjectorByBinPair.h | 11 +- .../TrivialDataSymmetriesForBins.h | 3 +- src/listmode_buildblock/LmToProjData.cxx | 39 +- src/listmode_utilities/list_lm_events.cxx | 1 + src/local/utilities/fillwithotherprojdata.cxx | 12 +- src/local/utilities/inverse_proj_data.cxx | 101 +++-- src/recon_buildblock/BackProjectorByBin.cxx | 15 +- .../BackProjectorByBinUsingInterpolation.cxx | 8 +- ...BackProjectorByBinUsingProjMatrixByBin.cxx | 35 +- ...ojectorByBinUsingSquareProjMatrixByBin.cxx | 7 + src/recon_buildblock/BinNormalisation.cxx | 55 ++- .../DataSymmetriesForBins.cxx | 10 +- .../ForwardProjectorByBin.cxx | 50 ++- ...wardProjectorByBinUsingProjMatrixByBin.cxx | 47 ++- .../ForwardProjectorByBinUsingRayTracing.cxx | 25 +- ...MeanAndListModeDataWithProjMatrixByBin.cxx | 376 +++++++++++------- .../PostsmoothingBackProjectorByBin.cxx | 39 +- .../PresmoothingForwardProjectorByBin.cxx | 29 +- src/recon_buildblock/ProjMatrixByBin.cxx | 3 + src/recon_buildblock/ProjectorByBinPair.cxx | 175 ++++++++ .../TrivialDataSymmetriesForBins.cxx | 7 +- ...ihoodWithLinearModelForMeanAndProjData.cxx | 81 ++-- src/test/test_proj_data_in_memory.cxx | 139 +++++++ src/utilities/forward_project.cxx | 2 +- src/utilities/poisson_noise.cxx | 45 ++- src/utilities/stir_math.cxx | 46 ++- 76 files changed, 1597 insertions(+), 638 deletions(-) diff --git a/src/buildblock/ML_norm.cxx b/src/buildblock/ML_norm.cxx index 873eacfdd1..a4290eb521 100644 --- a/src/buildblock/ML_norm.cxx +++ b/src/buildblock/ML_norm.cxx @@ -768,25 +768,28 @@ void make_fan_data(FanProjData& fan_data, for (bin.segment_num() = proj_data.get_min_segment_num(); bin.segment_num() <= proj_data.get_max_segment_num(); ++ bin.segment_num()) { - segment_ptr.reset(new SegmentBySinogram(proj_data.get_segment_by_sinogram(bin.segment_num()))); - - for (bin.axial_pos_num() = proj_data.get_min_axial_pos_num(bin.segment_num()); - bin.axial_pos_num() <= proj_data.get_max_axial_pos_num(bin.segment_num()); - ++bin.axial_pos_num()) - for (bin.view_num() = 0; bin.view_num() < num_detectors_per_ring/2; bin.view_num()++) - for (bin.tangential_pos_num() = -half_fan_size; - bin.tangential_pos_num() <= half_fan_size; - ++bin.tangential_pos_num()) - { - int ra = 0, a = 0; - int rb = 0, b = 0; - - proj_data_info_ptr->get_det_pair_for_bin(a, ra, b, rb, bin); - - fan_data(ra, a, rb, b) = - fan_data(rb, b, ra, a) = - (*segment_ptr)[bin.axial_pos_num()][bin.view_num()][bin.tangential_pos_num()]; + for (bin.timing_pos_num() = proj_data.get_min_tof_pos_num(); bin.timing_pos_num() <= proj_data.get_max_tof_pos_num(); ++ bin.timing_pos_num()) + { + segment_ptr.reset(new SegmentBySinogram(proj_data.get_segment_by_sinogram(bin.segment_num(),bin.timing_pos_num()))); + + for (bin.axial_pos_num() = proj_data.get_min_axial_pos_num(bin.segment_num()); + bin.axial_pos_num() <= proj_data.get_max_axial_pos_num(bin.segment_num()); + ++bin.axial_pos_num()) + for (bin.view_num() = 0; bin.view_num() < num_detectors_per_ring/2; bin.view_num()++) + for (bin.tangential_pos_num() = -half_fan_size; + bin.tangential_pos_num() <= half_fan_size; + ++bin.tangential_pos_num()) + { + int ra = 0, a = 0; + int rb = 0, b = 0; + + proj_data_info_ptr->get_det_pair_for_bin(a, ra, b, rb, bin); + + fan_data(ra, a, rb, b) = + fan_data(rb, b, ra, a) = + (*segment_ptr)[bin.axial_pos_num()][bin.view_num()][bin.tangential_pos_num()]; } + } } } @@ -811,25 +814,28 @@ void set_fan_data(ProjData& proj_data, for (bin.segment_num() = proj_data.get_min_segment_num(); bin.segment_num() <= proj_data.get_max_segment_num(); ++ bin.segment_num()) { - segment_ptr.reset(new SegmentBySinogram(proj_data.get_empty_segment_by_sinogram(bin.segment_num()))); - - for (bin.axial_pos_num() = proj_data.get_min_axial_pos_num(bin.segment_num()); - bin.axial_pos_num() <= proj_data.get_max_axial_pos_num(bin.segment_num()); - ++bin.axial_pos_num()) - for (bin.view_num() = 0; bin.view_num() < num_detectors_per_ring/2; bin.view_num()++) - for (bin.tangential_pos_num() = -half_fan_size; - bin.tangential_pos_num() <= half_fan_size; - ++bin.tangential_pos_num()) - { - int ra = 0, a = 0; - int rb = 0, b = 0; - - proj_data_info_ptr->get_det_pair_for_bin(a, ra, b, rb, bin); - - (*segment_ptr)[bin.axial_pos_num()][bin.view_num()][bin.tangential_pos_num()] = - fan_data(ra, a, rb, b); - } - proj_data.set_segment(*segment_ptr); + for (bin.timing_pos_num() = proj_data.get_min_tof_pos_num(); bin.timing_pos_num() <= proj_data.get_max_tof_pos_num(); ++ bin.timing_pos_num()) + { + segment_ptr.reset(new SegmentBySinogram(proj_data.get_empty_segment_by_sinogram(bin.segment_num(),bin.timing_pos_num()))); + + for (bin.axial_pos_num() = proj_data.get_min_axial_pos_num(bin.segment_num()); + bin.axial_pos_num() <= proj_data.get_max_axial_pos_num(bin.segment_num()); + ++bin.axial_pos_num()) + for (bin.view_num() = 0; bin.view_num() < num_detectors_per_ring/2; bin.view_num()++) + for (bin.tangential_pos_num() = -half_fan_size; + bin.tangential_pos_num() <= half_fan_size; + ++bin.tangential_pos_num()) + { + int ra = 0, a = 0; + int rb = 0, b = 0; + + proj_data_info_ptr->get_det_pair_for_bin(a, ra, b, rb, bin); + + (*segment_ptr)[bin.axial_pos_num()][bin.view_num()][bin.tangential_pos_num()] = + fan_data(ra, a, rb, b); + } + proj_data.set_segment(*segment_ptr); + } } } @@ -940,26 +946,29 @@ void make_fan_sum_data(Array<2,float>& data_fan_sums, for (bin.segment_num() = proj_data.get_min_segment_num(); bin.segment_num() <= proj_data.get_max_segment_num(); ++ bin.segment_num()) { - segment_ptr.reset(new SegmentBySinogram(proj_data.get_segment_by_sinogram(bin.segment_num()))); - - for (bin.axial_pos_num() = proj_data.get_min_axial_pos_num(bin.segment_num()); - bin.axial_pos_num() <= proj_data.get_max_axial_pos_num(bin.segment_num()); - ++bin.axial_pos_num()) - for (bin.view_num() = 0; bin.view_num() < num_detectors_per_ring/2; bin.view_num()++) - for (bin.tangential_pos_num() = -half_fan_size; - bin.tangential_pos_num() <= half_fan_size; - ++bin.tangential_pos_num()) - { - int ra = 0, a = 0; - int rb = 0, b = 0; - - proj_data_info_ptr->get_det_pair_for_bin(a, ra, b, rb, bin); - - const float value = - (*segment_ptr)[bin.axial_pos_num()][bin.view_num()][bin.tangential_pos_num()]; - data_fan_sums[ra][a] += value; - data_fan_sums[rb][b] += value; - } + for (bin.timing_pos_num() = proj_data.get_min_tof_pos_num(); bin.timing_pos_num() <= proj_data.get_max_tof_pos_num(); ++ bin.timing_pos_num()) + { + segment_ptr.reset(new SegmentBySinogram(proj_data.get_segment_by_sinogram(bin.segment_num(),bin.timing_pos_num()))); + + for (bin.axial_pos_num() = proj_data.get_min_axial_pos_num(bin.segment_num()); + bin.axial_pos_num() <= proj_data.get_max_axial_pos_num(bin.segment_num()); + ++bin.axial_pos_num()) + for (bin.view_num() = 0; bin.view_num() < num_detectors_per_ring/2; bin.view_num()++) + for (bin.tangential_pos_num() = -half_fan_size; + bin.tangential_pos_num() <= half_fan_size; + ++bin.tangential_pos_num()) + { + int ra = 0, a = 0; + int rb = 0, b = 0; + + proj_data_info_ptr->get_det_pair_for_bin(a, ra, b, rb, bin); + + const float value = + (*segment_ptr)[bin.axial_pos_num()][bin.view_num()][bin.tangential_pos_num()]; + data_fan_sums[ra][a] += value; + data_fan_sums[rb][b] += value; + } + } } } diff --git a/src/buildblock/ProjData.cxx b/src/buildblock/ProjData.cxx index c5b1e77e16..efa79a7c1c 100644 --- a/src/buildblock/ProjData.cxx +++ b/src/buildblock/ProjData.cxx @@ -222,36 +222,40 @@ read_from_file(const string& filename, Viewgram ProjData::get_empty_viewgram(const int view_num, const int segment_num, - const bool make_num_tangential_poss_odd) const + const bool make_num_tangential_poss_odd, + const int timing_pos) const { return - proj_data_info_ptr->get_empty_viewgram(view_num, segment_num, make_num_tangential_poss_odd); + proj_data_info_ptr->get_empty_viewgram(view_num, segment_num, make_num_tangential_poss_odd, timing_pos); } Sinogram ProjData::get_empty_sinogram(const int ax_pos_num, const int segment_num, - const bool make_num_tangential_poss_odd) const + const bool make_num_tangential_poss_odd, + const int timing_pos) const { return - proj_data_info_ptr->get_empty_sinogram(ax_pos_num, segment_num, make_num_tangential_poss_odd); + proj_data_info_ptr->get_empty_sinogram(ax_pos_num, segment_num, make_num_tangential_poss_odd, timing_pos); } SegmentBySinogram ProjData::get_empty_segment_by_sinogram(const int segment_num, - const bool make_num_tangential_poss_odd) const + const bool make_num_tangential_poss_odd, + const int timing_pos) const { return - proj_data_info_ptr->get_empty_segment_by_sinogram(segment_num, make_num_tangential_poss_odd); + proj_data_info_ptr->get_empty_segment_by_sinogram(segment_num, make_num_tangential_poss_odd, timing_pos); } SegmentByView ProjData::get_empty_segment_by_view(const int segment_num, - const bool make_num_tangential_poss_odd) const + const bool make_num_tangential_poss_odd, + const int timing_pos) const { return - proj_data_info_ptr->get_empty_segment_by_view(segment_num, make_num_tangential_poss_odd); + proj_data_info_ptr->get_empty_segment_by_view(segment_num, make_num_tangential_poss_odd, timing_pos); } @@ -259,23 +263,25 @@ RelatedViewgrams ProjData::get_empty_related_viewgrams(const ViewSegmentNumbers& view_segmnet_num, //const int view_num, const int segment_num, const shared_ptr& symmetries_used, - const bool make_num_tangential_poss_odd) const + const bool make_num_tangential_poss_odd, + const int timing_pos) const { return - proj_data_info_ptr->get_empty_related_viewgrams(view_segmnet_num, symmetries_used, make_num_tangential_poss_odd); + proj_data_info_ptr->get_empty_related_viewgrams(view_segmnet_num, symmetries_used, make_num_tangential_poss_odd, timing_pos); } RelatedViewgrams -ProjData::get_related_viewgrams(const ViewSegmentNumbers& view_segmnet_num, +ProjData::get_related_viewgrams(const ViewSegmentNumbers& view_segment_num, //const int view_num, const int segment_num, const shared_ptr& symmetries_used, - const bool make_num_bins_odd) const + const bool make_num_bins_odd, + const int timing_pos) const { vector pairs; symmetries_used->get_related_view_segment_numbers( pairs, - ViewSegmentNumbers(view_segmnet_num.view_num(),view_segmnet_num.segment_num()) + ViewSegmentNumbers(view_segment_num.view_num(),view_segment_num.segment_num()) ); vector > viewgrams; @@ -285,7 +291,7 @@ ProjData::get_related_viewgrams(const ViewSegmentNumbers& view_segmnet_num, { // TODO optimise to get shared proj_data_info_ptr viewgrams.push_back(get_viewgram(pairs[i].view_num(), - pairs[i].segment_num(), make_num_bins_odd)); + pairs[i].segment_num(), make_num_bins_odd,timing_pos)); } return RelatedViewgrams(viewgrams, symmetries_used); @@ -332,35 +338,32 @@ ProjData::set_related_viewgrams( const RelatedViewgrams& viewgrams) } #endif -SegmentBySinogram ProjData::get_segment_by_sinogram(const int segment_num, const int timing_num) const +SegmentBySinogram ProjData::get_segment_by_sinogram(const int segment_num, const int timing_pos) const { SegmentBySinogram segment = - proj_data_info_ptr->get_empty_segment_by_sinogram(segment_num,false); + proj_data_info_ptr->get_empty_segment_by_sinogram(segment_num,false,timing_pos); // TODO optimise to get shared proj_data_info_ptr for (int view_num = get_min_view_num(); view_num <= get_max_view_num(); ++view_num) - segment.set_viewgram(get_viewgram(view_num, segment_num, false, timing_num)); - + segment.set_viewgram(get_viewgram(view_num, segment_num, false, timing_pos)); return segment; } -SegmentByView ProjData::get_segment_by_view(const int segment_num, const int timing_num) const +SegmentByView ProjData::get_segment_by_view(const int segment_num, const int timing_pos) const { SegmentByView segment = - proj_data_info_ptr->get_empty_segment_by_view(segment_num,false); + proj_data_info_ptr->get_empty_segment_by_view(segment_num,false,timing_pos); // TODO optimise to get shared proj_data_info_ptr for (int view_num = get_min_view_num(); view_num <= get_max_view_num(); ++view_num) - segment.set_viewgram(get_viewgram(view_num, segment_num, false, timing_num)); - + segment.set_viewgram(get_viewgram(view_num, segment_num, false, timing_pos)); return segment; } Succeeded -ProjData::set_segment(const SegmentBySinogram& segment, - const int& timing_pos) +ProjData::set_segment(const SegmentBySinogram& segment) { for (int view_num = get_min_view_num(); view_num <= get_max_view_num(); ++view_num) { - if(set_viewgram(segment.get_viewgram(view_num), timing_pos) + if(set_viewgram(segment.get_viewgram(view_num)) == Succeeded::no) return Succeeded::no; } @@ -368,12 +371,11 @@ ProjData::set_segment(const SegmentBySinogram& segment, } Succeeded -ProjData::set_segment(const SegmentByView& segment, - const int& timing_pos) +ProjData::set_segment(const SegmentByView& segment) { for (int view_num = get_min_view_num(); view_num <= get_max_view_num(); ++view_num) { - if(set_viewgram(segment.get_viewgram(view_num), timing_pos) + if(set_viewgram(segment.get_viewgram(view_num)) == Succeeded::no) return Succeeded::no; } @@ -386,10 +388,13 @@ ProjData::fill(const float value) { for (int segment_num = this->get_min_segment_num(); segment_num <= this->get_max_segment_num(); ++segment_num) { - SegmentByView segment(this->get_empty_segment_by_view(segment_num)); - segment.fill(value); - if(this->set_segment(segment) == Succeeded::no) - error("Error setting segment of projection data"); + for (int timing_pos_num = this->get_min_tof_pos_num(); timing_pos_num <= this->get_max_tof_pos_num(); ++timing_pos_num) + { + SegmentByView segment(this->get_empty_segment_by_view(segment_num, false, timing_pos_num)); + segment.fill(value); + if(this->set_segment(segment) == Succeeded::no) + error("Error setting segment of projection data"); + } } } @@ -404,9 +409,12 @@ ProjData::fill(const ProjData& proj_data) for (int segment_num = this->get_min_segment_num(); segment_num <= this->get_max_segment_num(); ++segment_num) { - if(this->set_segment(proj_data.get_segment_by_view(segment_num)) - == Succeeded::no) - error("Error setting segment of projection data"); + for (int timing_pos_num = this->get_min_tof_pos_num(); timing_pos_num <= this->get_max_tof_pos_num(); ++timing_pos_num) + { + if(this->set_segment(proj_data.get_segment_by_view(segment_num, timing_pos_num)) + == Succeeded::no) + error("Error setting segment of projection data"); + } } } diff --git a/src/buildblock/ProjDataFromStream.cxx b/src/buildblock/ProjDataFromStream.cxx index 543b79338d..eda400bf18 100644 --- a/src/buildblock/ProjDataFromStream.cxx +++ b/src/buildblock/ProjDataFromStream.cxx @@ -188,10 +188,10 @@ ProjDataFromStream::get_viewgram(const int view_num, const int segment_num, error("ProjDataFromStream::get_viewgram: error after seekg\n"); } - Viewgram viewgram(proj_data_info_ptr, view_num, segment_num); + Viewgram viewgram(proj_data_info_ptr, view_num, segment_num, timing_pos); float scale = float(1); - if (get_storage_order() == Segment_AxialPos_View_TangPos) + if (get_storage_order() == Segment_AxialPos_View_TangPos) //|| get_storage_order() == Timing_Segment_AxialPos_View_TangPos) { for (int ax_pos_num = get_min_axial_pos_num(segment_num); ax_pos_num <= get_max_axial_pos_num(segment_num); ax_pos_num++) { @@ -208,7 +208,7 @@ ProjDataFromStream::get_viewgram(const int view_num, const int segment_num, } - else if (get_storage_order() == Segment_View_AxialPos_TangPos) + else if (get_storage_order() == Segment_View_AxialPos_TangPos || get_storage_order() == Timing_Segment_View_AxialPos_TangPos) { if(read_data(*sino_stream, viewgram, on_disk_data_type, scale, on_disk_byte_order) == Succeeded::no) @@ -373,7 +373,7 @@ ProjDataFromStream::get_offsets(const int view_num, const int segment_num, } Succeeded -ProjDataFromStream::set_viewgram(const Viewgram& v, const int &timing_pos) +ProjDataFromStream::set_viewgram(const Viewgram& v) { if (sino_stream == 0) { @@ -421,6 +421,7 @@ ProjDataFromStream::set_viewgram(const Viewgram& v, const int &timing_pos } int segment_num = v.get_segment_num(); int view_num = v.get_view_num(); + int timing_pos = v.get_timing_pos_num(); vector offsets = get_offsets(view_num,segment_num, timing_pos); @@ -747,7 +748,7 @@ ProjDataFromStream::get_sinogram(const int ax_pos_num, const int segment_num, error("ProjDataFromStream::get_sinogram: error after seekg\n"); } - Sinogram sinogram(proj_data_info_ptr, ax_pos_num, segment_num); + Sinogram sinogram(proj_data_info_ptr, ax_pos_num, segment_num, timing_pos); float scale = float(1); if (get_storage_order() == Segment_AxialPos_View_TangPos) @@ -760,7 +761,7 @@ ProjDataFromStream::get_sinogram(const int ax_pos_num, const int segment_num, } - else if (get_storage_order() == Segment_View_AxialPos_TangPos) + else if (get_storage_order() == Segment_View_AxialPos_TangPos || get_storage_order() == Timing_Segment_View_AxialPos_TangPos) { for (int view = get_min_view_num(); view <= get_max_view_num(); view++) { @@ -792,7 +793,7 @@ ProjDataFromStream::get_sinogram(const int ax_pos_num, const int segment_num, } Succeeded -ProjDataFromStream::set_sinogram(const Sinogram& s, const int &timing_pos) +ProjDataFromStream::set_sinogram(const Sinogram& s) { if (sino_stream == 0) { @@ -825,6 +826,7 @@ ProjDataFromStream::set_sinogram(const Sinogram& s, const int &timing_pos } int segment_num = s.get_segment_num(); int ax_pos_num = s.get_axial_pos_num(); + int timing_pos = s.get_timing_pos_num(); vector offsets = get_offsets_sino(ax_pos_num,segment_num, timing_pos); @@ -859,7 +861,7 @@ ProjDataFromStream::set_sinogram(const Sinogram& s, const int &timing_pos return Succeeded::yes; } - else if (get_storage_order() == Segment_View_AxialPos_TangPos) + else if (get_storage_order() == Segment_View_AxialPos_TangPos || get_storage_order() == Timing_Segment_View_AxialPos_TangPos) { for (int view = get_min_view_num();view <= get_max_view_num(); view++) { @@ -956,6 +958,9 @@ ProjDataFromStream::get_segment_by_sinogram(const int segment_num, const int tim } streamoff segment_offset = get_offset_segment(segment_num); + // Go to the right timing full 3D sinogram + segment_offset += get_offset_timing(timing_num) ; + sino_stream->seekg(segment_offset, ios::beg); if (! *sino_stream) { @@ -964,7 +969,7 @@ ProjDataFromStream::get_segment_by_sinogram(const int segment_num, const int tim if (get_storage_order() == Segment_AxialPos_View_TangPos) { - SegmentBySinogram segment(proj_data_info_ptr,segment_num); + SegmentBySinogram segment(proj_data_info_ptr,segment_num, timing_num); { float scale = float(1); if(read_data(*sino_stream, segment, on_disk_data_type, scale, on_disk_byte_order) @@ -982,7 +987,7 @@ ProjDataFromStream::get_segment_by_sinogram(const int segment_num, const int tim else { // TODO rewrite in terms of get_viewgram - return SegmentBySinogram (get_segment_by_view(segment_num)); + return SegmentBySinogram (get_segment_by_view(segment_num, timing_num)); } @@ -1001,10 +1006,12 @@ ProjDataFromStream::get_segment_by_view(const int segment_num, const int timing_ error("ProjDataFromStream::get_segment_by_view: error in stream state before reading\n"); } - if (get_storage_order() == Segment_View_AxialPos_TangPos) + if (get_storage_order() == Segment_View_AxialPos_TangPos || get_storage_order() == Timing_Segment_View_AxialPos_TangPos) { streamoff segment_offset = get_offset_segment(segment_num); + // Go to the right timing full 3D sinogram + segment_offset += get_offset_timing(timing_pos) ; sino_stream->seekg(segment_offset, ios::beg); if (! *sino_stream) @@ -1012,7 +1019,7 @@ ProjDataFromStream::get_segment_by_view(const int segment_num, const int timing_ error("ProjDataFromStream::get_segment_by_sinogram: error after seekg\n"); } - SegmentByView segment(proj_data_info_ptr,segment_num); + SegmentByView segment(proj_data_info_ptr,segment_num, timing_pos); { float scale = float(1.f); @@ -1027,39 +1034,9 @@ ProjDataFromStream::get_segment_by_view(const int segment_num, const int timing_ return segment; } - else if (get_storage_order() == Timing_Segment_View_AxialPos_TangPos) - { - //Get the right segment offset - streamoff segment_offset = get_offset_segment(segment_num); - // Go to the right timing full 3D sinogram - segment_offset += get_offset_timing(timing_pos) ; - - sino_stream->seekg(segment_offset, ios::beg); - - if (! *sino_stream) - { - error("ProjDataFromStream::get_segment_by_sinogram: error after seekg\n"); - } - - SegmentByView segment(proj_data_info_ptr,segment_num); - - { - float scale = float(1.f); - if(read_data(*sino_stream, segment, on_disk_data_type, scale, on_disk_byte_order) - == Succeeded::no) - error("ProjDataFromStream: error reading data\n"); - if(scale != 1) - error("ProjDataFromStream: error reading data: scale factor returned by read_data should be 1\n"); - } - - segment *= scale_factor; - - return segment; - - } else // TODO rewrite in terms of get_sinogram as this doubles memory temporarily - return SegmentByView (get_segment_by_sinogram(segment_num)); + return SegmentByView (get_segment_by_sinogram(segment_num, timing_pos)); } Succeeded @@ -1087,6 +1064,8 @@ ProjDataFromStream::set_segment(const SegmentBySinogram& segmentbysinogra int segment_num = segmentbysinogram_v.get_segment_num(); streamoff segment_offset = get_offset_segment(segment_num); + // Go to the right timing full 3D sinogram + segment_offset += get_offset_timing(segmentbysinogram_v.get_timing_pos_num()) ; sino_stream->seekp(segment_offset,ios::beg); @@ -1110,9 +1089,9 @@ ProjDataFromStream::set_segment(const SegmentBySinogram& segmentbysinogra == Succeeded::no || scale != scale_factor) { - warning("ProjDataFromStream::set_segment: segment (%d)" + warning("ProjDataFromStream::set_segment: segment (%d) tof bin (%d)" " corrupted due to problems with writing or the scale factor \n", - segment_num); + segment_num, segmentbysinogram_v.get_timing_pos_num()); return Succeeded::no; } @@ -1156,6 +1135,8 @@ ProjDataFromStream::set_segment(const SegmentByView& segmentbyview_v) int segment_num = segmentbyview_v.get_segment_num(); streamoff segment_offset = get_offset_segment(segment_num); + // Go to the right timing full 3D sinogram + segment_offset += get_offset_timing(segmentbyview_v.get_timing_pos_num()) ; sino_stream->seekp(segment_offset,ios::beg); @@ -1165,7 +1146,7 @@ ProjDataFromStream::set_segment(const SegmentByView& segmentbyview_v) return Succeeded::no; } - if (get_storage_order() == Segment_View_AxialPos_TangPos) + if (get_storage_order() == Segment_View_AxialPos_TangPos || get_storage_order() == Timing_Segment_View_AxialPos_TangPos) { // KT 03/07/2001 handle scale_factor appropriately if (on_disk_data_type.id != NumericType::FLOAT) @@ -1179,9 +1160,9 @@ ProjDataFromStream::set_segment(const SegmentByView& segmentbyview_v) == Succeeded::no || scale != scale_factor) { - warning("ProjDataFromStream::set_segment: segment (%d)" + warning("ProjDataFromStream::set_segment: segment (%d) tof bin (%d)" " corrupted due to problems with writing or the scale factor \n", - segment_num); + segment_num, segmentbyview_v.get_timing_pos_num()); return Succeeded::no; } diff --git a/src/buildblock/ProjDataGEAdvance.cxx b/src/buildblock/ProjDataGEAdvance.cxx index 7bf3c23058..fae969e54d 100644 --- a/src/buildblock/ProjDataGEAdvance.cxx +++ b/src/buildblock/ProjDataGEAdvance.cxx @@ -304,7 +304,7 @@ get_viewgram(const int view_num, const int segment_num, } -Succeeded ProjDataGEAdvance::set_viewgram(const Viewgram& v, const int &timing_pos) +Succeeded ProjDataGEAdvance::set_viewgram(const Viewgram& v) { // TODO // but this is difficult: how to adjust the scale factors when writing only 1 viewgram ? @@ -319,7 +319,7 @@ Sinogram ProjDataGEAdvance::get_sinogram(const int ax_pos_num, const int error("ProjDataGEAdvance::get_sinogram not implemented yet\n"); return get_empty_sinogram(ax_pos_num, segment_num);} -Succeeded ProjDataGEAdvance::set_sinogram(const Sinogram& s, const int &timing_pos) +Succeeded ProjDataGEAdvance::set_sinogram(const Sinogram& s) { // TODO warning("ProjDataGEAdvance::set_sinogram not implemented yet\n"); diff --git a/src/buildblock/ProjDataInMemory.cxx b/src/buildblock/ProjDataInMemory.cxx index 358b7e3c2f..6d7b3e7d3b 100644 --- a/src/buildblock/ProjDataInMemory.cxx +++ b/src/buildblock/ProjDataInMemory.cxx @@ -84,10 +84,14 @@ ProjDataInMemory(shared_ptr const& exam_info_sptr, if (initialise_with_0) { - for (int segment_num = proj_data_info_ptr->get_min_segment_num(); + for (int timing_pos_num = this->get_min_tof_pos_num(); + timing_pos_num <= this->get_max_tof_pos_num(); + ++timing_pos_num) + for (int segment_num = proj_data_info_ptr->get_min_segment_num(); segment_num <= proj_data_info_ptr->get_max_segment_num(); ++segment_num) - set_segment(proj_data_info_ptr->get_empty_segment_by_view(segment_num)); + + set_segment(proj_data_info_ptr->get_empty_segment_by_view(segment_num, false, timing_pos_num)); } } @@ -115,10 +119,14 @@ ProjDataInMemory(const ProjData& proj_data) // copy data // (note: cannot use fill(projdata) as that uses virtual functions, which won't work in a constructor - for (int segment_num = proj_data_info_ptr->get_min_segment_num(); + for (int timing_pos_num = this->get_min_tof_pos_num(); + timing_pos_num <= this->get_max_tof_pos_num(); + ++timing_pos_num) + for (int segment_num = proj_data_info_ptr->get_min_segment_num(); segment_num <= proj_data_info_ptr->get_max_segment_num(); ++segment_num) - set_segment(proj_data.get_segment_by_view(segment_num)); + + set_segment(proj_data.get_segment_by_view(segment_num, timing_pos_num)); } size_t @@ -134,6 +142,7 @@ get_size_of_buffer() const num_sinograms * proj_data_info_ptr->get_num_views() * proj_data_info_ptr->get_num_tangential_poss() * + proj_data_info_ptr->get_num_tof_poss() * sizeof(float); } @@ -145,17 +154,9 @@ write_to_file(const string& output_filename) const ProjDataInterfile out_projdata(get_exam_info_sptr(), this->proj_data_info_ptr, output_filename, ios::out); - Succeeded success=Succeeded::yes; - for (int segment_num = proj_data_info_ptr->get_min_segment_num(); - segment_num <= proj_data_info_ptr->get_max_segment_num(); - ++segment_num) - { - Succeeded success_this_segment = - out_projdata.set_segment(get_segment_by_view(segment_num)); - if (success==Succeeded::yes) - success = success_this_segment; - } - return success; + out_projdata.fill(*this); + // if no exception thrown, it succeeded + return Succeeded::yes; } diff --git a/src/buildblock/ProjDataInfo.cxx b/src/buildblock/ProjDataInfo.cxx index cf2f2e0b22..86d2e197b7 100644 --- a/src/buildblock/ProjDataInfo.cxx +++ b/src/buildblock/ProjDataInfo.cxx @@ -219,8 +219,8 @@ ProjDataInfo::set_tof_mash_factor(const int new_num) tof_bin_boundaries_mm[i].low_lim = cur_low; tof_bin_boundaries_mm[i].high_lim = cur_high; - tof_bin_boundaries_ps[i].low_lim = (tof_bin_boundaries_mm[i].low_lim * 3.33564095198f ) ; - tof_bin_boundaries_ps[i].high_lim = ( tof_bin_boundaries_mm[i].high_lim * 3.33564095198f); + tof_bin_boundaries_ps[i].low_lim = (tof_bin_boundaries_mm[i].low_lim / 0.299792458f ) ; + tof_bin_boundaries_ps[i].high_lim = ( tof_bin_boundaries_mm[i].high_lim / 0.299792458f); // I could imagine a better printing. info(boost::format("Tbin %1%: %2% - %3% mm (%4% - %5% ps) = %6%") %i % tof_bin_boundaries_mm[i].low_lim % tof_bin_boundaries_mm[i].high_lim % tof_bin_boundaries_ps[i].low_lim % tof_bin_boundaries_ps[i].high_lim % get_sampling_in_k(bin)); @@ -332,7 +332,8 @@ reduce_segment_range(const int min_segment_num, const int max_segment_num) Viewgram ProjDataInfo::get_empty_viewgram(const int view_num, const int segment_num, - const bool make_num_tangential_poss_odd) const + const bool make_num_tangential_poss_odd, + const int timing_pos_num) const { // we can't access the shared ptr, so we have to clone 'this'. shared_ptr proj_data_info_sptr(this->clone()); @@ -340,7 +341,7 @@ ProjDataInfo::get_empty_viewgram(const int view_num, if (make_num_tangential_poss_odd && (get_num_tangential_poss()%2==0)) proj_data_info_sptr->set_max_tangential_pos_num(get_max_tangential_pos_num() + 1); - Viewgram v(proj_data_info_sptr, view_num, segment_num); + Viewgram v(proj_data_info_sptr, view_num, segment_num, timing_pos_num); return v; } @@ -348,7 +349,8 @@ ProjDataInfo::get_empty_viewgram(const int view_num, Sinogram ProjDataInfo::get_empty_sinogram(const int axial_pos_num, const int segment_num, - const bool make_num_tangential_poss_odd) const + const bool make_num_tangential_poss_odd, + const int timing_pos_num) const { // we can't access the shared ptr, so we have to clone 'this'. shared_ptr proj_data_info_sptr(this->clone()); @@ -356,14 +358,15 @@ ProjDataInfo::get_empty_sinogram(const int axial_pos_num, const int segment_num, if (make_num_tangential_poss_odd && (get_num_tangential_poss()%2==0)) proj_data_info_sptr->set_max_tangential_pos_num(get_max_tangential_pos_num() + 1); - Sinogram s(proj_data_info_sptr, axial_pos_num, segment_num); + Sinogram s(proj_data_info_sptr, axial_pos_num, segment_num, timing_pos_num); return s; } SegmentBySinogram ProjDataInfo::get_empty_segment_by_sinogram(const int segment_num, - const bool make_num_tangential_poss_odd) const + const bool make_num_tangential_poss_odd, + const int timing_pos_num) const { assert(segment_num >= get_min_segment_num()); assert(segment_num <= get_max_segment_num()); @@ -374,7 +377,7 @@ ProjDataInfo::get_empty_segment_by_sinogram(const int segment_num, if (make_num_tangential_poss_odd && (get_num_tangential_poss()%2==0)) proj_data_info_sptr->set_max_tangential_pos_num(get_max_tangential_pos_num() + 1); - SegmentBySinogram s(proj_data_info_sptr, segment_num); + SegmentBySinogram s(proj_data_info_sptr, segment_num, timing_pos_num); return s; } @@ -382,7 +385,8 @@ ProjDataInfo::get_empty_segment_by_sinogram(const int segment_num, SegmentByView ProjDataInfo::get_empty_segment_by_view(const int segment_num, - const bool make_num_tangential_poss_odd) const + const bool make_num_tangential_poss_odd, + const int timing_pos_num) const { assert(segment_num >= get_min_segment_num()); assert(segment_num <= get_max_segment_num()); @@ -393,21 +397,22 @@ ProjDataInfo::get_empty_segment_by_view(const int segment_num, if (make_num_tangential_poss_odd && (get_num_tangential_poss()%2==0)) proj_data_info_sptr->set_max_tangential_pos_num(get_max_tangential_pos_num() + 1); - SegmentByView s(proj_data_info_sptr, segment_num); + SegmentByView s(proj_data_info_sptr, segment_num, timing_pos_num); return s; } RelatedViewgrams -ProjDataInfo::get_empty_related_viewgrams(const ViewSegmentNumbers& view_segmnet_num, +ProjDataInfo::get_empty_related_viewgrams(const ViewSegmentNumbers& view_segment_num, //const int view_num, const int segment_num, const shared_ptr& symmetries_used, - const bool make_num_tangential_poss_odd) const + const bool make_num_tangential_poss_odd, + const int timing_pos_num) const { vector pairs; symmetries_used->get_related_view_segment_numbers( pairs, - ViewSegmentNumbers(view_segmnet_num.view_num(),view_segmnet_num.segment_num()) + ViewSegmentNumbers(view_segment_num.view_num(),view_segment_num.segment_num()) ); vector > viewgrams; @@ -417,7 +422,9 @@ ProjDataInfo::get_empty_related_viewgrams(const ViewSegmentNumbers& view_segmnet { // TODO optimise to get shared proj_data_info_ptr viewgrams.push_back(get_empty_viewgram(pairs[i].view_num(), - pairs[i].segment_num(), make_num_tangential_poss_odd)); + pairs[i].segment_num(), + make_num_tangential_poss_odd, + timing_pos_num)); } return RelatedViewgrams(viewgrams, symmetries_used); diff --git a/src/buildblock/RelatedViewgrams.cxx b/src/buildblock/RelatedViewgrams.cxx index d633f46fbe..7abeadcec4 100644 --- a/src/buildblock/RelatedViewgrams.cxx +++ b/src/buildblock/RelatedViewgrams.cxx @@ -139,6 +139,16 @@ has_same_characteristics(self_type const& other, ); return false; } + if (this->get_basic_timing_pos_num() != + other.get_basic_timing_pos_num()) + { + explanation = + str(format("Differing basic timing position index: %1% vs %2%") + % this->get_basic_timing_pos_num() + % other.get_basic_timing_pos_num() + ); + return false; + } return true; } diff --git a/src/buildblock/Scanner.cxx b/src/buildblock/Scanner.cxx index 36d09a5d3c..f7a6b8c882 100644 --- a/src/buildblock/Scanner.cxx +++ b/src/buildblock/Scanner.cxx @@ -302,7 +302,10 @@ Scanner::Scanner(Type scanner_type) 24, 329, 293, 2 * 280, 886.2F/2.F, 8.4F, 6.54F, 2.397F, static_cast(-4.5490*_PI/180),//sign? - 4, 2, 6, 8, 1, 1, 1);// TODO not sure about sign of view_offset + 4, 2, 6, 8, 1, 1, 1, + (short int)(410), + (float)(10.0F), + (float)(400.0F) );// TODO not sure about sign of view_offset break; case DiscoveryRX: @@ -352,7 +355,10 @@ case PETMR_Signa: static_cast(-5.23*_PI/180),//sign? TODO value 5, 4, - 9, 4, 1, 1, 1); + 9, 4, 1, 1, 1, + (short int)(351), + (float)(89.0F/13.0F), //TODO + (float)(390.0F) ); break; case HZLR: @@ -428,6 +434,26 @@ case PETMR_Signa: break; + case Discovery690: + + set_params(Discovery690, string_list("GE Discovery 690", "Discovery 690"), + 24, + 381, + 331, // TODO + 2 * 288, + 405.1F, + 9.4F, + 6.54F, + 2.1306F, + static_cast(-5.021*_PI/180),//sign? TODO value + 4, + 2, + 6, 9, 1, 1, 1, + (short int)(55), + (float)(89.0F), + (float)(550.0F) ); + break; + case User_defined_scanner: // zlong, 08-04-2004, Userdefined support set_params(User_defined_scanner, string_list("Userdefined"), diff --git a/src/buildblock/Segment.cxx b/src/buildblock/Segment.cxx index cf4e385657..c227336b61 100644 --- a/src/buildblock/Segment.cxx +++ b/src/buildblock/Segment.cxx @@ -77,6 +77,16 @@ has_same_characteristics(self_type const& other, ); return false; } + if (this->get_timing_pos_num() != + other.get_timing_pos_num()) + { + explanation = + str(format("Differing timing position index: %1% vs %2%") + % this->get_timing_pos_num() + % other.get_timing_pos_num() + ); + return false; + } return true; } diff --git a/src/buildblock/SegmentBySinogram.cxx b/src/buildblock/SegmentBySinogram.cxx index 3653b69667..1edaa863cb 100644 --- a/src/buildblock/SegmentBySinogram.cxx +++ b/src/buildblock/SegmentBySinogram.cxx @@ -43,9 +43,10 @@ template SegmentBySinogram :: SegmentBySinogram(const Array<3,elemT>& v, const shared_ptr& pdi_ptr, - const int segment_num) + const int segment_num, + const int timing_pos_num) : - Segment(pdi_ptr, segment_num), + Segment(pdi_ptr, segment_num, timing_pos_num), Array<3,elemT>(v) { assert( get_min_view_num() == pdi_ptr->get_min_view_num()); @@ -59,9 +60,10 @@ SegmentBySinogram(const Array<3,elemT>& v, template SegmentBySinogram :: SegmentBySinogram(const shared_ptr& pdi_ptr, - const int segment_num) + const int segment_num, + const int timing_pos_num) : - Segment(pdi_ptr, segment_num), + Segment(pdi_ptr, segment_num, timing_pos_num), Array<3,elemT>(IndexRange3D(pdi_ptr->get_min_axial_pos_num(segment_num), pdi_ptr->get_max_axial_pos_num(segment_num), pdi_ptr->get_min_view_num(), @@ -74,7 +76,7 @@ template SegmentBySinogram:: SegmentBySinogram(const SegmentByView& s_v ) : Segment(s_v.get_proj_data_info_ptr()->create_shared_clone(), - s_v.get_segment_num()), + s_v.get_segment_num(), s_v.get_timing_pos_num()), Array<3,elemT> (IndexRange3D (s_v.get_min_axial_pos_num(), s_v.get_max_axial_pos_num(), s_v.get_min_view_num(), s_v.get_max_view_num(), s_v.get_min_tangential_pos_num(), s_v.get_max_tangential_pos_num())) @@ -107,7 +109,7 @@ SegmentBySinogram::get_viewgram(int view_num) const //KT 9/12 constructed a PETSinogram before... // CL&KT 15/12 added ring_difference stuff return Viewgram(pre_view, this->proj_data_info_ptr->create_shared_clone(), view_num, - this->get_segment_num()); + this->get_segment_num(), this->get_timing_pos_num()); } template @@ -118,8 +120,6 @@ SegmentBySinogram::set_viewgram(const Viewgram& viewgram) Array<3,elemT>::operator[](r)[viewgram.get_view_num()] = viewgram[r]; } - - /*! This makes sure that the new Array dimensions are the same as those in the ProjDataInfo member. diff --git a/src/buildblock/SegmentByView.cxx b/src/buildblock/SegmentByView.cxx index 4f3586e037..0cebf07305 100644 --- a/src/buildblock/SegmentByView.cxx +++ b/src/buildblock/SegmentByView.cxx @@ -41,9 +41,10 @@ template SegmentByView:: SegmentByView(const Array<3,elemT>& v, const shared_ptr& pdi_ptr, - const int segment_num) + const int segment_num, + const int timing_pos_num) : - Segment(pdi_ptr, segment_num), + Segment(pdi_ptr, segment_num, timing_pos_num), Array<3,elemT>(v) { assert( get_min_view_num() == pdi_ptr->get_min_view_num()); @@ -58,9 +59,10 @@ SegmentByView(const Array<3,elemT>& v, template SegmentByView:: SegmentByView(const shared_ptr& pdi_ptr, - const int segment_num) + const int segment_num, + const int timing_pos_num) : - Segment(pdi_ptr, segment_num), + Segment(pdi_ptr, segment_num, timing_pos_num), Array<3,elemT>(IndexRange3D(pdi_ptr->get_min_view_num(), pdi_ptr->get_max_view_num(), pdi_ptr->get_min_axial_pos_num(segment_num), @@ -72,7 +74,7 @@ SegmentByView(const shared_ptr& pdi_ptr, template SegmentByView::SegmentByView(const SegmentBySinogram& s_s) : Segment(s_s.get_proj_data_info_ptr()->create_shared_clone(), - s_s.get_segment_num()), + s_s.get_segment_num(),s_s.get_timing_pos_num()), Array<3,elemT> (IndexRange3D(s_s.get_min_view_num(),s_s.get_max_view_num(), s_s.get_min_axial_pos_num(),s_s.get_max_axial_pos_num(), @@ -105,7 +107,7 @@ SegmentByView::get_sinogram(int axial_pos_num) const pre_sino[v] = Array<3,elemT>::operator[](v)[axial_pos_num]; return Sinogram(pre_sino, this->proj_data_info_ptr, axial_pos_num, - this->get_segment_num()); + this->get_segment_num(), this->get_timing_pos_num()); } template diff --git a/src/buildblock/Sinogram.cxx b/src/buildblock/Sinogram.cxx index ef6c878c59..3c5b937c4a 100644 --- a/src/buildblock/Sinogram.cxx +++ b/src/buildblock/Sinogram.cxx @@ -81,6 +81,16 @@ has_same_characteristics(self_type const& other, ); return false; } + if (this->get_timing_pos_num() != + other.get_timing_pos_num()) + { + explanation = + str(format("Differing timing position index: %1% vs %2%") + % this->get_timing_pos_num() + % other.get_timing_pos_num() + ); + return false; + } return true; } diff --git a/src/buildblock/Viewgram.cxx b/src/buildblock/Viewgram.cxx index 6d0bc53050..84b7abf796 100644 --- a/src/buildblock/Viewgram.cxx +++ b/src/buildblock/Viewgram.cxx @@ -81,6 +81,16 @@ has_same_characteristics(self_type const& other, ); return false; } + if (this->get_timing_pos_num() != + other.get_timing_pos_num()) + { + explanation = + str(format("Differing timing position index: %1% vs %2%") + % this->get_timing_pos_num() + % other.get_timing_pos_num() + ); + return false; + } return true; } diff --git a/src/buildblock/interpolate_projdata.cxx b/src/buildblock/interpolate_projdata.cxx index 7a1fef3245..243485f7d0 100644 --- a/src/buildblock/interpolate_projdata.cxx +++ b/src/buildblock/interpolate_projdata.cxx @@ -154,7 +154,8 @@ namespace detail_interpolate_projdata const SegmentBySinogram& in_segment) { SegmentBySinogram out_segment = - non_interleaved_proj_data_info.get_empty_segment_by_sinogram(in_segment.get_segment_num()); + non_interleaved_proj_data_info.get_empty_segment_by_sinogram(in_segment.get_segment_num(), + in_segment.get_timing_pos_num()); make_non_interleaved_segment(out_segment, in_segment); return out_segment; diff --git a/src/include/stir/DynamicProjData.h b/src/include/stir/DynamicProjData.h index 3900160c6a..f7ec883242 100644 --- a/src/include/stir/DynamicProjData.h +++ b/src/include/stir/DynamicProjData.h @@ -98,14 +98,19 @@ class DynamicProjData : for (int segment_num = (this->_proj_datas[frame_num-1])->get_min_segment_num(); segment_num <= (this->_proj_datas[frame_num-1])->get_max_segment_num(); ++segment_num) { - SegmentByView segment_by_view - ((*(this->_proj_datas[frame_num-1])).get_segment_by_view(segment_num)); - segment_by_view *= cal_factor; - if ((*(this->_proj_datas[frame_num-1])).set_segment(segment_by_view) - ==Succeeded::no) - { - error("DynamicProjData:calibrate_frames failed because set_segment_by_view failed"); - } + for (int timing_pos_num = (this->_proj_datas[frame_num-1])->get_min_tof_pos_num(); + timing_pos_num <= (this->_proj_datas[frame_num-1])->get_max_tof_pos_num(); + ++timing_pos_num) + { + SegmentByView segment_by_view + ((*(this->_proj_datas[frame_num-1])).get_segment_by_view(segment_num,timing_pos_num)); + segment_by_view *= cal_factor; + if ((*(this->_proj_datas[frame_num-1])).set_segment(segment_by_view) + ==Succeeded::no) + { + error("DynamicProjData:calibrate_frames failed because set_segment_by_view failed"); + } + } } } @@ -119,14 +124,19 @@ class DynamicProjData : for (int segment_num = (this->_proj_datas[frame_num-1])->get_min_segment_num(); segment_num <= (this->_proj_datas[frame_num-1])->get_max_segment_num(); ++segment_num) { - SegmentByView segment_by_view = - (*(this->_proj_datas[frame_num-1])).get_segment_by_view(segment_num); - segment_by_view /= static_cast(this->get_time_frame_definitions().get_duration(frame_num)); - if ((*(this->_proj_datas[frame_num-1])).set_segment(segment_by_view) - ==Succeeded::no) - { - error("DynamicProjData:calibrate_frames failed because set_segment_by_view failed"); - } + for (int timing_pos_num = (this->_proj_datas[frame_num-1])->get_min_tof_pos_num(); + timing_pos_num <= (this->_proj_datas[frame_num-1])->get_max_tof_pos_num(); + ++timing_pos_num) + { + SegmentByView segment_by_view = + (*(this->_proj_datas[frame_num-1])).get_segment_by_view(segment_num,timing_pos_num); + segment_by_view /= static_cast(this->get_time_frame_definitions().get_duration(frame_num)); + if ((*(this->_proj_datas[frame_num-1])).set_segment(segment_by_view) + ==Succeeded::no) + { + error("DynamicProjData:calibrate_frames failed because set_segment_by_view failed"); + } + } } } diff --git a/src/include/stir/IO/InputStreamWithRecordsFromHDF5.inl b/src/include/stir/IO/InputStreamWithRecordsFromHDF5.inl index 27f07b54ed..0f9767efac 100644 --- a/src/include/stir/IO/InputStreamWithRecordsFromHDF5.inl +++ b/src/include/stir/IO/InputStreamWithRecordsFromHDF5.inl @@ -68,9 +68,9 @@ get_next_record(RecordT& record) const H5::DataSpace dataspace = dataset_sptr->getSpace(); int rank = dataspace.getSimpleExtentNdims(); - hsize_t dims_out[rank]; - dataspace.getSimpleExtentDims( dims_out, NULL); - uint64_t list_size = dims_out[0]; // should be equal to /HeaderData/ListHeader/sizeOfList + std::vector dims_out(rank); // VS needs a vector + dataspace.getSimpleExtentDims(&dims_out[0],NULL); + uint64_t list_size = dims_out[0]; // should be equal to /HeaderData/ListHeader/sizeOfList if (current_offset > (list_size - this->size_of_record_signature)) return Succeeded::no; diff --git a/src/include/stir/ProjData.h b/src/include/stir/ProjData.h index ac9db5f5d3..7e10f75cd3 100644 --- a/src/include/stir/ProjData.h +++ b/src/include/stir/ProjData.h @@ -148,66 +148,61 @@ class ProjData : public ExamData virtual Viewgram get_viewgram(const int view, const int segment_num, const bool make_num_tangential_poss_odd = false, - const int timing_pos = 0) const=0; + const int timing_pos = 0) const = 0; //! Set viewgram virtual Succeeded - set_viewgram(const Viewgram&, - const int& timing_pos = 0) = 0; + set_viewgram(const Viewgram&) = 0; //! Get sinogram virtual Sinogram get_sinogram(const int ax_pos_num, const int segment_num, const bool make_num_tangential_poss_odd = false, - const int timing_pos = 0) const=0; + const int timing_pos = 0) const = 0; //! Set sinogram virtual Succeeded - set_sinogram(const Sinogram&, - const int& timing_pos = 0) = 0; + set_sinogram(const Sinogram&) = 0; // //! Get Bin value //virtual float get_bin_value(const Bin& this_bin) const = 0; //! Get empty viewgram Viewgram get_empty_viewgram(const int view, const int segment_num, - const bool make_num_tangential_poss_odd = false) const; + const bool make_num_tangential_poss_odd = false, const int timing_pos = 0) const; //! Get empty_sinogram Sinogram get_empty_sinogram(const int ax_pos_num, const int segment_num, - const bool make_num_tangential_poss_odd = false) const; + const bool make_num_tangential_poss_odd = false, const int timing_pos = 0) const; //! Get empty segment sino SegmentByView get_empty_segment_by_view(const int segment_num, - const bool make_num_tangential_poss_odd = false) const; + const bool make_num_tangential_poss_odd = false, + const int timing_pos = 0) const; //! Get empty segment view SegmentBySinogram get_empty_segment_by_sinogram(const int segment_num, - const bool make_num_tangential_poss_odd = false) const; + const bool make_num_tangential_poss_odd = false, + const int timing_pos = 0) const; //! Get segment by sinogram virtual SegmentBySinogram - get_segment_by_sinogram(const int segment_num, - const int timing_num = 0) const; + get_segment_by_sinogram(const int segment_num, const int timing_pos = 0) const; //! Get segment by view virtual SegmentByView - get_segment_by_view(const int segment_num, - const int timing_num = 0) const; + get_segment_by_view(const int segment_num, const int timing_pos = 0) const; //! Set segment by sinogram - //! N.E. Extended to have timging positions. virtual Succeeded - set_segment(const SegmentBySinogram&, - const int& timing_pos = 0); + set_segment(const SegmentBySinogram&); //! Set segment by view - //! N.E. Extended to have timing positions. virtual Succeeded - set_segment(const SegmentByView&, - const int& timing_pos = 0); + set_segment(const SegmentByView&); //! Get related viewgrams virtual RelatedViewgrams get_related_viewgrams(const ViewSegmentNumbers&, const shared_ptr&, - const bool make_num_tangential_poss_odd = false) const; + const bool make_num_tangential_poss_odd = false, + const int timing_pos = 0) const; //! Set related viewgrams virtual Succeeded set_related_viewgrams(const RelatedViewgrams& viewgrams); // //! Get related bin values @@ -219,7 +214,8 @@ class ProjData : public ExamData get_empty_related_viewgrams(const ViewSegmentNumbers& view_segmnet_num, //const int view_num, const int segment_num, const shared_ptr& symmetries_ptr, - const bool make_num_tangential_poss_odd = false) const; + const bool make_num_tangential_poss_odd = false, + const int timing_pos = 0) const; //! set all bins to the same value @@ -243,22 +239,27 @@ class ProjData : public ExamData iterT init_pos = array_iter; for (int s=0; s<= this->get_max_segment_num(); ++s) { - SegmentBySinogram segment = this->get_empty_segment_by_sinogram(s); - // cannot use std::copy sadly as needs end-iterator for range - for (SegmentBySinogram::full_iterator seg_iter = segment.begin_all(); - seg_iter != segment.end_all(); - /*empty*/) - *seg_iter++ = *array_iter++; - this->set_segment(segment); - - if (s!=0) + for (int k=this->get_proj_data_info_ptr()->get_min_tof_pos_num(); + k<=this->get_proj_data_info_ptr()->get_max_tof_pos_num(); + ++k) { - segment = this->get_empty_segment_by_sinogram(-s); - for (SegmentBySinogram::full_iterator seg_iter = segment.begin_all(); - seg_iter != segment.end_all(); - /*empty*/) - *seg_iter++ = *array_iter++; - this->set_segment(segment); + SegmentBySinogram segment = this->get_empty_segment_by_sinogram(s, false, k); + // cannot use std::copy sadly as needs end-iterator for range + for (SegmentBySinogram::full_iterator seg_iter = segment.begin_all(); + seg_iter != segment.end_all(); + /*empty*/) + *seg_iter++ = *array_iter++; + this->set_segment(segment); + + if (s!=0) + { + segment = this->get_empty_segment_by_sinogram(-s,false,k); + for (SegmentBySinogram::full_iterator seg_iter = segment.begin_all(); + seg_iter != segment.end_all(); + /*empty*/) + *seg_iter++ = *array_iter++; + this->set_segment(segment); + } } } return std::distance(init_pos, array_iter); @@ -271,14 +272,19 @@ class ProjData : public ExamData iterT init_pos = array_iter; for (int s=0; s<= this->get_max_segment_num(); ++s) { - SegmentBySinogram segment= this->get_segment_by_sinogram(s); - std::copy(segment.begin_all_const(), segment.end_all_const(), array_iter); - std::advance(array_iter, segment.size_all()); - if (s!=0) + for (int k=this->get_proj_data_info_ptr()->get_min_tof_pos_num(); + k<=this->get_proj_data_info_ptr()->get_max_tof_pos_num(); + ++k) { - segment=this->get_segment_by_sinogram(-s); - std::copy(segment.begin_all_const(), segment.end_all_const(), array_iter); - std::advance(array_iter, segment.size_all()); + SegmentBySinogram segment= this->get_segment_by_sinogram(s,k); + std::copy(segment.begin_all_const(), segment.end_all_const(), array_iter); + std::advance(array_iter, segment.size_all()); + if (s!=0) + { + segment=this->get_segment_by_sinogram(-s,k); + std::copy(segment.begin_all_const(), segment.end_all_const(), array_iter); + std::advance(array_iter, segment.size_all()); + } } } return std::distance(init_pos, array_iter); diff --git a/src/include/stir/ProjDataFromStream.h b/src/include/stir/ProjDataFromStream.h index 40b160a71e..f8c5e4b932 100644 --- a/src/include/stir/ProjDataFromStream.h +++ b/src/include/stir/ProjDataFromStream.h @@ -128,16 +128,14 @@ class ProjDataFromStream : public ProjData Viewgram get_viewgram(const int view_num, const int segment_num, const bool make_num_tangential_poss_odd=false, const int timing_pos=0) const; - Succeeded set_viewgram(const Viewgram& v, - const int& timing_pos = 0); + Succeeded set_viewgram(const Viewgram& v); //! Get & set sinogram Sinogram get_sinogram(const int ax_pos_num, const int segment_num, const bool make_num_tangential_poss_odd=false, const int timing_pos=0) const; - Succeeded set_sinogram(const Sinogram& s, - const int& timing_pos = 0); + Succeeded set_sinogram(const Sinogram& s); //! Get all sinograms for the given segment SegmentBySinogram get_segment_by_sinogram(const int segment_num, diff --git a/src/include/stir/ProjDataGEAdvance.h b/src/include/stir/ProjDataGEAdvance.h index 50a289bdc0..a79a4e6e4d 100644 --- a/src/include/stir/ProjDataGEAdvance.h +++ b/src/include/stir/ProjDataGEAdvance.h @@ -68,12 +68,12 @@ class ProjDataGEAdvance : public ProjData //! Get & set viewgram Viewgram get_viewgram(const int view_num, const int segment_num, const bool make_num_tangential_poss_odd=false, const int timing_pos = 0) const; - Succeeded set_viewgram(const Viewgram& v, const int& timing_pos = 0); + Succeeded set_viewgram(const Viewgram& v); //! Get & set sinogram Sinogram get_sinogram(const int ax_pos_num, const int sergment_num, const bool make_num_tangential_poss_odd=false, const int timing_pos = 0) const; - Succeeded set_sinogram(const Sinogram& s, const int& timing_pos = 0); + Succeeded set_sinogram(const Sinogram& s); // float get_bin_value(const Bin& this_bin) const // { diff --git a/src/include/stir/ProjDataInfo.h b/src/include/stir/ProjDataInfo.h index 1f99ba631e..f7111eb507 100644 --- a/src/include/stir/ProjDataInfo.h +++ b/src/include/stir/ProjDataInfo.h @@ -221,6 +221,9 @@ class ProjDataInfo inline float get_coincidence_window_width() const; //@} + //! Determine if TOF data from tof_mash_factor and num_tof_bins + inline bool is_tof_data() const; + //| \name Functions that return geometrical info for a Bin //@{ //! Get tangent of the co-polar angle of the normal to the projection plane @@ -340,24 +343,27 @@ class ProjDataInfo //! Get empty viewgram Viewgram get_empty_viewgram(const int view_num, const int segment_num, - const bool make_num_tangential_poss_odd = false) const; + const bool make_num_tangential_poss_odd = false, const int timing_pos_num = 0) const; //! Get empty_sinogram Sinogram get_empty_sinogram(const int ax_pos_num, const int segment_num, - const bool make_num_tangential_poss_odd = false) const; + const bool make_num_tangential_poss_odd = false, const int timing_pos_num = 0) const; //! Get empty segment sino SegmentByView get_empty_segment_by_view(const int segment_num, - const bool make_num_tangential_poss_odd = false) const; + const bool make_num_tangential_poss_odd = false, + const int timing_pos_num = 0) const; //! Get empty segment view SegmentBySinogram get_empty_segment_by_sinogram(const int segment_num, - const bool make_num_tangential_poss_odd = false) const; + const bool make_num_tangential_poss_odd = false, + const int timing_pos_num = 0) const; //! Get empty related viewgrams, where the symmetries_ptr specifies the symmetries to use RelatedViewgrams get_empty_related_viewgrams(const ViewSegmentNumbers&, const shared_ptr&, - const bool make_num_tangential_poss_odd = false) const; + const bool make_num_tangential_poss_odd = false, + const int timing_pos_num = 0) const; //@} diff --git a/src/include/stir/ProjDataInfo.inl b/src/include/stir/ProjDataInfo.inl index 588d73b186..462b809aa1 100644 --- a/src/include/stir/ProjDataInfo.inl +++ b/src/include/stir/ProjDataInfo.inl @@ -70,6 +70,7 @@ ProjDataInfo::get_tof_bin(const double& delta) const delta < tof_bin_boundaries_ps[i].high_lim) return i; } + return 0; } int @@ -138,6 +139,26 @@ ProjDataInfo::get_coincidence_window_width() const return get_coincidence_window_in_pico_sec() * 0.299792458f; } +bool +ProjDataInfo::is_tof_data() const +{ + // First case: if tof_mash_factor == 0, scanner is not tof ready and no tof data + if (tof_mash_factor == 0) + { + if (num_tof_bins != 1) + { + error("Non-TOF data with inconsistent Time-of-Flight bin number - Aborted operation."); + } + return false; + } + // Second case: when tof_mash_factor is strictly positive, it means we have TOF data + else if (tof_mash_factor > 0) + { + return true; + } + return false; +} + float ProjDataInfo::get_costheta(const Bin& bin) const { diff --git a/src/include/stir/RelatedViewgrams.h b/src/include/stir/RelatedViewgrams.h index 099801625d..e28a583111 100644 --- a/src/include/stir/RelatedViewgrams.h +++ b/src/include/stir/RelatedViewgrams.h @@ -92,6 +92,9 @@ class RelatedViewgrams //! get 'basic' segment_num /*! see DataSymmetriesForViewSegmentNumbers for definition of 'basic' */ inline int get_basic_segment_num() const; + //! get 'basic' timing_pos_num + /*! see DataSymmetriesForViewSegmentNumbers for definition of 'basic' */ + inline int get_basic_timing_pos_num() const; //! get 'basic' view_segment_num /*! see DataSymmetriesForViewSegmentNumbers for definition of 'basic' */ inline ViewSegmentNumbers get_basic_view_segment_num() const; diff --git a/src/include/stir/RelatedViewgrams.inl b/src/include/stir/RelatedViewgrams.inl index f3a1eab689..50d2ca3faf 100644 --- a/src/include/stir/RelatedViewgrams.inl +++ b/src/include/stir/RelatedViewgrams.inl @@ -78,6 +78,14 @@ int RelatedViewgrams::get_basic_segment_num() const return viewgrams[0].get_segment_num(); } +template +int RelatedViewgrams::get_basic_timing_pos_num() const +{ + assert(viewgrams.size()>0); + check_state(); + return viewgrams[0].get_timing_pos_num(); +} + template ViewSegmentNumbers RelatedViewgrams:: get_basic_view_segment_num() const diff --git a/src/include/stir/Scanner.h b/src/include/stir/Scanner.h index be31c14c10..3095f894e8 100644 --- a/src/include/stir/Scanner.h +++ b/src/include/stir/Scanner.h @@ -114,7 +114,7 @@ class Scanner */ enum Type {E931, E951, E953, E921, E925, E961, E962, E966, E1080, test_scanner, Siemens_mMR, RPT,HiDAC, Advance, DiscoveryLS, DiscoveryST, DiscoverySTE, DiscoveryRX, Discovery600,PETMR_Signa, - HZLR, RATPET, PANDA, HYPERimage, nanoPET, HRRT, Allegro, GeminiTF, User_defined_scanner, + HZLR, RATPET, PANDA, HYPERimage, nanoPET, HRRT, Allegro, GeminiTF, Discovery690, User_defined_scanner, Unknown_scanner}; //! constructor that takes scanner type as an input argument diff --git a/src/include/stir/Segment.h b/src/include/stir/Segment.h index d63df3abb8..8c903e24a4 100644 --- a/src/include/stir/Segment.h +++ b/src/include/stir/Segment.h @@ -74,6 +74,8 @@ class Segment virtual StorageOrder get_storage_order() const = 0; //! Get the segment number inline int get_segment_num() const; + //! Get the timing position index + inline int get_timing_pos_num() const; virtual int get_min_axial_pos_num() const = 0; virtual int get_max_axial_pos_num() const = 0; virtual int get_min_view_num() const = 0; @@ -127,8 +129,9 @@ class Segment protected: shared_ptr proj_data_info_ptr; int segment_num; + int timing_pos_num; - inline Segment(const shared_ptr& proj_data_info_ptr_v,const int s_num); + inline Segment(const shared_ptr& proj_data_info_ptr_v,const int s_num, const int t_num = 0); }; END_NAMESPACE_STIR diff --git a/src/include/stir/Segment.inl b/src/include/stir/Segment.inl index f81d130d8c..314fb16d9c 100644 --- a/src/include/stir/Segment.inl +++ b/src/include/stir/Segment.inl @@ -33,10 +33,11 @@ START_NAMESPACE_STIR template Segment:: -Segment( const shared_ptr& proj_data_info_ptr_v,const int s_num) +Segment( const shared_ptr& proj_data_info_ptr_v,const int s_num, const int t_num) : proj_data_info_ptr(proj_data_info_ptr_v), - segment_num(s_num) + segment_num(s_num), + timing_pos_num(t_num) {} template @@ -44,6 +45,10 @@ int Segment:: get_segment_num() const { return segment_num; } +template +int +Segment:: get_timing_pos_num() const +{ return timing_pos_num; } template const ProjDataInfo* diff --git a/src/include/stir/SegmentBySinogram.h b/src/include/stir/SegmentBySinogram.h index b161c4e5e3..e800e35b40 100644 --- a/src/include/stir/SegmentBySinogram.h +++ b/src/include/stir/SegmentBySinogram.h @@ -66,11 +66,11 @@ class SegmentBySinogram : public Segment, public Array<3,elemT> //! Constructor that sets the data to a given 3d Array SegmentBySinogram(const Array<3,elemT>& v, const shared_ptr& proj_data_info_ptr_v, - const int segment_num); + const int segment_num, const int timing_pos_num = 0); //! Constructor that sets sizes via the ProjDataInfo object, initialising data to 0 SegmentBySinogram(const shared_ptr& proj_data_info_ptr_v, - const int segment_num); + const int segment_num, const int timing_pos_num = 0); //! Conversion from 1 storage order to the other @@ -81,7 +81,7 @@ class SegmentBySinogram : public Segment, public Array<3,elemT> inline int get_num_axial_poss() const; //! Get number of views inline int get_num_views() const; - //! Get number of tangetial positions + //! Get number of tangential positions inline int get_num_tangential_poss() const; //! Get minimum axial position number inline int get_min_axial_pos_num() const; @@ -96,13 +96,13 @@ class SegmentBySinogram : public Segment, public Array<3,elemT> //! Get maximum tangential position number inline int get_max_tangential_pos_num() const; //! Get sinogram - inline Sinogram get_sinogram(int axial_pos_num) const; + inline Sinogram get_sinogram(int axial_pos_num) const; //! Get viewgram Viewgram get_viewgram(int view_num) const; //! Set viewgram void set_viewgram(const Viewgram&); //! Set sinogram - inline void set_sinogram(Sinogram const &s, int axial_pos_num); + inline void set_sinogram(Sinogram const &s, int axial_pos_num); inline void set_sinogram(const Sinogram& s); //! Overloading Array::grow diff --git a/src/include/stir/SegmentBySinogram.inl b/src/include/stir/SegmentBySinogram.inl index a6d862714f..19a469b23c 100644 --- a/src/include/stir/SegmentBySinogram.inl +++ b/src/include/stir/SegmentBySinogram.inl @@ -109,7 +109,8 @@ SegmentBySinogram:: get_sinogram(int axial_pos_num) const { return Sinogram(Array<3,elemT>::operator[](axial_pos_num), Segment::proj_data_info_ptr, axial_pos_num, - Segment::get_segment_num()); } + Segment::get_segment_num(), + Segment::get_timing_pos_num()); } template void diff --git a/src/include/stir/SegmentByView.h b/src/include/stir/SegmentByView.h index 163690a9ff..d9de29042b 100644 --- a/src/include/stir/SegmentByView.h +++ b/src/include/stir/SegmentByView.h @@ -65,11 +65,13 @@ template class SegmentByView : public Segment, public Ar //! Constructor that sets the data to a given 3d Array SegmentByView(const Array<3,elemT>& v, const shared_ptr& proj_data_info_ptr, - const int segment_num); + const int segment_num, + const int timing_pos_num = 0); //! Constructor that sets sizes via the ProjDataInfo object, initialising data to 0 SegmentByView(const shared_ptr& proj_data_info_ptr, - const int segment_num); + const int segment_num, + const int timing_pos_num = 0); //! Conversion from 1 storage order to the other diff --git a/src/include/stir/SegmentByView.inl b/src/include/stir/SegmentByView.inl index 3ecbf127cd..067aa67bcc 100644 --- a/src/include/stir/SegmentByView.inl +++ b/src/include/stir/SegmentByView.inl @@ -107,7 +107,8 @@ SegmentByView::get_viewgram(int view_num) const { return Viewgram(Array<3,elemT>::operator[](view_num), this->proj_data_info_ptr->create_shared_clone(), view_num, - this->get_segment_num()); } + this->get_segment_num(), + this->get_timing_pos_num()); } template void diff --git a/src/include/stir/Sinogram.h b/src/include/stir/Sinogram.h index fc8cc9c584..52229de263 100644 --- a/src/include/stir/Sinogram.h +++ b/src/include/stir/Sinogram.h @@ -70,16 +70,18 @@ class Sinogram : public Array<2,elemT> public: //! Construct sinogram from proj_data_info pointer, ring and segment number. Data are set to 0. inline Sinogram(const shared_ptr& proj_data_info_ptr, - const int ax_pos_num, const int segment_num); + const int ax_pos_num, const int segment_num, const int timing_pos_num = 0); //! Construct sinogram with data set to the array. inline Sinogram(const Array<2,elemT>& p,const shared_ptr& proj_data_info_ptr, - const int ax_pos_num, const int segment_num); + const int ax_pos_num, const int segment_num, const int timing_pos_num = 0); //! Get segment number inline int get_segment_num() const; //! Get number of axial positions inline int get_axial_pos_num() const; + //! Get timing position index + inline int get_timing_pos_num() const; //! Get minimum view number inline int get_min_view_num() const; //! Get maximum view number @@ -145,6 +147,7 @@ class Sinogram : public Array<2,elemT> shared_ptr proj_data_info_ptr; int axial_pos_num; int segment_num; + int timing_pos_num; }; diff --git a/src/include/stir/Sinogram.inl b/src/include/stir/Sinogram.inl index 9ef42b77ef..ab5bc1496a 100644 --- a/src/include/stir/Sinogram.inl +++ b/src/include/stir/Sinogram.inl @@ -46,6 +46,11 @@ int Sinogram::get_axial_pos_num() const { return axial_pos_num; } +template +int +Sinogram::get_timing_pos_num() const +{ return timing_pos_num; } + template int Sinogram::get_min_view_num() const @@ -82,7 +87,7 @@ template Sinogram Sinogram::get_empty_copy(void) const { - Sinogram copy(proj_data_info_ptr, get_axial_pos_num(), get_segment_num()); + Sinogram copy(proj_data_info_ptr, get_axial_pos_num(), get_segment_num(), get_timing_pos_num()); return copy; } @@ -105,12 +110,13 @@ template Sinogram:: Sinogram(const Array<2,elemT>& p, const shared_ptr& pdi_ptr, - const int ax_pos_num, const int s_num) + const int ax_pos_num, const int s_num, const int t_num) : Array<2,elemT>(p), proj_data_info_ptr(pdi_ptr), axial_pos_num(ax_pos_num), - segment_num(s_num) + segment_num(s_num), + timing_pos_num(t_num) { assert(axial_pos_num <= proj_data_info_ptr->get_max_axial_pos_num(segment_num)); assert(axial_pos_num >= proj_data_info_ptr->get_min_axial_pos_num(segment_num)); @@ -127,7 +133,7 @@ Sinogram(const Array<2,elemT>& p, template Sinogram:: Sinogram(const shared_ptr& pdi_ptr, - const int ax_pos_num, const int s_num) + const int ax_pos_num, const int s_num, const int t_num) : Array<2,elemT>(IndexRange2D (pdi_ptr->get_min_view_num(), pdi_ptr->get_max_view_num(), @@ -135,7 +141,8 @@ Sinogram(const shared_ptr& pdi_ptr, pdi_ptr->get_max_tangential_pos_num())), proj_data_info_ptr(pdi_ptr), axial_pos_num(ax_pos_num), - segment_num(s_num) + segment_num(s_num), + timing_pos_num(t_num) { assert(axial_pos_num <= proj_data_info_ptr->get_max_axial_pos_num(segment_num)); assert(axial_pos_num >= proj_data_info_ptr->get_min_axial_pos_num(segment_num)); diff --git a/src/include/stir/ViewSegmentNumbers.h b/src/include/stir/ViewSegmentNumbers.h index 8fb271be85..b358759f4c 100644 --- a/src/include/stir/ViewSegmentNumbers.h +++ b/src/include/stir/ViewSegmentNumbers.h @@ -49,7 +49,7 @@ class ViewSegmentNumbers //! an empty constructor (sets everything to 0) inline ViewSegmentNumbers(); //! constructor taking view and segment number as arguments - inline ViewSegmentNumbers( const int view_num,const int segment_num); + inline ViewSegmentNumbers( const int view_num, const int segment_num); //! get segment number for const objects inline int segment_num() const; @@ -60,6 +60,8 @@ class ViewSegmentNumbers inline int& segment_num(); //! get reference to view number inline int& view_num(); + //! get reference to timing position index + inline int& timing_pos_num(); //! comparison operator, only useful for sorting diff --git a/src/include/stir/ViewSegmentNumbers.inl b/src/include/stir/ViewSegmentNumbers.inl index 198bd32999..c46969865c 100644 --- a/src/include/stir/ViewSegmentNumbers.inl +++ b/src/include/stir/ViewSegmentNumbers.inl @@ -48,11 +48,9 @@ ViewSegmentNumbers::view_num() const { return view;} - int& ViewSegmentNumbers::segment_num() { return segment;} - int& ViewSegmentNumbers::view_num() { return view;} diff --git a/src/include/stir/Viewgram.h b/src/include/stir/Viewgram.h index 23f8e1147c..c824ac25df 100644 --- a/src/include/stir/Viewgram.h +++ b/src/include/stir/Viewgram.h @@ -70,17 +70,19 @@ class Viewgram : public Array<2,elemT> public: //! Construct from proj_data_info pointer, view and segment number. Data are set to 0. inline Viewgram(const shared_ptr& proj_data_info_ptr, - const int v_num, const int s_num); + const int v_num, const int s_num, const int t_num = 0); //! Construct with data set to the array. inline Viewgram(const Array<2,elemT>& p,const shared_ptr& proj_data_info_ptr, - const int v_num, const int s_num); + const int v_num, const int s_num, const int t_num = 0); //! Get segment number inline int get_segment_num() const; //! Get number of views inline int get_view_num() const; + //! Get timing position index + inline int get_timing_pos_num() const; //! Get minimum number of axial positions inline int get_min_axial_pos_num() const; //! Get maximum number of axial positions @@ -143,7 +145,8 @@ class Viewgram : public Array<2,elemT> shared_ptr proj_data_info_ptr; int view_num; - int segment_num; + int segment_num; + int timing_pos_num; }; END_NAMESPACE_STIR diff --git a/src/include/stir/Viewgram.inl b/src/include/stir/Viewgram.inl index 5094ca3b36..610b0e9675 100644 --- a/src/include/stir/Viewgram.inl +++ b/src/include/stir/Viewgram.inl @@ -45,6 +45,11 @@ int Viewgram::get_view_num() const { return view_num; } +template +int +Viewgram::get_timing_pos_num() const +{ return timing_pos_num; } + template int Viewgram::get_min_axial_pos_num() const @@ -82,7 +87,7 @@ template Viewgram Viewgram::get_empty_copy(void) const { - Viewgram copy(proj_data_info_ptr, get_view_num(), get_segment_num()); + Viewgram copy(proj_data_info_ptr, get_view_num(), get_segment_num(), get_timing_pos_num()); return copy; } @@ -105,10 +110,10 @@ template Viewgram:: Viewgram(const Array<2,elemT>& p, const shared_ptr& pdi_ptr, - const int v_num, const int s_num) + const int v_num, const int s_num, const int t_num) : Array<2,elemT>(p), proj_data_info_ptr(pdi_ptr), - view_num(v_num), segment_num(s_num) + view_num(v_num), segment_num(s_num), timing_pos_num(t_num) { assert(view_num <= proj_data_info_ptr->get_max_view_num()); assert(view_num >= proj_data_info_ptr->get_min_view_num()); @@ -123,7 +128,7 @@ Viewgram(const Array<2,elemT>& p, template Viewgram:: Viewgram(const shared_ptr& pdi_ptr, - const int v_num, const int s_num) + const int v_num, const int s_num, const int t_num) : Array<2,elemT>(IndexRange2D (pdi_ptr->get_min_axial_pos_num(s_num), pdi_ptr->get_max_axial_pos_num(s_num), @@ -131,7 +136,8 @@ Viewgram(const shared_ptr& pdi_ptr, pdi_ptr->get_max_tangential_pos_num())), proj_data_info_ptr(pdi_ptr), view_num(v_num), - segment_num(s_num) + segment_num(s_num), + timing_pos_num(t_num) { assert(view_num <= proj_data_info_ptr->get_max_view_num()); assert(view_num >= proj_data_info_ptr->get_min_view_num()); diff --git a/src/include/stir/listmode/CListRecord.h b/src/include/stir/listmode/CListRecord.h index 0497ff549f..c46bb63c42 100644 --- a/src/include/stir/listmode/CListRecord.h +++ b/src/include/stir/listmode/CListRecord.h @@ -114,6 +114,7 @@ class CListEvent get_swapped() const {return swapped;} + double get_delta_time() const { return delta_time; } protected: //! The detection time difference, between the two photons. //! This will work for ROOT files, but not so sure about acquired data. @@ -154,6 +155,13 @@ class CListTime return set_time_in_millisecs(time_in_millisecs); } + //! Get the timing component of the bin. + virtual inline void get_bin(Bin&, const ProjDataInfo&) const + { + error("CListTime::get_bin() currently is implemented only " + "ROOT data. Abort."); + } + }; //! A class recording external input to the scanner (normally used for gating) @@ -203,6 +211,12 @@ class CListRecord virtual bool operator==(const CListRecord& e2) const = 0; bool operator!=(const CListRecord& e2) const { return !(*this == e2); } + + //! Used in TOF reconstruction to get both the geometric and the timing + //! component of the event + virtual void full_event(Bin&, const ProjDataInfo&) const + {error("CListRecord::full_event() is implemented only for records which " + "hold timing and spatial information.");} }; diff --git a/src/include/stir/listmode/CListRecordGESigna.h b/src/include/stir/listmode/CListRecordGESigna.h index e8e19f4153..f54cbc023b 100644 --- a/src/include/stir/listmode/CListRecordGESigna.h +++ b/src/include/stir/listmode/CListRecordGESigna.h @@ -322,8 +322,7 @@ dynamic_cast(&e2) != 0 && if (this->is_event()) { // set TOF info in ps - this->delta_time = this->event_data.get_tof_bin() * - this-> get_scanner_ptr()->get_size_of_timing_bin(); + this->delta_time = this->event_data.get_tof_bin() *this-> get_scanner_ptr()->get_size_of_timing_bin(); } diff --git a/src/include/stir/recon_buildblock/BackProjectorByBin.h b/src/include/stir/recon_buildblock/BackProjectorByBin.h index f7bcad25d2..ed5fa5c4d6 100644 --- a/src/include/stir/recon_buildblock/BackProjectorByBin.h +++ b/src/include/stir/recon_buildblock/BackProjectorByBin.h @@ -34,6 +34,8 @@ #include "stir/RegisteredObject.h" #include "stir/TimedObject.h" #include "stir/shared_ptr.h" +#include "stir/Bin.h" +#include "stir/recon_buildblock/ProjMatrixElemsForOneBin.h" START_NAMESPACE_STIR @@ -105,6 +107,9 @@ class BackProjectorByBin : const int min_axial_pos_num, const int max_axial_pos_num, const int min_tangential_pos_num, const int max_tangential_pos_num); + void back_project(DiscretisedDensity<3,float>&, + const Bin&); + protected: @@ -112,13 +117,21 @@ class BackProjectorByBin : const RelatedViewgrams&, const int min_axial_pos_num, const int max_axial_pos_num, const int min_tangential_pos_num, const int max_tangential_pos_num) = 0; + + virtual void actual_back_project(DiscretisedDensity<3,float>&, + const Bin&) = 0; + + //! True if TOF has been activated. + bool tof_enabled; + private: void do_segments(DiscretisedDensity<3,float>& image, const ProjData& proj_data_org, const int start_segment_num, const int end_segment_num, const int start_axial_pos_num, const int end_axial_pos_num, const int start_tang_pos_num,const int end_tang_pos_num, - const int start_view, const int end_view); + const int start_view, const int end_view, + const int start_timing_pos_num = 0, const int end_timing_pos_num = 0); }; diff --git a/src/include/stir/recon_buildblock/BackProjectorByBinUsingInterpolation.h b/src/include/stir/recon_buildblock/BackProjectorByBinUsingInterpolation.h index 7f01569856..63c82e45d5 100644 --- a/src/include/stir/recon_buildblock/BackProjectorByBinUsingInterpolation.h +++ b/src/include/stir/recon_buildblock/BackProjectorByBinUsingInterpolation.h @@ -228,6 +228,9 @@ struct ProjDataForIntBP const int min_axial_pos_num, const int max_axial_pos_num, const int min_tangential_pos_num, const int max_tangential_pos_num); + void actual_back_project(DiscretisedDensity<3,float>&, + const Bin&); + virtual void back_project_all_symmetries( VoxelsOnCartesianGrid& image, diff --git a/src/include/stir/recon_buildblock/BackProjectorByBinUsingProjMatrixByBin.h b/src/include/stir/recon_buildblock/BackProjectorByBinUsingProjMatrixByBin.h index f4958e1420..728ba92281 100644 --- a/src/include/stir/recon_buildblock/BackProjectorByBinUsingProjMatrixByBin.h +++ b/src/include/stir/recon_buildblock/BackProjectorByBinUsingProjMatrixByBin.h @@ -86,17 +86,26 @@ class BackProjectorByBinUsingProjMatrixByBin: shared_ptr & get_proj_matrix_sptr(){ return proj_matrix_ptr ;} - + + + BackProjectorByBin* get_original_back_projector() const; + + void enable_tof(ProjMatrixElemsForOneBin* ); protected: shared_ptr proj_matrix_ptr; + void actual_back_project(DiscretisedDensity<3,float>& image, + const Bin& bin); + private: virtual void set_defaults(); virtual void initialise_keymap(); virtual bool post_processing(); + ProjMatrixElemsForOneBin* tof_row; + }; diff --git a/src/include/stir/recon_buildblock/BackProjectorByBinUsingSquareProjMatrixByBin.h b/src/include/stir/recon_buildblock/BackProjectorByBinUsingSquareProjMatrixByBin.h index 487202e144..79a21fb766 100644 --- a/src/include/stir/recon_buildblock/BackProjectorByBinUsingSquareProjMatrixByBin.h +++ b/src/include/stir/recon_buildblock/BackProjectorByBinUsingSquareProjMatrixByBin.h @@ -79,6 +79,9 @@ class BackProjectorByBinUsingSquareProjMatrixByBin: shared_ptr proj_matrix_ptr; + void actual_back_project(DiscretisedDensity<3,float>& image, + const Bin& bin); + private: virtual void set_defaults(); virtual void initialise_keymap(); diff --git a/src/include/stir/recon_buildblock/DataSymmetriesForBins.h b/src/include/stir/recon_buildblock/DataSymmetriesForBins.h index e75fe52234..66d05083a5 100644 --- a/src/include/stir/recon_buildblock/DataSymmetriesForBins.h +++ b/src/include/stir/recon_buildblock/DataSymmetriesForBins.h @@ -115,7 +115,8 @@ class DataSymmetriesForBins : public DataSymmetriesForViewSegmentNumbers virtual void get_related_bins(std::vector&, const Bin& b, const int min_axial_pos_num, const int max_axial_pos_num, - const int min_tangential_pos_num, const int max_tangential_pos_num) const; + const int min_tangential_pos_num, const int max_tangential_pos_num, + const int min_timing_pos_num = 0, const int max_timing_pos_num = 0) const; //! fills in a vector with the axial and tangential position numbers related to this bin /*! range for axial_pos_num and tangential_pos_num is taken from the ProjDataInfo object diff --git a/src/include/stir/recon_buildblock/ForwardProjectorByBin.h b/src/include/stir/recon_buildblock/ForwardProjectorByBin.h index 1fc37ee1c4..0e0cfdbc71 100644 --- a/src/include/stir/recon_buildblock/ForwardProjectorByBin.h +++ b/src/include/stir/recon_buildblock/ForwardProjectorByBin.h @@ -36,6 +36,8 @@ #include "stir/TimedObject.h" #include "stir/VoxelsOnCartesianGrid.h" #include "stir/shared_ptr.h" +#include "stir/Bin.h" +#include "stir/recon_buildblock/ProjMatrixElemsForOneBin.h" START_NAMESPACE_STIR @@ -101,6 +103,21 @@ virtual void set_up( const int min_axial_pos_num, const int max_axial_pos_num, const int min_tangential_pos_num, const int max_tangential_pos_num); + //! Overloaded function mainly used in ListMode reconstruction. + void forward_project(Bin&, + const DiscretisedDensity<3,float>&); + + //! This is a really ungly way to sort the extra bit of information which are needed for + //! the TOF reconstruction. In this base class we are going to store a pointer the the current + //! ProjMatrixElements row and the two detection points. Which the are going to be accessed by the + //! child class in case that tof_enabled. + //! The common ProjMatrixElems row will provide a 'bridge' between forward and back projection as, + //! we'll be able to keep it and not calculate it again. + void set_tof_data(const CartesianCoordinate3D*, + const CartesianCoordinate3D*); + + ProjMatrixElemsForOneBin* get_tof_row() const; + virtual ~ForwardProjectorByBin(); protected: @@ -110,6 +127,17 @@ virtual void set_up( const int min_axial_pos_num, const int max_axial_pos_num, const int min_tangential_pos_num, const int max_tangential_pos_num) = 0; + //! This virtual function has to be implemented by the derived class. + virtual void actual_forward_project(Bin&, + const DiscretisedDensity<3,float>&) = 0; + + //! True if TOF has been activated. + bool tof_enabled; + + shared_ptr tof_probabilities; + const CartesianCoordinate3D* point1; + const CartesianCoordinate3D* point2; + }; END_NAMESPACE_STIR diff --git a/src/include/stir/recon_buildblock/ForwardProjectorByBinUsingProjMatrixByBin.h b/src/include/stir/recon_buildblock/ForwardProjectorByBinUsingProjMatrixByBin.h index c0743e306d..2dd75e7b7d 100644 --- a/src/include/stir/recon_buildblock/ForwardProjectorByBinUsingProjMatrixByBin.h +++ b/src/include/stir/recon_buildblock/ForwardProjectorByBinUsingProjMatrixByBin.h @@ -79,6 +79,7 @@ class ForwardProjectorByBinUsingProjMatrixByBin: const DataSymmetriesForViewSegmentNumbers * get_symmetries_used() const; + void enable_tof(const shared_ptr& _proj_data_info_sptr, const bool v); private: shared_ptr proj_matrix_ptr; @@ -89,6 +90,8 @@ class ForwardProjectorByBinUsingProjMatrixByBin: const int min_axial_pos_num, const int max_axial_pos_num, const int min_tangential_pos_num, const int max_tangential_pos_num); + void actual_forward_project(Bin&, const DiscretisedDensity<3,float>&); + virtual void set_defaults(); virtual void initialise_keymap(); virtual bool post_processing(); diff --git a/src/include/stir/recon_buildblock/ForwardProjectorByBinUsingRayTracing.h b/src/include/stir/recon_buildblock/ForwardProjectorByBinUsingRayTracing.h index ad723fad18..96af693885 100644 --- a/src/include/stir/recon_buildblock/ForwardProjectorByBinUsingRayTracing.h +++ b/src/include/stir/recon_buildblock/ForwardProjectorByBinUsingRayTracing.h @@ -113,6 +113,9 @@ class ForwardProjectorByBinUsingRayTracing : const int min_axial_pos_num, const int max_axial_pos_num, const int min_tangential_pos_num, const int max_tangential_pos_num); + void actual_forward_project(Bin&, + const DiscretisedDensity<3,float>&); + // KT 20/06/2001 changed type from 'const DataSymmetriesForViewSegmentNumbers *' shared_ptr symmetries_ptr; diff --git a/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.h b/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.h index a596588603..5a224a7ae2 100644 --- a/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.h +++ b/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.h @@ -37,6 +37,10 @@ #include "stir/ProjDataInMemory.h" #include "stir/recon_buildblock/ProjectorByBinPair.h" #include "stir/ExamInfo.h" +#include "stir/recon_buildblock/ForwardProjectorByBinUsingProjMatrixByBin.h" +#include "stir/recon_buildblock/BackProjectorByBinUsingProjMatrixByBin.h" +#include "stir/recon_buildblock/ProjMatrixByBinUsingRayTracing.h" +#include "stir/recon_buildblock/ProjectorByBinPairUsingSeparateProjectors.h" START_NAMESPACE_STIR @@ -69,7 +73,7 @@ typedef RegisteredParsingObject(); @@ -101,7 +105,10 @@ typedef RegisteredParsingObject PM_sptr; @@ -126,7 +133,7 @@ typedef RegisteredParsingObject&); + + void init_filtered_density_image(DiscretisedDensity<3, float> &); + + BackProjectorByBin* get_original_back_projector_ptr() const; private: @@ -86,6 +91,10 @@ class PostsmoothingBackProjectorByBin : const int min_axial_pos_num, const int max_axial_pos_num, const int min_tangential_pos_num, const int max_tangential_pos_num); + void actual_back_project(DiscretisedDensity<3,float>& density, + const Bin& bin); + + shared_ptr > filtered_density_sptr; virtual void set_defaults(); diff --git a/src/include/stir/recon_buildblock/PresmoothingForwardProjectorByBin.h b/src/include/stir/recon_buildblock/PresmoothingForwardProjectorByBin.h index 7eb1caee2a..6c82636dc3 100644 --- a/src/include/stir/recon_buildblock/PresmoothingForwardProjectorByBin.h +++ b/src/include/stir/recon_buildblock/PresmoothingForwardProjectorByBin.h @@ -55,6 +55,8 @@ class PresmoothingForwardProjectorByBin : PresmoothingForwardProjectorByBin(); ~ PresmoothingForwardProjectorByBin(); + + void update_filtered_density_image(const DiscretisedDensity<3,float>&); //! Stores all necessary geometric info /*! Note that the density_info_ptr is not stored in this object. It's only used to get some info on sizes etc. @@ -75,18 +77,22 @@ class PresmoothingForwardProjectorByBin : // class has other behaviour). const DataSymmetriesForViewSegmentNumbers * get_symmetries_used() const; + ForwardProjectorByBin* get_original_forward_projector_ptr() const; private: shared_ptr original_forward_projector_ptr; shared_ptr > > image_processor_ptr; + shared_ptr > filtered_density_sptr; + void actual_forward_project(RelatedViewgrams&, const DiscretisedDensity<3,float>&, const int min_axial_pos_num, const int max_axial_pos_num, const int min_tangential_pos_num, const int max_tangential_pos_num); - + void actual_forward_project(Bin&, + const DiscretisedDensity<3,float>&); virtual void set_defaults(); virtual void initialise_keymap(); diff --git a/src/include/stir/recon_buildblock/ProjMatrixByBin.inl b/src/include/stir/recon_buildblock/ProjMatrixByBin.inl index d691e2e4df..72e2ced515 100644 --- a/src/include/stir/recon_buildblock/ProjMatrixByBin.inl +++ b/src/include/stir/recon_buildblock/ProjMatrixByBin.inl @@ -61,6 +61,19 @@ get_proj_matrix_elems_for_one_bin( // set to empty probabilities.erase(); + if (proj_data_info_sptr && proj_data_info_sptr->is_tof_data()) + { + LORInAxialAndNoArcCorrSinogramCoordinates lor; + proj_data_info_sptr->get_LOR(lor, bin); + LORAs2Points lor2(lor); + this->get_proj_matrix_elems_for_one_bin_with_tof( + probabilities, + bin, + lor2.p1(), + lor2.p2()) ; + return; + } + if (cache_stores_only_basic_bins) { // find basic bin @@ -128,6 +141,7 @@ get_proj_matrix_elems_for_one_bin_with_tof( if (!tof_enabled) error("The function get_proj_matrix_elems_for_one_bin_with_tof() needs proper timing " "initialisation. Abort."); + // set to empty probabilities.erase(); ProjMatrixElemsForOneBin tmp_probabilities; diff --git a/src/include/stir/recon_buildblock/ProjectorByBinPair.h b/src/include/stir/recon_buildblock/ProjectorByBinPair.h index 6245607c51..9579f7cb4d 100644 --- a/src/include/stir/recon_buildblock/ProjectorByBinPair.h +++ b/src/include/stir/recon_buildblock/ProjectorByBinPair.h @@ -33,6 +33,7 @@ #include "stir/recon_buildblock/BackProjectorByBin.h" #include "stir/ParsingObject.h" #include "stir/shared_ptr.h" +#include "stir/recon_buildblock/ProjMatrixElemsForOneBin.h" START_NAMESPACE_STIR @@ -81,7 +82,15 @@ public ParsingObject //BackProjectorByBin const * const shared_ptr get_back_projector_sptr() const; - + + ProjMatrixElemsForOneBin* get_current_tof_row() const; + + void enable_tof(const shared_ptr& _proj_data_info_sptr, const bool v); + + void + set_tof_data(const CartesianCoordinate3D* _point1, + const CartesianCoordinate3D* _point2); + //! Provide access to the (minimal) symmetries used by the projectors /*! It is expected that the forward and back projector can handle the same diff --git a/src/include/stir/recon_buildblock/TrivialDataSymmetriesForBins.h b/src/include/stir/recon_buildblock/TrivialDataSymmetriesForBins.h index 22ba01fdc7..8b03c2b8ef 100644 --- a/src/include/stir/recon_buildblock/TrivialDataSymmetriesForBins.h +++ b/src/include/stir/recon_buildblock/TrivialDataSymmetriesForBins.h @@ -56,7 +56,8 @@ class TrivialDataSymmetriesForBins : public DataSymmetriesForBins virtual void get_related_bins(std::vector&, const Bin& b, const int min_axial_pos_num, const int max_axial_pos_num, - const int min_tangential_pos_num, const int max_tangential_pos_num) const; + const int min_tangential_pos_num, const int max_tangential_pos_num, + const int min_timing_pos_num = 0, const int max_timing_pos_num = 0) const; virtual void get_related_bins_factorised(std::vector&, const Bin& b, diff --git a/src/listmode_buildblock/LmToProjData.cxx b/src/listmode_buildblock/LmToProjData.cxx index 0dd9a0a262..1d85a97d94 100644 --- a/src/listmode_buildblock/LmToProjData.cxx +++ b/src/listmode_buildblock/LmToProjData.cxx @@ -121,7 +121,8 @@ static void allocate_segments(VectorWithOffset& segments, const int start_segment_index, const int end_segment_index, - const ProjDataInfo* proj_data_info_ptr); + const ProjDataInfo* proj_data_info_ptr, + const int timing_pos_num = 0); // In the next 2 functions, the 'output' parameter needs to be passed // because save_and_delete_segments needs it when we're not using SegmentByView @@ -134,7 +135,7 @@ save_and_delete_segments(shared_ptr& output, VectorWithOffset& segments, const int start_segment_index, const int end_segment_index, - ProjData& proj_data, const int timing_pos = 1); + ProjData& proj_data); static shared_ptr construct_proj_data(shared_ptr& output, @@ -519,7 +520,7 @@ process_data() { assert(!is_null_ptr(template_proj_data_info_ptr)); - if (template_proj_data_info_ptr->get_num_tof_poss() > 1) + if (template_proj_data_info_ptr->is_tof_data()) { use_tof = true; actual_process_data_with_tof(); @@ -597,7 +598,6 @@ actual_process_data_without_tof() start_segment_index <= proj_data_ptr->get_max_segment_num(); start_segment_index += num_segments_in_memory) { - const int end_segment_index = min( proj_data_ptr->get_max_segment_num()+1, start_segment_index + num_segments_in_memory) - 1; @@ -763,9 +763,6 @@ actual_process_data_with_tof() double time_of_last_stored_event = 0; long num_stored_events = 0; - VectorWithOffset - segments (template_proj_data_info_ptr->get_min_segment_num(), - template_proj_data_info_ptr->get_max_segment_num()); VectorWithOffset frame_start_positions(1, static_cast(frame_defs.get_num_frames())); @@ -815,6 +812,10 @@ actual_process_data_with_tof() start_segment_index+num_segments_in_memory. */ + VectorWithOffset + segments (template_proj_data_info_ptr->get_min_segment_num(), + template_proj_data_info_ptr->get_max_segment_num()); + for (int start_segment_index = proj_data_ptr->get_min_segment_num(); start_segment_index <= proj_data_ptr->get_max_segment_num(); start_segment_index += num_segments_in_memory) @@ -824,7 +825,7 @@ actual_process_data_with_tof() min( proj_data_ptr->get_max_segment_num()+1, start_segment_index + num_segments_in_memory) - 1; if (!interactive) - allocate_segments(segments, start_segment_index, end_segment_index, proj_data_ptr->get_proj_data_info_ptr()); + allocate_segments(segments, start_segment_index, end_segment_index, proj_data_ptr->get_proj_data_info_ptr(),current_timing_pos_index); // the next variable is used to see if there are more events to store for the current segments // num_events_to_store-more_events will be the number of allowed coincidence events currently seen in the file @@ -834,7 +835,7 @@ actual_process_data_with_tof() unsigned long int more_events = do_time_frame? 1 : num_events_to_store; - if (start_segment_index != proj_data_ptr->get_min_segment_num()) + if (start_segment_index != proj_data_ptr->get_min_segment_num() || current_timing_pos_index > proj_data_ptr->get_min_tof_pos_num()) { // we're going once more through the data (for the next batch of segments) cerr << "\nProcessing next batch of segments\n"; @@ -960,7 +961,7 @@ actual_process_data_with_tof() if (!interactive) save_and_delete_segments(output, segments, start_segment_index, end_segment_index, - *proj_data_ptr, current_timing_pos_index); + *proj_data_ptr); } // end of for loop for segment range } // end of for loop for timing positions @@ -1022,7 +1023,7 @@ LmToProjData::run_tof_test_function() min( proj_data_ptr->get_max_segment_num()+1, start_segment_index + num_segments_in_memory) - 1; if (!interactive) - allocate_segments(segments, start_segment_index, end_segment_index, proj_data_ptr->get_proj_data_info_ptr()); + allocate_segments(segments, start_segment_index, end_segment_index, proj_data_ptr->get_proj_data_info_ptr(), current_timing_pos_index); for (int ax_num = proj_data_ptr->get_proj_data_info_ptr()->get_min_axial_pos_num(start_segment_index); ax_num <= proj_data_ptr->get_proj_data_info_ptr()->get_max_axial_pos_num(start_segment_index); @@ -1043,7 +1044,7 @@ LmToProjData::run_tof_test_function() if (!interactive) save_and_delete_segments(output, segments, start_segment_index, end_segment_index, - *proj_data_ptr, current_timing_pos_index); + *proj_data_ptr); } // end of for loop for segment range } // end of for loop for timing positions @@ -1059,14 +1060,15 @@ void allocate_segments( VectorWithOffset& segments, const int start_segment_index, const int end_segment_index, - const ProjDataInfo* proj_data_info_ptr) + const ProjDataInfo* proj_data_info_ptr, + const int timing_pos_num) { for (int seg=start_segment_index ; seg<=end_segment_index; seg++) { #ifdef USE_SegmentByView segments[seg] = new SegmentByView( - proj_data_info_ptr->get_empty_segment_by_view (seg)); + proj_data_info_ptr->get_empty_segment_by_view (seg, false, timing_pos_num)); #else segments[seg] = new Array<3,elem_type>(IndexRange3D(0, proj_data_info_ptr->get_num_views()-1, @@ -1082,15 +1084,14 @@ save_and_delete_segments(shared_ptr& output, VectorWithOffset& segments, const int start_segment_index, const int end_segment_index, - ProjData& proj_data, - const int timing_pos) + ProjData& proj_data) { for (int seg=start_segment_index; seg<=end_segment_index; seg++) { { #ifdef USE_SegmentByView - proj_data.set_segment(*segments[seg], timing_pos); + proj_data.set_segment(*segments[seg]); #else (*segments[seg]).write_data(*output); #endif @@ -1113,8 +1114,8 @@ construct_proj_data(shared_ptr& output, shared_ptr proj_data_sptr; #ifdef USE_SegmentByView // don't need output stream in this case - if (proj_data_info_ptr->get_num_tof_poss() == 1) - proj_data_sptr.reset(new ProjDataInterfile(exam_info_sptr, + if (!proj_data_info_ptr->is_tof_data()) + proj_data_sptr.reset(new ProjDataInterfile(exam_info_sptr, proj_data_info_ptr, output_filename, ios::out, ProjDataFromStream::Segment_View_AxialPos_TangPos, OUTPUTNumericType)); diff --git a/src/listmode_utilities/list_lm_events.cxx b/src/listmode_utilities/list_lm_events.cxx index 33ee97d4c1..a234b495be 100644 --- a/src/listmode_utilities/list_lm_events.cxx +++ b/src/listmode_utilities/list_lm_events.cxx @@ -159,6 +159,7 @@ int main(int argc, char *argv[]) << ",r:" << det_pos.pos2().axial_coord() << ",l:" << det_pos.pos2().radial_coord() << ")"; + cout << " delta time: " << event_ptr->get_delta_time(); listed = true; } } diff --git a/src/local/utilities/fillwithotherprojdata.cxx b/src/local/utilities/fillwithotherprojdata.cxx index 585f80748f..8b17ad52cf 100644 --- a/src/local/utilities/fillwithotherprojdata.cxx +++ b/src/local/utilities/fillwithotherprojdata.cxx @@ -59,10 +59,14 @@ int main(int argc, char *argv[]) segment_num<=out_projdata_ptr->get_max_segment_num(); ++segment_num) { - SegmentByView segment = in_projdata_ptr->get_segment_by_view(segment_num); - if (out_projdata_ptr->set_segment(segment) == Succeeded::no) - return EXIT_FAILURE; - + for (int timing_pos_num=out_projdata_ptr->get_min_tof_pos_num(); + timing_pos_num<=out_projdata_ptr->get_max_tof_pos_num(); + ++timing_pos_num) + { + SegmentByView segment = in_projdata_ptr->get_segment_by_view(segment_num,timing_pos_num); + if (out_projdata_ptr->set_segment(segment) == Succeeded::no) + return EXIT_FAILURE; + } } return EXIT_SUCCESS; diff --git a/src/local/utilities/inverse_proj_data.cxx b/src/local/utilities/inverse_proj_data.cxx index da2120acf9..ecb5316cda 100644 --- a/src/local/utilities/inverse_proj_data.cxx +++ b/src/local/utilities/inverse_proj_data.cxx @@ -77,60 +77,75 @@ find_inverse( ProjData* proj_data_ptr_out, const ProjData* proj_data_ptr_in) int max_segment_num = ask_num("Maximum segment number to invert", min_segment_num,proj_data_info_ptr->get_max_segment_num(), min_segment_num); - + int min_timing_pos_num = 0; // Default cases of non-TOF data + int max_timing_pos_num = 0; + if (proj_data_info_ptr->is_tof_data()) + { + int min_timing_pos_num = ask_num("Minimum timing position index to invert", + proj_data_info_ptr->get_min_tof_pos_num(), proj_data_info_ptr->get_max_tof_pos_num(), 0); + int max_timing_pos_num = ask_num("Maximum timing position index to invert", + min_timing_pos_num,proj_data_info_ptr->get_max_tof_pos_num(), + max_timing_pos_num); + } float max_in_viewgram =0.F; for (int segment_num = min_segment_num; segment_num<= max_segment_num; segment_num++) { - SegmentByView segment_by_view = - projdatafromstream_in->get_segment_by_view(segment_num); - const float current_max_in_viewgram = segment_by_view.find_max(); - if ( current_max_in_viewgram >= max_in_viewgram) - max_in_viewgram = current_max_in_viewgram ; - else - continue; + for (int timing_pos_num = min_timing_pos_num; timing_pos_num<= max_timing_pos_num; + timing_pos_num++) + { + SegmentByView segment_by_view = + projdatafromstream_in->get_segment_by_view(segment_num,timing_pos_num); + const float current_max_in_viewgram = segment_by_view.find_max(); + if ( current_max_in_viewgram >= max_in_viewgram) + max_in_viewgram = current_max_in_viewgram ; + else + continue; + } } info(boost::format("Max number in viewgram is: %1%") % max_in_viewgram); for (int segment_num = min_segment_num; segment_num<= max_segment_num; segment_num++) - for ( int view_num = proj_data_info_ptr->get_min_view_num(); - view_num<=proj_data_info_ptr->get_max_view_num(); view_num++) - { - Viewgram viewgram_in = projdatafromstream_in->get_viewgram(view_num,segment_num); - Viewgram viewgram_out = proj_data_info_ptr_out->get_empty_viewgram(view_num,segment_num); - - // the following const was found in the ls_cyl.hs and the same value will - // be used for thresholding both kappa_0 and kappa_1. - // threshold = 10^-4/max_in_sinogram - // TODO - find out batter way of finding the threshold - // const float max_in_viewgram = 54.0F; - - //segment_by_view.find_max(); - - const float threshold = 0.0001F*max_in_viewgram; - - for (int i= viewgram_in.get_min_axial_pos_num(); i<=viewgram_in.get_max_axial_pos_num();i++) - for (int j= viewgram_in.get_min_tangential_pos_num(); j<=viewgram_in.get_max_tangential_pos_num();j++) - { - bin= viewgram_in[i][j]; - - if (bin >= threshold) - { - inv = 1.F/bin; - viewgram_out[i][j] = inv; - } - else - { - inv =1/threshold; - viewgram_out[i][j] = inv; - } - - } - projdatafromstream_out->set_viewgram(viewgram_out); - } + for (int timing_pos_num = min_timing_pos_num; timing_pos_num<= max_timing_pos_num; + timing_pos_num++) + for ( int view_num = proj_data_info_ptr->get_min_view_num(); + view_num<=proj_data_info_ptr->get_max_view_num(); view_num++) + { + Viewgram viewgram_in = projdatafromstream_in->get_viewgram(view_num,segment_num,false,timing_pos_num); + Viewgram viewgram_out = proj_data_info_ptr_out->get_empty_viewgram(view_num,segment_num,false,timing_pos_num); + + // the following const was found in the ls_cyl.hs and the same value will + // be used for thresholding both kappa_0 and kappa_1. + // threshold = 10^-4/max_in_sinogram + // TODO - find out batter way of finding the threshold + // const float max_in_viewgram = 54.0F; + + //segment_by_view.find_max(); + + const float threshold = 0.0001F*max_in_viewgram; + + for (int i= viewgram_in.get_min_axial_pos_num(); i<=viewgram_in.get_max_axial_pos_num();i++) + for (int j= viewgram_in.get_min_tangential_pos_num(); j<=viewgram_in.get_max_tangential_pos_num();j++) + { + bin= viewgram_in[i][j]; + + if (bin >= threshold) + { + inv = 1.F/bin; + viewgram_out[i][j] = inv; + } + else + { + inv =1/threshold; + viewgram_out[i][j] = inv; + } + + } + projdatafromstream_out->set_viewgram(viewgram_out); + } } diff --git a/src/recon_buildblock/BackProjectorByBin.cxx b/src/recon_buildblock/BackProjectorByBin.cxx index a10644722f..f606ab72d1 100644 --- a/src/recon_buildblock/BackProjectorByBin.cxx +++ b/src/recon_buildblock/BackProjectorByBin.cxx @@ -80,13 +80,18 @@ BackProjectorByBin::back_project(DiscretisedDensity<3,float>& image, for (int i=0; i(vs_nums_to_process.size()); ++i) { const ViewSegmentNumbers vs=vs_nums_to_process[i]; + + for (int k=proj_data.get_proj_data_info_ptr()->get_min_tof_pos_num(); + k<=proj_data.get_proj_data_info_ptr()->get_max_tof_pos_num(); + ++k) + { #ifdef STIR_OPENMP RelatedViewgrams viewgrams; #pragma omp critical (BACKPROJECTORBYBIN_GETVIEWGRAMS) viewgrams = proj_data.get_related_viewgrams(vs, symmetries_sptr); #else const RelatedViewgrams viewgrams = - proj_data.get_related_viewgrams(vs, symmetries_sptr); + proj_data.get_related_viewgrams(vs, symmetries_sptr, false, k); #endif #ifdef STIR_OPENMP const int thread_num=omp_get_thread_num(); @@ -97,6 +102,7 @@ BackProjectorByBin::back_project(DiscretisedDensity<3,float>& image, #else back_project(image, viewgrams); #endif + } } } #ifdef STIR_OPENMP @@ -172,5 +178,12 @@ back_project(DiscretisedDensity<3,float>& density, stop_timers(); } +void +BackProjectorByBin:: +back_project(DiscretisedDensity<3, float>& density, + const Bin &bin) +{ + actual_back_project(density, bin); +} END_NAMESPACE_STIR diff --git a/src/recon_buildblock/BackProjectorByBinUsingInterpolation.cxx b/src/recon_buildblock/BackProjectorByBinUsingInterpolation.cxx index e534282735..3345e9bce9 100644 --- a/src/recon_buildblock/BackProjectorByBinUsingInterpolation.cxx +++ b/src/recon_buildblock/BackProjectorByBinUsingInterpolation.cxx @@ -440,7 +440,13 @@ actual_back_project(DiscretisedDensity<3,float>& density, } - +void +BackProjectorByBinUsingInterpolation:: +actual_back_project(DiscretisedDensity<3,float>&, + const Bin&) +{ + error("BackProjectorByBinUsingInterpolation is not supported for list-mode reconstruction. Abort."); +} #if 0 diff --git a/src/recon_buildblock/BackProjectorByBinUsingProjMatrixByBin.cxx b/src/recon_buildblock/BackProjectorByBinUsingProjMatrixByBin.cxx index 0709594e33..78842daff6 100644 --- a/src/recon_buildblock/BackProjectorByBinUsingProjMatrixByBin.cxx +++ b/src/recon_buildblock/BackProjectorByBinUsingProjMatrixByBin.cxx @@ -139,6 +139,7 @@ actual_back_project(DiscretisedDensity<3,float>& image, const Viewgram& viewgram = *r_viewgrams_iter; const int view_num = viewgram.get_view_num(); const int segment_num = viewgram.get_segment_num(); + const int timing_num = viewgram.get_timing_pos_num(); for ( int tang_pos = min_tangential_pos_num ;tang_pos <= max_tangential_pos_num ;++tang_pos) for ( int ax_pos = min_axial_pos_num; ax_pos <= max_axial_pos_num ;++ax_pos) @@ -146,7 +147,7 @@ actual_back_project(DiscretisedDensity<3,float>& image, // KT 21/02/2002 added check on 0 if (viewgram[ax_pos][tang_pos] == 0) continue; - Bin bin(segment_num, view_num, ax_pos, tang_pos, viewgram[ax_pos][tang_pos]); + Bin bin(segment_num, view_num, ax_pos, tang_pos, timing_num, viewgram[ax_pos][tang_pos]); proj_matrix_ptr->get_proj_matrix_elems_for_one_bin(proj_matrix_row, bin); proj_matrix_row.back_project(image, bin); } @@ -175,7 +176,8 @@ actual_back_project(DiscretisedDensity<3,float>& image, Bin basic_bin(viewgrams.get_basic_segment_num(), viewgrams.get_basic_view_num(), ax_pos, - tang_pos); + tang_pos, + viewgrams.get_basic_timing_pos_num()); symmetries->find_basic_bin(basic_bin); proj_matrix_ptr->get_proj_matrix_elems_for_one_bin(proj_matrix_row, basic_bin); @@ -216,6 +218,7 @@ actual_back_project(DiscretisedDensity<3,float>& image, viewgram_iter->get_view_num(), axial_pos_tmp, tang_pos_tmp, + viewgram_iter->get_timing_pos_num(), (*viewgram_iter)[axial_pos_tmp][tang_pos_tmp]); auto_ptr symm_op_ptr = @@ -225,6 +228,7 @@ actual_back_project(DiscretisedDensity<3,float>& image, assert(bin.view_num() == basic_bin.view_num()); assert(bin.axial_pos_num() == basic_bin.axial_pos_num()); assert(bin.tangential_pos_num() == basic_bin.tangential_pos_num()); + assert(bin.timing_pos_num() == basic_bin.timing_pos_num()); symm_op_ptr->transform_proj_matrix_elems_for_one_bin(proj_matrix_row_copy); proj_matrix_row_copy.back_project(image, bin); @@ -239,5 +243,32 @@ actual_back_project(DiscretisedDensity<3,float>& image, } +void +BackProjectorByBinUsingProjMatrixByBin:: +actual_back_project(DiscretisedDensity<3,float>& image, + const Bin& bin) +{ + if (proj_matrix_ptr->is_cache_enabled() && !tof_enabled) + { + ProjMatrixElemsForOneBin proj_matrix_row; + proj_matrix_ptr->get_proj_matrix_elems_for_one_bin(proj_matrix_row, bin); + proj_matrix_row.back_project(image, bin); + } + else if (proj_matrix_ptr->is_cache_enabled() && tof_enabled) + { + tof_row->back_project(image, bin); + } + else + error("BackProjectorByBinUsingProjMatrixByBin: Symmetries should be handled by ProjMatrix. Abort. "); + +} + +void +BackProjectorByBinUsingProjMatrixByBin:: +enable_tof(ProjMatrixElemsForOneBin * for_row) +{ + tof_row = for_row; + tof_enabled = true; +} END_NAMESPACE_STIR diff --git a/src/recon_buildblock/BackProjectorByBinUsingSquareProjMatrixByBin.cxx b/src/recon_buildblock/BackProjectorByBinUsingSquareProjMatrixByBin.cxx index 8c624419be..5ccd4f7745 100644 --- a/src/recon_buildblock/BackProjectorByBinUsingSquareProjMatrixByBin.cxx +++ b/src/recon_buildblock/BackProjectorByBinUsingSquareProjMatrixByBin.cxx @@ -111,6 +111,13 @@ actual_back_project(DiscretisedDensity<3,float>& image, } } +void +BackProjectorByBinUsingSquareProjMatrixByBin:: +actual_back_project(DiscretisedDensity<3, float> &image, + const Bin& bin) +{ + error("BackProjectorByBinUsingSquareProjMatrixByBin is not supported for list-mode reconstruction. Abort."); +} void BackProjectorByBinUsingSquareProjMatrixByBin:: diff --git a/src/recon_buildblock/BinNormalisation.cxx b/src/recon_buildblock/BinNormalisation.cxx index 94a974161c..494207fb06 100644 --- a/src/recon_buildblock/BinNormalisation.cxx +++ b/src/recon_buildblock/BinNormalisation.cxx @@ -57,7 +57,7 @@ BinNormalisation::apply(RelatedViewgrams& viewgrams, { for (RelatedViewgrams::iterator iter = viewgrams.begin(); iter != viewgrams.end(); ++iter) { - Bin bin(iter->get_segment_num(),iter->get_view_num(), 0,0); + Bin bin(iter->get_segment_num(),iter->get_view_num(), 0,0,iter->get_timing_pos_num()); for (bin.axial_pos_num()= iter->get_min_axial_pos_num(); bin.axial_pos_num()<=iter->get_max_axial_pos_num(); ++bin.axial_pos_num()) @@ -75,7 +75,7 @@ undo(RelatedViewgrams& viewgrams,const double start_time, const double en { for (RelatedViewgrams::iterator iter = viewgrams.begin(); iter != viewgrams.end(); ++iter) { - Bin bin(iter->get_segment_num(),iter->get_view_num(), 0,0); + Bin bin(iter->get_segment_num(),iter->get_view_num(), 0,0,iter->get_timing_pos_num()); for (bin.axial_pos_num()= iter->get_min_axial_pos_num(); bin.axial_pos_num()<=iter->get_max_axial_pos_num(); ++bin.axial_pos_num()) @@ -109,26 +109,32 @@ apply(ProjData& proj_data,const double start_time, const double end_time, { const ViewSegmentNumbers vs=vs_nums_to_process[i]; - RelatedViewgrams viewgrams; + for (int k=proj_data.get_proj_data_info_ptr()->get_min_tof_pos_num(); + k<=proj_data.get_proj_data_info_ptr()->get_max_tof_pos_num(); + ++k) + { + + RelatedViewgrams viewgrams; #ifdef STIR_OPENMP - // reading/writing to streams is not safe in multi-threaded code - // so protect with a critical section - // note that the name of the section has to be same for the get/set - // function as they're reading from/writing to the same stream + // reading/writing to streams is not safe in multi-threaded code + // so protect with a critical section + // note that the name of the section has to be same for the get/set + // function as they're reading from/writing to the same stream #pragma omp critical (BINNORMALISATION_APPLY__VIEWGRAMS) #endif - { - viewgrams = - proj_data.get_related_viewgrams(vs, symmetries_sptr); - } + { + viewgrams = + proj_data.get_related_viewgrams(vs, symmetries_sptr, false, k); + } - this->apply(viewgrams, start_time, end_time); + this->apply(viewgrams, start_time, end_time); #ifdef STIR_OPENMP #pragma omp critical (BINNORMALISATION_APPLY__VIEWGRAMS) #endif - { - proj_data.set_related_viewgrams(viewgrams); + { + proj_data.set_related_viewgrams(viewgrams); + } } } } @@ -154,22 +160,27 @@ undo(ProjData& proj_data,const double start_time, const double end_time, { const ViewSegmentNumbers vs=vs_nums_to_process[i]; - RelatedViewgrams viewgrams; + for (int k=proj_data.get_proj_data_info_ptr()->get_min_tof_pos_num(); + k<=proj_data.get_proj_data_info_ptr()->get_max_tof_pos_num(); + ++k) + { + RelatedViewgrams viewgrams; #ifdef STIR_OPENMP #pragma omp critical (BINNORMALISATION_UNDO__VIEWGRAMS) #endif - { - viewgrams = - proj_data.get_related_viewgrams(vs, symmetries_sptr); - } + { + viewgrams = + proj_data.get_related_viewgrams(vs, symmetries_sptr,false,k); + } - this->undo(viewgrams, start_time, end_time); + this->undo(viewgrams, start_time, end_time); #ifdef STIR_OPENMP #pragma omp critical (BINNORMALISATION_UNDO__VIEWGRAMS) #endif - { - proj_data.set_related_viewgrams(viewgrams); + { + proj_data.set_related_viewgrams(viewgrams); + } } } } diff --git a/src/recon_buildblock/DataSymmetriesForBins.cxx b/src/recon_buildblock/DataSymmetriesForBins.cxx index ec694365d1..9d4e81be29 100644 --- a/src/recon_buildblock/DataSymmetriesForBins.cxx +++ b/src/recon_buildblock/DataSymmetriesForBins.cxx @@ -93,7 +93,8 @@ void DataSymmetriesForBins:: get_related_bins(vector& rel_b, const Bin& b, const int min_axial_pos_num, const int max_axial_pos_num, - const int min_tangential_pos_num, const int max_tangential_pos_num) const + const int min_tangential_pos_num, const int max_tangential_pos_num, + const int min_timing_pos_num, const int max_timing_pos_num) const { #ifndef NDEBUG Bin bin_copy = b; @@ -130,8 +131,11 @@ get_related_bins(vector& rel_b, const Bin& b, ax_tang_pos_ptr != ax_tang_poss.end(); ++ax_tang_pos_ptr) { - rel_b.push_back(Bin(view_seg_ptr->segment_num(), view_seg_ptr->view_num(), - (*ax_tang_pos_ptr)[1], (*ax_tang_pos_ptr)[2])); + for (int k=min_timing_pos_num;k<=max_timing_pos_num;++k) + { + rel_b.push_back(Bin(view_seg_ptr->segment_num(), view_seg_ptr->view_num(), + (*ax_tang_pos_ptr)[1], (*ax_tang_pos_ptr)[2],k)); + } } } } diff --git a/src/recon_buildblock/ForwardProjectorByBin.cxx b/src/recon_buildblock/ForwardProjectorByBin.cxx index 7abb00eda6..4a5525bf98 100644 --- a/src/recon_buildblock/ForwardProjectorByBin.cxx +++ b/src/recon_buildblock/ForwardProjectorByBin.cxx @@ -49,6 +49,7 @@ START_NAMESPACE_STIR ForwardProjectorByBin::ForwardProjectorByBin() { + tof_enabled = false; } ForwardProjectorByBin::~ForwardProjectorByBin() @@ -62,7 +63,7 @@ ForwardProjectorByBin::forward_project(ProjData& proj_data, // this->set_up(proj_data_ptr->get_proj_data_info_ptr()->clone(), // image_sptr); - + shared_ptr symmetries_sptr(this->get_symmetries_used()->clone()); @@ -77,18 +78,24 @@ ForwardProjectorByBin::forward_project(ProjData& proj_data, for (int i=0; i(vs_nums_to_process.size()); ++i) { const ViewSegmentNumbers vs=vs_nums_to_process[i]; - - info(boost::format("Processing view %1% of segment %2%") % vs.view_num() % vs.segment_num()); - - RelatedViewgrams viewgrams = - proj_data.get_empty_related_viewgrams(vs, symmetries_sptr); - forward_project(viewgrams, image); + for (int k=proj_data.get_proj_data_info_ptr()->get_min_tof_pos_num(); + k<=proj_data.get_proj_data_info_ptr()->get_max_tof_pos_num(); + ++k) + { + if (proj_data.get_proj_data_info_ptr()->is_tof_data()) + info(boost::format("Processing view %1% of segment %2% of TOF bin %3%") % vs.view_num() % vs.segment_num() % k); + else + info(boost::format("Processing view %1% of segment %2%") % vs.view_num() % vs.segment_num()); + RelatedViewgrams viewgrams = + proj_data.get_empty_related_viewgrams(vs, symmetries_sptr, false, k); + forward_project(viewgrams, image); #ifdef STIR_OPENMP #pragma omp critical (FORWARDPROJ_SETVIEWGRAMS) #endif - { - if (!(proj_data.set_related_viewgrams(viewgrams) == Succeeded::yes)) - error("Error set_related_viewgrams in forward projecting"); + { + if (!(proj_data.set_related_viewgrams(viewgrams) == Succeeded::yes)) + error("Error set_related_viewgrams in forward projecting"); + } } } @@ -156,5 +163,28 @@ forward_project(RelatedViewgrams& viewgrams, stop_timers(); } +void +ForwardProjectorByBin::forward_project(Bin& this_bin, + const DiscretisedDensity<3, float> & this_image) +{ + actual_forward_project(this_bin, + this_image); +} + +void +ForwardProjectorByBin:: +set_tof_data(const CartesianCoordinate3D* _point1, + const CartesianCoordinate3D* _point2) +{ + point1 = _point1; + point2 = _point2; +} + +ProjMatrixElemsForOneBin* +ForwardProjectorByBin:: +get_tof_row() const +{ + return tof_probabilities.get(); +} END_NAMESPACE_STIR diff --git a/src/recon_buildblock/ForwardProjectorByBinUsingProjMatrixByBin.cxx b/src/recon_buildblock/ForwardProjectorByBinUsingProjMatrixByBin.cxx index 3405dbcfe3..f81486ace0 100644 --- a/src/recon_buildblock/ForwardProjectorByBinUsingProjMatrixByBin.cxx +++ b/src/recon_buildblock/ForwardProjectorByBinUsingProjMatrixByBin.cxx @@ -128,9 +128,9 @@ ForwardProjectorByBinUsingProjMatrixByBin:: // straightforward version which relies on ProjMatrixByBin to sort out all // symmetries // would be slow if there's no caching at all, but is very fast if everything is cached - + ProjMatrixElemsForOneBin proj_matrix_row; - + RelatedViewgrams::iterator r_viewgrams_iter = viewgrams.begin(); while( r_viewgrams_iter!=viewgrams.end()) @@ -138,11 +138,12 @@ ForwardProjectorByBinUsingProjMatrixByBin:: Viewgram& viewgram = *r_viewgrams_iter; const int view_num = viewgram.get_view_num(); const int segment_num = viewgram.get_segment_num(); + const int timing_num = viewgram.get_timing_pos_num(); for ( int tang_pos = min_tangential_pos_num ;tang_pos <= max_tangential_pos_num ;++tang_pos) for ( int ax_pos = min_axial_pos_num; ax_pos <= max_axial_pos_num ;++ax_pos) { - Bin bin(segment_num, view_num, ax_pos, tang_pos, 0.f); + Bin bin(segment_num, view_num, ax_pos, tang_pos, timing_num, 0.f); proj_matrix_ptr->get_proj_matrix_elems_for_one_bin(proj_matrix_row, bin); proj_matrix_row.forward_project(bin,image); viewgram[ax_pos][tang_pos] = bin.get_bin_value(); @@ -152,6 +153,7 @@ ForwardProjectorByBinUsingProjMatrixByBin:: } else { + error("Need to do TOF stuff here"); // Complicated version which handles the symmetries explicitly. // Faster when no caching is performed, about just as fast when there is caching, // but of only basic bins. @@ -170,9 +172,10 @@ ForwardProjectorByBinUsingProjMatrixByBin:: if (already_processed[ax_pos][tang_pos]) continue; - Bin basic_bin(viewgrams.get_basic_segment_num(),viewgrams.get_basic_view_num(),ax_pos,tang_pos); + Bin basic_bin(viewgrams.get_basic_segment_num(),viewgrams.get_basic_view_num(),ax_pos,tang_pos, + viewgrams.get_basic_timing_pos_num()); symmetries->find_basic_bin(basic_bin); - + proj_matrix_ptr->get_proj_matrix_elems_for_one_bin(proj_matrix_row, basic_bin); vector r_ax_poss; @@ -208,7 +211,8 @@ ForwardProjectorByBinUsingProjMatrixByBin:: Bin bin(viewgram_iter->get_segment_num(), viewgram_iter->get_view_num(), axial_pos_tmp, - tang_pos_tmp); + tang_pos_tmp, + viewgram_iter->get_timing_pos_num()); auto_ptr symm_op_ptr = symmetries->find_symmetry_operation_from_basic_bin(bin); @@ -227,4 +231,35 @@ ForwardProjectorByBinUsingProjMatrixByBin:: } } +void +ForwardProjectorByBinUsingProjMatrixByBin:: + actual_forward_project(Bin& this_bin, + const DiscretisedDensity<3, float> &density) +{ + + if (proj_matrix_ptr->is_cache_enabled() && !tof_enabled) + { + ProjMatrixElemsForOneBin proj_matrix_row; + + proj_matrix_ptr->get_proj_matrix_elems_for_one_bin(proj_matrix_row, this_bin); + proj_matrix_row.forward_project(this_bin,density); + } + else if (proj_matrix_ptr->is_cache_enabled() && tof_enabled) + { + proj_matrix_ptr->get_proj_matrix_elems_for_one_bin_with_tof(*tof_probabilities, this_bin, *point1, *point2); + tof_probabilities->forward_project(this_bin,density); + } + else + error("ForwardProjectorByBinUsingProjMatrixByBin: Symmetries should be handled by ProjMatrix. Abort. "); +} + +void +ForwardProjectorByBinUsingProjMatrixByBin:: +enable_tof(const shared_ptr& _proj_data_info_sptr, const bool v) +{ + proj_matrix_ptr->enable_tof(_proj_data_info_sptr, v); + tof_enabled = v; + tof_probabilities.reset(new ProjMatrixElemsForOneBin()); +} + END_NAMESPACE_STIR diff --git a/src/recon_buildblock/ForwardProjectorByBinUsingRayTracing.cxx b/src/recon_buildblock/ForwardProjectorByBinUsingRayTracing.cxx index 5af0934820..9bf0c7e040 100644 --- a/src/recon_buildblock/ForwardProjectorByBinUsingRayTracing.cxx +++ b/src/recon_buildblock/ForwardProjectorByBinUsingRayTracing.cxx @@ -152,12 +152,18 @@ set_up(const shared_ptr& proj_data_info_ptr, segment_num <= proj_data_info_ptr->get_max_segment_num(); ++segment_num) { - const float num_planes_per_axial_pos = - symmetries_ptr->get_num_planes_per_axial_pos(segment_num); - if (fabs(round(num_planes_per_axial_pos) - num_planes_per_axial_pos) > 1.E-4) - error("ForwardProjectorByBinUsingRayTracing: the number of image planes " - "per axial_pos (which is %g for segment %d) should be an integer\n", - num_planes_per_axial_pos, segment_num); +// for (int k = proj_data_info_ptr->get_min_tof_pos_num(); +// k <= proj_data_info_ptr->get_max_tof_pos_num(); +// ++k) +// { + //TODO Check symmetries here, should we add TOF? + const float num_planes_per_axial_pos = + symmetries_ptr->get_num_planes_per_axial_pos(segment_num); + if (fabs(round(num_planes_per_axial_pos) - num_planes_per_axial_pos) > 1.E-4) + error("ForwardProjectorByBinUsingRayTracing: the number of image planes " + "per axial_pos (which is %g for segment %d) should be an integer\n", + num_planes_per_axial_pos, segment_num); +// } } @@ -1445,5 +1451,12 @@ forward_project_all_symmetries_2D(Viewgram & pos_view, stop_timers(); } +void +ForwardProjectorByBinUsingRayTracing:: + actual_forward_project(Bin& this_bin, + const DiscretisedDensity<3,float>& density) +{ + error("ForwardProjectorByBinUsingRayTracing is not supported for list-mode data. Abort."); +} END_NAMESPACE_STIR diff --git a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx index 5a49aca198..928f706a61 100644 --- a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx +++ b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx @@ -54,6 +54,9 @@ #include "stir/recon_buildblock/ProjMatrixByBinUsingRayTracing.h" #include "stir/recon_buildblock/ProjectorByBinPairUsingSeparateProjectors.h" +#include "stir/recon_buildblock/PresmoothingForwardProjectorByBin.h" +#include "stir/recon_buildblock/PostsmoothingBackProjectorByBin.h" + #ifdef STIR_MPI #include "stir/recon_buildblock/distributed_functions.h" #endif @@ -78,16 +81,17 @@ PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin() template void PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin:: -set_defaults() -{ +set_defaults() +{ base_type::set_defaults(); this->additive_proj_data_sptr.reset(); - this->additive_projection_data_filename ="0"; + this->additive_projection_data_filename ="0"; this->max_ring_difference_num_to_process =-1; this->PM_sptr.reset(new ProjMatrixByBinUsingRayTracing()); this->normalisation_sptr.reset(new TrivialBinNormalisation); this->do_time_frame = false; + this->use_projectors = false; } template @@ -99,8 +103,10 @@ initialise_keymap() this->parser.add_start_key("PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin Parameters"); this->parser.add_stop_key("End PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin Parameters"); this->parser.add_key("max ring difference num to process", &this->max_ring_difference_num_to_process); - this->parser.add_parsing_key("Matrix type", &this->PM_sptr); - this->parser.add_key("additive sinogram",&this->additive_projection_data_filename); + this->parser.add_parsing_key("Matrix type", &this->PM_sptr); + this->parser.add_parsing_key("Projector pair type", &this->projector_pair_ptr); + this->parser.add_key("use projectors", &use_projectors); + this->parser.add_key("additive sinogram",&this->additive_projection_data_filename); this->parser.add_key("num_events_to_store",&this->num_events_to_store); } @@ -128,39 +134,46 @@ actual_subsets_are_approximately_balanced(std::string& warning_message) const for (int subset_num=0; subset_numnum_subsets; ++subset_num) { - for (int segment_num = -this->max_ring_difference_num_to_process; - segment_num <= this->max_ring_difference_num_to_process; ++segment_num) - { - for (int axial_num = proj_data_info_cyl_sptr->get_min_axial_pos_num(segment_num); - axial_num < proj_data_info_cyl_sptr->get_max_axial_pos_num(segment_num); - axial_num ++) - { - // For debugging. - // std::cout <get_min_tangential_pos_num(); - tang_num < proj_data_info_cyl_sptr->get_max_tangential_pos_num(); - tang_num ++ ) - { - for(int view_num = proj_data_info_cyl_sptr->get_min_view_num() + subset_num; - view_num <= proj_data_info_cyl_sptr->get_max_view_num(); - view_num += this->num_subsets) - { - const Bin tmp_bin(segment_num, - view_num, - axial_num, - tang_num, 1); - - if (!this->PM_sptr->get_symmetries_ptr()->is_basic(tmp_bin) ) - continue; - - num_bins_in_subset[subset_num] += - symmetries.num_related_bins(tmp_bin); - - } - } - } - } + for (int timing_pos_num = proj_data_info_cyl_sptr->get_min_tof_pos_num(); + timing_pos_num <= proj_data_info_cyl_sptr->get_max_tof_pos_num(); + ++timing_pos_num) + { + for (int segment_num = -this->max_ring_difference_num_to_process; + segment_num <= this->max_ring_difference_num_to_process; ++segment_num) + { + for (int axial_num = proj_data_info_cyl_sptr->get_min_axial_pos_num(segment_num); + axial_num < proj_data_info_cyl_sptr->get_max_axial_pos_num(segment_num); + axial_num ++) + { + // For debugging. + // std::cout <get_min_tangential_pos_num(); + tang_num < proj_data_info_cyl_sptr->get_max_tangential_pos_num(); + tang_num ++ ) + { + for(int view_num = proj_data_info_cyl_sptr->get_min_view_num() + subset_num; + view_num <= proj_data_info_cyl_sptr->get_max_view_num(); + view_num += this->num_subsets) + { + const Bin tmp_bin(segment_num, + view_num, + axial_num, + tang_num, + timing_pos_num, + 1); + + if (!this->PM_sptr->get_symmetries_ptr()->is_basic(tmp_bin) ) + continue; + + num_bins_in_subset[subset_num] += + symmetries.num_related_bins(tmp_bin); + + } + } + } + } + } } for (int subset_num=1; subset_numnum_subsets; ++subset_num) @@ -196,20 +209,28 @@ set_up_before_sensitivity(shared_ptr const& target_sptr) distributed::send_int_value(100, -1); #endif + // Check if we have listmode projectors + if (!use_projectors) + { + // set projector to be used for the calculations + this->PM_sptr->set_up(proj_data_info_cyl_sptr->create_shared_clone(),target_sptr); - // set projector to be used for the calculations - this->PM_sptr->set_up(proj_data_info_cyl_sptr->create_shared_clone(),target_sptr); - - this->PM_sptr->enable_tof(proj_data_info_cyl_sptr->create_shared_clone(), this->use_tof); + this->PM_sptr->enable_tof(proj_data_info_cyl_sptr->create_shared_clone(), this->use_tof); - shared_ptr forward_projector_ptr(new ForwardProjectorByBinUsingProjMatrixByBin(this->PM_sptr)); - shared_ptr back_projector_ptr(new BackProjectorByBinUsingProjMatrixByBin(this->PM_sptr)); + shared_ptr forward_projector_ptr(new ForwardProjectorByBinUsingProjMatrixByBin(this->PM_sptr)); + shared_ptr back_projector_ptr(new BackProjectorByBinUsingProjMatrixByBin(this->PM_sptr)); - this->projector_pair_ptr.reset( - new ProjectorByBinPairUsingSeparateProjectors(forward_projector_ptr, back_projector_ptr)); + this->projector_pair_ptr.reset( + new ProjectorByBinPairUsingSeparateProjectors(forward_projector_ptr, back_projector_ptr)); - this->projector_pair_ptr->set_up(proj_data_info_cyl_sptr->create_shared_clone(),target_sptr); + this->projector_pair_ptr->set_up(proj_data_info_cyl_sptr->create_shared_clone(),target_sptr); + } + else + { + this->projector_pair_ptr->set_up(proj_data_info_cyl_sptr->create_shared_clone(),target_sptr); + this->projector_pair_ptr->enable_tof(proj_data_info_cyl_sptr->create_shared_clone(), this->use_tof); + } if (is_null_ptr(this->normalisation_sptr)) { @@ -235,21 +256,21 @@ set_up_before_sensitivity(shared_ptr const& target_sptr) } return Succeeded::yes; -} - - -template -bool +} + + +template +bool PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin::post_processing() -{ +{ + + if (base_type::post_processing() == true) + return true; - if (base_type::post_processing() == true) - return true; - #if 1 - if (is_null_ptr(this->PM_sptr)) + if (is_null_ptr(this->PM_sptr)) - { warning("You need to specify a projection matrix"); return true; } + { warning("You need to specify a projection matrix"); return true; } #else if(is_null_ptr(this->projector_pair_sptr->get_forward_projector_sptr())) @@ -266,7 +287,7 @@ PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBinmax_ring_difference_num_to_process == -1) { - this->max_ring_difference_num_to_process = + this->max_ring_difference_num_to_process = scanner_sptr->get_num_rings()-1; } @@ -306,7 +327,9 @@ PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin= proj.get_max_segment_num(); + add_proj.get_max_segment_num() >= proj.get_max_segment_num() && + add_proj.get_min_tof_pos_num() <= proj.get_min_tof_pos_num() && + add_proj.get_max_tof_pos_num() >= proj.get_max_tof_pos_num() ; for (int segment_num=proj.get_min_segment_num(); ok && segment_num<=proj.get_max_segment_num(); @@ -334,10 +357,10 @@ warning("PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrix return true; } - return false; + return false; + +} -} - template void PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin:: @@ -346,33 +369,38 @@ add_subset_sensitivity(TargetT& sensitivity, const int subset_num) const const int min_segment_num = proj_data_info_cyl_sptr->get_min_segment_num(); const int max_segment_num = proj_data_info_cyl_sptr->get_max_segment_num(); + const int min_timing_pos_num = proj_data_info_cyl_sptr->get_min_tof_pos_num(); + const int max_timing_pos_num = proj_data_info_cyl_sptr->get_max_tof_pos_num(); // 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) + for (int timing_pos_num = min_timing_pos_num; timing_pos_num <= min_timing_pos_num; ++timing_pos_num) { - for (int view = proj_data_info_cyl_sptr->get_min_view_num() + subset_num; - view <= proj_data_info_cyl_sptr->get_max_view_num(); - view += this->num_subsets) - { - const ViewSegmentNumbers view_segment_num(view, segment_num); - - if (! this->projector_pair_ptr->get_symmetries_used()->is_basic(view_segment_num)) - continue; - this->add_view_seg_to_sensitivity(sensitivity, view_segment_num); - } + for (int segment_num = min_segment_num; segment_num <= max_segment_num; ++segment_num) + { + for (int view = proj_data_info_cyl_sptr->get_min_view_num() + subset_num; + view <= proj_data_info_cyl_sptr->get_max_view_num(); + view += this->num_subsets) + { + const ViewSegmentNumbers view_segment_num(view, segment_num); + + if (! this->projector_pair_ptr->get_symmetries_used()->is_basic(view_segment_num)) + continue; + this->add_view_seg_to_sensitivity(sensitivity, view_segment_num, timing_pos_num); + } + } } } template void PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin:: -add_view_seg_to_sensitivity(TargetT& sensitivity, const ViewSegmentNumbers& view_seg_nums) const +add_view_seg_to_sensitivity(TargetT& sensitivity, const ViewSegmentNumbers& view_seg_nums, const int timing_pos_num) const { shared_ptr symmetries_used (this->projector_pair_ptr->get_symmetries_used()->clone()); RelatedViewgrams viewgrams = - proj_data_info_cyl_sptr->get_empty_related_viewgrams(view_seg_nums,symmetries_used); + proj_data_info_cyl_sptr->get_empty_related_viewgrams(view_seg_nums,symmetries_used, false, timing_pos_num); viewgrams.fill(1.F); // find efficiencies @@ -401,26 +429,26 @@ PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin (*proj_data_info_cyl_sptr, - 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) - ); + 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) + ); -} - -template -void -PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin:: -compute_sub_gradient_without_penalty_plus_sensitivity(TargetT& gradient, - const TargetT ¤t_estimate, - const int subset_num) -{ +} + +template +void +PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin:: +compute_sub_gradient_without_penalty_plus_sensitivity(TargetT& gradient, + const TargetT ¤t_estimate, + const int subset_num) +{ assert(subset_num>=0); assert(subset_numnum_subsets); @@ -446,13 +474,35 @@ compute_sub_gradient_without_penalty_plus_sensitivity(TargetT& gradient, // TODO implement function that will do this for a random time this->list_mode_data_sptr->reset(); double current_time = 0.; - ProjMatrixElemsForOneBin proj_matrix_row; + ProjMatrixElemsForOneBin proj_matrix_row; - shared_ptr record_sptr = this->list_mode_data_sptr->get_empty_record_sptr(); - CListRecord& record = *record_sptr; + shared_ptr record_sptr = this->list_mode_data_sptr->get_empty_record_sptr(); + CListRecord& record = *record_sptr; unsigned long int more_events = - this->do_time_frame? 1 : this->num_events_to_store; + this->do_time_frame? 1 : (this->num_events_to_store / this->num_subsets); + + if (use_projectors) + { + + PresmoothingForwardProjectorByBin* forward= + dynamic_cast (this->projector_pair_ptr->get_forward_projector_sptr().get()); + + if (!is_null_ptr(forward)) + forward->update_filtered_density_image(current_estimate); + + PostsmoothingBackProjectorByBin* back= + dynamic_cast (this->projector_pair_ptr->get_back_projector_sptr().get()); + + if (!is_null_ptr(back)) + back->init_filtered_density_image(gradient); + + if (this->use_tof) + { + projector_pair_ptr->set_tof_data(&lor_points.p1(), &lor_points.p2()); + } + + } while (more_events)//this->list_mode_data_sptr->get_next_record(record) == Succeeded::yes) { @@ -472,12 +522,12 @@ compute_sub_gradient_without_penalty_plus_sensitivity(TargetT& gradient, continue; } - if (record.is_event() && record.event().is_prompt()) - { + if (record.is_event() && record.event().is_prompt()) + { measured_bin.set_bin_value(1.0f); // this->use_tof ? record.full_event(measured_bin, *proj_data_info_cyl_sptr): - record.event().get_bin(measured_bin, *proj_data_info_cyl_sptr); + record.event().get_bin(measured_bin, *proj_data_info_cyl_sptr); // In theory we have already done all these checks so we can // remove this if statement. @@ -497,6 +547,7 @@ compute_sub_gradient_without_penalty_plus_sensitivity(TargetT& gradient, measured_bin.set_bin_value(1.0f); // If more than 1 subsets, check if the current bin belongs to // the current. + if (this->num_subsets > 1) { Bin basic_bin = measured_bin; @@ -509,46 +560,93 @@ compute_sub_gradient_without_penalty_plus_sensitivity(TargetT& gradient, } } - if(this->use_tof) - { - lor_points = record.event().get_LOR(); - this->PM_sptr->get_proj_matrix_elems_for_one_bin_with_tof(proj_matrix_row, - measured_bin, - lor_points.p1(), lor_points.p2()); - } - else - this->PM_sptr->get_proj_matrix_elems_for_one_bin(proj_matrix_row, measured_bin); - - //in_the_range++; - fwd_bin.set_bin_value(0.0f); - proj_matrix_row.forward_project(fwd_bin,current_estimate); - // additive sinogram - if (!is_null_ptr(this->additive_proj_data_sptr)) - { - float add_value = this->additive_proj_data_sptr->get_bin_value(measured_bin); - float value= fwd_bin.get_bin_value()+add_value; - fwd_bin.set_bin_value(value); - } - float measured_div_fwd = 0.0f; - - if(!this->do_time_frame) - more_events -=1 ; - - num_stored_events += 1; + if(!use_projectors) + { + if(this->use_tof) + { + lor_points = record.event().get_LOR(); + this->PM_sptr->get_proj_matrix_elems_for_one_bin_with_tof(proj_matrix_row, + measured_bin, + lor_points.p1(), lor_points.p2()); + } + else + this->PM_sptr->get_proj_matrix_elems_for_one_bin(proj_matrix_row, measured_bin); + + //in_the_range++; + fwd_bin.set_bin_value(0.0f); + proj_matrix_row.forward_project(fwd_bin,current_estimate); + // additive sinogram + if (!is_null_ptr(this->additive_proj_data_sptr)) + { + float add_value = this->additive_proj_data_sptr->get_bin_value(measured_bin); + float value= fwd_bin.get_bin_value()+add_value; + fwd_bin.set_bin_value(value); + } + float measured_div_fwd = 0.0f; + + if(!this->do_time_frame) + more_events -=1 ; + + num_stored_events += 1; + + if (num_stored_events%200000L==0) + info( boost::format("Stored Events: %1% ") % num_stored_events); + + if ( measured_bin.get_bin_value() <= max_quotient *fwd_bin.get_bin_value()) + measured_div_fwd = 1.0f /fwd_bin.get_bin_value(); + else + continue; + + measured_bin.set_bin_value(measured_div_fwd); + proj_matrix_row.back_project(gradient, measured_bin); + } + else + { + measured_bin.set_bin_value(0.0f); + + + if(this->use_tof) + lor_points = record.event().get_LOR(); + + projector_pair_ptr->get_forward_projector_sptr()->forward_project(measured_bin, + current_estimate); + + if (!is_null_ptr(this->additive_proj_data_sptr)) + { + float add_value = this->additive_proj_data_sptr->get_bin_value(measured_bin); + float value= measured_bin.get_bin_value()+add_value; + measured_bin.set_bin_value(value); + } + + float measured_div_fwd = 0.0f; + + if(!this->do_time_frame) + more_events -=1 ; + + num_stored_events += 1; + + if (num_stored_events%200000L==0) + info( boost::format("Stored Events: %1% ") % num_stored_events); + + if ( measured_bin.get_bin_value() <= max_quotient *measured_bin.get_bin_value()) + measured_div_fwd = 1.0f /measured_bin.get_bin_value(); + else + continue; + + measured_bin.set_bin_value(measured_div_fwd); + projector_pair_ptr->get_back_projector_sptr()->back_project(gradient, measured_bin); + } + } + } + if (use_projectors) + { + PostsmoothingBackProjectorByBin* back= + dynamic_cast (this->projector_pair_ptr->get_back_projector_sptr().get()); - if (num_stored_events%200000L==0) - info( boost::format("Stored Events: %1% ") % num_stored_events); + if (!is_null_ptr(back)) + back->update_filtered_density_image(gradient); - if ( measured_bin.get_bin_value() <= max_quotient *fwd_bin.get_bin_value()) - measured_div_fwd = 1.0f /fwd_bin.get_bin_value(); - else - continue; - - measured_bin.set_bin_value(measured_div_fwd); - proj_matrix_row.back_project(gradient, measured_bin); - - } - } + } info(boost::format("Number of used events: %1%") % num_stored_events); } diff --git a/src/recon_buildblock/PostsmoothingBackProjectorByBin.cxx b/src/recon_buildblock/PostsmoothingBackProjectorByBin.cxx index a2695c91f3..696f1f8229 100644 --- a/src/recon_buildblock/PostsmoothingBackProjectorByBin.cxx +++ b/src/recon_buildblock/PostsmoothingBackProjectorByBin.cxx @@ -71,6 +71,28 @@ PostsmoothingBackProjectorByBin:: set_defaults(); } +void PostsmoothingBackProjectorByBin:: +update_filtered_density_image(DiscretisedDensity<3, float> &density) +{ + image_processor_ptr->apply(*filtered_density_sptr); + density += *filtered_density_sptr; + filtered_density_sptr->fill(0.f); +} + +BackProjectorByBin* +PostsmoothingBackProjectorByBin::get_original_back_projector_ptr() const +{ + return original_back_projector_ptr.get(); +} + +void PostsmoothingBackProjectorByBin:: +init_filtered_density_image(DiscretisedDensity<3, float> &density) +{ + filtered_density_sptr.reset( + density.get_empty_discretised_density()); + assert(density.get_index_range() == filtered_density_sptr->get_index_range()); +} + PostsmoothingBackProjectorByBin:: PostsmoothingBackProjectorByBin( const shared_ptr& original_back_projector_ptr, @@ -126,7 +148,22 @@ actual_back_project(DiscretisedDensity<3,float>& density, min_tangential_pos_num, max_tangential_pos_num); } } - +void +PostsmoothingBackProjectorByBin:: +actual_back_project(DiscretisedDensity<3, float> &density, + const Bin& bin) +{ + if (!is_null_ptr(image_processor_ptr)) + { +// + original_back_projector_ptr->back_project(*filtered_density_sptr, bin); + + } + else + { + original_back_projector_ptr->back_project(density, bin); + } +} END_NAMESPACE_STIR diff --git a/src/recon_buildblock/PresmoothingForwardProjectorByBin.cxx b/src/recon_buildblock/PresmoothingForwardProjectorByBin.cxx index 29cbd64c0a..97a6f19c1a 100644 --- a/src/recon_buildblock/PresmoothingForwardProjectorByBin.cxx +++ b/src/recon_buildblock/PresmoothingForwardProjectorByBin.cxx @@ -94,6 +94,12 @@ set_up(const shared_ptr& proj_data_info_ptr, image_processor_ptr->set_up(*image_info_ptr); } +ForwardProjectorByBin* +PresmoothingForwardProjectorByBin::get_original_forward_projector_ptr() const +{ + return original_forward_projector_ptr.get(); +} + const DataSymmetriesForViewSegmentNumbers * PresmoothingForwardProjectorByBin:: get_symmetries_used() const @@ -101,6 +107,14 @@ get_symmetries_used() const return original_forward_projector_ptr->get_symmetries_used(); } +void PresmoothingForwardProjectorByBin:: +update_filtered_density_image(const DiscretisedDensity<3,float>& density) +{ + filtered_density_sptr.reset(density.get_empty_discretised_density()); + image_processor_ptr->apply(*filtered_density_sptr, density); + assert(density.get_index_range() == filtered_density_sptr->get_index_range()); +} + void PresmoothingForwardProjectorByBin:: actual_forward_project(RelatedViewgrams& viewgrams, @@ -125,6 +139,19 @@ actual_forward_project(RelatedViewgrams& viewgrams, } } - +void +PresmoothingForwardProjectorByBin:: +actual_forward_project(Bin& this_bin, + const DiscretisedDensity<3,float>& density) +{ + if (!is_null_ptr(image_processor_ptr)) + { + original_forward_projector_ptr->forward_project(this_bin, *filtered_density_sptr); + } + else + { + original_forward_projector_ptr->forward_project(this_bin, density); + } +} END_NAMESPACE_STIR diff --git a/src/recon_buildblock/ProjMatrixByBin.cxx b/src/recon_buildblock/ProjMatrixByBin.cxx index 951859650b..d25ef353f2 100644 --- a/src/recon_buildblock/ProjMatrixByBin.cxx +++ b/src/recon_buildblock/ProjMatrixByBin.cxx @@ -146,6 +146,9 @@ set_up( const int min_segment_num = proj_data_info_sptr->get_min_segment_num(); const int max_segment_num = proj_data_info_sptr->get_max_segment_num(); + if (proj_data_info_sptr->is_tof_data()) + enable_tof(proj_data_info_sptr,true); + this->cache_collection.recycle(); this->cache_collection.resize(min_view_num, max_view_num); #ifdef STIR_OPENMP diff --git a/src/recon_buildblock/ProjectorByBinPair.cxx b/src/recon_buildblock/ProjectorByBinPair.cxx index 106152a2cf..fed8902369 100644 --- a/src/recon_buildblock/ProjectorByBinPair.cxx +++ b/src/recon_buildblock/ProjectorByBinPair.cxx @@ -29,6 +29,12 @@ #include "stir/recon_buildblock/ProjectorByBinPair.h" #include "stir/Succeeded.h" +#include "stir/recon_buildblock/PresmoothingForwardProjectorByBin.h" +#include "stir/recon_buildblock/PostsmoothingBackProjectorByBin.h" +#include "stir/recon_buildblock/ForwardProjectorByBinUsingProjMatrixByBin.h" +#include "stir/recon_buildblock/BackProjectorByBinUsingProjMatrixByBin.h" +#include "stir/is_null_ptr.h" + START_NAMESPACE_STIR @@ -50,6 +56,175 @@ set_up(const shared_ptr& proj_data_info_ptr, return Succeeded::yes; } +void +ProjectorByBinPair:: +enable_tof(const shared_ptr& _proj_data_info_sptr, const bool v) +{ + // Check if it is PresmoothingForwardProjectorByBin + PresmoothingForwardProjectorByBin* forward= + dynamic_cast (forward_projector_sptr.get()); + + if (!is_null_ptr(forward)) + { + ForwardProjectorByBinUsingProjMatrixByBin* original_forward = + dynamic_cast (forward->get_original_forward_projector_ptr()); + + if (is_null_ptr(original_forward)) + error("Currently only ForwardProjectorByBinUsingProjMatrixByBin supports TOF reconstruction. Abort."); + else + original_forward->enable_tof(_proj_data_info_sptr, v); + } + else // if is ForwardProjectorByBinUsingProjMatrixByBin directly. + { + ForwardProjectorByBinUsingProjMatrixByBin* original_forward = + dynamic_cast (forward_projector_sptr.get()); + + if (is_null_ptr(original_forward)) + error("Currently only ForwardProjectorByBinUsingProjMatrixByBin supports TOF reconstruction. Abort."); + else + original_forward->enable_tof(_proj_data_info_sptr, v); + } + +// // Check if it is PostSmoothingBackProjectorByBin is used. +// PostsmoothingBackProjectorByBin* back= +// dynamic_cast (back_projector_sptr.get()); + +// if (!is_null_ptr(back)) +// { +// BackProjectorByBinUsingProjMatrixByBin* original_back = +// dynamic_cast (back->get_original_back_projector_ptr()); + +// if (is_null_ptr(original_back)) +// error("Currently only ForwardProjectorByBinUsingProjMatrixByBin supports TOF reconstruction. Abort."); +// else +// original_back->enable_tof(_proj_data_info_sptr, v); +// } +// else // if is ForwardProjectorByBinUsingProjMatrixByBin directly. +// { +// BackProjectorByBinUsingProjMatrixByBin* original_back = +// dynamic_cast (back_projector_sptr.get()); + +// if (is_null_ptr(original_back)) +// error("Currently only BackProjectorByBinUsingProjMatrixByBin supports TOF reconstruction. Abort."); +// else +// original_back->enable_tof(_proj_data_info_sptr, v); +// } + +} + +void +ProjectorByBinPair:: +set_tof_data(const CartesianCoordinate3D* _point1, + const CartesianCoordinate3D* _point2) +{ + // Check if it is PresmoothingForwardProjectorByBin + PresmoothingForwardProjectorByBin* forward= + dynamic_cast (forward_projector_sptr.get()); + + if (!is_null_ptr(forward)) + { + ForwardProjectorByBinUsingProjMatrixByBin* original_forward = + dynamic_cast (forward->get_original_forward_projector_ptr()); + + if (is_null_ptr(original_forward)) + error("Currently only ForwardProjectorByBinUsingProjMatrixByBin supports TOF reconstruction. Abort."); + else + { + original_forward->set_tof_data(_point1, _point2); + + PostsmoothingBackProjectorByBin* back= + dynamic_cast (back_projector_sptr.get()); + + if (!is_null_ptr(back)) + { + BackProjectorByBinUsingProjMatrixByBin* original_back = + dynamic_cast (back->get_original_back_projector_ptr()); + + if (is_null_ptr(original_back)) + error("Currently only ForwardProjectorByBinUsingProjMatrixByBin supports TOF reconstruction. Abort."); + else + original_back->enable_tof(original_forward->get_tof_row()); + } + else // if is ForwardProjectorByBinUsingProjMatrixByBin directly. + { + BackProjectorByBinUsingProjMatrixByBin* original_back = + dynamic_cast (back_projector_sptr.get()); + + if (is_null_ptr(original_back)) + error("Currently only BackProjectorByBinUsingProjMatrixByBin supports TOF reconstruction. Abort."); + else + original_back->enable_tof(original_forward->get_tof_row()); + } + } + } + else // if is ForwardProjectorByBinUsingProjMatrixByBin directly. + { + ForwardProjectorByBinUsingProjMatrixByBin* original_forward = + dynamic_cast (forward_projector_sptr.get()); + + if (is_null_ptr(original_forward)) + error("Currently only ForwardProjectorByBinUsingProjMatrixByBin supports TOF reconstruction. Abort."); + else + { + original_forward->set_tof_data(_point1, _point2); + + PostsmoothingBackProjectorByBin* back= + dynamic_cast (back_projector_sptr.get()); + + if (!is_null_ptr(back)) + { + BackProjectorByBinUsingProjMatrixByBin* original_back = + dynamic_cast (back->get_original_back_projector_ptr()); + + if (is_null_ptr(original_back)) + error("Currently only ForwardProjectorByBinUsingProjMatrixByBin supports TOF reconstruction. Abort."); + else + original_back->enable_tof(original_forward->get_tof_row()); + } + else // if is ForwardProjectorByBinUsingProjMatrixByBin directly. + { + BackProjectorByBinUsingProjMatrixByBin* original_back = + dynamic_cast (back_projector_sptr.get()); + + if (is_null_ptr(original_back)) + error("Currently only BackProjectorByBinUsingProjMatrixByBin supports TOF reconstruction. Abort."); + else + original_back->enable_tof(original_forward->get_tof_row()); + } + } + } +} + +ProjMatrixElemsForOneBin* +ProjectorByBinPair:: +get_current_tof_row() const +{ + // Check if it is PresmoothingForwardProjectorByBin + PresmoothingForwardProjectorByBin* forward= + dynamic_cast (forward_projector_sptr.get()); + + if (!is_null_ptr(forward)) + { + ForwardProjectorByBinUsingProjMatrixByBin* original_forward = + dynamic_cast (forward->get_original_forward_projector_ptr()); + + if (is_null_ptr(original_forward)) + error("Currently only ForwardProjectorByBinUsingProjMatrixByBin supports TOF reconstruction. Abort."); + else + return original_forward->get_tof_row(); + } + else // if is ForwardProjectorByBinUsingProjMatrixByBin directly. + { + ForwardProjectorByBinUsingProjMatrixByBin* original_forward = + dynamic_cast (forward_projector_sptr.get()); + + if (is_null_ptr(original_forward)) + error("Currently only ForwardProjectorByBinUsingProjMatrixByBin supports TOF reconstruction. Abort."); + else + return original_forward->get_tof_row(); + } +} + //ForwardProjectorByBin const * const shared_ptr ProjectorByBinPair:: diff --git a/src/recon_buildblock/TrivialDataSymmetriesForBins.cxx b/src/recon_buildblock/TrivialDataSymmetriesForBins.cxx index a394fe7973..258451a323 100644 --- a/src/recon_buildblock/TrivialDataSymmetriesForBins.cxx +++ b/src/recon_buildblock/TrivialDataSymmetriesForBins.cxx @@ -99,12 +99,15 @@ void TrivialDataSymmetriesForBins:: get_related_bins(vector& rel_b, const Bin& b, const int min_axial_pos_num, const int max_axial_pos_num, - const int min_tangential_pos_num, const int max_tangential_pos_num) const + const int min_tangential_pos_num, const int max_tangential_pos_num, + const int min_timing_pos_num, const int max_timing_pos_num) const { if (b.axial_pos_num() >= min_axial_pos_num && b.axial_pos_num() <= max_axial_pos_num && b.tangential_pos_num() >= min_tangential_pos_num && - b.tangential_pos_num() <= max_tangential_pos_num) + b.tangential_pos_num() <= max_tangential_pos_num && + b.timing_pos_num() >= min_timing_pos_num && + b.timing_pos_num() <= max_timing_pos_num) { rel_b.resize(1); rel_b[0] = b; diff --git a/src/recon_test/test_PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx b/src/recon_test/test_PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx index 88b0321c14..9aa649eb5b 100644 --- a/src/recon_test/test_PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx +++ b/src/recon_test/test_PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx @@ -187,17 +187,22 @@ construct_input_data(shared_ptr& density_sptr) seg_num<=proj_data_sptr->get_max_segment_num(); ++seg_num) { - SegmentByView segment = proj_data_sptr->get_empty_segment_by_view(seg_num); - // fill in some crazy values - float value=0; - for (SegmentByView::full_iterator iter = segment.begin_all(); - iter != segment.end_all(); - ++iter) - { - value = float(fabs((seg_num+.1)*value - 5)); // needs to be positive for Poisson - *iter = value; - } - proj_data_sptr->set_segment(segment); + for (int timing_pos_num = proj_data_sptr->get_min_tof_pos_num(); + timing_pos_num <= proj_data_sptr->get_max_tof_pos_num(); + ++timing_pos_num) + { + SegmentByView segment = proj_data_sptr->get_empty_segment_by_view(seg_num); + // fill in some crazy values + float value=0; + for (SegmentByView::full_iterator iter = segment.begin_all(); + iter != segment.end_all(); + ++iter) + { + value = float(fabs((seg_num+.1)*value - 5)); // needs to be positive for Poisson + *iter = value; + } + proj_data_sptr->set_segment(segment); + } } } else @@ -254,17 +259,22 @@ construct_input_data(shared_ptr& density_sptr) seg_num<=proj_data_sptr->get_max_segment_num(); ++seg_num) { - SegmentByView segment = proj_data_sptr->get_empty_segment_by_view(seg_num); - // fill in some crazy values - float value =0; - for (SegmentByView::full_iterator iter = segment.begin_all(); - iter != segment.end_all(); - ++iter) - { - value = float(fabs(seg_num*value - .2)); // needs to be positive for Poisson - *iter = value; - } - mult_proj_data_sptr->set_segment(segment); + for (int timing_pos_num = proj_data_sptr->get_min_tof_pos_num(); + timing_pos_num <= proj_data_sptr->get_max_tof_pos_num(); + ++timing_pos_num) + { + SegmentByView segment = proj_data_sptr->get_empty_segment_by_view(seg_num, false, timing_pos_num); + // fill in some crazy values + float value =0; + for (SegmentByView::full_iterator iter = segment.begin_all(); + iter != segment.end_all(); + ++iter) + { + value = float(fabs(seg_num*value - .2)); // needs to be positive for Poisson + *iter = value; + } + mult_proj_data_sptr->set_segment(segment); + } } bin_norm_sptr.reset(new BinNormalisationFromProjData(mult_proj_data_sptr)); } @@ -278,17 +288,22 @@ proj_data_sptr->get_proj_data_info_ptr()->create_shared_clone())); seg_num<=proj_data_sptr->get_max_segment_num(); ++seg_num) { - SegmentByView segment = proj_data_sptr->get_empty_segment_by_view(seg_num); - // fill in some crazy values - float value =0; - for (SegmentByView::full_iterator iter = segment.begin_all(); - iter != segment.end_all(); - ++iter) - { - value = float(fabs(seg_num*value - .3)); // needs to be positive for Poisson - *iter = value; - } - add_proj_data_sptr->set_segment(segment); + for (int timing_pos_num = proj_data_sptr->get_min_tof_pos_num(); + timing_pos_num <= proj_data_sptr->get_max_tof_pos_num(); + ++timing_pos_num) + { + SegmentByView segment = proj_data_sptr->get_empty_segment_by_view(seg_num, false, timing_pos_num); + // fill in some crazy values + float value =0; + for (SegmentByView::full_iterator iter = segment.begin_all(); + iter != segment.end_all(); + ++iter) + { + value = float(fabs(seg_num*value - .3)); // needs to be positive for Poisson + *iter = value; + } + add_proj_data_sptr->set_segment(segment); + } } } diff --git a/src/test/test_proj_data_in_memory.cxx b/src/test/test_proj_data_in_memory.cxx index 9f906c50af..024885b394 100644 --- a/src/test/test_proj_data_in_memory.cxx +++ b/src/test/test_proj_data_in_memory.cxx @@ -47,11 +47,22 @@ class ProjDataInMemoryTests: public RunTests { public: void run_tests(); + void run_tests_no_tof(); + void run_tests_tof(); }; + void ProjDataInMemoryTests:: run_tests() +{ + this->run_tests_no_tof(); + this->run_tests_tof(); +} + +void +ProjDataInMemoryTests:: +run_tests_no_tof() { std::cerr << "-------- Testing ProjDataInMemory --------\n"; shared_ptr scanner_sptr(new Scanner(Scanner::E953)); @@ -133,6 +144,134 @@ run_tests() // ); +// // construct without filling +// ProjDataInMemory proj_data2(exam_info_sptr, proj_data_info_sptr2, false); +// // this should call error, so we'll catch it +// try +// { +// proj_data2.fill(proj_data); +// check(false, "test fill wtih too small proj_data should have thrown"); +// } +// catch (...) +// { +// // ok +// } + } +} + +void +ProjDataInMemoryTests:: +run_tests_tof() +{ + std::cerr << "-------- Testing ProjDataInMemory --------\n"; + shared_ptr scanner_sptr(new Scanner(Scanner::PETMR_Signa)); + + shared_ptr proj_data_info_sptr + (ProjDataInfo::ProjDataInfoCTI(scanner_sptr, + /*span*/1, 10,/*views*/ 96, /*tang_pos*/128, /*arc_corrected*/ true, 11) + ); + shared_ptr exam_info_sptr(new ExamInfo); + + + // construct with filling to 0 + ProjDataInMemory proj_data(exam_info_sptr, proj_data_info_sptr); + { + check_if_equal( proj_data.get_sinogram(0,0,false,-2).get_timing_pos_num(), + -2, + "test get_sinogram timing position index"); + Sinogram sinogram = proj_data.get_sinogram(0,0,false,-2); + check_if_equal(sinogram.get_timing_pos_num(), + -2, + "test constructor and get_sinogram timing position index"); + check_if_equal(sinogram.find_min(), + 0.F, + "test constructor and get_sinogram"); + } + + const float value = 1.2F; + // test fill(float) + { + proj_data.fill(value); + Viewgram viewgram = proj_data.get_viewgram(0,0,false,-2); + check_if_equal(viewgram.get_timing_pos_num(), + -2, + "test constructor and get_viewgram timing position index"); + check_if_equal(viewgram.find_min(), + value, + "test fill(float) and get_viewgram"); + } + + // test set_viewgram + { + Viewgram viewgram = proj_data.get_empty_viewgram(1,1,false,-2); + viewgram.fill(value*2); + check(proj_data.set_viewgram(viewgram) == Succeeded::yes, + "test set_viewgram succeeded"); + + Viewgram viewgram2 = proj_data.get_viewgram(1,1,false,-2); + check_if_equal(viewgram2.get_timing_pos_num(), + -2, + "test set/get_viewgram timing position index"); + check_if_equal(viewgram2.find_min(), + viewgram.find_min(), + "test set/get_viewgram"); + } + // test set_segment_by_view + { + SegmentByView segment = proj_data.get_empty_segment_by_view(1,false,-2); + segment.fill(value*2); + check(proj_data.set_segment(segment) == Succeeded::yes, + "test set_segment succeeded"); + + SegmentByView segment2 = proj_data.get_segment_by_view(1,-2); + check_if_equal(segment2.get_timing_pos_num(), + -2, + "test set/get_segment_by_view timing position index"); + check_if_equal(segment2.find_min(), + segment.find_min(), + "test set/get_segment_by_view"); + } + // test making a copy + { + ProjDataInMemory proj_data2(proj_data); + check_if_equal(proj_data2.get_viewgram(0,0,false,-2).find_max(), + proj_data.get_viewgram(0,0,false,-2).find_max(), + "test 1 for copy-constructor and get_viewgram"); + check_if_equal(proj_data2.get_viewgram(1,1,false,-2).find_max(), + proj_data.get_viewgram(1,1,false,-2).find_max(), + "test 1 for copy-constructor and get_viewgram"); + check_if_equal(proj_data2.get_viewgram(1,1,false,-2).get_timing_pos_num(), + -2, + "test 2 for copy-constructor and get_viewgram"); + } + + // test fill with larger input + { + shared_ptr proj_data_info_sptr2 + (ProjDataInfo::ProjDataInfoCTI(scanner_sptr, + /*span*/1, 8,/*views*/ 96, /*tang_pos*/128, /*arc_corrected*/ true) + ); + + + // construct without filling + ProjDataInMemory proj_data2(exam_info_sptr, proj_data_info_sptr2, false); +// proj_data2.fill(proj_data); +// check_if_equal(proj_data2.get_viewgram(0,0).find_max(), +// proj_data.get_viewgram(0,0).find_max(), +// "test 1 for copy-constructor and get_viewgram"); +// check_if_equal(proj_data2.get_viewgram(1,1).find_max(), +// proj_data.get_viewgram(1,1).find_max(), +// "test 1 for copy-constructor and get_viewgram"); + } + + // test fill with smaller input + { +// shared_ptr proj_data_info_sptr2 +// (ProjDataInfo::ProjDataInfoCTI(scanner_sptr, +// /*span*/1, 12,/*views*/ 96, /*tang_pos*/128, /*arc_corrected*/ true) +// ); + + // // construct without filling // ProjDataInMemory proj_data2(exam_info_sptr, proj_data_info_sptr2, false); // // this should call error, so we'll catch it diff --git a/src/utilities/forward_project.cxx b/src/utilities/forward_project.cxx index b681b8c6ef..b9b5eb312b 100644 --- a/src/utilities/forward_project.cxx +++ b/src/utilities/forward_project.cxx @@ -109,7 +109,7 @@ main (int argc, char * argv[]) ProjDataInterfile output_projdata(template_proj_data_sptr->get_exam_info_sptr(), template_proj_data_sptr->get_proj_data_info_ptr()->create_shared_clone(), output_filename); - + forw_projector_sptr->forward_project(output_projdata, *image_density_sptr); return EXIT_SUCCESS; diff --git a/src/utilities/poisson_noise.cxx b/src/utilities/poisson_noise.cxx index 10bea5d513..094bdef660 100644 --- a/src/utilities/poisson_noise.cxx +++ b/src/utilities/poisson_noise.cxx @@ -167,26 +167,31 @@ poisson_noise(ProjData& output_projdata, seg<=input_projdata.get_max_segment_num(); seg++) { - SegmentByView seg_input= input_projdata.get_segment_by_view(seg); - SegmentByView seg_output= - output_projdata.get_empty_segment_by_view(seg,false); - - cerr << "Segment " << seg << endl; - - for(int view=seg_input.get_min_view_num();view<=seg_input.get_max_view_num();view++) - for(int ax_pos=seg_input.get_min_axial_pos_num();ax_pos<=seg_input.get_max_axial_pos_num();ax_pos++) - for(int tang_pos=seg_input.get_min_tangential_pos_num();tang_pos<=seg_input.get_max_tangential_pos_num();tang_pos++) - { - const float bin = seg_input[view][ax_pos][tang_pos]; - const int random_poisson = generate_poisson_random(bin*scaling_factor); - seg_output[view][ax_pos][tang_pos] = - preserve_mean ? - random_poisson / scaling_factor - : - static_cast(random_poisson); - } - if (output_projdata.set_segment(seg_output) == Succeeded::no) - exit(EXIT_FAILURE); + for (int timing_pos_num = input_projdata.get_min_tof_pos_num(); + timing_pos_num <= input_projdata.get_max_tof_pos_num(); + ++timing_pos_num) + { + SegmentByView seg_input= input_projdata.get_segment_by_view(seg, timing_pos_num); + SegmentByView seg_output= + output_projdata.get_empty_segment_by_view(seg,false, timing_pos_num); + + cerr << "Segment " << seg << endl; + + for(int view=seg_input.get_min_view_num();view<=seg_input.get_max_view_num();view++) + for(int ax_pos=seg_input.get_min_axial_pos_num();ax_pos<=seg_input.get_max_axial_pos_num();ax_pos++) + for(int tang_pos=seg_input.get_min_tangential_pos_num();tang_pos<=seg_input.get_max_tangential_pos_num();tang_pos++) + { + const float bin = seg_input[view][ax_pos][tang_pos]; + const int random_poisson = generate_poisson_random(bin*scaling_factor); + seg_output[view][ax_pos][tang_pos] = + preserve_mean ? + random_poisson / scaling_factor + : + static_cast(random_poisson); + } + if (output_projdata.set_segment(seg_output) == Succeeded::no) + exit(EXIT_FAILURE); + } } } diff --git a/src/utilities/stir_math.cxx b/src/utilities/stir_math.cxx index d342c8a148..522d086610 100644 --- a/src/utilities/stir_math.cxx +++ b/src/utilities/stir_math.cxx @@ -568,26 +568,32 @@ main(int argc, char **argv) segment_num <= out_proj_data_ptr->get_max_segment_num(); ++segment_num) { - if (verbose) - cout << "Processing segment num " << segment_num << " for all files" << endl; - SegmentByView segment_by_view = - (*all_proj_data[0]).get_segment_by_view(segment_num); - if (!no_math_on_data && !except_first ) - in_place_apply_function(segment_by_view, pow_times_add_object); - for (int i=1; i current_segment_by_view = - (*all_proj_data[i]).get_segment_by_view(segment_num); - if (!no_math_on_data) - in_place_apply_function(current_segment_by_view, pow_times_add_object); - if(do_add) - segment_by_view += current_segment_by_view; - else - segment_by_view *= current_segment_by_view; - } - - if (!(out_proj_data_ptr->set_segment(segment_by_view) == Succeeded::yes)) - warning("Error set_segment %d\n", segment_num); + if (verbose) + cout << "Processing segment num " << segment_num << " for all files" << endl; + + for (int k=out_proj_data_ptr->get_min_tof_pos_num(); + k<=out_proj_data_ptr->get_max_tof_pos_num(); + ++k) + { + SegmentByView segment_by_view = + (*all_proj_data[0]).get_segment_by_view(segment_num,k); + if (!no_math_on_data && !except_first ) + in_place_apply_function(segment_by_view, pow_times_add_object); + for (int i=1; i current_segment_by_view = + (*all_proj_data[i]).get_segment_by_view(segment_num,k); + if (!no_math_on_data) + in_place_apply_function(current_segment_by_view, pow_times_add_object); + if(do_add) + segment_by_view += current_segment_by_view; + else + segment_by_view *= current_segment_by_view; + } + + if (!(out_proj_data_ptr->set_segment(segment_by_view) == Succeeded::yes)) + warning("Error set_segment %d\n", segment_num); + } } } return EXIT_SUCCESS; From dcd3194bfc549e39859ab65084c0b4bc6f0472fc Mon Sep 17 00:00:00 2001 From: Elise Date: Wed, 22 Mar 2017 14:44:51 +0000 Subject: [PATCH 066/509] Fourier Rebinning: throws error for TOF data --- src/recon_buildblock/FourierRebinning.cxx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/recon_buildblock/FourierRebinning.cxx b/src/recon_buildblock/FourierRebinning.cxx index 634acc6118..1f6e69a02e 100644 --- a/src/recon_buildblock/FourierRebinning.cxx +++ b/src/recon_buildblock/FourierRebinning.cxx @@ -121,6 +121,12 @@ FourierRebinning:: rebin() { + if (proj_data_sptr->get_proj_data_info_ptr()->is_tof_data()) + { + error("FORE Rebinning :: Not supported for TOF data. Aborted"); + return Succeeded::no; + } + start_timers(); CPUTimer timer; timer.start(); From 6f61824efc83f046aee521ed9e1bd66644e8383e Mon Sep 17 00:00:00 2001 From: Elise Date: Fri, 31 Mar 2017 15:02:00 +0100 Subject: [PATCH 067/509] Fixes for TOF OSEM reconstructions + modified some test and utility functions for TOF --- src/buildblock/ArcCorrection.cxx | 37 +-- .../ProjDataInfoCylindricalArcCorr.cxx | 3 +- src/buildblock/RelatedViewgrams.cxx | 2 +- src/buildblock/SSRB.cxx | 3 + src/buildblock/zoom.cxx | 9 +- src/include/stir/Bin.inl | 2 +- .../DataSymmetriesForBins.inl | 4 +- ...elihoodWithLinearModelForMeanAndProjData.h | 8 +- .../stir/recon_buildblock/distributable.h | 3 +- .../distributableMPICacheEnabled.h | 3 +- src/recon_buildblock/BackProjectorByBin.cxx | 2 +- .../BackProjectorByBinUsingInterpolation.cxx | 4 +- ...ojectorByBinUsingSquareProjMatrixByBin.cxx | 3 +- .../BinNormalisationFromProjData.cxx | 12 +- .../ForwardProjectorByBinUsingRayTracing.cxx | 12 +- ...ihoodWithLinearModelForMeanAndProjData.cxx | 237 ++++++++++------- .../ProjMatrixByBinUsingRayTracing.cxx | 2 + .../ProjMatrixElemsForOneBin.cxx | 4 +- src/recon_buildblock/distributable.cxx | 39 +-- .../distributableMPICacheEnabled.cxx | 116 +++++---- .../distributed_functions.cxx | 12 +- .../distributed_test_functions.cxx | 2 + src/recon_test/bcktest.cxx | 163 ++++++------ src/recon_test/fwdtest.cxx | 96 ++++--- ...ihoodWithLinearModelForMeanAndProjData.cxx | 2 +- src/test/test_ArcCorrection.cxx | 42 ++- src/utilities/correct_projdata.cxx | 246 +++++++++--------- 27 files changed, 607 insertions(+), 461 deletions(-) diff --git a/src/buildblock/ArcCorrection.cxx b/src/buildblock/ArcCorrection.cxx index 57acefe493..ac1a5322f8 100644 --- a/src/buildblock/ArcCorrection.cxx +++ b/src/buildblock/ArcCorrection.cxx @@ -251,6 +251,7 @@ do_arc_correction(Sinogram& out, const Sinogram& in) const assert(*out.get_proj_data_info_ptr() == *_arc_corr_proj_data_info_sptr); assert(out.get_axial_pos_num() == in.get_axial_pos_num()); assert(out.get_segment_num() == in.get_segment_num()); + assert(out.get_timing_pos_num() == in.get_timing_pos_num()); for (int view_num=in.get_min_view_num(); view_num<=in.get_max_view_num(); ++view_num) do_arc_correction(out[view_num], in[view_num]); } @@ -261,7 +262,8 @@ do_arc_correction(const Sinogram& in) const { Sinogram out(_arc_corr_proj_data_info_sptr, in.get_axial_pos_num(), - in.get_segment_num()); + in.get_segment_num(), + in.get_timing_pos_num()); do_arc_correction(out, in); return out; } @@ -274,6 +276,7 @@ do_arc_correction(Viewgram& out, const Viewgram& in) const assert(*out.get_proj_data_info_ptr() == *_arc_corr_proj_data_info_sptr); assert(out.get_view_num() == in.get_view_num()); assert(out.get_segment_num() == in.get_segment_num()); + assert(out.get_timing_pos_num() == in.get_timing_pos_num()); for (int axial_pos_num=in.get_min_axial_pos_num(); axial_pos_num<=in.get_max_axial_pos_num(); ++axial_pos_num) do_arc_correction(out[axial_pos_num], in[axial_pos_num]); } @@ -284,7 +287,8 @@ do_arc_correction(const Viewgram& in) const { Viewgram out(_arc_corr_proj_data_info_sptr, in.get_view_num(), - in.get_segment_num()); + in.get_segment_num(), + in.get_timing_pos_num()); do_arc_correction(out, in); return out; } @@ -305,7 +309,7 @@ do_arc_correction(const RelatedViewgrams& in) const { RelatedViewgrams out = _arc_corr_proj_data_info_sptr->get_empty_related_viewgrams(in.get_basic_view_segment_num(), - in.get_symmetries_sptr()); + in.get_symmetries_sptr(), false, in.get_basic_timing_pos_num()); do_arc_correction(out, in); return out; } @@ -317,6 +321,7 @@ do_arc_correction(SegmentBySinogram& out, const SegmentBySinogram& assert(*in.get_proj_data_info_ptr() == *_noarc_corr_proj_data_info_sptr); assert(*out.get_proj_data_info_ptr() == *_arc_corr_proj_data_info_sptr); assert(out.get_segment_num() == in.get_segment_num()); + assert(out.get_timing_pos_num() == in.get_timing_pos_num()); for (int axial_pos_num=in.get_min_axial_pos_num(); axial_pos_num<=in.get_max_axial_pos_num(); ++axial_pos_num) for (int view_num=in.get_min_view_num(); view_num<=in.get_max_view_num(); ++view_num) do_arc_correction(out[axial_pos_num][view_num], in[axial_pos_num][view_num]); @@ -327,7 +332,7 @@ ArcCorrection:: do_arc_correction(const SegmentBySinogram& in) const { SegmentBySinogram out(_arc_corr_proj_data_info_sptr, - in.get_segment_num()); + in.get_segment_num(), in.get_timing_pos_num()); do_arc_correction(out, in); return out; } @@ -340,6 +345,7 @@ do_arc_correction(SegmentByView& out, const SegmentByView& in) con assert(*in.get_proj_data_info_ptr() == *_noarc_corr_proj_data_info_sptr); assert(*out.get_proj_data_info_ptr() == *_arc_corr_proj_data_info_sptr); assert(out.get_segment_num() == in.get_segment_num()); + assert(out.get_timing_pos_num() == in.get_timing_pos_num()); for (int view_num=in.get_min_view_num(); view_num<=in.get_max_view_num(); ++view_num) for (int axial_pos_num=in.get_min_axial_pos_num(); axial_pos_num<=in.get_max_axial_pos_num(); ++axial_pos_num) do_arc_correction(out[view_num][axial_pos_num], in[view_num][axial_pos_num]); @@ -351,7 +357,7 @@ ArcCorrection:: do_arc_correction(const SegmentByView& in) const { SegmentByView out(_arc_corr_proj_data_info_sptr, - in.get_segment_num()); + in.get_segment_num(), in.get_timing_pos_num()); do_arc_correction(out, in); return out; } @@ -366,16 +372,17 @@ do_arc_correction(ProjData& out, const ProjData& in) const // Declare temporary viewgram out of the loop to avoid reallocation // There is no default constructor, so we need to set it to some junk first. Viewgram viewgram = - _arc_corr_proj_data_info_sptr->get_empty_viewgram(in.get_min_view_num(), in.get_min_segment_num()); - for (int segment_num=in.get_min_segment_num(); segment_num<=in.get_max_segment_num(); ++segment_num) - for (int view_num=in.get_min_view_num(); view_num<=in.get_max_view_num(); ++view_num) - { - viewgram = - _arc_corr_proj_data_info_sptr->get_empty_viewgram(view_num, segment_num); - do_arc_correction(viewgram, in.get_viewgram(view_num, segment_num)); - if (out.set_viewgram(viewgram) == Succeeded::no) - return Succeeded::no; - } + _arc_corr_proj_data_info_sptr->get_empty_viewgram(in.get_min_view_num(), in.get_min_segment_num(),in.get_min_tof_pos_num()); + for (int timing_pos_num = in.get_min_tof_pos_num(); timing_pos_num <= in.get_max_tof_pos_num(); ++timing_pos_num) + for (int segment_num=in.get_min_segment_num(); segment_num<=in.get_max_segment_num(); ++segment_num) + for (int view_num=in.get_min_view_num(); view_num<=in.get_max_view_num(); ++view_num) + { + viewgram = + _arc_corr_proj_data_info_sptr->get_empty_viewgram(view_num, segment_num, false, timing_pos_num); + do_arc_correction(viewgram, in.get_viewgram(view_num, segment_num, false, timing_pos_num)); + if (out.set_viewgram(viewgram) == Succeeded::no) + return Succeeded::no; + } return Succeeded::yes; } diff --git a/src/buildblock/ProjDataInfoCylindricalArcCorr.cxx b/src/buildblock/ProjDataInfoCylindricalArcCorr.cxx index ac84029f32..15cdb8ef9f 100644 --- a/src/buildblock/ProjDataInfoCylindricalArcCorr.cxx +++ b/src/buildblock/ProjDataInfoCylindricalArcCorr.cxx @@ -66,8 +66,7 @@ ProjDataInfoCylindricalArcCorr:: ProjDataInfoCylindricalArcCorr(const shared_ptr bin_size(bin_size_v) { - // If tof_mash_factor == 1 then there is only tof bin ... effectively no TOF - if (tof_mash_factor > 1) + if (scanner_ptr->is_tof_ready()) set_tof_mash_factor(tof_mash_factor); } diff --git a/src/buildblock/RelatedViewgrams.cxx b/src/buildblock/RelatedViewgrams.cxx index 7abeadcec4..129e8449f8 100644 --- a/src/buildblock/RelatedViewgrams.cxx +++ b/src/buildblock/RelatedViewgrams.cxx @@ -374,7 +374,7 @@ grow(const IndexRange<2>& range) { iter->grow(range); *iter = Viewgram(*iter, pdi_shared_ptr, - iter->get_view_num(), iter->get_segment_num()); + iter->get_view_num(), iter->get_segment_num(),iter->get_timing_pos_num()); } check_state(); diff --git a/src/buildblock/SSRB.cxx b/src/buildblock/SSRB.cxx index 3a56290429..e096516e97 100644 --- a/src/buildblock/SSRB.cxx +++ b/src/buildblock/SSRB.cxx @@ -192,6 +192,9 @@ SSRB(ProjData& out_proj_data, const bool do_norm ) { + if (in_proj_data.get_proj_data_info_ptr()->is_tof_data()) + error("SSRB for TOF data is not currently implemented.\n"); + const ProjDataInfoCylindrical * const in_proj_data_info_ptr = dynamic_cast (in_proj_data.get_proj_data_info_ptr()); diff --git a/src/buildblock/zoom.cxx b/src/buildblock/zoom.cxx index cd80afc760..0f668004bc 100644 --- a/src/buildblock/zoom.cxx +++ b/src/buildblock/zoom.cxx @@ -133,7 +133,7 @@ zoom_viewgrams (RelatedViewgrams& in_viewgrams, out_viewgrams = new_proj_data_info_arccorr_ptr-> get_empty_related_viewgrams(in_viewgrams.get_basic_view_segment_num(), - symmetries_sptr); + symmetries_sptr,false,in_viewgrams.get_basic_timing_pos_num()); { RelatedViewgrams::iterator out_iter = out_viewgrams.begin(); @@ -176,7 +176,9 @@ zoom_viewgram (Viewgram& in_view, out_view = new_proj_data_info_arccorr_ptr-> get_empty_viewgram( in_view.get_view_num(), - in_view.get_segment_num()); + in_view.get_segment_num(), + false, + in_view.get_timing_pos_num()); zoom_viewgram(out_view, in_view, x_offset_in_mm, y_offset_in_mm); @@ -196,6 +198,9 @@ zoom_viewgram (Viewgram& out_view, assert(in_view.get_proj_data_info_ptr()->get_num_segments() == out_view.get_proj_data_info_ptr()->get_num_segments()); assert(in_view.get_segment_num() == out_view.get_segment_num()); + assert(in_view.get_proj_data_info_ptr()->get_num_tof_poss() == + out_view.get_proj_data_info_ptr()->get_num_tof_poss()); + assert(in_view.get_timing_pos_num() == out_view.get_timing_pos_num()); assert(in_view.get_min_axial_pos_num() == out_view.get_min_axial_pos_num()); assert(in_view.get_max_axial_pos_num() == out_view.get_max_axial_pos_num()); diff --git a/src/include/stir/Bin.inl b/src/include/stir/Bin.inl index 9b3872d145..d476a065a7 100644 --- a/src/include/stir/Bin.inl +++ b/src/include/stir/Bin.inl @@ -137,7 +137,7 @@ Bin::operator==(const Bin& bin2) const return segment == bin2.segment && view == bin2.view && axial_pos == bin2.axial_pos && tangential_pos == bin2.tangential_pos && -// && timing_pos == bin2.timing_pos + timing_pos == bin2.timing_pos && bin_value == bin2.bin_value; } diff --git a/src/include/stir/recon_buildblock/DataSymmetriesForBins.inl b/src/include/stir/recon_buildblock/DataSymmetriesForBins.inl index 9f56c6e730..ef3a739d4e 100644 --- a/src/include/stir/recon_buildblock/DataSymmetriesForBins.inl +++ b/src/include/stir/recon_buildblock/DataSymmetriesForBins.inl @@ -41,7 +41,9 @@ get_related_bins(std::vector& rel_b, const Bin& b) const proj_data_info_ptr->get_min_axial_pos_num(b.segment_num()), proj_data_info_ptr->get_max_axial_pos_num(b.segment_num()), proj_data_info_ptr->get_min_tangential_pos_num(), - proj_data_info_ptr->get_max_tangential_pos_num()); + proj_data_info_ptr->get_max_tangential_pos_num(), + proj_data_info_ptr->get_min_tof_pos_num(), + proj_data_info_ptr->get_max_tof_pos_num()); } diff --git a/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.h b/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.h index 15d2f30f7d..153420d90a 100644 --- a/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.h +++ b/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.h @@ -185,6 +185,7 @@ public RegisteredParsingObject& get_proj_data_sptr() const; const int get_max_segment_num_to_process() const; + const int get_max_timing_pos_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; @@ -206,6 +207,7 @@ public RegisteredParsingObject&); void set_max_segment_num_to_process(const int); + void set_max_timing_pos_num_to_process(const int); void set_zero_seg0_end_planes(const bool); //N.E. Changed to ExamData virtual void set_additive_proj_data_sptr(const shared_ptr&); @@ -279,6 +281,10 @@ public RegisteredParsingObject symmetries_sptr; void - add_view_seg_to_sensitivity(TargetT& sensitivity, const ViewSegmentNumbers& view_seg_nums) const; + add_view_seg_to_sensitivity(TargetT& sensitivity, const ViewSegmentNumbers& view_seg_nums, const int timing_pos_num = 0) const; }; #ifdef STIR_MPI diff --git a/src/include/stir/recon_buildblock/distributable.h b/src/include/stir/recon_buildblock/distributable.h index 4b34075e48..ca3f3ede75 100644 --- a/src/include/stir/recon_buildblock/distributable.h +++ b/src/include/stir/recon_buildblock/distributable.h @@ -181,7 +181,8 @@ void distributable_computation( const double start_time_of_frame, const double end_time_of_frame, RPC_process_related_viewgrams_type * RPC_process_related_viewgrams, - DistributedCachingInformation* caching_info_ptr); + DistributedCachingInformation* caching_info_ptr, + int min_timing_pos_num = 0, int max_timing_pos_num = 0); /*! \name Tag-names currently used by stir::distributable_computation and related functions0 diff --git a/src/include/stir/recon_buildblock/distributableMPICacheEnabled.h b/src/include/stir/recon_buildblock/distributableMPICacheEnabled.h index fc0ecd046b..eee7658863 100644 --- a/src/include/stir/recon_buildblock/distributableMPICacheEnabled.h +++ b/src/include/stir/recon_buildblock/distributableMPICacheEnabled.h @@ -73,7 +73,8 @@ void distributable_computation_cache_enabled( const double start_time_of_frame, const double end_time_of_frame, RPC_process_related_viewgrams_type * RPC_process_related_viewgrams, - DistributedCachingInformation* caching_info_ptr + DistributedCachingInformation* caching_info_ptr, + int min_timing_pos_num = 0, int max_timing_pos_num = 0 ); diff --git a/src/recon_buildblock/BackProjectorByBin.cxx b/src/recon_buildblock/BackProjectorByBin.cxx index f606ab72d1..5789bb3ea1 100644 --- a/src/recon_buildblock/BackProjectorByBin.cxx +++ b/src/recon_buildblock/BackProjectorByBin.cxx @@ -88,7 +88,7 @@ BackProjectorByBin::back_project(DiscretisedDensity<3,float>& image, #ifdef STIR_OPENMP RelatedViewgrams viewgrams; #pragma omp critical (BACKPROJECTORBYBIN_GETVIEWGRAMS) - viewgrams = proj_data.get_related_viewgrams(vs, symmetries_sptr); + viewgrams = proj_data.get_related_viewgrams(vs, symmetries_sptr, false, k); #else const RelatedViewgrams viewgrams = proj_data.get_related_viewgrams(vs, symmetries_sptr, false, k); diff --git a/src/recon_buildblock/BackProjectorByBinUsingInterpolation.cxx b/src/recon_buildblock/BackProjectorByBinUsingInterpolation.cxx index 3345e9bce9..0ee3950e3d 100644 --- a/src/recon_buildblock/BackProjectorByBinUsingInterpolation.cxx +++ b/src/recon_buildblock/BackProjectorByBinUsingInterpolation.cxx @@ -677,7 +677,7 @@ can only handle arc-corrected data (cast to ProjDataInfoCylindricalArcCorr)!\n") Array<4, float > Proj2424(IndexRange4D(0, 1, 0, 3, 0, 1, 1, 4)); // a variable which will be used in the loops over s to get s_in_mm - Bin bin(pos_view.get_segment_num(), pos_view.get_view_num(),min_axial_pos_num,0); + Bin bin(pos_view.get_segment_num(), pos_view.get_view_num(),min_axial_pos_num,pos_view.get_timing_pos_num(),0); // KT 20/06/2001 rewrite using get_phi const float cphi = cos(proj_data_info_cyl_ptr->get_phi(bin)); @@ -881,7 +881,7 @@ can only handle arc-corrected data (cast to ProjDataInfoCylindricalArcCorr)!\n") Array<4, float > Proj2424(IndexRange4D(0, 1, 0, 3, 0, 1, 1, 4)); // a variable which will be used in the loops over s to get s_in_mm - Bin bin(pos_view.get_segment_num(), pos_view.get_view_num(),min_axial_pos_num,0); + Bin bin(pos_view.get_segment_num(), pos_view.get_view_num(),min_axial_pos_num,0,pos_view.get_timing_pos_num()); // compute cos(phi) and sin(phi) /* KT included special cases for phi=0 and 90 degrees to try to avoid rounding problems diff --git a/src/recon_buildblock/BackProjectorByBinUsingSquareProjMatrixByBin.cxx b/src/recon_buildblock/BackProjectorByBinUsingSquareProjMatrixByBin.cxx index 5ccd4f7745..2f91dcc9c1 100644 --- a/src/recon_buildblock/BackProjectorByBinUsingSquareProjMatrixByBin.cxx +++ b/src/recon_buildblock/BackProjectorByBinUsingSquareProjMatrixByBin.cxx @@ -87,12 +87,13 @@ actual_back_project(DiscretisedDensity<3,float>& image, const Viewgram& viewgram = *r_viewgrams_iter; const int view_num = viewgram.get_view_num(); const int segment_num = viewgram.get_segment_num(); + const int timing_pos_num = viewgram.get_timing_pos_num(); for ( int tang_pos = min_tangential_pos_num ;tang_pos <= max_tangential_pos_num ;++tang_pos) for ( int ax_pos = min_axial_pos_num; ax_pos <= max_axial_pos_num ;++ax_pos) { - Bin bin(segment_num, view_num, ax_pos, tang_pos, viewgram[ax_pos][tang_pos]); + Bin bin(segment_num, view_num, ax_pos, tang_pos, timing_pos_num, viewgram[ax_pos][tang_pos]); proj_matrix_ptr->get_proj_matrix_elems_for_one_bin(proj_matrix_row, bin); ProjMatrixElemsForOneBin::iterator element_ptr = proj_matrix_row.begin(); diff --git a/src/recon_buildblock/BinNormalisationFromProjData.cxx b/src/recon_buildblock/BinNormalisationFromProjData.cxx index 57449e6346..8e8c38e45e 100644 --- a/src/recon_buildblock/BinNormalisationFromProjData.cxx +++ b/src/recon_buildblock/BinNormalisationFromProjData.cxx @@ -98,7 +98,11 @@ set_up(const shared_ptr& proj_data_info_ptr) (norm_proj.get_min_tangential_pos_num() ==proj.get_min_tangential_pos_num())&& (norm_proj.get_max_tangential_pos_num() ==proj.get_max_tangential_pos_num()) && norm_proj.get_min_segment_num() <= proj.get_min_segment_num() && - norm_proj.get_max_segment_num() >= proj.get_max_segment_num(); + norm_proj.get_max_segment_num() >= proj.get_max_segment_num() && + ((norm_proj.get_min_tof_pos_num() <= proj.get_min_tof_pos_num() && + norm_proj.get_max_tof_pos_num() >= proj.get_max_tof_pos_num()) + || + !norm_proj.is_tof_data()) ; for (int segment_num=proj.get_min_segment_num(); ok && segment_num<=proj.get_max_segment_num(); @@ -147,9 +151,10 @@ void BinNormalisationFromProjData::apply(RelatedViewgrams& viewgrams,const double start_time, const double end_time) const { const ViewSegmentNumbers vs_num=viewgrams.get_basic_view_segment_num(); + const int timing_pos_num = norm_proj_data_ptr->get_proj_data_info_sptr()->is_tof_data() ? viewgrams.get_basic_timing_pos_num() : 0; shared_ptr symmetries_sptr(viewgrams.get_symmetries_ptr()->clone()); viewgrams *= - norm_proj_data_ptr->get_related_viewgrams(vs_num,symmetries_sptr, false); + norm_proj_data_ptr->get_related_viewgrams(vs_num,symmetries_sptr, false,timing_pos_num); } void @@ -157,9 +162,10 @@ BinNormalisationFromProjData:: undo(RelatedViewgrams& viewgrams,const double start_time, const double end_time) const { const ViewSegmentNumbers vs_num=viewgrams.get_basic_view_segment_num(); + const int timing_pos_num = norm_proj_data_ptr->get_proj_data_info_sptr()->is_tof_data() ? viewgrams.get_basic_timing_pos_num() : 0; shared_ptr symmetries_sptr(viewgrams.get_symmetries_ptr()->clone()); viewgrams /= - norm_proj_data_ptr->get_related_viewgrams(vs_num,symmetries_sptr, false); + norm_proj_data_ptr->get_related_viewgrams(vs_num,symmetries_sptr, false, timing_pos_num); } diff --git a/src/recon_buildblock/ForwardProjectorByBinUsingRayTracing.cxx b/src/recon_buildblock/ForwardProjectorByBinUsingRayTracing.cxx index 9bf0c7e040..4bcf659fdc 100644 --- a/src/recon_buildblock/ForwardProjectorByBinUsingRayTracing.cxx +++ b/src/recon_buildblock/ForwardProjectorByBinUsingRayTracing.cxx @@ -152,18 +152,12 @@ set_up(const shared_ptr& proj_data_info_ptr, segment_num <= proj_data_info_ptr->get_max_segment_num(); ++segment_num) { -// for (int k = proj_data_info_ptr->get_min_tof_pos_num(); -// k <= proj_data_info_ptr->get_max_tof_pos_num(); -// ++k) -// { - //TODO Check symmetries here, should we add TOF? const float num_planes_per_axial_pos = symmetries_ptr->get_num_planes_per_axial_pos(segment_num); if (fabs(round(num_planes_per_axial_pos) - num_planes_per_axial_pos) > 1.E-4) error("ForwardProjectorByBinUsingRayTracing: the number of image planes " "per axial_pos (which is %g for segment %d) should be an integer\n", num_planes_per_axial_pos, segment_num); -// } } @@ -427,6 +421,7 @@ forward_project_all_symmetries( const int nviews = pos_view.get_proj_data_info_ptr()->get_num_views(); const int segment_num = pos_view.get_segment_num(); + const int timing_pos_num = pos_view.get_timing_pos_num(); const float delta = proj_data_info_ptr->get_average_ring_difference(segment_num); const int view = pos_view.get_view_num(); @@ -460,7 +455,7 @@ forward_project_all_symmetries( const float R = proj_data_info_ptr->get_ring_radius(); // a variable which will be used in the loops over tang_pos_num to get s_in_mm - Bin bin(pos_view.get_segment_num(), pos_view.get_view_num(),min_ax_pos_num,0); + Bin bin(pos_view.get_segment_num(), pos_view.get_view_num(),min_ax_pos_num,0,pos_view.get_timing_pos_num()); // KT 20/06/2001 rewrote using get_phi const float cphi = cos(proj_data_info_ptr->get_phi(bin)); @@ -1130,6 +1125,7 @@ forward_project_all_symmetries_2D(Viewgram & pos_view, const int nviews = pos_view.get_proj_data_info_ptr()->get_num_views(); const int segment_num = pos_view.get_segment_num(); + const int timing_pos_num = pos_view.get_timing_pos_num(); const float delta = proj_data_info_ptr->get_average_ring_difference(segment_num); const int view = pos_view.get_view_num(); @@ -1158,7 +1154,7 @@ forward_project_all_symmetries_2D(Viewgram & pos_view, const float R = proj_data_info_ptr->get_ring_radius(); // a variable which will be used in the loops over tang_pos_num to get s_in_mm - Bin bin(pos_view.get_segment_num(), pos_view.get_view_num(),min_axial_pos_num,0); + Bin bin(pos_view.get_segment_num(), pos_view.get_view_num(),min_axial_pos_num,0,pos_view.get_timing_pos_num()); // KT 20/06/2001 rewrote using get_phi const float cphi = cos(proj_data_info_ptr->get_phi(bin)); diff --git a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx index 8575180304..64ae469621 100644 --- a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx +++ b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx @@ -96,6 +96,7 @@ set_defaults() this->input_filename=""; this->max_segment_num_to_process=-1; + this->max_timing_pos_num_to_process=0; // KT 20/06/2001 disabled //num_views_to_add=1; this->proj_data_sptr.reset(); //MJ added @@ -113,7 +114,7 @@ set_defaults() 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)); + shared_ptr back_projector_ptr(new BackProjectorByBinUsingProjMatrixByBin(PM)); #endif this->projector_pair_ptr.reset( @@ -356,6 +357,12 @@ PoissonLogLikelihoodWithLinearModelForMeanAndProjData:: get_max_segment_num_to_process() const { return this->max_segment_num_to_process; } +template +const int +PoissonLogLikelihoodWithLinearModelForMeanAndProjData:: +get_max_timing_pos_num_to_process() const +{ return this->max_timing_pos_num_to_process; } + template const bool PoissonLogLikelihoodWithLinearModelForMeanAndProjData:: @@ -439,7 +446,14 @@ PoissonLogLikelihoodWithLinearModelForMeanAndProjData:: set_max_segment_num_to_process(const int arg) { this->max_segment_num_to_process = arg; +} +template +void +PoissonLogLikelihoodWithLinearModelForMeanAndProjData:: +set_max_timing_pos_num_to_process(const int arg) +{ + this->max_timing_pos_num_to_process = arg; } template @@ -518,15 +532,15 @@ actual_subsets_are_approximately_balanced(std::string& warning_message) const 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 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_numnum_subsets; ++subset_num) @@ -571,12 +585,15 @@ set_up_before_sensitivity(shared_ptr const& target_sptr) return Succeeded::no; } + this->max_timing_pos_num_to_process = + this->proj_data_sptr->get_max_tof_pos_num(); + 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; } @@ -659,7 +676,9 @@ compute_sub_gradient_without_penalty_plus_sensitivity(TargetT& gradient, this->zero_seg0_end_planes!=0, NULL, this->additive_proj_data_sptr - , caching_info_ptr + , caching_info_ptr, + -this->max_timing_pos_num_to_process, + this->max_timing_pos_num_to_process ); @@ -687,7 +706,9 @@ actual_compute_objective_function_without_penalty(const TargetT& current_estimat 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 + this->caching_info_ptr, + -this->max_timing_pos_num_to_process, + this->max_timing_pos_num_to_process ); @@ -703,29 +724,32 @@ 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 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(); - } + for (int timing_pos_num = -max_timing_pos_num_to_process; timing_pos_num <= max_timing_pos_num_to_process; ++timing_pos_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,false,timing_pos_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; @@ -741,24 +765,29 @@ add_subset_sensitivity(TargetT& sensitivity, const int subset_num) const { const int min_segment_num = -this->max_segment_num_to_process; const int max_segment_num = this->max_segment_num_to_process; + const int min_timing_pos_num = -this->max_timing_pos_num_to_process; + const int max_timing_pos_num = this->max_timing_pos_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) + for (int timing_pos_num = min_timing_pos_num; timing_pos_num <= max_timing_pos_num; ++timing_pos_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<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, timing_pos_num); + } + // cerr< void PoissonLogLikelihoodWithLinearModelForMeanAndProjData:: -add_view_seg_to_sensitivity(TargetT& sensitivity, const ViewSegmentNumbers& view_seg_nums) const +add_view_seg_to_sensitivity(TargetT& sensitivity, const ViewSegmentNumbers& view_seg_nums , const int timing_pos_num) const { - RelatedViewgrams viewgrams = - this->proj_data_sptr->get_empty_related_viewgrams(view_seg_nums, - this->symmetries_sptr); + RelatedViewgrams viewgrams = + this->proj_data_sptr->get_empty_related_viewgrams(view_seg_nums, + this->symmetries_sptr, + false, + timing_pos_num); viewgrams.fill(1.F); // find efficiencies { @@ -788,9 +819,8 @@ add_view_seg_to_sensitivity(TargetT& sensitivity, const ViewSegmentNumbers& view 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); + projector_pair_ptr->get_back_projector_sptr()->back_project(sensitivity, viewgrams, + min_ax_pos_num, max_ax_pos_num); } } @@ -827,44 +857,49 @@ actual_add_multiplication_with_approximate_sub_Hessian_without_penalty(TargetT& 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); + { + for (int timing_pos_num = -this->get_max_timing_pos_num_to_process(); + timing_pos_num<= this->get_max_timing_pos_num_to_process(); + ++timing_pos_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, false, timing_pos_num); + // 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, false, timing_pos_num); + 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 @@ -902,7 +937,8 @@ void distributable_compute_gradient(const shared_ptr& for bool zero_seg0_end_planes, double* log_likelihood_ptr, shared_ptr const& additive_binwise_correction, - DistributedCachingInformation* caching_info_ptr + DistributedCachingInformation* caching_info_ptr, + int min_timing_pos_num, int max_timing_pos_num ) { @@ -918,7 +954,8 @@ void distributable_compute_gradient(const shared_ptr& for additive_binwise_correction, /* normalisation info to be ignored */ shared_ptr(), 0., 0., &RPC_process_related_viewgrams_gradient, - caching_info_ptr + caching_info_ptr, + min_timing_pos_num, max_timing_pos_num ); } @@ -937,7 +974,8 @@ void distributable_accumulate_loglikelihood( shared_ptr const& normalisation_sptr, const double start_time_of_frame, const double end_time_of_frame, - DistributedCachingInformation* caching_info_ptr + DistributedCachingInformation* caching_info_ptr, + int min_timing_pos_num, int max_timing_pos_num ) { @@ -955,7 +993,8 @@ void distributable_accumulate_loglikelihood( start_time_of_frame, end_time_of_frame, &RPC_process_related_viewgrams_accumulate_loglikelihood, - caching_info_ptr + caching_info_ptr, + min_timing_pos_num, max_timing_pos_num ); } diff --git a/src/recon_buildblock/ProjMatrixByBinUsingRayTracing.cxx b/src/recon_buildblock/ProjMatrixByBinUsingRayTracing.cxx index 0702526c1f..1935bcb8ad 100644 --- a/src/recon_buildblock/ProjMatrixByBinUsingRayTracing.cxx +++ b/src/recon_buildblock/ProjMatrixByBinUsingRayTracing.cxx @@ -542,6 +542,8 @@ calculate_proj_matrix_elems_for_one_bin( } const Bin bin = lor.get_bin(); + assert(bin.timing_pos_num() >= proj_data_info_ptr->get_min_tof_pos_num()); + assert(bin.timing_pos_num() <= proj_data_info_ptr->get_max_tof_pos_num()); assert(bin.segment_num() >= proj_data_info_ptr->get_min_segment_num()); assert(bin.segment_num() <= proj_data_info_ptr->get_max_segment_num()); diff --git a/src/recon_buildblock/ProjMatrixElemsForOneBin.cxx b/src/recon_buildblock/ProjMatrixElemsForOneBin.cxx index 5bb369b30f..81c94279c5 100644 --- a/src/recon_buildblock/ProjMatrixElemsForOneBin.cxx +++ b/src/recon_buildblock/ProjMatrixElemsForOneBin.cxx @@ -128,9 +128,9 @@ Succeeded ProjMatrixElemsForOneBin::check_state() const { if (value_type::coordinates_equal(*lor_iter, *(lor_iter+1))) { - warning("ProjMatrixElemsForOneBin: coordinates occur more than once %d,%d,%d for bin s=%d, v=%d, a=%d, t=%d\n", + warning("ProjMatrixElemsForOneBin: coordinates occur more than once %d,%d,%d for bin s=%d, tofbin=%d, v=%d, a=%d, t=%d\n", lor_iter->coord1(), lor_iter->coord2(), lor_iter->coord3(), - bin.segment_num(), bin.view_num(), + bin.segment_num(), bin.timing_pos_num(), bin.view_num(), bin.axial_pos_num(), bin.tangential_pos_num()); #if 0 const_iterator iter = begin(); diff --git a/src/recon_buildblock/distributable.cxx b/src/recon_buildblock/distributable.cxx index f5a2e79d63..5df9a65805 100644 --- a/src/recon_buildblock/distributable.cxx +++ b/src/recon_buildblock/distributable.cxx @@ -173,7 +173,8 @@ void get_viewgrams(shared_ptr >& y, const double start_time_of_frame, const double end_time_of_frame, const shared_ptr& symmetries_ptr, - const ViewSegmentNumbers& view_segment_num + const ViewSegmentNumbers& view_segment_num, + const int timing_pos_num ) { if (!is_null_ptr(binwise_correction)) @@ -184,10 +185,10 @@ void get_viewgrams(shared_ptr >& y, #if !defined(_MSC_VER) || _MSC_VER>1300 additive_binwise_correction_viewgrams.reset( new RelatedViewgrams - (binwise_correction->get_related_viewgrams(view_segment_num, symmetries_ptr))); + (binwise_correction->get_related_viewgrams(view_segment_num, symmetries_ptr, false, timing_pos_num))); #else RelatedViewgrams tmp(binwise_correction-> - get_related_viewgrams(view_segment_num, symmetries_ptr)); + get_related_viewgrams(view_segment_num, symmetries_ptr, false, timing_pos_num)); additive_binwise_correction_viewgrams.reset(new RelatedViewgrams(tmp)); #endif } @@ -199,25 +200,25 @@ void get_viewgrams(shared_ptr >& y, #endif #if !defined(_MSC_VER) || _MSC_VER>1300 y.reset(new RelatedViewgrams - (proj_dat_ptr->get_related_viewgrams(view_segment_num, symmetries_ptr))); + (proj_dat_ptr->get_related_viewgrams(view_segment_num, symmetries_ptr, false, timing_pos_num))); #else // workaround VC++ 6.0 bug RelatedViewgrams tmp(proj_dat_ptr-> - get_related_viewgrams(view_segment_num, symmetries_ptr)); + get_related_viewgrams(view_segment_num, symmetries_ptr, false, timing_pos_num)); y.reset(new RelatedViewgrams(tmp)); #endif } else { y.reset(new RelatedViewgrams - (proj_dat_ptr->get_empty_related_viewgrams(view_segment_num, symmetries_ptr))); + (proj_dat_ptr->get_empty_related_viewgrams(view_segment_num, symmetries_ptr, false, timing_pos_num))); } // multiplicative correction if (!is_null_ptr(normalisation_sptr) && !normalisation_sptr->is_trivial()) { mult_viewgrams_sptr.reset( - new RelatedViewgrams(proj_dat_ptr->get_empty_related_viewgrams(view_segment_num, symmetries_ptr))); + new RelatedViewgrams(proj_dat_ptr->get_empty_related_viewgrams(view_segment_num, symmetries_ptr, false, timing_pos_num))); mult_viewgrams_sptr->fill(1.F); #ifdef STIR_OPENMP #pragma omp critical(MULT) @@ -240,6 +241,7 @@ void send_viewgrams(const shared_ptr >& y, const int next_receiver) { distributed::send_view_segment_numbers( y->get_basic_view_segment_num(), NEW_VIEWGRAM_TAG, next_receiver); + distributed::send_int_value( y->get_timing_pos_num(), next_receiver); #ifndef NDEBUG //test sending related viegrams @@ -294,7 +296,8 @@ void distributable_computation( const double start_time_of_frame, const double end_time_of_frame, RPC_process_related_viewgrams_type * RPC_process_related_viewgrams, - DistributedCachingInformation* caching_info_ptr) + DistributedCachingInformation* caching_info_ptr, + int min_timing_pos_num, int max_timing_pos_num) { #ifdef STIR_MPI @@ -334,7 +337,8 @@ void distributable_computation( start_time_of_frame, end_time_of_frame, RPC_process_related_viewgrams, - caching_info_ptr); + caching_info_ptr, + min_timing_pos_num, max_timing_pos_num); return; } @@ -374,6 +378,7 @@ void distributable_computation( wall_clock_timer.start(); assert(min_segment_num <= max_segment_num); + assert(min_timing_pos_num <= max_timing_pos_num); assert(subset_num >=0); assert(subset_num < num_subsets); @@ -423,6 +428,9 @@ void distributable_computation( } #pragma omp for schedule(runtime) #endif + + for (int timing_pos_num = min_timing_pos_num; timing_pos_num <= max_timing_pos_num; ++timing_pos_num) + { // note: older versions of openmp need an int as loop for (int i=0; i(vs_nums_to_process.size()); ++i) { @@ -437,7 +445,7 @@ void distributable_computation( zero_seg0_end_planes, binwise_correction, normalisation_sptr, start_time_of_frame, end_time_of_frame, - symmetries_ptr, view_segment_num); + symmetries_ptr, view_segment_num, timing_pos_num); #ifdef STIR_MPI //send viewgrams, the slave will immediatelly start calculation @@ -465,12 +473,12 @@ void distributable_computation( #ifdef STIR_OPENMP const int thread_num=omp_get_thread_num(); - info(boost::format("Thread %d/%d calculating segment_num: %d, view_num: %d") + info(boost::format("Thread %d/%d calculating segment_num: %d, view_num: %d, timing_pos_num: %d") % thread_num % omp_get_num_threads() - % view_segment_num.segment_num() % view_segment_num.view_num()); + % view_segment_num.segment_num() % view_segment_num.view_num() % timing_pos_num); #else - info(boost::format("calculating segment_num: %d, view_num: %d") - % view_segment_num.segment_num() % view_segment_num.view_num()); + info(boost::format("calculating segment_num: %d, view_num: %d, timing_pos_num: %d") + % view_segment_num.segment_num() % view_segment_num.view_num() % timing_pos_num); #endif #ifdef STIR_OPENMP if (output_image_ptr != NULL) @@ -495,7 +503,8 @@ void distributable_computation( mult_viewgrams_sptr.get()); #endif // OPENMP #endif // MPI - } // end of for-loop + } // end of for-loop + } // end of for-loop over timing_pos_num } // end of parallel section of openmp #ifdef STIR_OPENMP diff --git a/src/recon_buildblock/distributableMPICacheEnabled.cxx b/src/recon_buildblock/distributableMPICacheEnabled.cxx index b89b117734..c3343f7fcc 100644 --- a/src/recon_buildblock/distributableMPICacheEnabled.cxx +++ b/src/recon_buildblock/distributableMPICacheEnabled.cxx @@ -92,7 +92,8 @@ void get_viewgrams(shared_ptr >& y, const double start_time_of_frame, const double end_time_of_frame, const shared_ptr& symmetries_ptr, - const ViewSegmentNumbers& view_segment_num + const ViewSegmentNumbers& view_segment_num, + const int timing_pos_num ) { if (!is_null_ptr(binwise_correction)) @@ -100,10 +101,10 @@ void get_viewgrams(shared_ptr >& y, #if !defined(_MSC_VER) || _MSC_VER>1300 additive_binwise_correction_viewgrams.reset( new RelatedViewgrams - (binwise_correction->get_related_viewgrams(view_segment_num, symmetries_ptr))); + (binwise_correction->get_related_viewgrams(view_segment_num, symmetries_ptr, false, timing_pos_num))); #else RelatedViewgrams tmp(binwise_correction-> - get_related_viewgrams(view_segment_num, symmetries_ptr)); + get_related_viewgrams(view_segment_num, symmetries_ptr, false, timing_pos_num)); additive_binwise_correction_viewgrams = new RelatedViewgrams(tmp); #endif } @@ -112,25 +113,25 @@ void get_viewgrams(shared_ptr >& y, { #if !defined(_MSC_VER) || _MSC_VER>1300 y.reset(new RelatedViewgrams - (proj_dat_ptr->get_related_viewgrams(view_segment_num, symmetries_ptr))); + (proj_dat_ptr->get_related_viewgrams(view_segment_num, symmetries_ptr, false, timing_pos_num))); #else // workaround VC++ 6.0 bug RelatedViewgrams tmp(proj_dat_ptr-> - get_related_viewgrams(view_segment_num, symmetries_ptr)); + get_related_viewgrams(view_segment_num, symmetries_ptr, false, timing_pos_num)); y = new RelatedViewgrams(tmp); #endif } else { y.reset(new RelatedViewgrams - (proj_dat_ptr->get_empty_related_viewgrams(view_segment_num, symmetries_ptr))); + (proj_dat_ptr->get_empty_related_viewgrams(view_segment_num, symmetries_ptr, false, timing_pos_num))); } // multiplicative correction if (!is_null_ptr(normalisation_sptr) && !normalisation_sptr->is_trivial()) { mult_viewgrams_sptr.reset( - new RelatedViewgrams(proj_dat_ptr->get_empty_related_viewgrams(view_segment_num, symmetries_ptr))); + new RelatedViewgrams(proj_dat_ptr->get_empty_related_viewgrams(view_segment_num, symmetries_ptr, false, timing_pos_num))); mult_viewgrams_sptr->fill(1.F); normalisation_sptr->undo(*mult_viewgrams_sptr,start_time_of_frame,end_time_of_frame); } @@ -149,6 +150,7 @@ static void send_viewgrams(const shared_ptr >& y, const int next_receiver) { distributed::send_view_segment_numbers( y->get_basic_view_segment_num(), NEW_VIEWGRAM_TAG, next_receiver); + distributed::send_int_value( y->get_basic_timing_pos_num(), next_receiver); #ifndef NDEBUG //test sending related viewgrams @@ -202,7 +204,8 @@ void distributable_computation_cache_enabled( const double start_time_of_frame, const double end_time_of_frame, RPC_process_related_viewgrams_type * RPC_process_related_viewgrams, - DistributedCachingInformation* caching_info_ptr + DistributedCachingInformation* caching_info_ptr, + int min_timing_pos_num, int max_timing_pos_num ) { //test distributed functions (see DistributedTestFunctions.h for details) @@ -232,6 +235,7 @@ void distributable_computation_cache_enabled( distributed::send_bool_value(!is_null_ptr(output_image_ptr),USE_OUTPUT_IMAGE_ARG_TAG,-1); assert(min_segment_num <= max_segment_num); + assert(min_timing_pos_num <= max_timing_pos_num); assert(subset_num >=0); assert(subset_num < num_subsets); @@ -269,56 +273,60 @@ void distributable_computation_cache_enabled( for (std::size_t processed_count = 1; processed_count <= num_vs; ++processed_count) { ViewSegmentNumbers view_segment_num; - - //check whether the slave will receive a new or an already cached viewgram - const bool new_viewgrams = - caching_info_ptr->get_unprocessed_vs_num(view_segment_num, next_receiver); - // view_segment_num = vs_nums_to_process[processed_count-1]; - - //the slave has not yet processed this vs_num, so the viewgrams have to be sent - if (new_viewgrams==true) - { - info(boost::format("Sending segment %1%, view %2% to slave %3%\n") % view_segment_num.segment_num() % view_segment_num.view_num() % next_receiver); - shared_ptr > y; - shared_ptr > additive_binwise_correction_viewgrams; - shared_ptr > mult_viewgrams_sptr; + //check whether the slave will receive a new or an already cached viewgram + const bool new_viewgrams = + caching_info_ptr->get_unprocessed_vs_num(view_segment_num, next_receiver); + // view_segment_num = vs_nums_to_process[processed_count-1]; - get_viewgrams(y, additive_binwise_correction_viewgrams, mult_viewgrams_sptr, - proj_dat_ptr, read_from_proj_dat, - zero_seg0_end_planes, - binwise_correction, - normalise_sptr, start_time_of_frame, end_time_of_frame, - symmetries_ptr, view_segment_num); + for (int timing_pos_num = min_timing_pos_num; timing_pos_num <= max_timing_pos_num; ++timing_pos_num) + { - //send viewgrams, the slave will immediatelly start calculation - send_viewgrams(y, additive_binwise_correction_viewgrams, mult_viewgrams_sptr, - next_receiver); - } // if(new_viewgram) - else - { - info(boost::format("Re-using segment %1%, view %2% to slave %3%\n") % view_segment_num.segment_num() % view_segment_num.view_num() % next_receiver); - //send vs_num with reuse-tag, the slave will immediatelly start calculation - distributed::send_view_segment_numbers( view_segment_num, REUSE_VIEWGRAM_TAG, next_receiver); - } + //the slave has not yet processed this vs_num, so the viewgrams have to be sent + if (new_viewgrams==true) + { + info(boost::format("Sending segment %1%, view %2%, TOF bin %3% to slave %4%\n") % view_segment_num.segment_num() % view_segment_num.view_num() % timing_pos_num % next_receiver); - working_slaves_count++; - - if (int(processed_count) > y; + shared_ptr > additive_binwise_correction_viewgrams; + shared_ptr > mult_viewgrams_sptr; + + get_viewgrams(y, additive_binwise_correction_viewgrams, mult_viewgrams_sptr, + proj_dat_ptr, read_from_proj_dat, + zero_seg0_end_planes, + binwise_correction, + normalise_sptr, start_time_of_frame, end_time_of_frame, + symmetries_ptr, view_segment_num, timing_pos_num); + + //send viewgrams, the slave will immediatelly start calculation + send_viewgrams(y, additive_binwise_correction_viewgrams, mult_viewgrams_sptr, + next_receiver); + } // if(new_viewgram) + else + { + info(boost::format("Re-using segment %1%, view %2%, TOF bin %3% to slave %4%\n") % view_segment_num.segment_num() % view_segment_num.view_num() % timing_pos_num % next_receiver); + //send vs_num with reuse-tag, the slave will immediatelly start calculation + distributed::send_view_segment_numbers( view_segment_num, REUSE_VIEWGRAM_TAG, next_receiver); + } + + working_slaves_count++; + + if (int(processed_count) (proj_data_info_ptr, v_num, s_num); + viewgram_ptr= new stir::Viewgram(proj_data_info_ptr, v_num, s_num, t_num); //allocate receive-buffer const int buffer_size=(viewgram_values[1]-viewgram_values[0]+1)*(viewgram_values[3]-viewgram_values[2]+1); diff --git a/src/recon_buildblock/distributed_test_functions.cxx b/src/recon_buildblock/distributed_test_functions.cxx index a3a3d08ce3..a0344d22c9 100644 --- a/src/recon_buildblock/distributed_test_functions.cxx +++ b/src/recon_buildblock/distributed_test_functions.cxx @@ -68,6 +68,8 @@ namespace distributed if (vg->get_view_num()!=viewgram.get_view_num()) printf("-----Test sending viewgram failed!!!!-------------\n"); assert(vg->get_segment_num()==viewgram.get_segment_num()); if (vg->get_segment_num()!=viewgram.get_segment_num()) printf("-----Test sending viewgram failed!!!!-------------\n"); + assert(vg->get_timing_pos_num()==viewgram.get_timing_pos_num()); + if (vg->get_timing_pos_num()!=viewgram.get_timing_pos_num()) printf("-----Test sending viewgram failed!!!!-------------\n"); delete vg; printf("\n-----Test sending viewgram done-----------\n"); diff --git a/src/recon_test/bcktest.cxx b/src/recon_test/bcktest.cxx index 3b077f5436..4841d31a24 100644 --- a/src/recon_test/bcktest.cxx +++ b/src/recon_test/bcktest.cxx @@ -88,6 +88,7 @@ START_NAMESPACE_STIR void do_segments(DiscretisedDensity<3,float>& image, ProjData& proj_data_org, + const int start_timing_num, const int end_timing_num, const int start_segment_num, const int end_segment_num, const int start_axial_pos_num, const int end_axial_pos_num, const int start_tang_pos_num,const int end_tang_pos_num, @@ -101,81 +102,85 @@ do_segments(DiscretisedDensity<3,float>& image, list already_processed; - - for (int segment_num = start_segment_num; segment_num <= end_segment_num; ++segment_num) - for (int view= start_view; view<=end_view; view++) - { - ViewSegmentNumbers vs(view, segment_num); - symmetries_sptr->find_basic_view_segment_numbers(vs); - if (find(already_processed.begin(), already_processed.end(), vs) - != already_processed.end()) - continue; - - already_processed.push_back(vs); - - cerr << "Processing view " << vs.view_num() - << " of segment " < viewgrams_empty= - proj_data_org.get_empty_related_viewgrams(vs, symmetries_sptr); - //proj_data_org.get_empty_related_viewgrams(vs.view_num(),vs.segment_num(), symmetries_sptr); - - RelatedViewgrams::iterator r_viewgrams_iter = viewgrams_empty.begin(); - while(r_viewgrams_iter!=viewgrams_empty.end()) - { - Viewgram& single_viewgram = *r_viewgrams_iter; - if (start_view <= single_viewgram.get_view_num() && - single_viewgram.get_view_num() <= end_view && - single_viewgram.get_segment_num() >= start_segment_num && - single_viewgram.get_segment_num() <= end_segment_num) - { - single_viewgram.fill(1.F); - } - r_viewgrams_iter++; - } - - back_projector_ptr->back_project(image,viewgrams_empty, - std::max(start_axial_pos_num, viewgrams_empty.get_min_axial_pos_num()), - std::min(end_axial_pos_num, viewgrams_empty.get_max_axial_pos_num()), - start_tang_pos_num, end_tang_pos_num); - } - else - { - RelatedViewgrams viewgrams = - proj_data_org.get_related_viewgrams(vs, - //proj_data_org.get_related_viewgrams(vs.view_num(),vs.segment_num(), - symmetries_sptr); - RelatedViewgrams::iterator r_viewgrams_iter = viewgrams.begin(); - - while(r_viewgrams_iter!=viewgrams.end()) - { - Viewgram& single_viewgram = *r_viewgrams_iter; - { - if (start_view <= single_viewgram.get_view_num() && - single_viewgram.get_view_num() <= end_view && - single_viewgram.get_segment_num() >= start_segment_num && - single_viewgram.get_segment_num() <= end_segment_num) - { - // ok - } - else - { - // set to 0 to prevent it being backprojected - single_viewgram.fill(0); - } - } - ++r_viewgrams_iter; - } - - back_projector_ptr->back_project(image,viewgrams, - std::max(start_axial_pos_num, viewgrams.get_min_axial_pos_num()), - std::min(end_axial_pos_num, viewgrams.get_max_axial_pos_num()), - start_tang_pos_num, end_tang_pos_num); - } // fill - } // for view_num, segment_num + for (int timing_num = start_timing_num; timing_num <= end_timing_num; ++timing_num) + { + already_processed.clear(); + for (int segment_num = start_segment_num; segment_num <= end_segment_num; ++segment_num) + for (int view = start_view; view <= end_view; view++) + { + ViewSegmentNumbers vs(view, segment_num); + symmetries_sptr->find_basic_view_segment_numbers(vs); + if (find(already_processed.begin(), already_processed.end(), vs) + != already_processed.end()) + continue; + + already_processed.push_back(vs); + + cerr << "Processing view " << vs.view_num() + << " of segment " << vs.segment_num() + << " of timing position index " << timing_num + << endl; + + if (fill_with_1) + { + RelatedViewgrams viewgrams_empty = + proj_data_org.get_empty_related_viewgrams(vs, symmetries_sptr, false, timing_num); + //proj_data_org.get_empty_related_viewgrams(vs.view_num(),vs.segment_num(), symmetries_sptr); + + RelatedViewgrams::iterator r_viewgrams_iter = viewgrams_empty.begin(); + while (r_viewgrams_iter != viewgrams_empty.end()) + { + Viewgram& single_viewgram = *r_viewgrams_iter; + if (start_view <= single_viewgram.get_view_num() && + single_viewgram.get_view_num() <= end_view && + single_viewgram.get_segment_num() >= start_segment_num && + single_viewgram.get_segment_num() <= end_segment_num) + { + single_viewgram.fill(1.F); + } + r_viewgrams_iter++; + } + + back_projector_ptr->back_project(image, viewgrams_empty, + std::max(start_axial_pos_num, viewgrams_empty.get_min_axial_pos_num()), + std::min(end_axial_pos_num, viewgrams_empty.get_max_axial_pos_num()), + start_tang_pos_num, end_tang_pos_num); + } + else + { + RelatedViewgrams viewgrams = + proj_data_org.get_related_viewgrams(vs, + //proj_data_org.get_related_viewgrams(vs.view_num(),vs.segment_num(), + symmetries_sptr, false, timing_num); + RelatedViewgrams::iterator r_viewgrams_iter = viewgrams.begin(); + + while (r_viewgrams_iter != viewgrams.end()) + { + Viewgram& single_viewgram = *r_viewgrams_iter; + { + if (start_view <= single_viewgram.get_view_num() && + single_viewgram.get_view_num() <= end_view && + single_viewgram.get_segment_num() >= start_segment_num && + single_viewgram.get_segment_num() <= end_segment_num) + { + // ok + } + else + { + // set to 0 to prevent it being backprojected + single_viewgram.fill(0); + } + } + ++r_viewgrams_iter; + } + + back_projector_ptr->back_project(image, viewgrams, + std::max(start_axial_pos_num, viewgrams.get_min_axial_pos_num()), + std::min(end_axial_pos_num, viewgrams.get_max_axial_pos_num()), + start_tang_pos_num, end_tang_pos_num); + } // fill + } // for view_num, segment_num + } // for timing_pos_num } @@ -274,7 +279,12 @@ main(int argc, char **argv) do { - + int min_timing_num = ask_num("Minimum timing position index to backproject", + proj_data_info_ptr->get_min_tof_pos_num(), proj_data_info_ptr->get_max_tof_pos_num(), + proj_data_info_ptr->get_min_tof_pos_num()); + int max_timing_num = ask_num("Maximum timing position index to backproject", + min_timing_num, proj_data_info_ptr->get_max_tof_pos_num(), + min_timing_num); int min_segment_num = ask_num("Minimum segment number to backproject", proj_data_info_ptr->get_min_segment_num(), proj_data_info_ptr->get_max_segment_num(), 0); int max_segment_num = ask_num("Maximum segment number to backproject", @@ -343,6 +353,7 @@ main(int argc, char **argv) do_segments(*image_sptr, *proj_data_ptr, + min_timing_num, max_timing_num, min_segment_num, max_segment_num, start_axial_pos_num,end_axial_pos_num, start_tang_pos_num,end_tang_pos_num, diff --git a/src/recon_test/fwdtest.cxx b/src/recon_test/fwdtest.cxx index 655e207d4c..ebf53cc1b0 100644 --- a/src/recon_test/fwdtest.cxx +++ b/src/recon_test/fwdtest.cxx @@ -81,6 +81,7 @@ USING_NAMESPACE_STIR static void do_segments(const VoxelsOnCartesianGrid& image, ProjData& s3d, + const int start_timing_pos_num, const int end_timing_pos_num, const int start_segment_num, const int end_segment_num, const int start_view, const int end_view, const int start_tangential_pos_num, const int end_tangential_pos_num, @@ -248,6 +249,7 @@ main(int argc, char *argv[]) do_segments(*vox_image_ptr, *proj_data_ptr, + proj_data_ptr->get_min_tof_pos_num(), proj_data_ptr->get_max_tof_pos_num(), proj_data_ptr->get_min_segment_num(), proj_data_ptr->get_max_segment_num(), proj_data_ptr->get_min_view_num(), proj_data_ptr->get_max_view_num(), @@ -268,14 +270,17 @@ main(int argc, char *argv[]) // first set all data to 0 cerr << "Filling output file with 0\n"; + for (int timing_pos_num = proj_data_ptr->get_min_tof_pos_num(); + timing_pos_num <= proj_data_ptr->get_max_tof_pos_num(); + ++timing_pos_num) for (int segment_num = proj_data_ptr->get_min_segment_num(); segment_num <= proj_data_ptr->get_max_segment_num(); ++segment_num) { const SegmentByView segment = - proj_data_ptr->get_empty_segment_by_view(segment_num, false); + proj_data_ptr->get_empty_segment_by_view(segment_num, false,timing_pos_num); if (!(proj_data_ptr->set_segment(segment) == Succeeded::yes)) - warning("Error set_segment %d\n", segment_num); + warning("Error set_segment %d of timing position index %d\n", segment_num,timing_pos_num); } do { @@ -283,6 +288,11 @@ main(int argc, char *argv[]) timer.reset(); timer.start(); + const int timing_pos_num = + ask_num("Timing position index to forward project", + proj_data_ptr->get_min_tof_pos_num(), + proj_data_ptr->get_max_tof_pos_num(), + 0); const int segment_num = ask_num("Segment number to forward project (related segments will be done as well)", proj_data_ptr->get_min_segment_num(), @@ -305,6 +315,7 @@ main(int argc, char *argv[]) ask_num("End tangential_pos_num ", start_tangential_pos_num, max_tangential_pos_num, max_tangential_pos_num); do_segments(*vox_image_ptr,*proj_data_ptr, + timing_pos_num, timing_pos_num, segment_num,segment_num, start_view, end_view, start_tangential_pos_num, end_tangential_pos_num, @@ -324,45 +335,50 @@ main(int argc, char *argv[]) /******************* Implementation local functions *******************/ void -do_segments(const VoxelsOnCartesianGrid& image, - ProjData& proj_data, - const int start_segment_num, const int end_segment_num, - const int start_view, const int end_view, - const int start_tangential_pos_num, const int end_tangential_pos_num, - ForwardProjectorByBin& forw_projector, - const bool disp) +do_segments(const VoxelsOnCartesianGrid& image, + ProjData& proj_data, + const int start_timing_pos_num, const int end_timing_pos_num, + const int start_segment_num, const int end_segment_num, + const int start_view, const int end_view, + const int start_tangential_pos_num, const int end_tangential_pos_num, + ForwardProjectorByBin& forw_projector, + const bool disp) { - shared_ptr - symmetries_sptr(forw_projector.get_symmetries_used()->clone()); - - std::list already_processed; - - for (int segment_num = start_segment_num; segment_num <= end_segment_num; ++segment_num) - for (int view= start_view; view<=end_view; view++) - { - ViewSegmentNumbers vs(view, segment_num); - symmetries_sptr->find_basic_view_segment_numbers(vs); - if (find(already_processed.begin(), already_processed.end(), vs) - != already_processed.end()) - continue; - - already_processed.push_back(vs); - - cerr << "Processing view " << vs.view_num() - << " of segment " < viewgrams = - proj_data.get_empty_related_viewgrams(vs, symmetries_sptr,false); - forw_projector.forward_project(viewgrams, image, - viewgrams.get_min_axial_pos_num(), - viewgrams.get_max_axial_pos_num(), - start_tangential_pos_num, end_tangential_pos_num); - if (disp) - display(viewgrams, viewgrams.find_max()); - if (!(proj_data.set_related_viewgrams(viewgrams) == Succeeded::yes)) - error("Error set_related_viewgrams\n"); - } + shared_ptr + symmetries_sptr(forw_projector.get_symmetries_used()->clone()); + + std::list already_processed; + for (int timing_pos_num = start_timing_pos_num; timing_pos_num <= end_timing_pos_num; ++timing_pos_num) + { + already_processed.clear(); + for (int segment_num = start_segment_num; segment_num <= end_segment_num; ++segment_num) + for (int view = start_view; view <= end_view; view++) + { + ViewSegmentNumbers vs(view, segment_num); + symmetries_sptr->find_basic_view_segment_numbers(vs); + if (find(already_processed.begin(), already_processed.end(), vs) + != already_processed.end()) + continue; + + already_processed.push_back(vs); + + cerr << "Processing view " << vs.view_num() + << " of segment " << vs.segment_num() + << " of timing position index " << timing_pos_num + << endl; + + RelatedViewgrams viewgrams = + proj_data.get_empty_related_viewgrams(vs, symmetries_sptr, false,timing_pos_num); + forw_projector.forward_project(viewgrams, image, + viewgrams.get_min_axial_pos_num(), + viewgrams.get_max_axial_pos_num(), + start_tangential_pos_num, end_tangential_pos_num); + if (disp) + display(viewgrams, viewgrams.find_max()); + if (!(proj_data.set_related_viewgrams(viewgrams) == Succeeded::yes)) + error("Error set_related_viewgrams\n"); + } + } } diff --git a/src/recon_test/test_PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx b/src/recon_test/test_PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx index 9aa649eb5b..2320cd54c1 100644 --- a/src/recon_test/test_PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx +++ b/src/recon_test/test_PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx @@ -191,7 +191,7 @@ construct_input_data(shared_ptr& density_sptr) timing_pos_num <= proj_data_sptr->get_max_tof_pos_num(); ++timing_pos_num) { - SegmentByView segment = proj_data_sptr->get_empty_segment_by_view(seg_num); + SegmentByView segment = proj_data_sptr->get_empty_segment_by_view(seg_num,false,timing_pos_num); // fill in some crazy values float value=0; for (SegmentByView::full_iterator iter = segment.begin_all(); diff --git a/src/test/test_ArcCorrection.cxx b/src/test/test_ArcCorrection.cxx index 17ef8e33c5..ea9885bb0d 100644 --- a/src/test/test_ArcCorrection.cxx +++ b/src/test/test_ArcCorrection.cxx @@ -61,6 +61,7 @@ class ArcCorrectionTests: public RunTests { public: void run_tests(); + void run_tests_tof(); protected: void run_tests_for_specific_proj_data_info(const ArcCorrection&); }; @@ -76,24 +77,27 @@ run_tests_for_specific_proj_data_info(const ArcCorrection& arc_correction) const float sampling_in_s = proj_data_info_arc_corr.get_tangential_sampling(); + for (int timing_pos_num = proj_data_info_noarc_corr.get_min_tof_pos_num(); + timing_pos_num <= proj_data_info_noarc_corr.get_max_tof_pos_num(); + ++timing_pos_num) for (int segment_num=proj_data_info_noarc_corr.get_min_segment_num(); segment_num<=proj_data_info_noarc_corr.get_max_segment_num(); ++segment_num) { const int axial_pos_num = 0; Sinogram noarccorr_sinogram = - proj_data_info_noarc_corr.get_empty_sinogram(axial_pos_num, segment_num); + proj_data_info_noarc_corr.get_empty_sinogram(axial_pos_num, segment_num, false, timing_pos_num); Sinogram arccorr_sinogram = - proj_data_info_arc_corr.get_empty_sinogram(axial_pos_num, segment_num); + proj_data_info_arc_corr.get_empty_sinogram(axial_pos_num, segment_num, false, timing_pos_num); for (int view_num=proj_data_info_noarc_corr.get_min_view_num(); view_num<=proj_data_info_noarc_corr.get_max_view_num(); view_num+=3) { Viewgram noarccorr_viewgram = - proj_data_info_noarc_corr.get_empty_viewgram(view_num, segment_num); + proj_data_info_noarc_corr.get_empty_viewgram(view_num, segment_num, false, timing_pos_num); Viewgram arccorr_viewgram = - proj_data_info_arc_corr.get_empty_viewgram(view_num, segment_num); + proj_data_info_arc_corr.get_empty_viewgram(view_num, segment_num, false, timing_pos_num); // test geometry by checking if single non-zero value gets put in the right bin { for (int tangential_pos_num=proj_data_info_noarc_corr.get_min_tangential_pos_num(); @@ -113,10 +117,10 @@ run_tests_for_specific_proj_data_info(const ArcCorrection& arc_correction) const float noarccorr_s = proj_data_info_noarc_corr. - get_s(Bin(segment_num,view_num,axial_pos_num,tangential_pos_num)); + get_s(Bin(segment_num,view_num,axial_pos_num,tangential_pos_num,timing_pos_num)); const float arccorr_s = proj_data_info_arc_corr. - get_s(Bin(segment_num,view_num,axial_pos_num,arccorr_tangential_pos_num_at_max)); + get_s(Bin(segment_num,view_num,axial_pos_num,arccorr_tangential_pos_num_at_max,timing_pos_num)); check((arccorr_s - noarccorr_s)/sampling_in_s < 1.1, "correspondence in location of maximum after arc-correction"); } @@ -176,6 +180,31 @@ ArcCorrectionTests::run_tests() } } +void +ArcCorrectionTests::run_tests_tof() +{ + cerr << "-------- Testing ArcCorrection for TOF scanner --------\n"; + ArcCorrection arc_correction; + shared_ptr scanner_ptr(new Scanner(Scanner::PETMR_Signa)); + + shared_ptr proj_data_info_ptr( + ProjDataInfo::ProjDataInfoGE(scanner_ptr, + /*max_delta*/ 10,/*views*/ 224, /*tang_pos*/ 357, /*arc_corrected*/ false, /*tof_mashing_factor*/ 39)); + + cerr << "Using default range and bin-size\n"; + { + arc_correction.set_up(proj_data_info_ptr); + run_tests_for_specific_proj_data_info(arc_correction); + } + cerr << "Using non-default range and bin-size\n"; + { + arc_correction.set_up(proj_data_info_ptr, + 357, + scanner_ptr->get_default_bin_size() * 2); + run_tests_for_specific_proj_data_info(arc_correction); + } +} + END_NAMESPACE_STIR @@ -185,5 +214,6 @@ int main() { ArcCorrectionTests tests; tests.run_tests(); + tests.run_tests_tof(); return tests.main_return_value(); } diff --git a/src/utilities/correct_projdata.cxx b/src/utilities/correct_projdata.cxx index db1b14c161..86e7b1a9d8 100644 --- a/src/utilities/correct_projdata.cxx +++ b/src/utilities/correct_projdata.cxx @@ -243,133 +243,133 @@ run() const : forward_projector_ptr->get_symmetries_used()->clone() ); - - for (int segment_num = output_projdata.get_min_segment_num(); segment_num <= output_projdata.get_max_segment_num() ; segment_num++) - { - cerr<is_basic(view_seg_nums)) - continue; + for (int timing_pos_num = output_projdata.get_min_tof_pos_num(); timing_pos_num <= output_projdata.get_max_tof_pos_num(); timing_pos_num++) + for (int segment_num = output_projdata.get_min_segment_num(); segment_num <= output_projdata.get_max_segment_num() ; segment_num++) + { + cerr<is_basic(view_seg_nums)) + continue; - // ** first fill in the data ** - RelatedViewgrams - viewgrams = input_projdata.get_empty_related_viewgrams(view_seg_nums, - symmetries_ptr); - if (use_data_or_set_to_1) - { - viewgrams += - input_projdata.get_related_viewgrams(view_seg_nums, - symmetries_ptr); - } - else - { - viewgrams.fill(1.F); - } + // ** first fill in the data ** + RelatedViewgrams + viewgrams = input_projdata.get_empty_related_viewgrams(view_seg_nums,symmetries_ptr, + false,timing_pos_num); + if (use_data_or_set_to_1) + { + viewgrams += + input_projdata.get_related_viewgrams(view_seg_nums, + symmetries_ptr,false,timing_pos_num); + } + else + { + viewgrams.fill(1.F); + } - if (do_arc_correction && !apply_or_undo_correction) - { - error("Cannot undo arc-correction yet. Sorry."); - // TODO - //arc_correction_sptr->undo_arc_correction(output_viewgrams, viewgrams); - } - - if (do_scatter && !apply_or_undo_correction) - { - viewgrams += - scatter_projdata_ptr->get_related_viewgrams(view_seg_nums, - symmetries_ptr); - } - - if (do_randoms && apply_or_undo_correction) - { - viewgrams -= - randoms_projdata_ptr->get_related_viewgrams(view_seg_nums, - symmetries_ptr); - } -#if 0 - if (frame_num==-1) - { - int num_frames = frame_def.get_num_frames(); - for ( int i = 1; i<=num_frames; i++) - { - //cerr << "Doing frame " << i << endl; - const double start_frame = frame_def.get_start_time(i); - const double end_frame = frame_def.get_end_time(i); - //cerr << "Start time " << start_frame << endl; - //cerr << " End time " << end_frame << endl; - // ** normalisation ** - if (apply_or_undo_correction) - { - normalisation_ptr->apply(viewgrams,start_frame,end_frame); - } - else - { - normalisation_ptr->undo(viewgrams,start_frame,end_frame); - } - } - } - - - - else -#endif - { - const double start_frame = frame_defs.get_start_time(frame_num); - const double end_frame = frame_defs.get_end_time(frame_num); - if (apply_or_undo_correction) - { - normalisation_ptr->apply(viewgrams,start_frame,end_frame); - } - else - { - normalisation_ptr->undo(viewgrams,start_frame,end_frame); - } - } - if (do_scatter && apply_or_undo_correction) - { - viewgrams -= - scatter_projdata_ptr->get_related_viewgrams(view_seg_nums, - symmetries_ptr); - } - - if (do_randoms && !apply_or_undo_correction) - { - viewgrams += - randoms_projdata_ptr->get_related_viewgrams(view_seg_nums, - symmetries_ptr); - } - - if (do_arc_correction && apply_or_undo_correction) - { - viewgrams = arc_correction_sptr->do_arc_correction(viewgrams); - } - - // output - { - // Unfortunately, segment range in output_projdata and input_projdata can be - // different. - // Hence, output_projdata.set_related_viewgrams(viewgrams) would not work. - // So, we need an extra viewgrams object to take this into account. - // The trick relies on calling Array::operator+= instead of - // RelatedViewgrams::operator= - RelatedViewgrams - output_viewgrams = - output_projdata.get_empty_related_viewgrams(view_seg_nums, - symmetries_ptr); - output_viewgrams += viewgrams; - - if (!(output_projdata.set_related_viewgrams(viewgrams) == Succeeded::yes)) - { - warning("CorrectProjData: Error set_related_viewgrams\n"); - return Succeeded::no; - } - } + if (do_arc_correction && !apply_or_undo_correction) + { + error("Cannot undo arc-correction yet. Sorry."); + // TODO + //arc_correction_sptr->undo_arc_correction(output_viewgrams, viewgrams); + } + + if (do_scatter && !apply_or_undo_correction) + { + viewgrams += + scatter_projdata_ptr->get_related_viewgrams(view_seg_nums, + symmetries_ptr,false,timing_pos_num); + } + + if (do_randoms && apply_or_undo_correction) + { + viewgrams -= + randoms_projdata_ptr->get_related_viewgrams(view_seg_nums, + symmetries_ptr,false,timing_pos_num); + } + #if 0 + if (frame_num==-1) + { + int num_frames = frame_def.get_num_frames(); + for ( int i = 1; i<=num_frames; i++) + { + //cerr << "Doing frame " << i << endl; + const double start_frame = frame_def.get_start_time(i); + const double end_frame = frame_def.get_end_time(i); + //cerr << "Start time " << start_frame << endl; + //cerr << " End time " << end_frame << endl; + // ** normalisation ** + if (apply_or_undo_correction) + { + normalisation_ptr->apply(viewgrams,start_frame,end_frame); + } + else + { + normalisation_ptr->undo(viewgrams,start_frame,end_frame); + } + } + } + + + + else + #endif + { + const double start_frame = frame_defs.get_start_time(frame_num); + const double end_frame = frame_defs.get_end_time(frame_num); + if (apply_or_undo_correction) + { + normalisation_ptr->apply(viewgrams,start_frame,end_frame); + } + else + { + normalisation_ptr->undo(viewgrams,start_frame,end_frame); + } + } + if (do_scatter && apply_or_undo_correction) + { + viewgrams -= + scatter_projdata_ptr->get_related_viewgrams(view_seg_nums, + symmetries_ptr,false,timing_pos_num); + } + + if (do_randoms && !apply_or_undo_correction) + { + viewgrams += + randoms_projdata_ptr->get_related_viewgrams(view_seg_nums, + symmetries_ptr,false,timing_pos_num); + } + + if (do_arc_correction && apply_or_undo_correction) + { + viewgrams = arc_correction_sptr->do_arc_correction(viewgrams); + } + + // output + { + // Unfortunately, segment range in output_projdata and input_projdata can be + // different. + // Hence, output_projdata.set_related_viewgrams(viewgrams) would not work. + // So, we need an extra viewgrams object to take this into account. + // The trick relies on calling Array::operator+= instead of + // RelatedViewgrams::operator= + RelatedViewgrams + output_viewgrams = + output_projdata.get_empty_related_viewgrams(view_seg_nums, + symmetries_ptr,false,timing_pos_num); + output_viewgrams += viewgrams; + + if (!(output_projdata.set_related_viewgrams(viewgrams) == Succeeded::yes)) + { + warning("CorrectProjData: Error set_related_viewgrams\n"); + return Succeeded::no; + } + } - } + } - } + } return Succeeded::yes; } From 1cda37c4d583ffab32c8ab5add449c31cb983926 Mon Sep 17 00:00:00 2001 From: Elise Date: Tue, 11 Apr 2017 12:43:14 +0100 Subject: [PATCH 068/509] Fix for sensitivity calculation (use of non-TOF backprojector) and calculate_attenuation_coefficients (will use non-TOF template if a TOF template is provided as input + warning) --- src/include/stir/ProjDataInfo.h | 3 ++ src/include/stir/ProjDataInfo.inl | 9 ++++ .../recon_buildblock/BackProjectorByBin.h | 2 + .../BackProjectorByBinUsingInterpolation.h | 2 + .../BackProjectorByBinUsingProjMatrixByBin.h | 5 +- ...elihoodWithLinearModelForMeanAndProjData.h | 5 +- .../PostsmoothingBackProjectorByBin.h | 2 + .../BackProjectorByBinUsingInterpolation.cxx | 6 +++ ...BackProjectorByBinUsingProjMatrixByBin.cxx | 7 +++ ...ihoodWithLinearModelForMeanAndProjData.cxx | 52 +++++++++---------- .../PostsmoothingBackProjectorByBin.cxx | 6 +++ .../calculate_attenuation_coefficients.cxx | 9 +++- 12 files changed, 74 insertions(+), 34 deletions(-) diff --git a/src/include/stir/ProjDataInfo.h b/src/include/stir/ProjDataInfo.h index f7111eb507..0a8002fe4b 100644 --- a/src/include/stir/ProjDataInfo.h +++ b/src/include/stir/ProjDataInfo.h @@ -125,6 +125,9 @@ class ProjDataInfo //! Like clone() but return a shared_ptr inline shared_ptr create_shared_clone() const; + //! Similar to create_shared_clone() but returns a non-tof version of ProjDataInfo setting tof mashing factor = 0 + inline shared_ptr create_non_tof_clone() const; + //! Destructor virtual ~ProjDataInfo() {} diff --git a/src/include/stir/ProjDataInfo.inl b/src/include/stir/ProjDataInfo.inl index 462b809aa1..812e3fc172 100644 --- a/src/include/stir/ProjDataInfo.inl +++ b/src/include/stir/ProjDataInfo.inl @@ -40,6 +40,15 @@ create_shared_clone() const return sptr; } +shared_ptr +ProjDataInfo:: +create_non_tof_clone() const +{ + shared_ptr sptr(this->clone()); + sptr->set_tof_mash_factor(0); // tof mashing factor = 0 is a trigger for non-tof data + return sptr; +} + int ProjDataInfo::get_num_segments() const { return (max_axial_pos_per_seg.get_length());} diff --git a/src/include/stir/recon_buildblock/BackProjectorByBin.h b/src/include/stir/recon_buildblock/BackProjectorByBin.h index ed5fa5c4d6..5f955435c6 100644 --- a/src/include/stir/recon_buildblock/BackProjectorByBin.h +++ b/src/include/stir/recon_buildblock/BackProjectorByBin.h @@ -110,6 +110,8 @@ class BackProjectorByBin : void back_project(DiscretisedDensity<3,float>&, const Bin&); + virtual BackProjectorByBin* clone() const =0; + protected: diff --git a/src/include/stir/recon_buildblock/BackProjectorByBinUsingInterpolation.h b/src/include/stir/recon_buildblock/BackProjectorByBinUsingInterpolation.h index 63c82e45d5..8dce8fb6a5 100644 --- a/src/include/stir/recon_buildblock/BackProjectorByBinUsingInterpolation.h +++ b/src/include/stir/recon_buildblock/BackProjectorByBinUsingInterpolation.h @@ -180,6 +180,8 @@ class BackProjectorByBinUsingInterpolation : */ void use_piecewise_linear_interpolation(const bool use_piecewise_linear_interpolation); + BackProjectorByBinUsingInterpolation* clone() const; + private: // KT 20/06/2001 changed type to enable use of more methods diff --git a/src/include/stir/recon_buildblock/BackProjectorByBinUsingProjMatrixByBin.h b/src/include/stir/recon_buildblock/BackProjectorByBinUsingProjMatrixByBin.h index 728ba92281..9b1d7cf9ee 100644 --- a/src/include/stir/recon_buildblock/BackProjectorByBinUsingProjMatrixByBin.h +++ b/src/include/stir/recon_buildblock/BackProjectorByBinUsingProjMatrixByBin.h @@ -87,10 +87,9 @@ class BackProjectorByBinUsingProjMatrixByBin: shared_ptr & get_proj_matrix_sptr(){ return proj_matrix_ptr ;} - - BackProjectorByBin* get_original_back_projector() const; - void enable_tof(ProjMatrixElemsForOneBin* ); + + BackProjectorByBinUsingProjMatrixByBin* clone() const; protected: diff --git a/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.h b/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.h index 153420d90a..ec7c8a33be 100644 --- a/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.h +++ b/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.h @@ -316,6 +316,9 @@ public RegisteredParsingObject projector_pair_ptr; + //! Backprojector used for sensitivity computation + shared_ptr sens_backprojector_sptr; + //! signals whether to zero the data in the end planes of the projection data bool zero_seg0_end_planes; @@ -364,7 +367,7 @@ public RegisteredParsingObject symmetries_sptr; void - add_view_seg_to_sensitivity(TargetT& sensitivity, const ViewSegmentNumbers& view_seg_nums, const int timing_pos_num = 0) const; + add_view_seg_to_sensitivity(TargetT& sensitivity, const ViewSegmentNumbers& view_seg_nums) const; }; #ifdef STIR_MPI diff --git a/src/include/stir/recon_buildblock/PostsmoothingBackProjectorByBin.h b/src/include/stir/recon_buildblock/PostsmoothingBackProjectorByBin.h index c2e68e7105..629845b5bc 100644 --- a/src/include/stir/recon_buildblock/PostsmoothingBackProjectorByBin.h +++ b/src/include/stir/recon_buildblock/PostsmoothingBackProjectorByBin.h @@ -81,6 +81,8 @@ class PostsmoothingBackProjectorByBin : BackProjectorByBin* get_original_back_projector_ptr() const; + PostsmoothingBackProjectorByBin* clone() const; + private: shared_ptr original_back_projector_ptr; diff --git a/src/recon_buildblock/BackProjectorByBinUsingInterpolation.cxx b/src/recon_buildblock/BackProjectorByBinUsingInterpolation.cxx index 0ee3950e3d..f58c2b5962 100644 --- a/src/recon_buildblock/BackProjectorByBinUsingInterpolation.cxx +++ b/src/recon_buildblock/BackProjectorByBinUsingInterpolation.cxx @@ -183,6 +183,12 @@ use_exact_Jacobian(const bool use_exact_Jacobian) use_exact_Jacobian_now = use_exact_Jacobian; } +BackProjectorByBinUsingInterpolation* +BackProjectorByBinUsingInterpolation:: +clone() const +{ + return new BackProjectorByBinUsingInterpolation(*this); +} void BackProjectorByBinUsingInterpolation:: diff --git a/src/recon_buildblock/BackProjectorByBinUsingProjMatrixByBin.cxx b/src/recon_buildblock/BackProjectorByBinUsingProjMatrixByBin.cxx index 78842daff6..4e1837264a 100644 --- a/src/recon_buildblock/BackProjectorByBinUsingProjMatrixByBin.cxx +++ b/src/recon_buildblock/BackProjectorByBinUsingProjMatrixByBin.cxx @@ -271,4 +271,11 @@ enable_tof(ProjMatrixElemsForOneBin * for_row) tof_enabled = true; } +BackProjectorByBinUsingProjMatrixByBin* +BackProjectorByBinUsingProjMatrixByBin:: +clone() const +{ + return new BackProjectorByBinUsingProjMatrixByBin(*this); +} + END_NAMESPACE_STIR diff --git a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx index 64ae469621..17d74351e0 100644 --- a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx +++ b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx @@ -620,6 +620,10 @@ set_up_before_sensitivity(shared_ptr const& target_sptr) this->projector_pair_ptr->set_up(proj_data_info_sptr, target_sptr); + + // sets non-tof backprojector for sensitivity calculation (clone of the back_projector + set projdatainfo to non-tof) + this->sens_backprojector_sptr.reset(projector_pair_ptr->get_back_projector_sptr()->clone()); + this->sens_backprojector_sptr->set_up(proj_data_info_sptr->create_non_tof_clone(), target_sptr); // TODO check compatibility between symmetries for forward and backprojector this->symmetries_sptr.reset( @@ -765,43 +769,36 @@ add_subset_sensitivity(TargetT& sensitivity, const int subset_num) const { const int min_segment_num = -this->max_segment_num_to_process; const int max_segment_num = this->max_segment_num_to_process; - const int min_timing_pos_num = -this->max_timing_pos_num_to_process; - const int max_timing_pos_num = this->max_timing_pos_num_to_process; // warning: has to be same as subset scheme used as in distributable_computation - for (int timing_pos_num = min_timing_pos_num; timing_pos_num <= max_timing_pos_num; ++timing_pos_num) - { - 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, timing_pos_num); - } - // cerr<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 PoissonLogLikelihoodWithLinearModelForMeanAndProjData:: -add_view_seg_to_sensitivity(TargetT& sensitivity, const ViewSegmentNumbers& view_seg_nums , const int timing_pos_num) const +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, - false, - timing_pos_num); + this->symmetries_sptr); viewgrams.fill(1.F); // find efficiencies { @@ -819,8 +816,7 @@ add_view_seg_to_sensitivity(TargetT& sensitivity, const ViewSegmentNumbers& view const int max_ax_pos_num = viewgrams.get_max_axial_pos_num() - range_to_zero; - projector_pair_ptr->get_back_projector_sptr()->back_project(sensitivity, viewgrams, - min_ax_pos_num, max_ax_pos_num); + this->sens_backprojector_sptr->back_project(sensitivity, viewgrams, min_ax_pos_num, max_ax_pos_num); } } diff --git a/src/recon_buildblock/PostsmoothingBackProjectorByBin.cxx b/src/recon_buildblock/PostsmoothingBackProjectorByBin.cxx index 696f1f8229..5f3dce67a6 100644 --- a/src/recon_buildblock/PostsmoothingBackProjectorByBin.cxx +++ b/src/recon_buildblock/PostsmoothingBackProjectorByBin.cxx @@ -85,6 +85,12 @@ PostsmoothingBackProjectorByBin::get_original_back_projector_ptr() const return original_back_projector_ptr.get(); } +PostsmoothingBackProjectorByBin* +PostsmoothingBackProjectorByBin::clone() const +{ + return new PostsmoothingBackProjectorByBin(*this); +} + void PostsmoothingBackProjectorByBin:: init_filtered_density_image(DiscretisedDensity<3, float> &density) { diff --git a/src/utilities/calculate_attenuation_coefficients.cxx b/src/utilities/calculate_attenuation_coefficients.cxx index 1c637a6220..c4f0715476 100644 --- a/src/utilities/calculate_attenuation_coefficients.cxx +++ b/src/utilities/calculate_attenuation_coefficients.cxx @@ -126,11 +126,16 @@ main (int argc, char * argv[]) cerr << "\n\nForward projector used:\n" << forw_projector_ptr->parameter_info(); + if (template_proj_data_ptr->get_proj_data_info_ptr()->is_tof_data()) + { + warning("The scanner template provided contains timing information. The calculation of the attenuation coefficients will not take them into consideration.\n"); + } + const std::string output_file_name = argv[1]; shared_ptr out_proj_data_ptr( new ProjDataInterfile(template_proj_data_ptr->get_exam_info_sptr(),// TODO this should possibly come from the image, or say it's an ACF File - template_proj_data_ptr->get_proj_data_info_ptr()->create_shared_clone(), + template_proj_data_ptr->get_proj_data_info_ptr()->create_non_tof_clone(), output_file_name, std::ios::in|std::ios::out|std::ios::trunc)); @@ -143,7 +148,7 @@ main (int argc, char * argv[]) forw_projector_ptr)); if ( - normalisation_ptr->set_up(template_proj_data_ptr->get_proj_data_info_ptr()->create_shared_clone()) + normalisation_ptr->set_up(template_proj_data_ptr->get_proj_data_info_ptr()->create_non_tof_clone()) != Succeeded::yes) { warning("calculate_attenuation_coefficients: set-up of normalisation failed\n"); From 3d21216bcdc5aa2af4d1b32a28b07f157a8c2f18 Mon Sep 17 00:00:00 2001 From: Elise Date: Tue, 11 Apr 2017 18:58:19 +0100 Subject: [PATCH 069/509] Fix for sensitivity calculation 2 --- src/recon_buildblock/ProjMatrixByBin.cxx | 5 +++++ src/recon_buildblock/ProjMatrixByBinUsingRayTracing.cxx | 2 -- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/recon_buildblock/ProjMatrixByBin.cxx b/src/recon_buildblock/ProjMatrixByBin.cxx index d25ef353f2..554cbc1f67 100644 --- a/src/recon_buildblock/ProjMatrixByBin.cxx +++ b/src/recon_buildblock/ProjMatrixByBin.cxx @@ -148,6 +148,11 @@ set_up( if (proj_data_info_sptr->is_tof_data()) enable_tof(proj_data_info_sptr,true); + else if (this->tof_enabled) // this case happens when we transform a TOF projector into a non-TOF projector + { + tof_enabled = false; + this->proj_data_info_sptr = proj_data_info_sptr; + } this->cache_collection.recycle(); this->cache_collection.resize(min_view_num, max_view_num); diff --git a/src/recon_buildblock/ProjMatrixByBinUsingRayTracing.cxx b/src/recon_buildblock/ProjMatrixByBinUsingRayTracing.cxx index 1935bcb8ad..0702526c1f 100644 --- a/src/recon_buildblock/ProjMatrixByBinUsingRayTracing.cxx +++ b/src/recon_buildblock/ProjMatrixByBinUsingRayTracing.cxx @@ -542,8 +542,6 @@ calculate_proj_matrix_elems_for_one_bin( } const Bin bin = lor.get_bin(); - assert(bin.timing_pos_num() >= proj_data_info_ptr->get_min_tof_pos_num()); - assert(bin.timing_pos_num() <= proj_data_info_ptr->get_max_tof_pos_num()); assert(bin.segment_num() >= proj_data_info_ptr->get_min_segment_num()); assert(bin.segment_num() <= proj_data_info_ptr->get_max_segment_num()); From 22dbe615bd22097404968fb22420783c7bc24c30 Mon Sep 17 00:00:00 2001 From: Elise Date: Thu, 13 Apr 2017 21:17:52 +0100 Subject: [PATCH 070/509] Fix for clone() + clean-up + removed duplicate of proj_data_info in ProjMatrixByBinUsingRayTracing --- ...orMeanAndListModeDataWithProjMatrixByBin.h | 5 ++- .../stir/recon_buildblock/ProjMatrixByBin.h | 4 +- .../ProjMatrixByBinFromFile.h | 2 + .../recon_buildblock/ProjMatrixByBinSPECTUB.h | 2 + .../ProjMatrixByBinUsingInterpolation.h | 2 + .../ProjMatrixByBinUsingRayTracing.h | 5 +-- ...BackProjectorByBinUsingProjMatrixByBin.cxx | 4 +- ...MeanAndListModeDataWithProjMatrixByBin.cxx | 38 ++++++++-------- src/recon_buildblock/ProjMatrixByBin.cxx | 2 +- .../ProjMatrixByBinFromFile.cxx | 6 +++ .../ProjMatrixByBinSPECTUB.cxx | 6 +++ .../ProjMatrixByBinUsingInterpolation.cxx | 7 +++ .../ProjMatrixByBinUsingRayTracing.cxx | 45 ++++++++++--------- 13 files changed, 81 insertions(+), 47 deletions(-) diff --git a/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.h b/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.h index 5a224a7ae2..25606ea8df 100644 --- a/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.h +++ b/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.h @@ -115,6 +115,9 @@ typedef RegisteredParsingObject projector_pair_ptr; + //! Backprojector used for sensitivity computation + shared_ptr sens_backprojector_sptr; + //! points to the additive projection data shared_ptr additive_proj_data_sptr; @@ -133,7 +136,7 @@ typedef RegisteredParsingObject >& density_info_ptr // TODO should be Info only ) = 0; + virtual ProjMatrixByBin* clone() const = 0; + //! get a pointer to an object encoding all symmetries that are used by this ProjMatrixByBin inline const DataSymmetriesForBins* get_symmetries_ptr() const; //! get a shared_ptr to an object encoding all symmetries that are used by this ProjMatrixByBin @@ -229,7 +231,7 @@ class ProjMatrixByBin : //! cartesian coordinates of each voxel. shared_ptr > image_info_sptr; - //! We need a local copy of the proj_data_info to get the integration boundaries. + //! We need a local copy of the proj_data_info to get the integration boundaries and RayTracing shared_ptr proj_data_info_sptr; //! The method to store data in the cache. diff --git a/src/include/stir/recon_buildblock/ProjMatrixByBinFromFile.h b/src/include/stir/recon_buildblock/ProjMatrixByBinFromFile.h index 5332fe8368..12e6c38621 100644 --- a/src/include/stir/recon_buildblock/ProjMatrixByBinFromFile.h +++ b/src/include/stir/recon_buildblock/ProjMatrixByBinFromFile.h @@ -108,6 +108,8 @@ static Succeeded const shared_ptr >& density_info_ptr // TODO should be Info only ); + virtual ProjMatrixByBinFromFile* clone() const; + private: std::string parsed_version; diff --git a/src/include/stir/recon_buildblock/ProjMatrixByBinSPECTUB.h b/src/include/stir/recon_buildblock/ProjMatrixByBinSPECTUB.h index 5ad639418d..2e9739d076 100644 --- a/src/include/stir/recon_buildblock/ProjMatrixByBinSPECTUB.h +++ b/src/include/stir/recon_buildblock/ProjMatrixByBinSPECTUB.h @@ -107,6 +107,8 @@ class ProjMatrixByBinSPECTUB : const shared_ptr >& density_info_ptr // TODO should be Info only ); + virtual ProjMatrixByBinSPECTUB* clone() const; + private: // parameters that will be parsed diff --git a/src/include/stir/recon_buildblock/ProjMatrixByBinUsingInterpolation.h b/src/include/stir/recon_buildblock/ProjMatrixByBinUsingInterpolation.h index 62a9da20a3..1c567fe76f 100644 --- a/src/include/stir/recon_buildblock/ProjMatrixByBinUsingInterpolation.h +++ b/src/include/stir/recon_buildblock/ProjMatrixByBinUsingInterpolation.h @@ -78,6 +78,8 @@ public : const shared_ptr >& density_info_ptr // TODO should be Info only ); + virtual ProjMatrixByBinUsingInterpolation* clone() const; + private: bool do_symmetry_90degrees_min_phi; bool do_symmetry_180degrees_min_phi; diff --git a/src/include/stir/recon_buildblock/ProjMatrixByBinUsingRayTracing.h b/src/include/stir/recon_buildblock/ProjMatrixByBinUsingRayTracing.h index a274c2e6bf..cad31ddc3f 100644 --- a/src/include/stir/recon_buildblock/ProjMatrixByBinUsingRayTracing.h +++ b/src/include/stir/recon_buildblock/ProjMatrixByBinUsingRayTracing.h @@ -140,6 +140,8 @@ public : const shared_ptr >& density_info_sptr_v // TODO should be Info only ); + virtual ProjMatrixByBinUsingRayTracing* clone() const; + //! \name If a cylindrical FOV or the whole image will be handled //!@{ bool get_restrict_to_cylindrical_FOV() const; @@ -199,9 +201,6 @@ public : CartesianCoordinate3D min_index; CartesianCoordinate3D max_index; - shared_ptr proj_data_info_ptr; - - virtual void calculate_proj_matrix_elems_for_one_bin( ProjMatrixElemsForOneBin&) const; diff --git a/src/recon_buildblock/BackProjectorByBinUsingProjMatrixByBin.cxx b/src/recon_buildblock/BackProjectorByBinUsingProjMatrixByBin.cxx index 4e1837264a..08a0f53021 100644 --- a/src/recon_buildblock/BackProjectorByBinUsingProjMatrixByBin.cxx +++ b/src/recon_buildblock/BackProjectorByBinUsingProjMatrixByBin.cxx @@ -275,7 +275,9 @@ BackProjectorByBinUsingProjMatrixByBin* BackProjectorByBinUsingProjMatrixByBin:: clone() const { - return new BackProjectorByBinUsingProjMatrixByBin(*this); + BackProjectorByBinUsingProjMatrixByBin* sptr(new BackProjectorByBinUsingProjMatrixByBin(*this)); + sptr->proj_matrix_ptr.reset(this->proj_matrix_ptr->clone()); + return sptr; } END_NAMESPACE_STIR diff --git a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx index 928f706a61..40dd8208b4 100644 --- a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx +++ b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx @@ -232,6 +232,10 @@ set_up_before_sensitivity(shared_ptr const& target_sptr) this->projector_pair_ptr->enable_tof(proj_data_info_cyl_sptr->create_shared_clone(), this->use_tof); } + // sets non-tof backprojector for sensitivity calculation (clone of the back_projector + set projdatainfo to non-tof) + this->sens_backprojector_sptr.reset(projector_pair_ptr->get_back_projector_sptr()->clone()); + this->sens_backprojector_sptr->set_up(proj_data_info_cyl_sptr->create_non_tof_clone(), target_sptr); + if (is_null_ptr(this->normalisation_sptr)) { warning("Invalid normalisation object"); @@ -369,38 +373,33 @@ add_subset_sensitivity(TargetT& sensitivity, const int subset_num) const const int min_segment_num = proj_data_info_cyl_sptr->get_min_segment_num(); const int max_segment_num = proj_data_info_cyl_sptr->get_max_segment_num(); - const int min_timing_pos_num = proj_data_info_cyl_sptr->get_min_tof_pos_num(); - const int max_timing_pos_num = proj_data_info_cyl_sptr->get_max_tof_pos_num(); // warning: has to be same as subset scheme used as in distributable_computation - for (int timing_pos_num = min_timing_pos_num; timing_pos_num <= min_timing_pos_num; ++timing_pos_num) - { - for (int segment_num = min_segment_num; segment_num <= max_segment_num; ++segment_num) + for (int segment_num = min_segment_num; segment_num <= max_segment_num; ++segment_num) + { + for (int view = proj_data_info_cyl_sptr->get_min_view_num() + subset_num; + view <= proj_data_info_cyl_sptr->get_max_view_num(); + view += this->num_subsets) { - for (int view = proj_data_info_cyl_sptr->get_min_view_num() + subset_num; - view <= proj_data_info_cyl_sptr->get_max_view_num(); - view += this->num_subsets) - { - const ViewSegmentNumbers view_segment_num(view, segment_num); - - if (! this->projector_pair_ptr->get_symmetries_used()->is_basic(view_segment_num)) - continue; - this->add_view_seg_to_sensitivity(sensitivity, view_segment_num, timing_pos_num); - } + const ViewSegmentNumbers view_segment_num(view, segment_num); + + if (! this->projector_pair_ptr->get_symmetries_used()->is_basic(view_segment_num)) + continue; + this->add_view_seg_to_sensitivity(sensitivity, view_segment_num); } - } + } } template void PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin:: -add_view_seg_to_sensitivity(TargetT& sensitivity, const ViewSegmentNumbers& view_seg_nums, const int timing_pos_num) const +add_view_seg_to_sensitivity(TargetT& sensitivity, const ViewSegmentNumbers& view_seg_nums) const { shared_ptr symmetries_used (this->projector_pair_ptr->get_symmetries_used()->clone()); RelatedViewgrams viewgrams = - proj_data_info_cyl_sptr->get_empty_related_viewgrams(view_seg_nums,symmetries_used, false, timing_pos_num); + proj_data_info_cyl_sptr->get_empty_related_viewgrams(view_seg_nums,symmetries_used); viewgrams.fill(1.F); // find efficiencies @@ -416,8 +415,7 @@ add_view_seg_to_sensitivity(TargetT& sensitivity, const ViewSegmentNumbers& view const int max_ax_pos_num = viewgrams.get_max_axial_pos_num(); - this->projector_pair_ptr->get_back_projector_sptr()-> - back_project(sensitivity, viewgrams, + this->sens_backprojector_sptr->back_project(sensitivity, viewgrams, min_ax_pos_num, max_ax_pos_num); } diff --git a/src/recon_buildblock/ProjMatrixByBin.cxx b/src/recon_buildblock/ProjMatrixByBin.cxx index 554cbc1f67..28ffb96d35 100644 --- a/src/recon_buildblock/ProjMatrixByBin.cxx +++ b/src/recon_buildblock/ProjMatrixByBin.cxx @@ -148,7 +148,7 @@ set_up( if (proj_data_info_sptr->is_tof_data()) enable_tof(proj_data_info_sptr,true); - else if (this->tof_enabled) // this case happens when we transform a TOF projector into a non-TOF projector + else { tof_enabled = false; this->proj_data_info_sptr = proj_data_info_sptr; diff --git a/src/recon_buildblock/ProjMatrixByBinFromFile.cxx b/src/recon_buildblock/ProjMatrixByBinFromFile.cxx index fec274a423..6e60899418 100644 --- a/src/recon_buildblock/ProjMatrixByBinFromFile.cxx +++ b/src/recon_buildblock/ProjMatrixByBinFromFile.cxx @@ -241,6 +241,12 @@ set_up( error("Something wrong reading the matrix from file. Exiting."); } +ProjMatrixByBinFromFile* +ProjMatrixByBinFromFile::clone() const +{ + return new ProjMatrixByBinFromFile(*this); +} + // anonymous namespace for local functions namespace { diff --git a/src/recon_buildblock/ProjMatrixByBinSPECTUB.cxx b/src/recon_buildblock/ProjMatrixByBinSPECTUB.cxx index 833f2b89a5..4ce6ef8c94 100644 --- a/src/recon_buildblock/ProjMatrixByBinSPECTUB.cxx +++ b/src/recon_buildblock/ProjMatrixByBinSPECTUB.cxx @@ -575,6 +575,12 @@ set_up( this->already_setup= true; } +ProjMatrixByBinSPECTUB* +ProjMatrixByBinSPECTUB::clone() const +{ + return new ProjMatrixByBinSPECTUB(*this); +} + ProjMatrixByBinSPECTUB:: ~ProjMatrixByBinSPECTUB() { diff --git a/src/recon_buildblock/ProjMatrixByBinUsingInterpolation.cxx b/src/recon_buildblock/ProjMatrixByBinUsingInterpolation.cxx index fe4bf6e3b6..24696971fc 100644 --- a/src/recon_buildblock/ProjMatrixByBinUsingInterpolation.cxx +++ b/src/recon_buildblock/ProjMatrixByBinUsingInterpolation.cxx @@ -161,6 +161,13 @@ set_up( } } } + +ProjMatrixByBinUsingInterpolation* +ProjMatrixByBinUsingInterpolation::clone() const +{ + return new ProjMatrixByBinUsingInterpolation(*this); +} + // point should be w.r.t. middle of the scanner! static inline void diff --git a/src/recon_buildblock/ProjMatrixByBinUsingRayTracing.cxx b/src/recon_buildblock/ProjMatrixByBinUsingRayTracing.cxx index 0702526c1f..8e45163295 100644 --- a/src/recon_buildblock/ProjMatrixByBinUsingRayTracing.cxx +++ b/src/recon_buildblock/ProjMatrixByBinUsingRayTracing.cxx @@ -275,8 +275,7 @@ set_up( { ProjMatrixByBin::set_up(proj_data_info_sptr_v, density_info_sptr_v); - proj_data_info_ptr= proj_data_info_sptr_v; - image_info_sptr.reset( + image_info_sptr.reset( dynamic_cast* > (density_info_sptr_v->clone() )); // const VoxelsOnCartesianGrid * image_info_ptr = // dynamic_cast*> (density_info_ptr.get()); @@ -289,7 +288,7 @@ set_up( image_info_sptr->get_regular_range(min_index, max_index); symmetries_sptr.reset( - new DataSymmetriesForBins_PET_CartesianGrid(proj_data_info_ptr, + new DataSymmetriesForBins_PET_CartesianGrid(proj_data_info_sptr, density_info_sptr_v, do_symmetry_90degrees_min_phi, do_symmetry_180degrees_min_phi, @@ -297,7 +296,7 @@ set_up( do_symmetry_swap_s, do_symmetry_shift_z)); const float sampling_distance_of_adjacent_LORs_xy = - proj_data_info_ptr->get_sampling_in_s(Bin(0,0,0,0)); + proj_data_info_sptr->get_sampling_in_s(Bin(0,0,0,0)); if(sampling_distance_of_adjacent_LORs_xy/num_tangential_LORs > voxel_size.x() + 1.E-3 || sampling_distance_of_adjacent_LORs_xy/num_tangential_LORs > voxel_size.y() + 1.E-3) @@ -313,7 +312,7 @@ set_up( if (use_actual_detector_boundaries) { const ProjDataInfoCylindricalNoArcCorr * proj_data_info_cyl_ptr = - dynamic_cast(proj_data_info_ptr.get()); + dynamic_cast(proj_data_info_sptr.get()); if (proj_data_info_cyl_ptr== 0) { warning("ProjMatrixByBinUsingRayTracing: use_actual_detector_boundaries" @@ -364,6 +363,12 @@ set_up( this->clear_cache(); }; +ProjMatrixByBinUsingRayTracing* +ProjMatrixByBinUsingRayTracing::clone() const +{ + return new ProjMatrixByBinUsingRayTracing(*this); +} + /* this is used when (tantheta==0 && sampling_distance_of_adjacent_LORs_z==2*voxel_size.z()) it adds two adjacents z with their half value @@ -542,13 +547,13 @@ calculate_proj_matrix_elems_for_one_bin( } const Bin bin = lor.get_bin(); - assert(bin.segment_num() >= proj_data_info_ptr->get_min_segment_num()); - assert(bin.segment_num() <= proj_data_info_ptr->get_max_segment_num()); + assert(bin.segment_num() >= proj_data_info_sptr->get_min_segment_num()); + assert(bin.segment_num() <= proj_data_info_sptr->get_max_segment_num()); assert(lor.size() == 0); float phi; - float s_in_mm = proj_data_info_ptr->get_s(bin); + float s_in_mm = proj_data_info_sptr->get_s(bin); /* Implementation note. KT initialised s_in_mm above instead of in the if because this meant that gcc 3.0.1 generated identical results to the previous version of this file. @@ -562,19 +567,19 @@ calculate_proj_matrix_elems_for_one_bin( */ if (!use_actual_detector_boundaries) { - phi = proj_data_info_ptr->get_phi(bin); - //s_in_mm = proj_data_info_ptr->get_s(bin); + phi = proj_data_info_sptr->get_phi(bin); + //s_in_mm = proj_data_info_sptr->get_s(bin); } else { // can be static_cast later on const ProjDataInfoCylindricalNoArcCorr& proj_data_info_noarccor = - dynamic_cast(*proj_data_info_ptr); + dynamic_cast(*proj_data_info_sptr); // TODO check on 180 degrees for views const int num_detectors = - proj_data_info_ptr->get_scanner_ptr()->get_num_detectors_per_ring(); + proj_data_info_sptr->get_scanner_ptr()->get_num_detectors_per_ring(); const float ring_radius = - proj_data_info_ptr->get_scanner_ptr()->get_effective_ring_radius(); + proj_data_info_sptr->get_scanner_ptr()->get_effective_ring_radius(); int det_num1=0, det_num2=0; proj_data_info_noarccor. @@ -583,13 +588,13 @@ calculate_proj_matrix_elems_for_one_bin( bin.view_num(), bin.tangential_pos_num()); phi = static_cast((det_num1+det_num2)*_PI/num_detectors-_PI/2); - const float old_phi=proj_data_info_ptr->get_phi(bin); + const float old_phi=proj_data_info_sptr->get_phi(bin); if (fabs(phi-old_phi)>2*_PI/num_detectors) warning("view %d old_phi %g new_phi %g\n",bin.view_num(), old_phi, phi); s_in_mm = static_cast(ring_radius*sin((det_num1-det_num2)*_PI/num_detectors+_PI/2)); - const float old_s_in_mm=proj_data_info_ptr->get_s(bin); - if (fabs(s_in_mm-old_s_in_mm)>proj_data_info_ptr->get_sampling_in_s(bin)*.0001) + const float old_s_in_mm=proj_data_info_sptr->get_s(bin); + if (fabs(s_in_mm-old_s_in_mm)>proj_data_info_sptr->get_sampling_in_s(bin)*.0001) warning("tangential_pos_num %d old_s_in_mm %g new_s_in_mm %g\n",bin.tangential_pos_num(), old_s_in_mm, s_in_mm); } @@ -597,12 +602,12 @@ calculate_proj_matrix_elems_for_one_bin( const float cphi = cos(phi); const float sphi = sin(phi); - const float tantheta = proj_data_info_ptr->get_tantheta(bin); + const float tantheta = proj_data_info_sptr->get_tantheta(bin); const float costheta = 1/sqrt(1+square(tantheta)); - const float t_in_mm = proj_data_info_ptr->get_t(bin); + const float t_in_mm = proj_data_info_sptr->get_t(bin); const float sampling_distance_of_adjacent_LORs_z = - proj_data_info_ptr->get_sampling_in_t(bin)/costheta; + proj_data_info_sptr->get_sampling_in_t(bin)/costheta; // find number of LORs we have to take, such that we don't miss voxels @@ -697,7 +702,7 @@ calculate_proj_matrix_elems_for_one_bin( // interleaved case has a sampling which is twice as high const float s_inc = (!use_actual_detector_boundaries ? 1 : 2) * - proj_data_info_ptr->get_sampling_in_s(bin)/num_tangential_LORs; + proj_data_info_sptr->get_sampling_in_s(bin)/num_tangential_LORs; float current_s_in_mm = s_in_mm - s_inc*(num_tangential_LORs-1)/2.F; for (int s_LOR_num=1; s_LOR_num<=num_tangential_LORs; ++s_LOR_num, current_s_in_mm+=s_inc) From a177b6012d36b590be72a76b4a19a2a3fa26eea5 Mon Sep 17 00:00:00 2001 From: Elise Date: Sat, 6 May 2017 19:03:23 +0100 Subject: [PATCH 071/509] Fix to add the possibility to use a TOF template to calculate sensitivity images if specified in the recon par file --- ...orMeanAndListModeDataWithProjMatrixByBin.h | 3 ++ ...elihoodWithLinearModelForMeanAndProjData.h | 3 ++ ...MeanAndListModeDataWithProjMatrixByBin.cxx | 51 ++++++++++-------- ...ihoodWithLinearModelForMeanAndProjData.cxx | 53 +++++++++++-------- src/utilities/poisson_noise.cxx | 2 +- 5 files changed, 67 insertions(+), 45 deletions(-) diff --git a/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.h b/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.h index 25606ea8df..ea82529356 100644 --- a/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.h +++ b/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.h @@ -106,6 +106,9 @@ typedef RegisteredParsingObjectnormalisation_sptr.reset(new TrivialBinNormalisation); this->do_time_frame = false; + this->use_tofsens = false; this->use_projectors = false; } @@ -102,6 +103,7 @@ initialise_keymap() base_type::initialise_keymap(); this->parser.add_start_key("PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin Parameters"); this->parser.add_stop_key("End PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin Parameters"); + this->parser.add_key("use time-of-flight sensitivities", &this->use_tofsens); this->parser.add_key("max ring difference num to process", &this->max_ring_difference_num_to_process); this->parser.add_parsing_key("Matrix type", &this->PM_sptr); this->parser.add_parsing_key("Projector pair type", &this->projector_pair_ptr); @@ -234,7 +236,8 @@ set_up_before_sensitivity(shared_ptr const& target_sptr) // sets non-tof backprojector for sensitivity calculation (clone of the back_projector + set projdatainfo to non-tof) this->sens_backprojector_sptr.reset(projector_pair_ptr->get_back_projector_sptr()->clone()); - this->sens_backprojector_sptr->set_up(proj_data_info_cyl_sptr->create_non_tof_clone(), target_sptr); + if (!this->use_tofsens) + this->sens_backprojector_sptr->set_up(proj_data_info_cyl_sptr->create_non_tof_clone(), target_sptr); if (is_null_ptr(this->normalisation_sptr)) { @@ -395,29 +398,35 @@ void PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin:: add_view_seg_to_sensitivity(TargetT& sensitivity, const ViewSegmentNumbers& view_seg_nums) const { - shared_ptr symmetries_used - (this->projector_pair_ptr->get_symmetries_used()->clone()); + for (int timing_pos_num = this->proj_data_info_cyl_sptr->get_min_tof_pos_num(); + timing_pos_num <= this->proj_data_info_cyl_sptr->get_max_tof_pos_num(); + ++timing_pos_num) + { + shared_ptr symmetries_used + (this->projector_pair_ptr->get_symmetries_used()->clone()); - RelatedViewgrams viewgrams = - proj_data_info_cyl_sptr->get_empty_related_viewgrams(view_seg_nums,symmetries_used); + RelatedViewgrams viewgrams = + proj_data_info_cyl_sptr->get_empty_related_viewgrams( + view_seg_nums, symmetries_used, false, timing_pos_num); - viewgrams.fill(1.F); - // find efficiencies - { - const double start_frame = this->frame_defs.get_start_time(this->current_frame_num); - const double end_frame = this->frame_defs.get_end_time(this->current_frame_num); - this->normalisation_sptr->undo(viewgrams,start_frame,end_frame); - } - // backproject - { - const int min_ax_pos_num = - viewgrams.get_min_axial_pos_num(); - const int max_ax_pos_num = - viewgrams.get_max_axial_pos_num(); + viewgrams.fill(1.F); + // find efficiencies + { + const double start_frame = this->frame_defs.get_start_time(this->current_frame_num); + const double end_frame = this->frame_defs.get_end_time(this->current_frame_num); + this->normalisation_sptr->undo(viewgrams, start_frame, end_frame); + } + // backproject + { + const int min_ax_pos_num = + viewgrams.get_min_axial_pos_num(); + const int max_ax_pos_num = + viewgrams.get_max_axial_pos_num(); - this->sens_backprojector_sptr->back_project(sensitivity, viewgrams, - min_ax_pos_num, max_ax_pos_num); - } + this->sens_backprojector_sptr->back_project(sensitivity, viewgrams, + min_ax_pos_num, max_ax_pos_num); + } + } } diff --git a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx index 17d74351e0..192ddb02d4 100644 --- a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx +++ b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx @@ -101,6 +101,7 @@ set_defaults() //num_views_to_add=1; this->proj_data_sptr.reset(); //MJ added this->zero_seg0_end_planes = 0; + this->use_tofsens = false; this->additive_projection_data_filename = "0"; this->additive_proj_data_sptr.reset(); @@ -155,6 +156,7 @@ initialise_keymap() base_type::initialise_keymap(); this->parser.add_start_key("PoissonLogLikelihoodWithLinearModelForMeanAndProjData Parameters"); this->parser.add_stop_key("End PoissonLogLikelihoodWithLinearModelForMeanAndProjData Parameters"); + this->parser.add_key("use time-of-flight sensitivities", &this->use_tofsens); this->parser.add_key("input file",&this->input_filename); // KT 20/06/2001 disabled //parser.add_key("mash x views", &num_views_to_add); @@ -623,7 +625,8 @@ set_up_before_sensitivity(shared_ptr const& target_sptr) // sets non-tof backprojector for sensitivity calculation (clone of the back_projector + set projdatainfo to non-tof) this->sens_backprojector_sptr.reset(projector_pair_ptr->get_back_projector_sptr()->clone()); - this->sens_backprojector_sptr->set_up(proj_data_info_sptr->create_non_tof_clone(), target_sptr); + if (!this->use_tofsens) + this->sens_backprojector_sptr->set_up(proj_data_info_sptr->create_non_tof_clone(), target_sptr); // TODO check compatibility between symmetries for forward and backprojector this->symmetries_sptr.reset( @@ -796,28 +799,32 @@ void PoissonLogLikelihoodWithLinearModelForMeanAndProjData:: 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->sens_backprojector_sptr->back_project(sensitivity, viewgrams, min_ax_pos_num, max_ax_pos_num); - } + for (int timing_pos_num = -this->max_timing_pos_num_to_process; + timing_pos_num <= this->max_timing_pos_num_to_process; ++ timing_pos_num) + { + RelatedViewgrams viewgrams = + this->proj_data_sptr->get_empty_related_viewgrams(view_seg_nums, + this->symmetries_sptr, false, timing_pos_num); + 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->sens_backprojector_sptr->back_project(sensitivity, viewgrams, min_ax_pos_num, max_ax_pos_num); + } + } } diff --git a/src/utilities/poisson_noise.cxx b/src/utilities/poisson_noise.cxx index 094bdef660..2c7cf7b567 100644 --- a/src/utilities/poisson_noise.cxx +++ b/src/utilities/poisson_noise.cxx @@ -175,7 +175,7 @@ poisson_noise(ProjData& output_projdata, SegmentByView seg_output= output_projdata.get_empty_segment_by_view(seg,false, timing_pos_num); - cerr << "Segment " << seg << endl; + cerr << "Segment " << seg << " Timing position index " << timing_pos_num << endl; for(int view=seg_input.get_min_view_num();view<=seg_input.get_max_view_num();view++) for(int ax_pos=seg_input.get_min_axial_pos_num();ax_pos<=seg_input.get_max_axial_pos_num();ax_pos++) From 623e88bee38d9847c70d724ae6cfc028c7bf078b Mon Sep 17 00:00:00 2001 From: Elise Date: Sun, 7 May 2017 08:49:37 +0100 Subject: [PATCH 072/509] Fix for last commit (min & max timing_pos_num correctly set depending on the value of the flag) --- ...LinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx | 6 +++--- ...oissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx index 9dd4725346..ffd4736cd8 100644 --- a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx +++ b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx @@ -398,9 +398,9 @@ void PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin:: add_view_seg_to_sensitivity(TargetT& sensitivity, const ViewSegmentNumbers& view_seg_nums) const { - for (int timing_pos_num = this->proj_data_info_cyl_sptr->get_min_tof_pos_num(); - timing_pos_num <= this->proj_data_info_cyl_sptr->get_max_tof_pos_num(); - ++timing_pos_num) + int min_timing_pos_num = use_tofsens ? this->proj_data_info_cyl_sptr->get_min_tof_pos_num() : 0; + int max_timing_pos_num = use_tofsens ? this->proj_data_info_cyl_sptr->get_max_tof_pos_num() : 0; + for (int timing_pos_num = min_timing_pos_num; timing_pos_num <= max_timing_pos_num; ++timing_pos_num) { shared_ptr symmetries_used (this->projector_pair_ptr->get_symmetries_used()->clone()); diff --git a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx index 192ddb02d4..37e8578eb7 100644 --- a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx +++ b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx @@ -799,8 +799,9 @@ void PoissonLogLikelihoodWithLinearModelForMeanAndProjData:: add_view_seg_to_sensitivity(TargetT& sensitivity, const ViewSegmentNumbers& view_seg_nums) const { - for (int timing_pos_num = -this->max_timing_pos_num_to_process; - timing_pos_num <= this->max_timing_pos_num_to_process; ++ timing_pos_num) + int min_timing_pos_num = use_tofsens ? -this->max_timing_pos_num_to_process : 0; + int max_timing_pos_num = use_tofsens ? this->max_timing_pos_num_to_process : 0; + for (int timing_pos_num = min_timing_pos_num; timing_pos_num <= min_timing_pos_num; ++ timing_pos_num) { RelatedViewgrams viewgrams = this->proj_data_sptr->get_empty_related_viewgrams(view_seg_nums, From f9cb151845f210ec3c28b57ccc414dc19c77eaa0 Mon Sep 17 00:00:00 2001 From: Elise Date: Sun, 7 May 2017 08:58:38 +0100 Subject: [PATCH 073/509] Fix2 --- .../PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx index 37e8578eb7..a81c87284c 100644 --- a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx +++ b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx @@ -801,7 +801,7 @@ add_view_seg_to_sensitivity(TargetT& sensitivity, const ViewSegmentNumbers& view { int min_timing_pos_num = use_tofsens ? -this->max_timing_pos_num_to_process : 0; int max_timing_pos_num = use_tofsens ? this->max_timing_pos_num_to_process : 0; - for (int timing_pos_num = min_timing_pos_num; timing_pos_num <= min_timing_pos_num; ++ timing_pos_num) + for (int timing_pos_num = min_timing_pos_num; timing_pos_num <= max_timing_pos_num; ++timing_pos_num) { RelatedViewgrams viewgrams = this->proj_data_sptr->get_empty_related_viewgrams(view_seg_nums, From c23a6044868ca79725a382ab6b30bd88e9bd4548 Mon Sep 17 00:00:00 2001 From: Elise Date: Wed, 31 May 2017 19:20:36 +0100 Subject: [PATCH 074/509] Fix for Swig --- src/display/CMakeLists.txt | 2 +- src/swig/stir.i | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/display/CMakeLists.txt b/src/display/CMakeLists.txt index adbfbd1a3b..2dcc3ed41e 100644 --- a/src/display/CMakeLists.txt +++ b/src/display/CMakeLists.txt @@ -29,7 +29,7 @@ if( "${GRAPHICS}" STREQUAL "X") elseif("${GRAPHICS}" STREQUAL "PGM") ADD_DEFINITIONS(-DSTIR_PGM) -else() +#else() # TODO #ifeq "$(GRAPHICS)" "MATHLINK" # this presumably needs a Mathlink library, depends on your system though diff --git a/src/swig/stir.i b/src/swig/stir.i index 05ee21e48f..769046d350 100644 --- a/src/swig/stir.i +++ b/src/swig/stir.i @@ -1291,6 +1291,7 @@ namespace stir { %ignore stir::Bin::axial_pos_num(); %ignore stir::Bin::view_num(); %ignore stir::Bin::tangential_pos_num(); +%ignore stir::Bin::timing_pos_num(); %include "stir/Bin.h" %newobject stir::ProjDataInfo::ProjDataInfoGE; %newobject stir::ProjDataInfo::ProjDataInfoCTI; From 4dc60a96eb057b91b6a875971e4fb571b5453427 Mon Sep 17 00:00:00 2001 From: Elise Date: Mon, 19 Jun 2017 18:46:08 +0100 Subject: [PATCH 075/509] Fix for GCC-c++11 in test_time_of_flight --- src/test/test_time_of_flight.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/test_time_of_flight.cxx b/src/test/test_time_of_flight.cxx index 24e9ec45d5..77ba7ba42b 100644 --- a/src/test/test_time_of_flight.cxx +++ b/src/test/test_time_of_flight.cxx @@ -83,7 +83,7 @@ class cache_index{ // Helper class. class FloatFloat{ public: - FloatFloat() { float1 = 0f; float2 = 0f;} + FloatFloat() { float1 = 0.f; float2 = 0.f;} float float1; float float2; }; From 61cf1ec000bc67f4f3da8d6ce6160d1cb4d5b508 Mon Sep 17 00:00:00 2001 From: Ottavia Date: Tue, 27 Jun 2017 17:37:24 +0100 Subject: [PATCH 076/509] needed to add forward_project for a single bin, but currently just exits with error --- .../recon_buildblock/PostsmoothingForwardProjectorByBin.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/include/local/stir/recon_buildblock/PostsmoothingForwardProjectorByBin.h b/src/include/local/stir/recon_buildblock/PostsmoothingForwardProjectorByBin.h index 8a6b0fd212..26dd3253dd 100644 --- a/src/include/local/stir/recon_buildblock/PostsmoothingForwardProjectorByBin.h +++ b/src/include/local/stir/recon_buildblock/PostsmoothingForwardProjectorByBin.h @@ -79,6 +79,10 @@ class PostsmoothingForwardProjectorByBin : const int min_axial_pos_num, const int max_axial_pos_num, const int min_tangential_pos_num, const int max_tangential_pos_num); + virtual void actual_forward_project(Bin&, + const DiscretisedDensity<3,float>&) + { error("Postsmoothing forward projector currently doesn't support projection by Bin"); } + void smooth(Viewgram&, const int min_axial_pos_num, const int max_axial_pos_num, const int min_tangential_pos_num, const int max_tangential_pos_num) const; From a897f197367b0c8bb49ebd7a69fc84b9b7e9ddc0 Mon Sep 17 00:00:00 2001 From: Elise Date: Wed, 19 Jul 2017 18:10:55 +0100 Subject: [PATCH 077/509] Fix for TOF unlisting (tested on ROOT files and for Signa listmode) - make test should fail though --- src/buildblock/ProjDataInfo.cxx | 2 +- src/buildblock/ProjDataInfoCylindrical.cxx | 18 +++- .../ProjDataInfoCylindricalArcCorr.cxx | 7 +- .../ProjDataInfoCylindricalNoArcCorr.cxx | 9 +- src/buildblock/Scanner.cxx | 5 +- src/include/stir/DetectionPositionPair.h | 6 +- src/include/stir/DetectionPositionPair.inl | 21 +++- src/include/stir/LORCoordinates.h | 13 ++- src/include/stir/LORCoordinates.inl | 10 +- src/include/stir/ProjDataInfo.h | 2 +- src/include/stir/ProjDataInfoCylindrical.h | 9 +- .../stir/ProjDataInfoCylindricalArcCorr.h | 2 +- .../stir/ProjDataInfoCylindricalNoArcCorr.h | 15 +-- .../stir/ProjDataInfoCylindricalNoArcCorr.inl | 31 ++++-- src/include/stir/Scanner.inl | 2 +- ...ylindricalScannerWithDiscreteDetectors.inl | 5 +- ...calScannerWithViewTangRingRingEncoding.inl | 1 + .../stir/listmode/CListRecordGESigna.h | 20 +++- src/listmode_buildblock/CListEvent.cxx | 4 +- src/listmode_buildblock/CListModeDataROOT.cxx | 14 +-- src/listmode_buildblock/CListRecordROOT.cxx | 23 +---- src/listmode_buildblock/LmToProjData.cxx | 3 +- ...ataSymmetriesForBins_PET_CartesianGrid.cxx | 47 ++++++--- src/test/test_proj_data_info.cxx | 99 +++++++++++-------- 24 files changed, 230 insertions(+), 138 deletions(-) diff --git a/src/buildblock/ProjDataInfo.cxx b/src/buildblock/ProjDataInfo.cxx index 86d2e197b7..646a19094a 100644 --- a/src/buildblock/ProjDataInfo.cxx +++ b/src/buildblock/ProjDataInfo.cxx @@ -78,7 +78,7 @@ ProjDataInfo::get_k(const Bin& bin) const { // Probably, This condition should be removed, since I have the check odd number in the // set_tof_mash_factor(). - if (!num_tof_bins%2) + if (!(num_tof_bins%2)) return bin.timing_pos_num() * tof_increament_in_mm; else return (bin.timing_pos_num() * tof_increament_in_mm) - tof_increament_in_mm/2.f; diff --git a/src/buildblock/ProjDataInfoCylindrical.cxx b/src/buildblock/ProjDataInfoCylindrical.cxx index 0d54ea6587..37b2cc2bf7 100644 --- a/src/buildblock/ProjDataInfoCylindrical.cxx +++ b/src/buildblock/ProjDataInfoCylindrical.cxx @@ -547,7 +547,8 @@ get_LOR(LORInAxialAndNoArcCorrSinogramCoordinates& lor, LORInAxialAndNoArcCorrSinogramCoordinates(z1, z2, phi, asin(s_in_mm/get_ring_radius()), - get_ring_radius()); + get_ring_radius(), + bin.timing_pos_num()>=0); } void @@ -578,16 +579,20 @@ get_LOR_as_two_points(CartesianCoordinate3D& coord_1, coord_2.x() = s_in_mm*cos(phi) + max_a*sin(phi); coord_2.y() = s_in_mm*sin(phi) - min_a*cos(phi); coord_2.z() = m_in_mm - min_a*tantheta; + + if (bin.timing_pos_num()<0) + std::swap(coord_1, coord_2); } void ProjDataInfoCylindrical:: get_LOR_as_two_points_alt(CartesianCoordinate3D& coord_1, CartesianCoordinate3D& coord_2, - const int& det1, - const int& det2, - const int& ring1, - const int& ring2) const + const int det1, + const int det2, + const int ring1, + const int ring2, + const int timing_pos) const { const int num_detectors_per_ring = get_scanner_ptr()->get_num_detectors_per_ring(); @@ -612,6 +617,9 @@ get_LOR_as_two_points_alt(CartesianCoordinate3D& coord_1, LORAs2Points lor(cyl_coords); coord_1 = lor.p1(); coord_2 = lor.p2(); + + if (timing_pos<0) + std::swap(coord_1, coord_2); } string diff --git a/src/buildblock/ProjDataInfoCylindricalArcCorr.cxx b/src/buildblock/ProjDataInfoCylindricalArcCorr.cxx index 15cdb8ef9f..5df18fec42 100644 --- a/src/buildblock/ProjDataInfoCylindricalArcCorr.cxx +++ b/src/buildblock/ProjDataInfoCylindricalArcCorr.cxx @@ -124,9 +124,14 @@ ProjDataInfoCylindricalArcCorr::parameter_info() const Bin ProjDataInfoCylindricalArcCorr:: -get_bin(const LOR& lor) const +get_bin(const LOR& lor,const double delta_time) const { + if (delta_time != 0) + { + error("TODO NO TOF YET"); + } + Bin bin; LORInAxialAndSinogramCoordinates lor_coords; if (lor.change_representation(lor_coords, get_ring_radius()) == Succeeded::no) diff --git a/src/buildblock/ProjDataInfoCylindricalNoArcCorr.cxx b/src/buildblock/ProjDataInfoCylindricalNoArcCorr.cxx index 83a498ae8a..191d49040f 100644 --- a/src/buildblock/ProjDataInfoCylindricalNoArcCorr.cxx +++ b/src/buildblock/ProjDataInfoCylindricalNoArcCorr.cxx @@ -363,6 +363,7 @@ get_all_det_pos_pairs_for_bin(vector >& dps, dps[current_dp_num].pos1().axial_coord() = rings_iter->first; dps[current_dp_num].pos2().tangential_coord() = det2_num; dps[current_dp_num].pos2().axial_coord() = rings_iter->second; + dps[current_dp_num].timing_pos() = bin.timing_pos_num(); ++current_dp_num; } } @@ -579,7 +580,7 @@ find_bin_given_cartesian_coordinates_of_detection(Bin& bin, Bin ProjDataInfoCylindricalNoArcCorr:: -get_bin(const LOR& lor) const +get_bin(const LOR& lor,const double delta_time) const { Bin bin; #ifndef STIR_DEVEL @@ -607,7 +608,7 @@ get_bin(const LOR& lor) const if (ring1 >=0 && ring1=0 && ring2= get_min_tangential_pos_num() && bin.tangential_pos_num() <= get_max_tangential_pos_num()) { @@ -681,6 +682,10 @@ get_bin(const LOR& lor) const #else // find nearest segment { + if (delta_time!=0) + { + error("TODO TOF"); + } const float delta = (swap_direction ? lor_coords.z1()-lor_coords.z2() diff --git a/src/buildblock/Scanner.cxx b/src/buildblock/Scanner.cxx index f7a6b8c882..8ff31e3d02 100644 --- a/src/buildblock/Scanner.cxx +++ b/src/buildblock/Scanner.cxx @@ -1064,7 +1064,10 @@ if (!close_enough(energy_resolution, scanner.energy_resolution) && (num_transaxial_crystals_per_block == scanner.num_transaxial_crystals_per_block) && (num_detector_layers == scanner.num_detector_layers) && (num_axial_crystals_per_singles_unit == scanner.num_axial_crystals_per_singles_unit) && - (num_transaxial_crystals_per_singles_unit == scanner.num_transaxial_crystals_per_singles_unit); + (num_transaxial_crystals_per_singles_unit == scanner.num_transaxial_crystals_per_singles_unit) && + (max_num_of_timing_bins == scanner.max_num_of_timing_bins) && + close_enough(size_timing_bin, scanner.size_timing_bin) && + close_enough(timing_resolution, scanner.timing_resolution); } diff --git a/src/include/stir/DetectionPositionPair.h b/src/include/stir/DetectionPositionPair.h index b3359bb8f2..6bd6046c9e 100644 --- a/src/include/stir/DetectionPositionPair.h +++ b/src/include/stir/DetectionPositionPair.h @@ -44,12 +44,15 @@ class DetectionPositionPair inline DetectionPositionPair(); inline DetectionPositionPair(const DetectionPosition&, - const DetectionPosition&); + const DetectionPosition&, + const coordT timing_pos = static_cast(0)); inline const DetectionPosition& pos1() const; inline const DetectionPosition& pos2() const; + inline const coordT timing_pos() const; inline DetectionPosition& pos1(); inline DetectionPosition& pos2(); + inline coordT& timing_pos(); //! comparison operators inline bool operator==(const DetectionPositionPair&) const; inline bool operator!=(const DetectionPositionPair&) const; @@ -57,6 +60,7 @@ class DetectionPositionPair private : DetectionPosition p1; DetectionPosition p2; + coordT _timing_pos; }; END_NAMESPACE_STIR diff --git a/src/include/stir/DetectionPositionPair.inl b/src/include/stir/DetectionPositionPair.inl index 73b0776715..5391eeb19c 100644 --- a/src/include/stir/DetectionPositionPair.inl +++ b/src/include/stir/DetectionPositionPair.inl @@ -33,8 +33,9 @@ DetectionPositionPair() template DetectionPositionPair:: DetectionPositionPair(const DetectionPosition& pos1, - const DetectionPosition& pos2) - : p1(pos1), p2(pos2) + const DetectionPosition& pos2, + const coordT timing_pos) + : p1(pos1), p2(pos2), _timing_pos(timing_pos) {} template @@ -49,6 +50,12 @@ DetectionPositionPair:: pos2() const { return p2; } +template +const coordT +DetectionPositionPair:: +timing_pos() const +{ return _timing_pos; } + template DetectionPosition& DetectionPositionPair:: @@ -61,6 +68,12 @@ DetectionPositionPair:: pos2() { return p2; } +template +coordT& +DetectionPositionPair:: +timing_pos() +{ return _timing_pos; } + //! comparison operators template bool @@ -68,8 +81,8 @@ DetectionPositionPair:: operator==(const DetectionPositionPair& p) const { return - (pos1() == p.pos1() && pos2() == p.pos2()) || - (pos1() == p.pos2() && pos2() == p.pos1()) ; + (pos1() == p.pos1() && pos2() == p.pos2() && timing_pos() == p.timing_pos()) || + (pos1() == p.pos2() && pos2() == p.pos1() && timing_pos() == -p.timing_pos()) ; } template diff --git a/src/include/stir/LORCoordinates.h b/src/include/stir/LORCoordinates.h index 3c62f99136..cbbf4e0137 100644 --- a/src/include/stir/LORCoordinates.h +++ b/src/include/stir/LORCoordinates.h @@ -346,7 +346,8 @@ class LORInAxialAndSinogramCoordinates coordT& s() { check_state(); return _s; } coordT beta() const { check_state(); return asin(_s/private_base_type::_radius); } - + bool is_swapped() const { check_state(); return _swapped; } + bool is_swapped() { check_state(); return _swapped; } inline explicit LORInAxialAndSinogramCoordinates(const coordT radius = 1); @@ -360,7 +361,8 @@ class LORInAxialAndSinogramCoordinates const coordT z2, const coordT phi, const coordT s, - const coordT radius =1); + const coordT radius =1, + const bool swapped =false); inline LORInAxialAndSinogramCoordinates(const LORInCylinderCoordinates&); @@ -422,6 +424,7 @@ class LORInAxialAndSinogramCoordinates private: coordT _phi; coordT _s; + bool _swapped; }; @@ -455,6 +458,8 @@ class LORInAxialAndNoArcCorrSinogramCoordinates coordT& phi() { check_state(); return _phi; } coordT beta() const { check_state(); return _beta; } coordT& beta() { check_state(); return _beta; } + bool is_swapped() const { check_state(); return _swapped; } + bool is_swapped() { check_state(); return _swapped; } coordT s() const { check_state(); return private_base_type::_radius*sin(_beta); } @@ -491,7 +496,8 @@ class LORInAxialAndNoArcCorrSinogramCoordinates const coordT z2, const coordT phi, const coordT beta, - const coordT radius =1); + const coordT radius =1, + const bool swapped =false); inline LORInAxialAndNoArcCorrSinogramCoordinates(const LORInCylinderCoordinates&); @@ -532,6 +538,7 @@ class LORInAxialAndNoArcCorrSinogramCoordinates private: coordT _phi; coordT _beta; + bool _swapped; }; /*! \ingroup LOR diff --git a/src/include/stir/LORCoordinates.inl b/src/include/stir/LORCoordinates.inl index 6c84a0947d..9304e733f1 100644 --- a/src/include/stir/LORCoordinates.inl +++ b/src/include/stir/LORCoordinates.inl @@ -88,10 +88,11 @@ LORInAxialAndSinogramCoordinates(const coordT z1, const coordT z2, const coordT phi, const coordT s, - const coordT radius) + const coordT radius, + const bool swapped) : LORCylindricalCoordinates_z_and_radius(z1, z2, radius), - _phi(phi), _s(s) + _phi(phi), _s(s), _swapped(swapped) { check_state(); } @@ -112,9 +113,10 @@ LORInAxialAndNoArcCorrSinogramCoordinates(const coordT z1, const coordT z2, const coordT phi, const coordT beta, - const coordT radius) + const coordT radius, + const bool swapped) : LORCylindricalCoordinates_z_and_radius(z1, z2, radius), - _phi(phi), _beta(beta) + _phi(phi), _beta(beta), _swapped(swapped) { check_state(); diff --git a/src/include/stir/ProjDataInfo.h b/src/include/stir/ProjDataInfo.h index 0a8002fe4b..7fc5221954 100644 --- a/src/include/stir/ProjDataInfo.h +++ b/src/include/stir/ProjDataInfo.h @@ -331,7 +331,7 @@ class ProjDataInfo */ virtual Bin - get_bin(const LOR&) const = 0; + get_bin(const LOR&,const double delta_time = 0.0) const = 0; //! \name Equality of ProjDataInfo objects //@{ diff --git a/src/include/stir/ProjDataInfoCylindrical.h b/src/include/stir/ProjDataInfoCylindrical.h index 8604973aa6..26043bbfd0 100644 --- a/src/include/stir/ProjDataInfoCylindrical.h +++ b/src/include/stir/ProjDataInfoCylindrical.h @@ -112,10 +112,11 @@ class ProjDataInfoCylindrical: public ProjDataInfo void get_LOR_as_two_points_alt(CartesianCoordinate3D& coord_1, CartesianCoordinate3D& coord_2, - const int& det1, - const int& det2, - const int& ring1, - const int& ring2) const; + const int det1, + const int det2, + const int ring1, + const int ring2, + const int timing_pos) const; void set_azimuthal_angle_sampling(const float angle); diff --git a/src/include/stir/ProjDataInfoCylindricalArcCorr.h b/src/include/stir/ProjDataInfoCylindricalArcCorr.h index be7fb6a14c..a7e620f147 100644 --- a/src/include/stir/ProjDataInfoCylindricalArcCorr.h +++ b/src/include/stir/ProjDataInfoCylindricalArcCorr.h @@ -79,7 +79,7 @@ class ProjDataInfoCylindricalArcCorr : public ProjDataInfoCylindrical virtual Bin - get_bin(const LOR&) const; + get_bin(const LOR&, const double delta_time = 0.0) const; virtual std::string parameter_info() const; private: diff --git a/src/include/stir/ProjDataInfoCylindricalNoArcCorr.h b/src/include/stir/ProjDataInfoCylindricalNoArcCorr.h index 99a6ab3c26..8399b7dc99 100644 --- a/src/include/stir/ProjDataInfoCylindricalNoArcCorr.h +++ b/src/include/stir/ProjDataInfoCylindricalNoArcCorr.h @@ -241,27 +241,18 @@ class ProjDataInfoCylindricalNoArcCorr : public ProjDataInfoCylindrical inline Succeeded get_bin_for_det_pair(Bin&, const int det1_num, const int ring1_num, - const int det2_num, const int ring2_num) const; - + const int det2_num, const int ring2_num, + const int timing_pos_num = 0) const; //! This routine gets the detector pair corresponding to a bin. - /*! - \see get_det_pair_for_view_tangential_pos_num() for - restrictions. In addition, this routine only works for span=1 data, - i.e. no axial compression. - \obsolete */ inline void - get_det_pair_for_bin( - int& det1_num, int& ring1_num, - int& det2_num, int& ring2_num, - const Bin&) const; //@} virtual Bin - get_bin(const LOR&) const; + get_bin(const LOR&,const double delta_time) const; //! \name set of obsolete functions to go between bins<->LORs (will disappear!) diff --git a/src/include/stir/ProjDataInfoCylindricalNoArcCorr.inl b/src/include/stir/ProjDataInfoCylindricalNoArcCorr.inl index 10442913e0..1b9894d8e6 100644 --- a/src/include/stir/ProjDataInfoCylindricalNoArcCorr.inl +++ b/src/include/stir/ProjDataInfoCylindricalNoArcCorr.inl @@ -30,6 +30,7 @@ #include "stir/Bin.h" #include "stir/Succeeded.h" +#include "stir/round.h" #include START_NAMESPACE_STIR @@ -135,12 +136,19 @@ Succeeded ProjDataInfoCylindricalNoArcCorr:: get_bin_for_det_pair(Bin& bin, const int det_num1, const int ring_num1, - const int det_num2, const int ring_num2) const + const int det_num2, const int ring_num2, + const int timing_pos_num) const { if (get_view_tangential_pos_num_for_det_num_pair(bin.view_num(), bin.tangential_pos_num(), det_num1, det_num2)) - return get_segment_axial_pos_num_for_ring_pair(bin.segment_num(), bin.axial_pos_num(), ring_num1, ring_num2); + { + bin.timing_pos_num() = timing_pos_num; + return get_segment_axial_pos_num_for_ring_pair(bin.segment_num(), bin.axial_pos_num(), ring_num1, ring_num2); + } else + { + bin.timing_pos_num() = -timing_pos_num; return get_segment_axial_pos_num_for_ring_pair(bin.segment_num(), bin.axial_pos_num(), ring_num2, ring_num1); + } } Succeeded @@ -148,12 +156,14 @@ ProjDataInfoCylindricalNoArcCorr:: get_bin_for_det_pos_pair(Bin& bin, const DetectionPositionPair<>& dp) const { + assert(this->get_tof_mash_factor()>0); return get_bin_for_det_pair(bin, dp.pos1().tangential_coord(), dp.pos1().axial_coord(), - dp.pos2().tangential_coord(), - dp.pos2().axial_coord()); + dp.pos2().tangential_coord(), + dp.pos2().axial_coord(), + stir::round((float)dp.timing_pos()/this->get_tof_mash_factor())); } void ProjDataInfoCylindricalNoArcCorr:: @@ -162,8 +172,16 @@ get_det_pair_for_bin( int& det_num2, int& ring_num2, const Bin& bin) const { - get_det_num_pair_for_view_tangential_pos_num(det_num1, det_num2, bin.view_num(), bin.tangential_pos_num()); - get_ring_pair_for_segment_axial_pos_num( ring_num1, ring_num2, bin.segment_num(), bin.axial_pos_num()); + if (bin.timing_pos_num()>=0) + { + get_det_num_pair_for_view_tangential_pos_num(det_num1, det_num2, bin.view_num(), bin.tangential_pos_num()); + get_ring_pair_for_segment_axial_pos_num( ring_num1, ring_num2, bin.segment_num(), bin.axial_pos_num()); + } + else + { + get_det_num_pair_for_view_tangential_pos_num(det_num2, det_num1, bin.view_num(), bin.tangential_pos_num()); + get_ring_pair_for_segment_axial_pos_num( ring_num2, ring_num1, bin.segment_num(), bin.axial_pos_num()); + } } void @@ -183,6 +201,7 @@ get_det_pos_pair_for_bin( dp.pos1().axial_coord()=a1; dp.pos2().tangential_coord()=t2; dp.pos2().axial_coord()=a2; + dp.timing_pos() = std::abs(bin.timing_pos_num())*this->get_tof_mash_factor(); #else diff --git a/src/include/stir/Scanner.inl b/src/include/stir/Scanner.inl index 952dce0db1..4dcb149cba 100644 --- a/src/include/stir/Scanner.inl +++ b/src/include/stir/Scanner.inl @@ -247,7 +247,7 @@ float Scanner::get_timing_resolution() const bool Scanner::is_tof_ready() const { return (max_num_of_timing_bins > 0 - && timing_resolution > 0.0f + && size_timing_bin > 0.0f && timing_resolution > 0.0f); } diff --git a/src/include/stir/listmode/CListEventCylindricalScannerWithDiscreteDetectors.inl b/src/include/stir/listmode/CListEventCylindricalScannerWithDiscreteDetectors.inl index d6d337b072..6f0b3f45ea 100644 --- a/src/include/stir/listmode/CListEventCylindricalScannerWithDiscreteDetectors.inl +++ b/src/include/stir/listmode/CListEventCylindricalScannerWithDiscreteDetectors.inl @@ -40,7 +40,8 @@ CListEventCylindricalScannerWithDiscreteDetectors(const shared_ptr& sca 1, scanner_sptr->get_num_rings()-1, scanner_sptr->get_num_detectors_per_ring()/2, scanner_sptr->get_default_num_arccorrected_bins(), - false))); + false, + /*TOF mashing factor*/1))); } LORAs2Points @@ -86,8 +87,6 @@ get_bin(Bin& bin, const ProjDataInfo& proj_data_info) const else { bin.set_bin_value(1); - if (proj_data_info.get_num_tof_poss() > 1) - bin.timing_pos_num() = proj_data_info.get_tof_bin(delta_time); } } diff --git a/src/include/stir/listmode/CListEventCylindricalScannerWithViewTangRingRingEncoding.inl b/src/include/stir/listmode/CListEventCylindricalScannerWithViewTangRingRingEncoding.inl index c599375c5f..14bb23fbf2 100644 --- a/src/include/stir/listmode/CListEventCylindricalScannerWithViewTangRingRingEncoding.inl +++ b/src/include/stir/listmode/CListEventCylindricalScannerWithViewTangRingRingEncoding.inl @@ -47,6 +47,7 @@ get_detection_position(DetectionPositionPair<>& det_pos) const view_num, tangential_pos_num); det_pos.pos1().tangential_coord() = det_num_1; det_pos.pos2().tangential_coord() = det_num_2; + det_pos.timing_pos() = this->get_uncompressed_proj_data_info_sptr()->get_tof_bin(delta_time); } template diff --git a/src/include/stir/listmode/CListRecordGESigna.h b/src/include/stir/listmode/CListRecordGESigna.h index f54cbc023b..d6b35efc82 100644 --- a/src/include/stir/listmode/CListRecordGESigna.h +++ b/src/include/stir/listmode/CListRecordGESigna.h @@ -67,10 +67,22 @@ class CListEventDataGESigna } inline void get_detection_position(DetectionPositionPair<>& det_pos) const { - det_pos.pos1().tangential_coord() = loXtalTransAxID; - det_pos.pos1().axial_coord() = loXtalAxialID; - det_pos.pos2().tangential_coord() = hiXtalTransAxID; - det_pos.pos2().axial_coord() = hiXtalAxialID; + if (deltaTime<0) + { + det_pos.pos1().tangential_coord() = hiXtalTransAxID; + det_pos.pos1().axial_coord() = hiXtalAxialID; + det_pos.pos2().tangential_coord() = loXtalTransAxID; + det_pos.pos2().axial_coord() = loXtalAxialID; + det_pos.timing_pos() = -get_tof_bin(); + } + else + { + det_pos.pos1().tangential_coord() = loXtalTransAxID; + det_pos.pos1().axial_coord() = loXtalAxialID; + det_pos.pos2().tangential_coord() = hiXtalTransAxID; + det_pos.pos2().axial_coord() = hiXtalAxialID; + det_pos.timing_pos() = get_tof_bin(); + } } inline bool is_event() const { diff --git a/src/listmode_buildblock/CListEvent.cxx b/src/listmode_buildblock/CListEvent.cxx index 3f5f92bbd0..5edcf8b2d3 100644 --- a/src/listmode_buildblock/CListEvent.cxx +++ b/src/listmode_buildblock/CListEvent.cxx @@ -45,9 +45,7 @@ void CListEvent:: get_bin(Bin& bin, const ProjDataInfo& proj_data_info) const { - bin = proj_data_info.get_bin(get_LOR()); - if (proj_data_info.get_num_tof_poss() > 1) - bin.timing_pos_num() = proj_data_info.get_tof_bin(delta_time); + bin = proj_data_info.get_bin(get_LOR(),delta_time); } END_NAMESPACE_STIR diff --git a/src/listmode_buildblock/CListModeDataROOT.cxx b/src/listmode_buildblock/CListModeDataROOT.cxx index 5fe8ead23a..7c3899f86e 100644 --- a/src/listmode_buildblock/CListModeDataROOT.cxx +++ b/src/listmode_buildblock/CListModeDataROOT.cxx @@ -54,7 +54,7 @@ CListModeDataROOT(const std::string& listmode_filename) max_num_timing_bins = -1; size_timing_bin = -1.f; timing_resolution = -1.f; - tof_mash_factor = -1; + tof_mash_factor = 1; // Scanner related & Physical dimentions. this->parser.add_key("originating system", &this->originating_system); @@ -76,8 +76,8 @@ CListModeDataROOT(const std::string& listmode_filename) // this->parser.add_key("%number_of_segments", &number_of_segments); this->parser.add_key("number of TOF time bins", &this->max_num_timing_bins); - this->parser.add_key("Size of timing bin (in picoseconds)", &this->size_timing_bin); - this->parser.add_key("Timing resolution (in picoseconds)", &this->timing_resolution); + this->parser.add_key("Size of timing bin (ps)", &this->size_timing_bin); + this->parser.add_key("Timing resolution (ps)", &this->timing_resolution); this->parser.add_key("%TOF mashing factor", &this->tof_mash_factor); // @@ -160,9 +160,11 @@ CListModeDataROOT(const std::string& listmode_filename) num_rings-1, num_detectors_per_ring/2, max_num_non_arccorrected_bins, - /* arc_correction*/false)); - if (tof_mash_factor > 0) - this->proj_data_info_sptr->set_tof_mash_factor(tof_mash_factor); + /* arc_correction*/false, + tof_mash_factor)); + + if (tof_mash_factor != 1) + error("TOF mashing factor for ROOT different from 1 not implemented yet."); } std::string diff --git a/src/listmode_buildblock/CListRecordROOT.cxx b/src/listmode_buildblock/CListRecordROOT.cxx index 4aa4051dc6..8711fa3ae9 100644 --- a/src/listmode_buildblock/CListRecordROOT.cxx +++ b/src/listmode_buildblock/CListRecordROOT.cxx @@ -51,6 +51,7 @@ void CListEventROOT::get_detection_position(DetectionPositionPair<>& _det_pos) c _det_pos.pos1() = det1; _det_pos.pos2() = det2; + _det_pos.timing_pos() = this->get_uncompressed_proj_data_info_sptr()->get_tof_bin(delta_time); } void CListEventROOT::set_detection_position(const DetectionPositionPair<>&) @@ -77,24 +78,10 @@ void CListEventROOT::init_from_data(const int& _ring1, const int& _ring2, else if ( det2 >= scanner_sptr->get_num_detectors_per_ring()) det2 = det2 - scanner_sptr->get_num_detectors_per_ring(); - if (det1 > det2) - { - int tmp = det1; - det1 = det2; - det2 = tmp; - - ring1 = _ring2; - ring2 = _ring1; - delta_time = -_delta_time; - swapped = true; - } - else - { - ring1 = _ring1; - ring2 = _ring2; - delta_time = _delta_time; - swapped = false; - } + ring1 = _ring1; + ring2 = _ring2; + delta_time = _delta_time; + swapped = false; } END_NAMESPACE_STIR diff --git a/src/listmode_buildblock/LmToProjData.cxx b/src/listmode_buildblock/LmToProjData.cxx index 81c01d7131..0fc7b8a2e7 100644 --- a/src/listmode_buildblock/LmToProjData.cxx +++ b/src/listmode_buildblock/LmToProjData.cxx @@ -316,7 +316,8 @@ post_processing() 1, scanner_ptr->get_num_rings()-1, scanner_ptr->get_num_detectors_per_ring()/2, scanner_ptr->get_default_num_arccorrected_bins(), - false))); + false, + 1))); if ( normalisation_ptr->set_up(proj_data_info_cyl_uncompressed_ptr) != Succeeded::yes) diff --git a/src/recon_test/test_DataSymmetriesForBins_PET_CartesianGrid.cxx b/src/recon_test/test_DataSymmetriesForBins_PET_CartesianGrid.cxx index 25206f03c8..1ca8acba3d 100644 --- a/src/recon_test/test_DataSymmetriesForBins_PET_CartesianGrid.cxx +++ b/src/recon_test/test_DataSymmetriesForBins_PET_CartesianGrid.cxx @@ -138,15 +138,16 @@ run_tests_2_proj_matrices_1_bin(const ProjMatrixByBin& proj_matrix_no_symm, cerr << "Current bin: segment = " << bin.segment_num() << ", axial pos " << bin.axial_pos_num() << ", view = " << bin.view_num() - << ", tangential_pos_num = " << bin.tangential_pos_num() << "\n"; + << ", tangential_pos_num = " << bin.tangential_pos_num() + << ", timing position index = " << bin.timing_pos_num() << "\n"; ProjMatrixElemsForOneBin::const_iterator no_sym_iter= elems_no_sym.begin(); ProjMatrixElemsForOneBin::const_iterator with_sym_iter = elems_with_sym.begin(); while (no_sym_iter!= elems_no_sym.end() || with_sym_iter!=elems_with_sym.end()) - { + { if (no_sym_iter==elems_no_sym.end() || with_sym_iter==elems_with_sym.end() || no_sym_iter->get_coords()!= with_sym_iter->get_coords() || - fabs(no_sym_iter->get_value()/with_sym_iter->get_value() -1) > .0002) + fabs(no_sym_iter->get_value()/with_sym_iter->get_value() -1) > .01) { bool inc_no_sym_iter = false; if (no_sym_iter!=elems_no_sym.end() && @@ -195,16 +196,19 @@ run_tests_2_proj_matrices(const ProjMatrixByBin& proj_matrix_no_symm, for (int v=proj_data_info_sptr->get_min_view_num(); v <= proj_data_info_sptr->get_max_view_num(); ++v) - for (int a=proj_data_info_sptr->get_min_axial_pos_num(s); - a <= proj_data_info_sptr->get_max_axial_pos_num(s); - ++a) - for (int t=-6; t<=6; t+=3) - { - const Bin bin(s,v,a,t); - //SYM if (proj_matrix_with_symm.get_symmetries_ptr()->is_basic(bin)) - run_tests_2_proj_matrices_1_bin(proj_matrix_no_symm, - proj_matrix_with_symm, bin); - } + for (int timing_pos=proj_data_info_sptr->get_min_tof_pos_num(); + timing_pos<=proj_data_info_sptr->get_max_tof_pos_num(); + ++timing_pos) + for (int a=proj_data_info_sptr->get_min_axial_pos_num(s); + a <= proj_data_info_sptr->get_max_axial_pos_num(s); + ++a) + for (int t=-6; t<=6; t+=3) + { + const Bin bin(s,v,a,t,timing_pos); + //SYM if (proj_matrix_with_symm.get_symmetries_ptr()->is_basic(bin)) + run_tests_2_proj_matrices_1_bin(proj_matrix_no_symm, + proj_matrix_with_symm, bin); + } #else const int oblique_seg_num = proj_data_info_sptr->get_max_segment_num(); @@ -711,6 +715,23 @@ DataSymmetriesForBins_PET_CartesianGridTests::run_tests() run_tests_for_1_projdata(proj_data_info_sptr); } + { + cerr << "Testing with proj_data_info with time-of-flight"; + // warning: make sure that parameters are ok such that hard-wired + // bins above are fine (e.g. segment 3 should be allowed) + shared_ptr scanner_sptr(new Scanner(Scanner::PETMR_Signa)); + proj_data_info_sptr.reset( + ProjDataInfo::ProjDataInfoCTI(scanner_sptr, + /*span=*/11, + /*max_delta=*/scanner_sptr->get_num_rings()-1, + /*num_views=*/scanner_sptr->get_num_detectors_per_ring()/8, + /*num_tang_poss=*/64, + /*arc_corrected*/false, + /*tof_mashing*/39)); + + + run_tests_for_1_projdata(proj_data_info_sptr); + } } else { diff --git a/src/test/test_proj_data_info.cxx b/src/test/test_proj_data_info.cxx index 45b153c991..e159b49a2e 100644 --- a/src/test/test_proj_data_info.cxx +++ b/src/test/test_proj_data_info.cxx @@ -594,7 +594,7 @@ run_tests() /*views*/ scanner_ptr->get_num_detectors_per_ring()/2, /*tang_pos*/64, /*arc_corrected*/ false)); - test_proj_data_info(dynamic_cast(*proj_data_info_ptr)); + test_proj_data_info(dynamic_cast(*proj_data_info_ptr)); cerr << "\nTests with proj_data_info with mashing and axial compression\n\n"; proj_data_info_ptr.reset( @@ -603,9 +603,18 @@ run_tests() /*views*/ scanner_ptr->get_num_detectors_per_ring()/2/8, /*tang_pos*/64, /*arc_corrected*/ false)); - test_proj_data_info(dynamic_cast(*proj_data_info_ptr)); + test_proj_data_info(dynamic_cast(*proj_data_info_ptr)); cerr << "\nTests with proj_data_info with time-of-flight\n\n"; + shared_ptr scanner_tof_ptr(new Scanner(Scanner::PETMR_Signa)); + proj_data_info_ptr.reset( + ProjDataInfo::ProjDataInfoCTI(scanner_tof_ptr, + /*span*/11, scanner_tof_ptr->get_num_rings()-1, + /*views*/ scanner_tof_ptr->get_num_detectors_per_ring()/2, + /*tang_pos*/64, + /*arc_corrected*/ false, + /*tof_mashing*/39)); + test_proj_data_info(dynamic_cast(*proj_data_info_ptr)); } void @@ -759,50 +768,54 @@ test_proj_data_info(ProjDataInfoCylindricalNoArcCorr& proj_data_info) Bin bin(0,0,0,0,0,0.0f); // set value for comparison later on bin.set_bin_value(0.f); - for (bin.segment_num() = max(-5,proj_data_info.get_min_segment_num()); - bin.segment_num() <= min(5,proj_data_info.get_max_segment_num()); - ++bin.segment_num()) - for (bin.axial_pos_num() = proj_data_info.get_min_axial_pos_num(bin.segment_num()); - bin.axial_pos_num() <= proj_data_info.get_max_axial_pos_num(bin.segment_num()); - ++bin.axial_pos_num()) + for (bin.timing_pos_num() = proj_data_info.get_min_tof_pos_num(); + bin.timing_pos_num() <= proj_data_info.get_max_tof_pos_num(); + ++bin.timing_pos_num()) + for (bin.segment_num() = max(-5,proj_data_info.get_min_segment_num()); + bin.segment_num() <= min(5,proj_data_info.get_max_segment_num()); + ++bin.segment_num()) + for (bin.axial_pos_num() = proj_data_info.get_min_axial_pos_num(bin.segment_num()); + bin.axial_pos_num() <= proj_data_info.get_max_axial_pos_num(bin.segment_num()); + ++bin.axial_pos_num()) #ifdef STIR_OPENMP - // insert a parallel for here for testing. - // we do it at this level to avoid too much overhead for the thread creation, while still having enough jobs to do - // Note that the omp construct needs an int loop variable + // insert a parallel for here for testing. + // we do it at this level to avoid too much overhead for the thread creation, while still having enough jobs to do // Note that the omp construct needs an int loop variable #pragma omp parallel for firstprivate(bin) #endif - for (int tangential_pos_num = -(num_detectors/2)+1; - tangential_pos_num < num_detectors/2; - ++tangential_pos_num) - for (bin.view_num() = 0; bin.view_num() < num_detectors/2; ++bin.view_num()) - { - // set from for-loop variable - bin.tangential_pos_num() = tangential_pos_num; - Bin new_bin(0,0,0,0,0,0.0f); - // set value for comparison with bin - new_bin.set_bin_value(0); - DetectionPositionPair<> det_pos_pair; - proj_data_info.get_det_pos_pair_for_bin(det_pos_pair, bin); - - const bool there_is_a_bin = - proj_data_info.get_bin_for_det_pos_pair(new_bin, - det_pos_pair) == - Succeeded::yes; - if (!check(there_is_a_bin, "checking if there is a bin for this det_pos_pair") || - !check(bin == new_bin, "checking if we round-trip to the same bin")) - { - cerr << "Problem at segment = " << bin.segment_num() - << ", axial pos " << bin.axial_pos_num() - << ", view = " << bin.view_num() - << ", tangential_pos_num = " << bin.tangential_pos_num() << "\n"; - if (there_is_a_bin) - cerr << " bin -> dets -> bin, gives new numbers:\n\t" - << "segment = " << new_bin.segment_num() - << ", axial pos " << new_bin.axial_pos_num() - << ", view = " << new_bin.view_num() - << ", tangential_pos_num = " << new_bin.tangential_pos_num() - << endl; - } + for (int tangential_pos_num = -(num_detectors/2)+1; + tangential_pos_num < num_detectors/2; + ++tangential_pos_num) + for (bin.view_num() = 0; bin.view_num() < num_detectors/2; ++bin.view_num()) + { + // set from for-loop variable + bin.tangential_pos_num() = tangential_pos_num; + Bin new_bin(0,0,0,0,0,0.0f); + // set value for comparison with bin + new_bin.set_bin_value(0); + DetectionPositionPair<> det_pos_pair; + proj_data_info.get_det_pos_pair_for_bin(det_pos_pair, bin); + + const bool there_is_a_bin = + proj_data_info.get_bin_for_det_pos_pair(new_bin, + det_pos_pair) == + Succeeded::yes; + if (!check(there_is_a_bin, "checking if there is a bin for this det_pos_pair") || + !check(bin == new_bin, "checking if we round-trip to the same bin")) + { + cerr << "Problem at segment = " << bin.segment_num() + << ", axial pos " << bin.axial_pos_num() + << ", view = " << bin.view_num() + << ", tangential_pos_num = " << bin.tangential_pos_num() + << ", timing pos num = " << bin.timing_pos_num() << "\n"; + if (there_is_a_bin) + cerr << " bin -> dets -> bin, gives new numbers:\n\t" + << "segment = " << new_bin.segment_num() + << ", axial pos " << new_bin.axial_pos_num() + << ", view = " << new_bin.view_num() + << ", tangential_pos_num = " << new_bin.tangential_pos_num() + << ", timing pos num = " << new_bin.timing_pos_num() + << endl; + } } // end of get_det_pos_pair_for_bin and back code } From d2f39d994882f63eccdd47072f9d09c0664e5a17 Mon Sep 17 00:00:00 2001 From: Nikos Efthimiou Date: Wed, 19 Jul 2017 20:24:21 +0100 Subject: [PATCH 078/509] New improved and cleaner apply_tof_kernel() and minor corrections On tests on individula lors I found tha this method of application works a bit better and is much cleaner. --- .../stir/ProjDataInfoCylindricalNoArcCorr.h | 6 +++- .../stir/ProjDataInfoCylindricalNoArcCorr.inl | 18 +++++------ .../stir/recon_buildblock/ProjMatrixByBin.inl | 32 ++++++++----------- 3 files changed, 28 insertions(+), 28 deletions(-) diff --git a/src/include/stir/ProjDataInfoCylindricalNoArcCorr.h b/src/include/stir/ProjDataInfoCylindricalNoArcCorr.h index 8399b7dc99..2e5e758eb9 100644 --- a/src/include/stir/ProjDataInfoCylindricalNoArcCorr.h +++ b/src/include/stir/ProjDataInfoCylindricalNoArcCorr.h @@ -245,8 +245,12 @@ class ProjDataInfoCylindricalNoArcCorr : public ProjDataInfoCylindrical const int timing_pos_num = 0) const; //! This routine gets the detector pair corresponding to a bin. - */ + inline void + get_det_pair_for_bin( + int& det_num1, int& ring_num1, + int& det_num2, int& ring_num2, + const Bin& bin) const; //@} diff --git a/src/include/stir/ProjDataInfoCylindricalNoArcCorr.inl b/src/include/stir/ProjDataInfoCylindricalNoArcCorr.inl index 1b9894d8e6..c08e3214c5 100644 --- a/src/include/stir/ProjDataInfoCylindricalNoArcCorr.inl +++ b/src/include/stir/ProjDataInfoCylindricalNoArcCorr.inl @@ -168,18 +168,18 @@ get_bin_for_det_pos_pair(Bin& bin, void ProjDataInfoCylindricalNoArcCorr:: get_det_pair_for_bin( - int& det_num1, int& ring_num1, - int& det_num2, int& ring_num2, - const Bin& bin) const + int& det_num1, int& ring_num1, + int& det_num2, int& ring_num2, + const Bin& bin) const { if (bin.timing_pos_num()>=0) { - get_det_num_pair_for_view_tangential_pos_num(det_num1, det_num2, bin.view_num(), bin.tangential_pos_num()); + get_det_num_pair_for_view_tangential_pos_num(det_num1, det_num2, bin.view_num(), bin.tangential_pos_num()); get_ring_pair_for_segment_axial_pos_num( ring_num1, ring_num2, bin.segment_num(), bin.axial_pos_num()); } else { - get_det_num_pair_for_view_tangential_pos_num(det_num2, det_num1, bin.view_num(), bin.tangential_pos_num()); + get_det_num_pair_for_view_tangential_pos_num(det_num2, det_num1, bin.view_num(), bin.tangential_pos_num()); get_ring_pair_for_segment_axial_pos_num( ring_num2, ring_num1, bin.segment_num(), bin.axial_pos_num()); } } @@ -187,12 +187,12 @@ get_det_pair_for_bin( void ProjDataInfoCylindricalNoArcCorr:: get_det_pos_pair_for_bin( - DetectionPositionPair<>& dp, - const Bin& bin) const + DetectionPositionPair<>& dp, + const Bin& bin) const { //lousy work around because types don't match TODO remove! #if 1 - int t1=dp.pos1().tangential_coord(), + int t1=dp.pos1().tangential_coord(), a1=dp.pos1().axial_coord(), t2=dp.pos2().tangential_coord(), a2=dp.pos2().axial_coord(); @@ -207,7 +207,7 @@ get_det_pos_pair_for_bin( get_det_pair_for_bin(dp.pos1().tangential_coord(), dp.pos1().axial_coord(), - dp.pos2().tangential_coord(), + dp.pos2().tangential_coord(), dp.pos2().axial_coord(), bin); #endif diff --git a/src/include/stir/recon_buildblock/ProjMatrixByBin.inl b/src/include/stir/recon_buildblock/ProjMatrixByBin.inl index 72e2ced515..5bfb35b068 100644 --- a/src/include/stir/recon_buildblock/ProjMatrixByBin.inl +++ b/src/include/stir/recon_buildblock/ProjMatrixByBin.inl @@ -193,9 +193,13 @@ ProjMatrixByBin::apply_tof_kernel(ProjMatrixElemsForOneBin& nonTOF_probabilities float low_dist = 0.f; float high_dist = 0.f; - float lor_length = std::sqrt((point1.x() - point2.x()) *(point1.x() - point2.x()) + + float lor_length = 1 / (0.5 * std::sqrt((point1.x() - point2.x()) *(point1.x() - point2.x()) + (point1.y() - point2.y()) *(point1.y() - point2.y()) + - (point1.z() - point2.z()) *(point1.z() - point2.z())); + (point1.z() - point2.z()) *(point1.z() - point2.z()))); + + // THe direction can be from 1 -> 2 depending on the bin sign. + const CartesianCoordinate3D middle = (point1 + point2)/2.f; + const CartesianCoordinate3D difference = point2 - middle; for (ProjMatrixElemsForOneBin::iterator element_ptr = nonTOF_probabilities.begin(); element_ptr != nonTOF_probabilities.end(); ++element_ptr) @@ -205,30 +209,22 @@ ProjMatrixByBin::apply_tof_kernel(ProjMatrixElemsForOneBin& nonTOF_probabilities project_point_on_a_line(point1, point2, voxel_center ); - float d1 = std::sqrt((point1.x() - voxel_center.x()) *(point1.x() - voxel_center.x()) + - (point1.y() - voxel_center.y()) *(point1.y() - voxel_center.y()) + - (point1.z() - voxel_center.z()) *(point1.z() - voxel_center.z())); - - // This might be risky. - // The advantage is significant speed up. - // float d2 = std::sqrt( (point2.x() - voxel_center.x()) *(point2.x() - voxel_center.x()) + - // (point2.y() - voxel_center.y()) *(point2.y() - voxel_center.y()) + - // (point2.z() - voxel_center.z()) *(point2.z() - voxel_center.z())); + CartesianCoordinate3D x = voxel_center - middle; - float m = (lor_length - d1 - d1) * 0.5f; - low_dist = (proj_data_info_sptr->tof_bin_boundaries_mm[tof_probabilities.get_bin_ptr()->timing_pos_num()].low_lim - m) * r_sqrt2_gauss_sigma; - high_dist = (proj_data_info_sptr->tof_bin_boundaries_mm[tof_probabilities.get_bin_ptr()->timing_pos_num()].high_lim - m) * r_sqrt2_gauss_sigma; + float d1 = - inner_product(x, difference) * lor_length; - // Cut-off really small values. - // Currently deactivate untill I run all the tests. - //if (abs(low_dist) > 5.5 && abs(high_dist) > 5.5) - //continue; + low_dist = (proj_data_info_sptr->tof_bin_boundaries_mm[tof_probabilities.get_bin_ptr()->timing_pos_num()].low_lim -d1) * r_sqrt2_gauss_sigma; + high_dist = (proj_data_info_sptr->tof_bin_boundaries_mm[tof_probabilities.get_bin_ptr()->timing_pos_num()].high_lim -d1) * r_sqrt2_gauss_sigma; get_tof_value(low_dist, high_dist, new_value); +// std::cout <get_value(); + //if (new_value <= 0.000001F) // Too small to bother? + // continue; tof_probabilities.push_back(ProjMatrixElemsForOneBin::value_type(element_ptr->get_coords(), new_value)); } +// int nikos= 0; } void From 915ce908dd6162edef7d61b13f29a0a5978f83d9 Mon Sep 17 00:00:00 2001 From: Nikos Efthimiou Date: Wed, 19 Jul 2017 20:26:10 +0100 Subject: [PATCH 079/509] Forgot to remove some silly comments. --- src/include/stir/recon_buildblock/ProjMatrixByBin.inl | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/include/stir/recon_buildblock/ProjMatrixByBin.inl b/src/include/stir/recon_buildblock/ProjMatrixByBin.inl index 5bfb35b068..77b0612ae8 100644 --- a/src/include/stir/recon_buildblock/ProjMatrixByBin.inl +++ b/src/include/stir/recon_buildblock/ProjMatrixByBin.inl @@ -198,7 +198,7 @@ ProjMatrixByBin::apply_tof_kernel(ProjMatrixElemsForOneBin& nonTOF_probabilities (point1.z() - point2.z()) *(point1.z() - point2.z()))); // THe direction can be from 1 -> 2 depending on the bin sign. - const CartesianCoordinate3D middle = (point1 + point2)/2.f; + const CartesianCoordinate3D middle = (point1 + point2)*0.5f; const CartesianCoordinate3D difference = point2 - middle; for (ProjMatrixElemsForOneBin::iterator element_ptr = nonTOF_probabilities.begin(); @@ -217,14 +217,12 @@ ProjMatrixByBin::apply_tof_kernel(ProjMatrixElemsForOneBin& nonTOF_probabilities high_dist = (proj_data_info_sptr->tof_bin_boundaries_mm[tof_probabilities.get_bin_ptr()->timing_pos_num()].high_lim -d1) * r_sqrt2_gauss_sigma; get_tof_value(low_dist, high_dist, new_value); -// std::cout <get_value(); - //if (new_value <= 0.000001F) // Too small to bother? - // continue; + tof_probabilities.push_back(ProjMatrixElemsForOneBin::value_type(element_ptr->get_coords(), new_value)); } -// int nikos= 0; } void From 2945feefbd6ab897590ce4c5ba0fa428317164c5 Mon Sep 17 00:00:00 2001 From: Elise Date: Thu, 20 Jul 2017 10:25:56 +0100 Subject: [PATCH 080/509] Fix of previous commit --- src/include/stir/ProjDataInfoCylindricalNoArcCorr.h | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/include/stir/ProjDataInfoCylindricalNoArcCorr.h b/src/include/stir/ProjDataInfoCylindricalNoArcCorr.h index 8399b7dc99..bb3bf952f3 100644 --- a/src/include/stir/ProjDataInfoCylindricalNoArcCorr.h +++ b/src/include/stir/ProjDataInfoCylindricalNoArcCorr.h @@ -245,8 +245,17 @@ class ProjDataInfoCylindricalNoArcCorr : public ProjDataInfoCylindrical const int timing_pos_num = 0) const; //! This routine gets the detector pair corresponding to a bin. + /*! + \see get_det_pair_for_view_tangential_pos_num() for + restrictions. In addition, this routine only works for span=1 data, + i.e. no axial compression. + \obsolete */ inline void + get_det_pair_for_bin( + int& det1_num, int& ring1_num, + int& det2_num, int& ring2_num, + const Bin&) const; //@} From fa6e5bb34dc7aab2daffaefaeeaac574c9a8ac3f Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sat, 9 Sep 2017 16:46:12 +0100 Subject: [PATCH 081/509] Bug fixes for TOF This commit fixes some bugs found with Elise Emond and Nikos Efthimiou: - Fixed bug in get_tof_bin (a delta in the last bin was assigned to bin 0). Also, let it return 0 if non-tof such that it is always usable. - Fixed bugs in conversion from mm to TOF-time and vice versa (factor 2). This also needed a fix in the ROOT support (as the same factor 2 was present). - Removed "minimum timing step (in picoseconds)" keyword from ROOT header as this should be hard-wired. - introduced mm_to_tof_delta_time, tof_delta_time_to_mm and get_tof_delta_time in ProjDataInfo - fixed (probably irrelevant) bug in get_bin_for_det_pos_pair for non-TOF data, where timing_pos was set undefined (and an assert was thrown) - let DetectionPositionPair constructors set timing_pos to 0 if not specified as this is what non-TOF would have expected (this fixes an assert in the test code). - Add TOF-loops to test_proj_data_info Tests still fail on swapping of the TOF-bin. --- src/IO/InputStreamFromROOTFile.cxx | 5 +- src/buildblock/ProjDataInfo.cxx | 14 +- .../ProjDataInfoCylindricalNoArcCorr.cxx | 2 +- src/include/stir/DetectionPositionPair.h | 7 +- src/include/stir/DetectionPositionPair.inl | 12 +- src/include/stir/ProjDataInfo.h | 15 +- src/include/stir/ProjDataInfo.inl | 34 ++- .../stir/ProjDataInfoCylindricalNoArcCorr.inl | 5 +- src/recon_buildblock/ProjMatrixByBin.cxx | 2 +- src/test/test_proj_data_info.cxx | 287 ++++++++++-------- src/test/test_time_of_flight.cxx | 6 +- 11 files changed, 242 insertions(+), 147 deletions(-) diff --git a/src/IO/InputStreamFromROOTFile.cxx b/src/IO/InputStreamFromROOTFile.cxx index 3e78e1244b..b8e38dc7a0 100644 --- a/src/IO/InputStreamFromROOTFile.cxx +++ b/src/IO/InputStreamFromROOTFile.cxx @@ -28,6 +28,7 @@ InputStreamFromROOTFile() { starting_stream_position = 0; reset(); + least_significant_clock_bit = 1.0e+12; // TODO remove cst or rename } @@ -42,13 +43,13 @@ InputStreamFromROOTFile(std::string filename, low_energy_window(low_energy_window), up_energy_window(up_energy_window), offset_dets(offset_dets) { starting_stream_position = 0; + least_significant_clock_bit = 1.0e+12; reset(); } void InputStreamFromROOTFile::set_defaults() { - least_significant_clock_bit = 1.0; } void @@ -62,7 +63,6 @@ InputStreamFromROOTFile::initialise_keymap() this->parser.add_key("offset (num of detectors)", &this->offset_dets); this->parser.add_key("low energy window (keV)", &this->low_energy_window); this->parser.add_key("upper energy window (keV)", &this->up_energy_window); - this->parser.add_key("minimum timing step (in picoseconds)", &this->least_significant_clock_bit); } bool @@ -88,7 +88,6 @@ InputStreamFromROOTFile::post_processing() stream_ptr->SetBranchAddress("comptonPhantom1", &comptonphantom1); stream_ptr->SetBranchAddress("comptonPhantom2", &comptonphantom2); - least_significant_clock_bit = 5.0e+11;// / least_significant_clock_bit; return false; } diff --git a/src/buildblock/ProjDataInfo.cxx b/src/buildblock/ProjDataInfo.cxx index 167d40929b..a7e622813a 100644 --- a/src/buildblock/ProjDataInfo.cxx +++ b/src/buildblock/ProjDataInfo.cxx @@ -75,14 +75,18 @@ START_NAMESPACE_STIR float ProjDataInfo::get_k(const Bin& bin) const { - // Probably, This condition should be removed, since I have the check odd number in the - // set_tof_mash_factor(). if (!(num_tof_bins%2)) return bin.timing_pos_num() * tof_increament_in_mm; else return (bin.timing_pos_num() * tof_increament_in_mm) - tof_increament_in_mm/2.f; } +double +ProjDataInfo::get_tof_delta_time(const Bin& bin) const +{ + return mm_to_tof_delta_time(get_k(bin) + tof_increament_in_mm / 2.f); // get_k gives "left" edge +} + float ProjDataInfo::get_sampling_in_k(const Bin& bin) const { @@ -192,7 +196,7 @@ ProjDataInfo::set_tof_mash_factor(const int new_num) "the scanner's number of max timing bins. Abort."); tof_mash_factor = new_num; - tof_increament_in_mm = (tof_mash_factor * scanner_ptr->get_size_of_timing_bin() * 0.299792458f); + tof_increament_in_mm = tof_delta_time_to_mm(tof_mash_factor * scanner_ptr->get_size_of_timing_bin()); min_tof_pos_num = - (scanner_ptr->get_num_max_of_timing_bins() / tof_mash_factor)/2; max_tof_pos_num = min_tof_pos_num + (scanner_ptr->get_num_max_of_timing_bins() / tof_mash_factor) -1; @@ -218,8 +222,8 @@ ProjDataInfo::set_tof_mash_factor(const int new_num) tof_bin_boundaries_mm[i].low_lim = cur_low; tof_bin_boundaries_mm[i].high_lim = cur_high; - tof_bin_boundaries_ps[i].low_lim = (tof_bin_boundaries_mm[i].low_lim / 0.299792458f ) ; - tof_bin_boundaries_ps[i].high_lim = ( tof_bin_boundaries_mm[i].high_lim / 0.299792458f); + tof_bin_boundaries_ps[i].low_lim = static_cast(mm_to_tof_delta_time(tof_bin_boundaries_mm[i].low_lim)); + tof_bin_boundaries_ps[i].high_lim = static_cast(mm_to_tof_delta_time(tof_bin_boundaries_mm[i].high_lim)); // I could imagine a better printing. info(boost::format("Tbin %1%: %2% - %3% mm (%4% - %5% ps) = %6%") %i % tof_bin_boundaries_mm[i].low_lim % tof_bin_boundaries_mm[i].high_lim % tof_bin_boundaries_ps[i].low_lim % tof_bin_boundaries_ps[i].high_lim % get_sampling_in_k(bin)); diff --git a/src/buildblock/ProjDataInfoCylindricalNoArcCorr.cxx b/src/buildblock/ProjDataInfoCylindricalNoArcCorr.cxx index 191d49040f..9afcad10fa 100644 --- a/src/buildblock/ProjDataInfoCylindricalNoArcCorr.cxx +++ b/src/buildblock/ProjDataInfoCylindricalNoArcCorr.cxx @@ -608,7 +608,7 @@ get_bin(const LOR& lor,const double delta_time) const if (ring1 >=0 && ring1=0 && ring2= get_min_tangential_pos_num() && bin.tangential_pos_num() <= get_max_tangential_pos_num()) { diff --git a/src/include/stir/DetectionPositionPair.h b/src/include/stir/DetectionPositionPair.h index 6bd6046c9e..4387ad40bc 100644 --- a/src/include/stir/DetectionPositionPair.h +++ b/src/include/stir/DetectionPositionPair.h @@ -6,9 +6,11 @@ \ingroup projdata \brief Declaration of class stir::DetectionPositionPair \author Kris Thielemans + \author Elise Emond */ /* Copyright (C) 2002- 2009, Hammersmith Imanet Ltd + Copyright 2017, University College London This file is part of STIR. This file is free software; you can redistribute it and/or modify @@ -32,7 +34,8 @@ START_NAMESPACE_STIR /*! \ingroup projdata \brief - A class for storing 2 coordinates-sets of a detection, as suitable for PET. + A class for storing 2 coordinates-sets of a detection, together with a timing-position index (for TOF), + as suitable for PET. \see DetectionPosition for details on what we mean with a Detector Position */ @@ -41,6 +44,8 @@ template class DetectionPositionPair { public: + //! default constructor + /*! sets TOF bin to 0, but leaves others coordinates undefined*/ inline DetectionPositionPair(); inline DetectionPositionPair(const DetectionPosition&, diff --git a/src/include/stir/DetectionPositionPair.inl b/src/include/stir/DetectionPositionPair.inl index 5391eeb19c..379de77c8a 100644 --- a/src/include/stir/DetectionPositionPair.inl +++ b/src/include/stir/DetectionPositionPair.inl @@ -6,9 +6,11 @@ \ingroup projdata \brief Implementation of inline methods of class stir::DetectionPositionPair \author Kris Thielemans + \author Elise Emond */ /* Copyright (C) 2002- 2009, Hammersmith Imanet Ltd + Copyright 2017, University College London This file is part of STIR. This file is free software; you can redistribute it and/or modify @@ -28,6 +30,7 @@ START_NAMESPACE_STIR template DetectionPositionPair:: DetectionPositionPair() + : _timing_pos(static_cast(0)) {} template @@ -80,9 +83,16 @@ bool DetectionPositionPair:: operator==(const DetectionPositionPair& p) const { + // Slightly complicated as we need to be able to cope with reverse order of detectors. If so, + // the TOF bin should swap as well. However, currently, coordT is unsigned, so timing_pos is + // always positive so sign reversal can never occur. Below implementation is ok, but + // generates a compiler warning on many compilers for unsigned. + // For an unsigned type, we should check + // timing_pos() == coordT(0) && p.timing_pos() == coordT(0) + // TODO. differentiate between types return (pos1() == p.pos1() && pos2() == p.pos2() && timing_pos() == p.timing_pos()) || - (pos1() == p.pos2() && pos2() == p.pos1() && timing_pos() == -p.timing_pos()) ; + (pos1() == p.pos2() && pos2() == p.pos1() && timing_pos() == -p.timing_pos()); } template diff --git a/src/include/stir/ProjDataInfo.h b/src/include/stir/ProjDataInfo.h index a38b1f09d8..7c6eb5a544 100644 --- a/src/include/stir/ProjDataInfo.h +++ b/src/include/stir/ProjDataInfo.h @@ -5,6 +5,7 @@ Copyright (C) 2000 - 2011-10-14, Hammersmith Imanet Ltd Copyright (C) 2011-07-01 - 2011, Kris Thielemans Copyright (C) 2016-17, University of Hull + Copyright (C) 2017, University College London This file is part of STIR. This file is free software; you can redistribute it and/or modify @@ -27,6 +28,7 @@ \author Nikos Efthimiou \author Sanida Mustafovic \author Kris Thielemans + \author Elise Emond \author PARAPET project */ @@ -112,6 +114,14 @@ class ProjDataInfo const int num_views, const int num_tangential_poss, const bool arc_corrected = true, const int tof_mash_factor = 0); + //! \name Conversion functions between TOF delta_time and mm + //@{ + inline static double + mm_to_tof_delta_time(const float dist); + inline static float + tof_delta_time_to_mm(const double delta_time); + //@} + /************ constructors ***********/ // TODO should probably be protected @@ -283,10 +293,13 @@ class ProjDataInfo normal to the projection plane */ virtual float get_s(const Bin&) const =0; - //! Get value ot the TOF location along the LOR (in mm) + //! Get value of the TOF location along the LOR (in mm) //! k is a line segment connecting the centers of the two detectors. float get_k(const Bin&) const; + //! Get the value of the TOF timing difference (in ps) + double get_tof_delta_time(const Bin&) const; + //! Get LOR corresponding to a given bin /*! \see get_bin() diff --git a/src/include/stir/ProjDataInfo.inl b/src/include/stir/ProjDataInfo.inl index 812e3fc172..c9ebc75bab 100644 --- a/src/include/stir/ProjDataInfo.inl +++ b/src/include/stir/ProjDataInfo.inl @@ -5,6 +5,7 @@ Copyright (C) 2000 - 2011-10-14, Hammersmith Imanet Ltd Copyright (C) 2011-07-01 - 2011, Kris Thielemans Copyright (C) 2016, University of Hull + Copyright (C) 2017, University College London This file is part of STIR. This file is free software; you can redistribute it and/or modify @@ -26,11 +27,24 @@ \author Nikos Efthimiou \author Sanida Mustafovic \author Kris Thielemans + \author Elise Emond \author PARAPET project */ +#include "boost/format.hpp" + START_NAMESPACE_STIR +double +ProjDataInfo::mm_to_tof_delta_time(const float dist) +{ + return dist / (0.299792458 / 2); +} +float +ProjDataInfo::tof_delta_time_to_mm(const double delta_time) +{ + return static_cast(delta_time * (0.299792458 / 2)); +} shared_ptr ProjDataInfo:: @@ -73,13 +87,18 @@ ProjDataInfo::get_num_tof_poss() const int ProjDataInfo::get_tof_bin(const double& delta) const { - for (int i = min_tof_pos_num; i < max_tof_pos_num; i++) - { - if ( delta > tof_bin_boundaries_ps[i].low_lim && - delta < tof_bin_boundaries_ps[i].high_lim) - return i; - } + if (!is_tof_data()) return 0; + + for (int i = min_tof_pos_num; i <= max_tof_pos_num; i++) + { + if (delta >= tof_bin_boundaries_ps[i].low_lim && + delta < tof_bin_boundaries_ps[i].high_lim) + return i; + } + // TODO handle differently + error(boost::format("TOF delta time %g out of range") % delta); + return 0; } int @@ -144,8 +163,7 @@ ProjDataInfo::get_coincidence_window_in_pico_sec() const float ProjDataInfo::get_coincidence_window_width() const { - // Speed of light 0.299792458 mm / psec. - return get_coincidence_window_in_pico_sec() * 0.299792458f; + return tof_delta_time_to_mm(get_coincidence_window_in_pico_sec()); } bool diff --git a/src/include/stir/ProjDataInfoCylindricalNoArcCorr.inl b/src/include/stir/ProjDataInfoCylindricalNoArcCorr.inl index c08e3214c5..db68876db0 100644 --- a/src/include/stir/ProjDataInfoCylindricalNoArcCorr.inl +++ b/src/include/stir/ProjDataInfoCylindricalNoArcCorr.inl @@ -156,14 +156,15 @@ ProjDataInfoCylindricalNoArcCorr:: get_bin_for_det_pos_pair(Bin& bin, const DetectionPositionPair<>& dp) const { - assert(this->get_tof_mash_factor()>0); return get_bin_for_det_pair(bin, dp.pos1().tangential_coord(), dp.pos1().axial_coord(), dp.pos2().tangential_coord(), dp.pos2().axial_coord(), - stir::round((float)dp.timing_pos()/this->get_tof_mash_factor())); + this->get_tof_mash_factor()==0 + ? 0 // use timing_pos==0 in the nonTOF case + : stir::round((float)dp.timing_pos()/this->get_tof_mash_factor())); } void ProjDataInfoCylindricalNoArcCorr:: diff --git a/src/recon_buildblock/ProjMatrixByBin.cxx b/src/recon_buildblock/ProjMatrixByBin.cxx index 28ffb96d35..f666d48af1 100644 --- a/src/recon_buildblock/ProjMatrixByBin.cxx +++ b/src/recon_buildblock/ProjMatrixByBin.cxx @@ -83,7 +83,7 @@ enable_tof(const shared_ptr& _proj_data_info_sptr, const bool v) { tof_enabled = true; proj_data_info_sptr = _proj_data_info_sptr; - gauss_sigma_in_mm = (proj_data_info_sptr->get_scanner_ptr()->get_timing_resolution() * 0.299792458f) / 2.355f; + gauss_sigma_in_mm = ProjDataInfo::tof_delta_time_to_mm(proj_data_info_sptr->get_scanner_ptr()->get_timing_resolution()) / 2.355f; r_sqrt2_gauss_sigma = 1.0f/ (gauss_sigma_in_mm * static_cast(sqrt(2.0))); } } diff --git a/src/test/test_proj_data_info.cxx b/src/test/test_proj_data_info.cxx index a31720aa0b..0373af5bb1 100644 --- a/src/test/test_proj_data_info.cxx +++ b/src/test/test_proj_data_info.cxx @@ -108,6 +108,7 @@ test_generic_proj_data_info(ProjDataInfo& proj_data_info) int max_diff_view_num=0; int max_diff_axial_pos_num=0; int max_diff_tangential_pos_num=0; + int max_diff_timing_pos_num = 0; #ifdef STIR_OPENMP #pragma omp parallel for schedule(dynamic) #endif @@ -118,7 +119,7 @@ test_generic_proj_data_info(ProjDataInfo& proj_data_info) for (int view_num=proj_data_info.get_min_view_num(); view_num<=proj_data_info.get_max_view_num(); view_num+=3) - { + { // loop over axial_positions. Avoid using first and last positions, as // if there is axial compression, the central LOR of a bin might actually not // fall within the scanner. In this case, the get_bin(get_LOR(org_bin)) code @@ -143,100 +144,130 @@ test_generic_proj_data_info(ProjDataInfo& proj_data_info) tangential_pos_num<=proj_data_info.get_max_tangential_pos_num()-1; tangential_pos_num+=1) { - const Bin org_bin(segment_num,view_num,axial_pos_num,tangential_pos_num, /* value*/1.f); - LORInAxialAndNoArcCorrSinogramCoordinates lor; - proj_data_info.get_LOR(lor, org_bin); - { - const Bin new_bin = proj_data_info.get_bin(lor); + for (int timing_pos_num = proj_data_info.get_min_tof_pos_num(); + timing_pos_num <= proj_data_info.get_max_tof_pos_num(); + timing_pos_num += std::max(1,(proj_data_info.get_max_tof_pos_num() - proj_data_info.get_min_tof_pos_num()) / 2))// take 3 or 1 steps, always going through 0 + { + const Bin org_bin(segment_num,view_num,axial_pos_num,tangential_pos_num, timing_pos_num, /* value*/1.f); + const double delta_time = proj_data_info.get_tof_delta_time(org_bin); + LORInAxialAndNoArcCorrSinogramCoordinates lor; + proj_data_info.get_LOR(lor, org_bin); + { + const Bin new_bin = proj_data_info.get_bin(lor, delta_time); +#ifdef STIR_TOF_DEBUG + std::cerr << "T:" << org_bin.timing_pos_num() << ", swapped=" << lor.is_swapped() + << ", z1=" << lor.z1() << ", z2=" << lor.z2() << ", phi=" << lor.phi() << ", beta=" << lor.beta() << std::endl; +#endif #if 1 - const int diff_segment_num = - intabs(org_bin.segment_num() - new_bin.segment_num()); - const int diff_view_num = - intabs(org_bin.view_num() - new_bin.view_num()); - const int diff_axial_pos_num = - intabs(org_bin.axial_pos_num() - new_bin.axial_pos_num()); - const int diff_tangential_pos_num = - intabs(org_bin.tangential_pos_num() - new_bin.tangential_pos_num()); - if (new_bin.get_bin_value()>0) - { - if (diff_segment_num>max_diff_segment_num) - max_diff_segment_num=diff_segment_num; - if (diff_view_num>max_diff_view_num) - max_diff_view_num=diff_view_num; - if (diff_axial_pos_num>max_diff_axial_pos_num) - max_diff_axial_pos_num=diff_axial_pos_num; - if (diff_tangential_pos_num>max_diff_tangential_pos_num) - max_diff_tangential_pos_num=diff_tangential_pos_num; - } - if (!check(org_bin.get_bin_value() == new_bin.get_bin_value(), "round-trip get_LOR then get_bin: value") || - !check(diff_segment_num<=0, "round-trip get_LOR then get_bin: segment") || - !check(diff_view_num<=1, "round-trip get_LOR then get_bin: view") || - !check(diff_axial_pos_num<=1, "round-trip get_LOR then get_bin: axial_pos") || - !check(diff_tangential_pos_num<=1, "round-trip get_LOR then get_bin: tangential_pos")) + const int diff_segment_num = + intabs(org_bin.segment_num() - new_bin.segment_num()); + const int diff_view_num = + intabs(org_bin.view_num() - new_bin.view_num()); + const int diff_axial_pos_num = + intabs(org_bin.axial_pos_num() - new_bin.axial_pos_num()); + const int diff_tangential_pos_num = + intabs(org_bin.tangential_pos_num() - new_bin.tangential_pos_num()); + const int diff_timing_pos_num = + intabs(org_bin.timing_pos_num() - new_bin.timing_pos_num()); + if (new_bin.get_bin_value()>0) + { + if (diff_segment_num>max_diff_segment_num) + max_diff_segment_num=diff_segment_num; + if (diff_view_num>max_diff_view_num) + max_diff_view_num=diff_view_num; + if (diff_axial_pos_num>max_diff_axial_pos_num) + max_diff_axial_pos_num=diff_axial_pos_num; + if (diff_tangential_pos_num>max_diff_tangential_pos_num) + max_diff_tangential_pos_num=diff_tangential_pos_num; + if (diff_timing_pos_num>max_diff_timing_pos_num) + max_diff_timing_pos_num = diff_timing_pos_num; + } + if (!check(org_bin.get_bin_value() == new_bin.get_bin_value(), "round-trip get_LOR then get_bin: value") || + !check(diff_segment_num<=0, "round-trip get_LOR then get_bin: segment") || + !check(diff_view_num<=1, "round-trip get_LOR then get_bin: view") || + !check(diff_axial_pos_num<=1, "round-trip get_LOR then get_bin: axial_pos") || + !check(diff_tangential_pos_num<=1, "round-trip get_LOR then get_bin: tangential_pos") || + !check(diff_timing_pos_num == 0, "round-trip get_LOR then get_bin: timing_pos")) #else - if (!check(org_bin == new_bin, "round-trip get_LOR then get_bin")) + if (!check(org_bin == new_bin, "round-trip get_LOR then get_bin")) #endif - { - cerr << "\tProblem at segment = " << org_bin.segment_num() - << ", axial pos " << org_bin.axial_pos_num() - << ", view = " << org_bin.view_num() - << ", tangential_pos_num = " << org_bin.tangential_pos_num() << "\n"; - if (new_bin.get_bin_value()>0) - cerr << "\tround-trip to segment = " << new_bin.segment_num() - << ", axial pos " << new_bin.axial_pos_num() - << ", view = " << new_bin.view_num() - << ", tangential_pos_num = " << new_bin.tangential_pos_num() - <<'\n'; - } - } - // repeat test but with different type of LOR - { - LORAs2Points lor_as_points; - lor.get_intersections_with_cylinder(lor_as_points, lor.radius()); - const Bin new_bin = proj_data_info.get_bin(lor_as_points); + { + cerr << "\tProblem at segment = " << org_bin.segment_num() + << ", axial pos " << org_bin.axial_pos_num() + << ", view = " << org_bin.view_num() + << ", tangential_pos = " << org_bin.tangential_pos_num() + << ", timing_pos = " << org_bin.timing_pos_num() << "\n"; + if (new_bin.get_bin_value()>0) + cerr << "\tround-trip to segment = " << new_bin.segment_num() + << ", axial pos " << new_bin.axial_pos_num() + << ", view = " << new_bin.view_num() + << ", tangential_pos = " << new_bin.tangential_pos_num() + << ", timing_pos = " << new_bin.timing_pos_num() + <<'\n'; + } + } + // repeat test but with different type of LOR + { + LORAs2Points lor_as_points; + lor.get_intersections_with_cylinder(lor_as_points, lor.radius()); +#ifdef STIR_TOF_DEBUG + std::cerr + << " z1=" << lor_as_points.p1().z() << ", y1=" << lor_as_points.p1().y() << ", x1=" << lor_as_points.p1().x() + << "\n z2=" << lor_as_points.p2().z() << ", y2=" << lor_as_points.p2().y() << ", x2=" << lor_as_points.p2().x() + << std::endl; +#endif + const Bin new_bin = proj_data_info.get_bin(lor_as_points, proj_data_info.get_tof_delta_time(org_bin)); #if 1 - const int diff_segment_num = - intabs(org_bin.segment_num() - new_bin.segment_num()); - const int diff_view_num = - intabs(org_bin.view_num() - new_bin.view_num()); - const int diff_axial_pos_num = - intabs(org_bin.axial_pos_num() - new_bin.axial_pos_num()); - const int diff_tangential_pos_num = - intabs(org_bin.tangential_pos_num() - new_bin.tangential_pos_num()); - if (new_bin.get_bin_value()>0) - { - if (diff_segment_num>max_diff_segment_num) - max_diff_segment_num=diff_segment_num; - if (diff_view_num>max_diff_view_num) - max_diff_view_num=diff_view_num; - if (diff_axial_pos_num>max_diff_axial_pos_num) - max_diff_axial_pos_num=diff_axial_pos_num; - if (diff_tangential_pos_num>max_diff_tangential_pos_num) - max_diff_tangential_pos_num=diff_tangential_pos_num; - } - if (!check(org_bin.get_bin_value() == new_bin.get_bin_value(), "round-trip get_LOR then get_bin (LORAs2Points): value") || - !check(diff_segment_num<=0, "round-trip get_LOR then get_bin (LORAs2Points): segment") || - !check(diff_view_num<=1, "round-trip get_LOR then get_bin (LORAs2Points): view") || - !check(diff_axial_pos_num<=1, "round-trip get_LOR then get_bin (LORAs2Points): axial_pos") || - !check(diff_tangential_pos_num<=1, "round-trip get_LOR then get_bin (LORAs2Points): tangential_pos")) + const int diff_segment_num = + intabs(org_bin.segment_num() - new_bin.segment_num()); + const int diff_view_num = + intabs(org_bin.view_num() - new_bin.view_num()); + const int diff_axial_pos_num = + intabs(org_bin.axial_pos_num() - new_bin.axial_pos_num()); + const int diff_tangential_pos_num = + intabs(org_bin.tangential_pos_num() - new_bin.tangential_pos_num()); + const int diff_timing_pos_num = + intabs(org_bin.timing_pos_num() - new_bin.timing_pos_num()); + if (new_bin.get_bin_value()>0) + { + if (diff_segment_num>max_diff_segment_num) + max_diff_segment_num=diff_segment_num; + if (diff_view_num>max_diff_view_num) + max_diff_view_num=diff_view_num; + if (diff_axial_pos_num>max_diff_axial_pos_num) + max_diff_axial_pos_num=diff_axial_pos_num; + if (diff_tangential_pos_num>max_diff_tangential_pos_num) + max_diff_tangential_pos_num=diff_tangential_pos_num; + if (diff_timing_pos_num>max_diff_timing_pos_num) + max_diff_timing_pos_num = diff_timing_pos_num; + } + if (!check(org_bin.get_bin_value() == new_bin.get_bin_value(), "round-trip get_LOR then get_bin (LORAs2Points): value") || + !check(diff_segment_num<=0, "round-trip get_LOR then get_bin (LORAs2Points): segment") || + !check(diff_view_num<=1, "round-trip get_LOR then get_bin (LORAs2Points): view") || + !check(diff_axial_pos_num<=1, "round-trip get_LOR then get_bin (LORAs2Points): axial_pos") || + !check(diff_tangential_pos_num<=1, "round-trip get_LOR then get_bin (LORAs2Points): tangential_pos") || + !check(diff_timing_pos_num == 0, "round-trip get_LOR then get_bin (LORAs2Points): timing_pos")) #else - if (!check(org_bin == new_bin, "round-trip get_LOR then get_bin")) + if (!check(org_bin == new_bin, "round-trip get_LOR then get_bin")) #endif - { - cerr << "\tProblem at segment = " << org_bin.segment_num() - << ", axial pos " << org_bin.axial_pos_num() - << ", view = " << org_bin.view_num() - << ", tangential_pos_num = " << org_bin.tangential_pos_num() << "\n"; - if (new_bin.get_bin_value()>0) - cerr << "\tround-trip to segment = " << new_bin.segment_num() - << ", axial pos " << new_bin.axial_pos_num() - << ", view = " << new_bin.view_num() - << ", tangential_pos_num = " << new_bin.tangential_pos_num() - <<'\n'; - } - } + { + cerr << "\tProblem at segment = " << org_bin.segment_num() + << ", axial pos " << org_bin.axial_pos_num() + << ", view = " << org_bin.view_num() + << ", tangential_pos_num = " << org_bin.tangential_pos_num() + << ", timing_pos = " << org_bin.timing_pos_num() << "\n"; + if (new_bin.get_bin_value()>0) + cerr << "\tround-trip to segment = " << new_bin.segment_num() + << ", axial pos " << new_bin.axial_pos_num() + << ", view = " << new_bin.view_num() + << ", tangential_pos_num = " << new_bin.tangential_pos_num() + << ", timing_pos = " << new_bin.timing_pos_num() + <<'\n'; + } + } + } } } } @@ -244,7 +275,8 @@ test_generic_proj_data_info(ProjDataInfo& proj_data_info) cerr << "Max Deviation: segment = " << max_diff_segment_num << ", axial pos " << max_diff_axial_pos_num << ", view = " << max_diff_view_num - << ", tangential_pos_num = " << max_diff_tangential_pos_num << "\n"; + << ", tangential_pos_num = " << max_diff_tangential_pos_num + << ", timing_pos_num = " << max_diff_timing_pos_num << "\n"; } @@ -608,7 +640,8 @@ run_tests() /*views*/ scanner_ptr->get_num_detectors_per_ring()/2, /*tang_pos*/64, /*arc_corrected*/ false)); - test_proj_data_info(dynamic_cast(*proj_data_info_ptr)); +#ifndef STIR_TOF_DEBUG // disable these for speed of testing + test_proj_data_info(dynamic_cast(*proj_data_info_ptr)); cerr << "\nTests with proj_data_info with mashing and axial compression (span 5)\n\n"; proj_data_info_ptr = @@ -627,7 +660,7 @@ run_tests() /*tang_pos*/64, /*arc_corrected*/ false); test_proj_data_info(dynamic_cast(*proj_data_info_ptr)); - +#endif // STIR_TOF_DEBUG cerr << "\nTests with proj_data_info with time-of-flight\n\n"; shared_ptr scanner_tof_ptr(new Scanner(Scanner::PETMR_Signa)); proj_data_info_ptr = @@ -751,49 +784,54 @@ test_proj_data_info(ProjDataInfoCylindricalNoArcCorr& proj_data_info) for (det_pos_pair.pos2().tangential_coord() = 0; det_pos_pair.pos2().tangential_coord() < (unsigned)num_detectors; det_pos_pair.pos2().tangential_coord()++) - { - // set from for-loop variable - det_pos_pair.pos1().tangential_coord() = (unsigned)tangential_coord1; - // skip case of equal detector numbers (as this is either a singular LOR) - // or an LOR parallel to the scanner axis - if (det_pos_pair.pos1().tangential_coord() == det_pos_pair.pos2().tangential_coord()) - continue; - Bin bin(0,0,0,0,0,0.0f); - DetectionPositionPair<> new_det_pos_pair; - const bool there_is_a_bin = - proj_data_info.get_bin_for_det_pos_pair(bin, det_pos_pair) == + for (det_pos_pair.timing_pos() = 0; // currently unsigned so start from 0 + det_pos_pair.timing_pos() <= (unsigned)proj_data_info.get_max_tof_pos_num(); + det_pos_pair.timing_pos() += (unsigned)std::max(1,proj_data_info.get_max_tof_pos_num())) + { + + // set from for-loop variable + det_pos_pair.pos1().tangential_coord() = (unsigned)tangential_coord1; + // skip case of equal detector numbers (as this is either a singular LOR) + // or an LOR parallel to the scanner axis + if (det_pos_pair.pos1().tangential_coord() == det_pos_pair.pos2().tangential_coord()) + continue; + Bin bin(0,0,0,0,0,0.0f); + DetectionPositionPair<> new_det_pos_pair; + const bool there_is_a_bin = + proj_data_info.get_bin_for_det_pos_pair(bin, det_pos_pair) == Succeeded::yes; - if (there_is_a_bin) - proj_data_info.get_det_pos_pair_for_bin(new_det_pos_pair, bin); - if (!check(there_is_a_bin, "checking if there is a bin for this det_pos_pair") || - !check(det_pos_pair == new_det_pos_pair, "checking if we round-trip to the same detection positions")) + if (there_is_a_bin) + proj_data_info.get_det_pos_pair_for_bin(new_det_pos_pair, bin); + if (!check(there_is_a_bin, "checking if there is a bin for this det_pos_pair") || + !check(det_pos_pair == new_det_pos_pair, "checking if we round-trip to the same detection positions")) { cerr << "Problem at det1 = " << det_pos_pair.pos1().tangential_coord() << ", det2 = " << det_pos_pair.pos2().tangential_coord() << ", ring1 = " << det_pos_pair.pos1().axial_coord() << ", ring2 = " << det_pos_pair.pos2().axial_coord() + << ", timing_pos = " << det_pos_pair.timing_pos() << endl; if (there_is_a_bin) - cerr << " dets,rings -> bin -> dets,rings, gives new numbers:\n\t" - << "det1 = " << new_det_pos_pair.pos1().tangential_coord() + cerr << " dets,rings -> bin -> dets,rings, gives new numbers:\n\t" + << "det1 = " << new_det_pos_pair.pos1().tangential_coord() - << ", det2 = " << new_det_pos_pair.pos2().tangential_coord() - << ", ring1 = " << new_det_pos_pair.pos1().axial_coord() - << ", ring2 = " << new_det_pos_pair.pos2().axial_coord() - << endl; - } + << ", det2 = " << new_det_pos_pair.pos2().tangential_coord() + << ", ring1 = " << new_det_pos_pair.pos1().axial_coord() + << ", ring2 = " << new_det_pos_pair.pos2().axial_coord() + << ", timing_pos = " << det_pos_pair.timing_pos() + << endl; + } - } // end of get_bin_for_det_pos_pair and vice versa code + } // end of get_bin_for_det_pos_pair and vice versa code cerr << "\n\tTest code for bin -> detector,ring and back conversions. (This might take a while...)"; - { Bin bin(0,0,0,0,0,0.0f); // set value for comparison later on bin.set_bin_value(0.f); for (bin.timing_pos_num() = proj_data_info.get_min_tof_pos_num(); bin.timing_pos_num() <= proj_data_info.get_max_tof_pos_num(); - ++bin.timing_pos_num()) + bin.timing_pos_num() += std::max(1,(proj_data_info.get_max_tof_pos_num() - proj_data_info.get_min_tof_pos_num()) / 2))// take 3 or 1 steps, always going through 0) for (bin.segment_num() = max(-5,proj_data_info.get_min_segment_num()); bin.segment_num() <= min(5,proj_data_info.get_max_segment_num()); ++bin.segment_num()) @@ -828,15 +866,15 @@ test_proj_data_info(ProjDataInfoCylindricalNoArcCorr& proj_data_info) cerr << "Problem at segment = " << bin.segment_num() << ", axial pos " << bin.axial_pos_num() << ", view = " << bin.view_num() - << ", tangential_pos_num = " << bin.tangential_pos_num() - << ", timing pos num = " << bin.timing_pos_num() << "\n"; + << ", tangential_pos_num = " << bin.tangential_pos_num() + << ", timing pos num = " << bin.timing_pos_num() << "\n"; if (there_is_a_bin) cerr << " bin -> dets -> bin, gives new numbers:\n\t" << "segment = " << new_bin.segment_num() << ", axial pos " << new_bin.axial_pos_num() << ", view = " << new_bin.view_num() << ", tangential_pos_num = " << new_bin.tangential_pos_num() - << ", timing pos num = " << new_bin.timing_pos_num() + << ", timing pos num = " << new_bin.timing_pos_num() << endl; } @@ -866,6 +904,9 @@ test_proj_data_info(ProjDataInfoCylindricalNoArcCorr& proj_data_info) for (bin.tangential_pos_num() = proj_data_info.get_min_tangential_pos_num(); bin.tangential_pos_num() <= proj_data_info.get_max_tangential_pos_num(); ++bin.tangential_pos_num()) + for (bin.timing_pos_num() = proj_data_info.get_min_tof_pos_num(); + bin.timing_pos_num() <= proj_data_info.get_max_tof_pos_num(); + bin.timing_pos_num() += std::max(1,(proj_data_info.get_max_tof_pos_num() - proj_data_info.get_min_tof_pos_num()) / 2))// take 3 or 1 steps, always going through 0) { proj_data_info.get_all_det_pos_pairs_for_bin(det_pos_pairs, bin); Bin new_bin(0,0,0,0,0,0.0f); @@ -885,14 +926,16 @@ test_proj_data_info(ProjDataInfoCylindricalNoArcCorr& proj_data_info) cerr << "Problem at segment = " << bin.segment_num() << ", axial pos " << bin.axial_pos_num() << ", view = " << bin.view_num() - << ", tangential_pos_num = " << bin.tangential_pos_num() << "\n"; + << ", tangential_pos = " << bin.tangential_pos_num() + << ", timing_pos - " << bin.timing_pos_num() << "\n"; if (there_is_a_bin) cerr << " bin -> dets -> bin, gives new numbers:\n\t" << "segment = " << new_bin.segment_num() << ", axial pos " << new_bin.axial_pos_num() << ", view = " << new_bin.view_num() << ", tangential_pos_num = " << new_bin.tangential_pos_num() - << endl; + << ", timing_pos - " << new_bin.timing_pos_num() + << endl; } } // end of iteration of det_pos_pairs } // end of loop over all bins @@ -961,9 +1004,11 @@ int main() { set_default_num_threads(); +#ifndef STIR_TOF_DEBUG // disable for speed of testing ProjDataInfoCylindricalArcCorrTests tests; tests.run_tests(); +#endif ProjDataInfoCylindricalNoArcCorrTests tests1; tests1.run_tests(); - return tests.main_return_value(); + return tests1.main_return_value(); } diff --git a/src/test/test_time_of_flight.cxx b/src/test/test_time_of_flight.cxx index 77ba7ba42b..c70ce53322 100644 --- a/src/test/test_time_of_flight.cxx +++ b/src/test/test_time_of_flight.cxx @@ -175,9 +175,9 @@ TOF_Tests::test_tof_proj_data_info() const int correct_tof_mashing_factor = 39; const int num_timing_positions = 9; float correct_width_of_tof_bin = test_scanner_sptr->get_size_of_timing_bin() * - test_proj_data_info_sptr->get_tof_mash_factor() * 0.299792458f; - float correct_timing_locations[num_timing_positions] = {-360.201f, -280.156f, -200.111f, -120.067f, -40.022f, 40.022f, - 120.067f, 200.111f, 280.156f}; + test_proj_data_info_sptr->get_tof_mash_factor() * 0.299792458f/2; + float correct_timing_locations[num_timing_positions] = {-360.201f/2, -280.156f/2, -200.111f/2, -120.067f/2, -40.022f/2, 40.022f/2, + 120.067f/2, 200.111f/2, 280.156f/2}; check_if_equal(correct_tof_mashing_factor, test_proj_data_info_sptr->get_tof_mash_factor(), "Different TOF mashing factor."); From 8241496a9a1b4457ca3eb83f5b5d6931f2e1aaf7 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sat, 9 Sep 2017 21:57:41 +0100 Subject: [PATCH 082/509] More TOF fixes - fix LOR and derived classes to take direction into account - fix ProjDataInfo::get_bin for TOF swapping - add all TOF bins to ProjDataInfoCylindricalNoArcCorr:get_all_det_pos_pairs_for_bin - reduce items in some loops in test_proj_data_info and switch to Discovery 690 (as opposed to Signa) to save some time --- src/buildblock/ProjDataInfoCylindrical.cxx | 2 +- .../ProjDataInfoCylindricalNoArcCorr.cxx | 46 +++++++++++++------ src/include/stir/LORCoordinates.h | 27 ++++++++++- src/include/stir/LORCoordinates.inl | 37 ++++++++++----- src/test/test_proj_data_info.cxx | 22 +++++---- 5 files changed, 95 insertions(+), 39 deletions(-) diff --git a/src/buildblock/ProjDataInfoCylindrical.cxx b/src/buildblock/ProjDataInfoCylindrical.cxx index 37b2cc2bf7..5431c00d98 100644 --- a/src/buildblock/ProjDataInfoCylindrical.cxx +++ b/src/buildblock/ProjDataInfoCylindrical.cxx @@ -548,7 +548,7 @@ get_LOR(LORInAxialAndNoArcCorrSinogramCoordinates& lor, phi, asin(s_in_mm/get_ring_radius()), get_ring_radius(), - bin.timing_pos_num()>=0); + false);// needs to set "swapped" to false given above code } void diff --git a/src/buildblock/ProjDataInfoCylindricalNoArcCorr.cxx b/src/buildblock/ProjDataInfoCylindricalNoArcCorr.cxx index 9afcad10fa..f0688de474 100644 --- a/src/buildblock/ProjDataInfoCylindricalNoArcCorr.cxx +++ b/src/buildblock/ProjDataInfoCylindricalNoArcCorr.cxx @@ -35,7 +35,7 @@ #include "stir/CartesianCoordinate3D.h" #include "stir/LORCoordinates.h" #include "stir/round.h" - +#include #ifdef BOOST_NO_STRINGSTREAM #include #else @@ -327,7 +327,8 @@ get_num_det_pos_pairs_for_bin(const Bin& bin) const return get_num_ring_pairs_for_segment_axial_pos_num(bin.segment_num(), bin.axial_pos_num())* - get_view_mashing_factor(); + get_view_mashing_factor()* + std::max(1,get_tof_mash_factor()); } void @@ -344,7 +345,8 @@ get_all_det_pos_pairs_for_bin(vector >& dps, bin.axial_pos_num()); // not sure how to handle mashing with non-zero view offset... assert(get_min_view_num()==0); - + // not sure how to handle even tof mashing + assert(!is_tof_data() || (get_tof_mash_factor() % 2 == 1)); unsigned int current_dp_num=0; for (int uncompressed_view_num=bin.view_num()*get_view_mashing_factor(); uncompressed_view_num<(bin.view_num()+1)*get_view_mashing_factor(); @@ -355,17 +357,31 @@ get_all_det_pos_pairs_for_bin(vector >& dps, const int det2_num = uncompressed_view_tangpos_to_det1det2[uncompressed_view_num][bin.tangential_pos_num()].det2_num; for (ProjDataInfoCylindrical::RingNumPairs::const_iterator rings_iter = ring_pairs.begin(); - rings_iter != ring_pairs.end(); - ++rings_iter) - { - assert(current_dp_num < get_num_det_pos_pairs_for_bin(bin)); - dps[current_dp_num].pos1().tangential_coord() = det1_num; - dps[current_dp_num].pos1().axial_coord() = rings_iter->first; - dps[current_dp_num].pos2().tangential_coord() = det2_num; - dps[current_dp_num].pos2().axial_coord() = rings_iter->second; - dps[current_dp_num].timing_pos() = bin.timing_pos_num(); - ++current_dp_num; - } + rings_iter != ring_pairs.end(); + ++rings_iter) + { + for (int uncompressed_timing_pos_num = bin.timing_pos_num()*get_tof_mash_factor() - (get_tof_mash_factor() / 2); + uncompressed_timing_pos_num <= bin.timing_pos_num()*get_tof_mash_factor() + (get_tof_mash_factor() / 2); + ++uncompressed_timing_pos_num) + { + assert(current_dp_num < get_num_det_pos_pairs_for_bin(bin)); + dps[current_dp_num].pos1().tangential_coord() = det1_num; + dps[current_dp_num].pos1().axial_coord() = rings_iter->first; + dps[current_dp_num].pos2().tangential_coord() = det2_num; + dps[current_dp_num].pos2().axial_coord() = rings_iter->second; + // need to keep dp.timing_pos positive + if (uncompressed_timing_pos_num > 0) + { + dps[current_dp_num].timing_pos() = static_cast(uncompressed_timing_pos_num); + } + else + { + std::swap(dps[current_dp_num].pos1(), dps[current_dp_num].pos2()); + dps[current_dp_num].timing_pos() = static_cast(-uncompressed_timing_pos_num); + } + ++current_dp_num; + } + } } assert(current_dp_num == get_num_det_pos_pairs_for_bin(bin)); } @@ -608,7 +624,7 @@ get_bin(const LOR& lor,const double delta_time) const if (ring1 >=0 && ring1=0 && ring2= get_min_tangential_pos_num() && bin.tangential_pos_num() <= get_max_tangential_pos_num()) { diff --git a/src/include/stir/LORCoordinates.h b/src/include/stir/LORCoordinates.h index cbbf4e0137..882493cdec 100644 --- a/src/include/stir/LORCoordinates.h +++ b/src/include/stir/LORCoordinates.h @@ -53,6 +53,10 @@ template class LORAs2Points; a line in several ways, each if which is more convenient for some application. This class provides a common base for all of these. + Note that we take direction of the line into account (since after STIR 3.0). This is + necessary for TOF support for instance. This is currently done by providing the is_swapped() + member. + \warning This is all preliminary and likely to change. */ template @@ -64,6 +68,10 @@ class LOR virtual LOR * clone() const = 0; + //! Return if the LOR direction is opposite from normal. + virtual + bool is_swapped() const = 0; + virtual Succeeded change_representation(LORInCylinderCoordinates&, @@ -178,7 +186,15 @@ class LORInCylinderCoordinates : public LOR const PointOnCylinder& p2() const { return _p2; } PointOnCylinder& p2() { return _p2; } - void reset(coordT radius=1) + //! \copybrief LOR::is_swapped() + /*! In this class, this currently always return \c false. You can swap the points if + you want to swap the direction of the LOR. + */ + bool is_swapped() const + { + return false; + } + void reset(coordT radius = 1) { // set psi such that the new LOR does intersect that cylinder _p1.psi()=0; _p2.psi()=static_cast(_PI); _radius=radius; @@ -291,6 +307,15 @@ class LORAs2Points : public LOR LOR* #endif clone() const { return new self_type(*this); } + + //! \copybrief LOR::is_swapped() + /*! In this class, this currently always return \c false. You can swap the points if + you want to swap the direction of the LOR. + */ + bool is_swapped() const + { + return false; + } virtual Succeeded diff --git a/src/include/stir/LORCoordinates.inl b/src/include/stir/LORCoordinates.inl index 9304e733f1..ec13b6fe13 100644 --- a/src/include/stir/LORCoordinates.inl +++ b/src/include/stir/LORCoordinates.inl @@ -77,7 +77,7 @@ template LORInAxialAndSinogramCoordinates:: LORInAxialAndSinogramCoordinates(const coordT radius) : LORCylindricalCoordinates_z_and_radius(radius), - _phi(0),_s(0) // set _phi,_s to value to avoid assert + _phi(0),_s(0), _swapped(false) // set _phi,_s to value to avoid assert { check_state(); } @@ -101,7 +101,7 @@ template LORInAxialAndNoArcCorrSinogramCoordinates:: LORInAxialAndNoArcCorrSinogramCoordinates(const coordT radius) : LORCylindricalCoordinates_z_and_radius(radius), - _phi(0),_beta(0) // set _phi,_beta to value to avoid assert + _phi(0),_beta(0), _swapped(false) // set _phi,_beta to value to avoid assert { check_state(); @@ -135,6 +135,8 @@ LORInCylinderCoordinates(const LORInAxialAndNoArcCorrSinogramCoordinates _p2.z() = na_coords.z2(); _p1.psi() = to_0_2pi(na_coords.phi() + na_coords.beta()); _p2.psi() = to_0_2pi(na_coords.phi() - na_coords.beta() + static_cast(_PI)); + if (na_coords.is_swapped()) + std::swap(_p1, _p2); check_state(); } @@ -147,6 +149,8 @@ LORInCylinderCoordinates(const LORInAxialAndSinogramCoordinates& coords) _p2.z() = coords.z2(); _p1.psi() = to_0_2pi(coords.phi() + coords.beta()); _p2.psi() = to_0_2pi(coords.phi() - coords.beta() + static_cast(_PI)); + if (coords.is_swapped()) + std::swap(_p1, _p2); check_state(); } @@ -156,6 +160,7 @@ get_sino_coords( coordT& _z1, coordT& _z2, coordT& _phi, coordT& _beta, + bool& _swapped, const LORInCylinderCoordinates& cyl_coords) { _beta = to_0_2pi((cyl_coords.p1().psi() - cyl_coords.p2().psi() + static_cast(_PI))/2); @@ -170,20 +175,23 @@ get_sino_coords( coordT& _z1, if (_beta >= static_cast(_PI)/2) { _beta = static_cast(_PI) - _beta; - _z2 = cyl_coords.p1().z(); - _z1 = cyl_coords.p2().z(); + _z2 = cyl_coords.p1().z(); + _z1 = cyl_coords.p2().z(); + _swapped = true; } else if (_beta < -static_cast(_PI)/2) { _beta = -static_cast(_PI) - _beta; - _z2 = cyl_coords.p1().z(); - _z1 = cyl_coords.p2().z(); + _z2 = cyl_coords.p1().z(); + _z1 = cyl_coords.p2().z(); + _swapped = false; } else { _z1 = cyl_coords.p1().z(); _z2 = cyl_coords.p2().z(); + _swapped = false; } } else @@ -195,18 +203,21 @@ get_sino_coords( coordT& _z1, _beta -= static_cast(_PI); _z1 = cyl_coords.p1().z(); _z2 = cyl_coords.p2().z(); + _swapped = false; } else if (_beta < -static_cast(_PI)/2) { _beta += static_cast(_PI); _z1 = cyl_coords.p1().z(); _z2 = cyl_coords.p2().z(); + _swapped = true; } else { _beta *= -1; _z2 = cyl_coords.p1().z(); _z1 = cyl_coords.p2().z(); + _swapped = true; } } assert(_phi>=0); @@ -225,7 +236,7 @@ LORInAxialAndNoArcCorrSinogramCoordinates(const LORInCylinderCoordinates _phi=0; _beta=0; #endif - get_sino_coords(z1(), z2(), _phi, _beta, + get_sino_coords(z1(), z2(), _phi, _beta, _swapped, cyl_coords); check_state(); } @@ -241,7 +252,7 @@ LORInAxialAndSinogramCoordinates(const LORInCylinderCoordinates& cyl_coo _phi=0; _s=0; #endif - get_sino_coords(z1(), z2(), _phi, beta, + get_sino_coords(z1(), z2(), _phi, beta, _swapped, cyl_coords); _s = this->_radius*sin(beta); check_state(); @@ -252,7 +263,8 @@ LORInAxialAndSinogramCoordinates:: LORInAxialAndSinogramCoordinates(const LORInAxialAndNoArcCorrSinogramCoordinates& coords) : LORCylindricalCoordinates_z_and_radius(coords.z1(), coords.z2(), coords.radius()), _phi(coords.phi()), - _s(coords.s()) + _s(coords.s()), + _swapped(coords.is_swapped()) { check_state(); } @@ -262,7 +274,8 @@ LORInAxialAndNoArcCorrSinogramCoordinates:: LORInAxialAndNoArcCorrSinogramCoordinates(const LORInAxialAndSinogramCoordinates& coords) : LORCylindricalCoordinates_z_and_radius(coords.z1(), coords.z2(), coords.radius()), _phi(coords.phi()), - _beta(coords.beta()) + _beta(coords.beta()), + _swapped(coords.is_swapped()) { check_state(); } @@ -338,8 +351,8 @@ find_LOR_intersections_with_cylinder(LORAs2Points& intersection_coords, return Succeeded::no; // LOR is outside detector radius const coordT2 root = static_cast(sqrt(argsqrt)); - const coordT2 l1 = static_cast((- (d.x()*c1.x() + d.y()*c1.y())+root)/dxy2); - const coordT2 l2 = static_cast((- (d.x()*c1.x() + d.y()*c1.y())-root)/dxy2); + const coordT2 l2 = static_cast((- (d.x()*c1.x() + d.y()*c1.y())+root)/dxy2); + const coordT2 l1 = static_cast((- (d.x()*c1.x() + d.y()*c1.y())-root)/dxy2); // TODO won't work when coordT1!=coordT2 intersection_coords.p1() = d*l1 + c1; intersection_coords.p2() = d*l2 + c1; diff --git a/src/test/test_proj_data_info.cxx b/src/test/test_proj_data_info.cxx index 0373af5bb1..543c48136e 100644 --- a/src/test/test_proj_data_info.cxx +++ b/src/test/test_proj_data_info.cxx @@ -52,6 +52,8 @@ using std::max; using std::size_t; #endif +//#define STIR_TOF_DEBUG 1 + START_NAMESPACE_STIR static inline @@ -154,7 +156,7 @@ test_generic_proj_data_info(ProjDataInfo& proj_data_info) proj_data_info.get_LOR(lor, org_bin); { const Bin new_bin = proj_data_info.get_bin(lor, delta_time); -#ifdef STIR_TOF_DEBUG +#if STIR_TOF_DEBUG>1 std::cerr << "T:" << org_bin.timing_pos_num() << ", swapped=" << lor.is_swapped() << ", z1=" << lor.z1() << ", z2=" << lor.z2() << ", phi=" << lor.phi() << ", beta=" << lor.beta() << std::endl; #endif @@ -211,7 +213,7 @@ test_generic_proj_data_info(ProjDataInfo& proj_data_info) { LORAs2Points lor_as_points; lor.get_intersections_with_cylinder(lor_as_points, lor.radius()); -#ifdef STIR_TOF_DEBUG +#if STIR_TOF_DEBUG>1 std::cerr << " z1=" << lor_as_points.p1().z() << ", y1=" << lor_as_points.p1().y() << ", x1=" << lor_as_points.p1().x() << "\n z2=" << lor_as_points.p2().z() << ", y2=" << lor_as_points.p2().y() << ", x2=" << lor_as_points.p2().x() @@ -662,14 +664,14 @@ run_tests() test_proj_data_info(dynamic_cast(*proj_data_info_ptr)); #endif // STIR_TOF_DEBUG cerr << "\nTests with proj_data_info with time-of-flight\n\n"; - shared_ptr scanner_tof_ptr(new Scanner(Scanner::PETMR_Signa)); + shared_ptr scanner_tof_ptr(new Scanner(Scanner::Discovery690)); proj_data_info_ptr = ProjDataInfo::construct_proj_data_info(scanner_tof_ptr, /*span*/11, scanner_tof_ptr->get_num_rings()-1, /*views*/ scanner_tof_ptr->get_num_detectors_per_ring()/2, /*tang_pos*/64, /*arc_corrected*/ false, - /*tof_mashing*/39); + /*tof_mashing*/5); test_proj_data_info(dynamic_cast(*proj_data_info_ptr)); } @@ -837,7 +839,7 @@ test_proj_data_info(ProjDataInfoCylindricalNoArcCorr& proj_data_info) ++bin.segment_num()) for (bin.axial_pos_num() = proj_data_info.get_min_axial_pos_num(bin.segment_num()); bin.axial_pos_num() <= proj_data_info.get_max_axial_pos_num(bin.segment_num()); - ++bin.axial_pos_num()) + bin.axial_pos_num() += std::min(3, proj_data_info.get_num_axial_poss(bin.segment_num()))) #ifdef STIR_OPENMP // insert a parallel for here for testing. // we do it at this level to avoid too much overhead for the thread creation, while still having enough jobs to do // Note that the omp construct needs an int loop variable @@ -894,16 +896,16 @@ test_proj_data_info(ProjDataInfoCylindricalNoArcCorr& proj_data_info) #endif for (bin.segment_num() = proj_data_info.get_min_segment_num(); bin.segment_num() <= proj_data_info.get_max_segment_num(); - ++bin.segment_num()) + bin.segment_num() += std::max(1, proj_data_info.get_num_segments()/2)) for (bin.axial_pos_num() = proj_data_info.get_min_axial_pos_num(bin.segment_num()); bin.axial_pos_num() <= proj_data_info.get_max_axial_pos_num(bin.segment_num()); - ++bin.axial_pos_num()) + bin.axial_pos_num()+=std::min(3, proj_data_info.get_num_axial_poss(bin.segment_num()))) for (bin.view_num() = proj_data_info.get_min_view_num(); - bin.view_num() <= proj_data_info.get_max_view_num(); - ++bin.view_num()) + bin.view_num() <= proj_data_info.get_max_view_num(); + bin.view_num()+= std::min(5, proj_data_info.get_num_views())) for (bin.tangential_pos_num() = proj_data_info.get_min_tangential_pos_num(); bin.tangential_pos_num() <= proj_data_info.get_max_tangential_pos_num(); - ++bin.tangential_pos_num()) + bin.tangential_pos_num()+= std::min(7, proj_data_info.get_num_tangential_poss())) for (bin.timing_pos_num() = proj_data_info.get_min_tof_pos_num(); bin.timing_pos_num() <= proj_data_info.get_max_tof_pos_num(); bin.timing_pos_num() += std::max(1,(proj_data_info.get_max_tof_pos_num() - proj_data_info.get_min_tof_pos_num()) / 2))// take 3 or 1 steps, always going through 0) From 5c68b72181d12ecf95351c71cbebf57a7e684180 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sun, 10 Sep 2017 00:56:56 +0100 Subject: [PATCH 083/509] replace auto_ptr with unique_ptr --- src/include/stir/IO/GESignaListmodeInputFileFormat.h | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/include/stir/IO/GESignaListmodeInputFileFormat.h b/src/include/stir/IO/GESignaListmodeInputFileFormat.h index cd9500cabf..1ee6846597 100644 --- a/src/include/stir/IO/GESignaListmodeInputFileFormat.h +++ b/src/include/stir/IO/GESignaListmodeInputFileFormat.h @@ -89,19 +89,17 @@ std::cout << "\n Manufacturer : " << read_str_manufacturer << "\n\n"; } } public: - virtual std::auto_ptr + virtual unique_ptr read_from_file(std::istream& input) const { warning("read_from_file for GESigna listmode data with istream not implemented %s:%s. Sorry", __FILE__, __LINE__); - return - std::auto_ptr - (0); + return unique_ptr(); } - virtual std::auto_ptr + virtual unique_ptr read_from_file(const std::string& filename) const { - return std::auto_ptr(new CListModeDataGESigna(filename)); + return unique_ptr(new CListModeDataGESigna(filename)); } }; From b7f5729e7fb2fab44c9b86270298b3ec6de0f9b1 Mon Sep 17 00:00:00 2001 From: Nikos Efthimiou Date: Mon, 11 Sep 2017 09:26:24 +0100 Subject: [PATCH 084/509] Patch to make TOF reconstruction work DetectorPositionPair can accomondate negative TOF bins. LOR Cartesian coordinates flip correctly --- .../ProjDataInfoCylindricalNoArcCorr.cxx | 27 ++++++++++++++++--- src/include/stir/DetectionPositionPair.h | 4 +-- src/include/stir/DetectionPositionPair.inl | 2 +- .../stir/ProjDataInfoCylindricalNoArcCorr.inl | 16 +++++------ 4 files changed, 34 insertions(+), 15 deletions(-) diff --git a/src/buildblock/ProjDataInfoCylindricalNoArcCorr.cxx b/src/buildblock/ProjDataInfoCylindricalNoArcCorr.cxx index f0688de474..2ad4a440ea 100644 --- a/src/buildblock/ProjDataInfoCylindricalNoArcCorr.cxx +++ b/src/buildblock/ProjDataInfoCylindricalNoArcCorr.cxx @@ -484,6 +484,25 @@ find_cartesian_coordinates_given_scanner_coordinates (CartesianCoordinate3Dget_num_detectors_per_ring(); + int d1, d2, r1, r2; + + this->initialise_det1det2_to_uncompressed_view_tangpos_if_not_done_yet(); + + if (!det1det2_to_uncompressed_view_tangpos[det1][det2].swap_detectors) + { + d1 = det2; + d2 = det1; + r1 = Ring_B; + r2 = Ring_A; + } + else + { + d1 = det1; + d2 = det2; + r1 = Ring_A; + r2 = Ring_B; + } + #if 0 const float df1 = (2.*_PI/num_detectors_per_ring)*(det1); const float df2 = (2.*_PI/num_detectors_per_ring)*(det2); @@ -510,10 +529,10 @@ find_cartesian_coordinates_given_scanner_coordinates (CartesianCoordinate3D cyl_coords(get_scanner_ptr()->get_effective_ring_radius()); - cyl_coords.p1().psi() = static_cast((2.*_PI/num_detectors_per_ring)*(det1)); - cyl_coords.p2().psi() = static_cast((2.*_PI/num_detectors_per_ring)*(det2)); - cyl_coords.p1().z() = Ring_A*get_scanner_ptr()->get_ring_spacing(); - cyl_coords.p2().z() = Ring_B*get_scanner_ptr()->get_ring_spacing(); + cyl_coords.p1().psi() = static_cast((2.*_PI/num_detectors_per_ring)*(d1)); + cyl_coords.p2().psi() = static_cast((2.*_PI/num_detectors_per_ring)*(d2)); + cyl_coords.p1().z() = r1*get_scanner_ptr()->get_ring_spacing(); + cyl_coords.p2().z() = r2*get_scanner_ptr()->get_ring_spacing(); LORAs2Points lor(cyl_coords); coord_1 = lor.p1(); coord_2 = lor.p2(); diff --git a/src/include/stir/DetectionPositionPair.h b/src/include/stir/DetectionPositionPair.h index 4387ad40bc..d419f47af6 100644 --- a/src/include/stir/DetectionPositionPair.h +++ b/src/include/stir/DetectionPositionPair.h @@ -57,7 +57,7 @@ class DetectionPositionPair inline const coordT timing_pos() const; inline DetectionPosition& pos1(); inline DetectionPosition& pos2(); - inline coordT& timing_pos(); + inline int& timing_pos(); //! comparison operators inline bool operator==(const DetectionPositionPair&) const; inline bool operator!=(const DetectionPositionPair&) const; @@ -65,7 +65,7 @@ class DetectionPositionPair private : DetectionPosition p1; DetectionPosition p2; - coordT _timing_pos; + int _timing_pos; }; END_NAMESPACE_STIR diff --git a/src/include/stir/DetectionPositionPair.inl b/src/include/stir/DetectionPositionPair.inl index 379de77c8a..dbca441c81 100644 --- a/src/include/stir/DetectionPositionPair.inl +++ b/src/include/stir/DetectionPositionPair.inl @@ -72,7 +72,7 @@ pos2() { return p2; } template -coordT& +int& DetectionPositionPair:: timing_pos() { return _timing_pos; } diff --git a/src/include/stir/ProjDataInfoCylindricalNoArcCorr.inl b/src/include/stir/ProjDataInfoCylindricalNoArcCorr.inl index db68876db0..b4ffa53c20 100644 --- a/src/include/stir/ProjDataInfoCylindricalNoArcCorr.inl +++ b/src/include/stir/ProjDataInfoCylindricalNoArcCorr.inl @@ -173,16 +173,16 @@ get_det_pair_for_bin( int& det_num2, int& ring_num2, const Bin& bin) const { - if (bin.timing_pos_num()>=0) - { + //if (bin.timing_pos_num()>=0) + // { get_det_num_pair_for_view_tangential_pos_num(det_num1, det_num2, bin.view_num(), bin.tangential_pos_num()); get_ring_pair_for_segment_axial_pos_num( ring_num1, ring_num2, bin.segment_num(), bin.axial_pos_num()); - } - else - { - get_det_num_pair_for_view_tangential_pos_num(det_num2, det_num1, bin.view_num(), bin.tangential_pos_num()); - get_ring_pair_for_segment_axial_pos_num( ring_num2, ring_num1, bin.segment_num(), bin.axial_pos_num()); - } + //} + //else + //{ + // get_det_num_pair_for_view_tangential_pos_num(det_num2, det_num1, bin.view_num(), bin.tangential_pos_num()); + // get_ring_pair_for_segment_axial_pos_num( ring_num2, ring_num1, bin.segment_num(), bin.axial_pos_num()); + // } } void From cabd3ab5cd2a6157e0c771661e7f92ae95c965f0 Mon Sep 17 00:00:00 2001 From: Ottavia Date: Tue, 12 Sep 2017 17:57:23 +0100 Subject: [PATCH 085/509] Add facility to LmToProjData to process multiple TOF bins in one go --- src/include/stir/listmode/LmToProjData.h | 4 +- src/listmode_buildblock/LmToProjData.cxx | 134 ++++++++++++++--------- 2 files changed, 83 insertions(+), 55 deletions(-) diff --git a/src/include/stir/listmode/LmToProjData.h b/src/include/stir/listmode/LmToProjData.h index 2cd25a678e..e268dd5401 100644 --- a/src/include/stir/listmode/LmToProjData.h +++ b/src/include/stir/listmode/LmToProjData.h @@ -110,7 +110,8 @@ class CListTime; ; if you're short of RAM (i.e. a single projdata does not fit into memory), ; you can use this to process the list mode data in multiple passes. num_segments_in_memory := -1 - + ; same for TOF bins + num_TOF_bins_in_memory := 1 End := \endverbatim @@ -241,6 +242,7 @@ class LmToProjData : public ParsingObject //! Use TOF information bool use_tof; int num_segments_in_memory; + int num_timing_poss_in_memory; // TODO make long (or even unsigned long) but can't do this yet because we can't parse longs yet int num_events_to_store; int max_segment_num_to_process; diff --git a/src/listmode_buildblock/LmToProjData.cxx b/src/listmode_buildblock/LmToProjData.cxx index 0fc7b8a2e7..7f6da73a27 100644 --- a/src/listmode_buildblock/LmToProjData.cxx +++ b/src/listmode_buildblock/LmToProjData.cxx @@ -118,11 +118,12 @@ typedef SegmentByView segment_type; static void -allocate_segments(VectorWithOffset& segments, - const int start_segment_index, - const int end_segment_index, - const ProjDataInfo* proj_data_info_ptr, - const int timing_pos_num = 0); +allocate_segments(VectorWithOffset >& segments, + const int start_timing_pos_index, + const int end_timing_pos_index, + const int start_segment_index, + const int end_segment_index, + const ProjDataInfo* proj_data_info_ptr); // In the next 2 functions, the 'output' parameter needs to be passed // because save_and_delete_segments needs it when we're not using SegmentByView @@ -132,7 +133,9 @@ allocate_segments(VectorWithOffset& segments, */ static void save_and_delete_segments(shared_ptr& output, - VectorWithOffset& segments, + VectorWithOffset >& segments, + const int start_timing_pos_index, + const int end_timing_pos_index, const int start_segment_index, const int end_segment_index, ProjData& proj_data); @@ -155,6 +158,7 @@ set_defaults() store_delayeds = true; interactive=false; num_segments_in_memory = -1; + num_timing_poss_in_memory = 1; normalisation_ptr.reset(new TrivialBinNormalisation); post_normalisation_ptr.reset(new TrivialBinNormalisation); do_pre_normalisation =0; @@ -176,6 +180,7 @@ initialise_keymap() parser.add_parsing_key("Bin Normalisation type for post-normalisation", &post_normalisation_ptr); parser.add_key("maximum absolute segment number to process", &max_segment_num_to_process); parser.add_key("do pre normalisation ", &do_pre_normalisation); + parser.add_key("num_TOF_bins_in_memory", &num_timing_poss_in_memory); parser.add_key("num_segments_in_memory", &num_segments_in_memory); //if (lm_data_ptr->has_delayeds()) TODO we haven't read the CListModeData yet, so cannot access has_delayeds() yet @@ -548,9 +553,13 @@ actual_process_data_without_tof() double time_of_last_stored_event = 0; long num_stored_events = 0; - VectorWithOffset - segments (template_proj_data_info_ptr->get_min_segment_num(), - template_proj_data_info_ptr->get_max_segment_num()); + VectorWithOffset > + segments (0,0); + for (int timing_pos_num=segments.get_min_index(); timing_pos_num<=segments.get_max_index(); ++timing_pos_num) + { + segments[timing_pos_num].resize(template_proj_data_info_ptr->get_min_segment_num(), + template_proj_data_info_ptr->get_max_segment_num()); + } VectorWithOffset frame_start_positions(1, static_cast(frame_defs.get_num_frames())); @@ -607,7 +616,7 @@ actual_process_data_without_tof() min( proj_data_ptr->get_max_segment_num()+1, start_segment_index + num_segments_in_memory) - 1; if (!interactive) - allocate_segments(segments, start_segment_index, end_segment_index, proj_data_ptr->get_proj_data_info_ptr()); + allocate_segments(segments, 0,0,start_segment_index, end_segment_index, proj_data_ptr->get_proj_data_info_ptr()); // the next variable is used to see if there are more events to store for the current segments // num_events_to_store-more_events will be the number of allowed coincidence events currently seen in the file @@ -713,7 +722,7 @@ actual_process_data_without_tof() bin.segment_num(), bin.view_num(), bin.axial_pos_num(), bin.tangential_pos_num(), current_time, event_increment); else - (*segments[bin.segment_num()])[bin.view_num()][bin.axial_pos_num()][bin.tangential_pos_num()] += + (*segments[0][bin.segment_num()])[bin.view_num()][bin.axial_pos_num()][bin.tangential_pos_num()] += bin.get_bin_value() * event_increment; } @@ -733,7 +742,7 @@ actual_process_data_without_tof() if (!interactive) save_and_delete_segments(output, segments, - start_segment_index, end_segment_index, + 0,0, start_segment_index, end_segment_index, *proj_data_ptr); } // end of for loop for segment range cerr << "\nNumber of prompts stored in this time period : " << num_prompts_in_frame @@ -810,20 +819,27 @@ actual_process_data_with_tof() const double start_time = frame_defs.get_start_time(current_frame_num); const double end_time = frame_defs.get_end_time(current_frame_num); - for (int current_timing_pos_index = proj_data_ptr->get_min_tof_pos_num(); - current_timing_pos_index <= proj_data_ptr->get_max_tof_pos_num(); - current_timing_pos_index += 1) + VectorWithOffset > + segments (template_proj_data_info_ptr->get_min_tof_pos_num(), + template_proj_data_info_ptr->get_max_tof_pos_num()); + for (int timing_pos_num=segments.get_min_index(); timing_pos_num<=segments.get_max_index(); ++timing_pos_num) + { + segments[timing_pos_num].resize(template_proj_data_info_ptr->get_min_segment_num(), + template_proj_data_info_ptr->get_max_segment_num()); + } + for (int start_timing_pos_index = proj_data_ptr->get_min_tof_pos_num(); + start_timing_pos_index <= proj_data_ptr->get_max_tof_pos_num(); + start_timing_pos_index += num_timing_poss_in_memory) { - /* - For each start_segment_index, we check which events occur in the - segments between start_segment_index and - start_segment_index+num_segments_in_memory. - */ - - VectorWithOffset - segments (template_proj_data_info_ptr->get_min_segment_num(), - template_proj_data_info_ptr->get_max_segment_num()); - + const int end_timing_pos_index = + min( proj_data_ptr->get_max_tof_pos_num()+1, + start_timing_pos_index + num_timing_poss_in_memory) - 1; + + /* + For each start_segment_index, we check which events occur in the + segments between start_segment_index and + start_segment_index+num_segments_in_memory. + */ for (int start_segment_index = proj_data_ptr->get_min_segment_num(); start_segment_index <= proj_data_ptr->get_max_segment_num(); start_segment_index += num_segments_in_memory) @@ -833,7 +849,10 @@ actual_process_data_with_tof() min( proj_data_ptr->get_max_segment_num()+1, start_segment_index + num_segments_in_memory) - 1; if (!interactive) - allocate_segments(segments, start_segment_index, end_segment_index, proj_data_ptr->get_proj_data_info_ptr(),current_timing_pos_index); + allocate_segments(segments, + start_timing_pos_index,end_timing_pos_index, + start_segment_index, end_segment_index, + proj_data_ptr->get_proj_data_info_ptr()); // the next variable is used to see if there are more events to store for the current segments // num_events_to_store-more_events will be the number of allowed coincidence events currently seen in the file @@ -843,10 +862,10 @@ actual_process_data_with_tof() unsigned long int more_events = do_time_frame? 1 : num_events_to_store; - if (start_segment_index != proj_data_ptr->get_min_segment_num() || current_timing_pos_index > proj_data_ptr->get_min_tof_pos_num()) + if (start_segment_index != proj_data_ptr->get_min_segment_num() || start_timing_pos_index > proj_data_ptr->get_min_tof_pos_num()) { // we're going once more through the data (for the next batch of segments) - cerr << "\nProcessing next batch of segments\n"; + cerr << "\nProcessing next batch of segments for start TOF bin " << start_timing_pos_index <<"\n"; // go to the beginning of the listmode data for this frame lm_data_ptr->set_get_position(frame_start_positions[current_frame_num]); current_time = start_time; @@ -924,8 +943,8 @@ actual_process_data_with_tof() if (!do_time_frame) more_events -= event_increment; - // Check if the timing position of the bin is the current one. - if (bin.timing_pos_num() == current_timing_pos_index) + // Check if the timing position of the bin is in the range + if (bin.timing_pos_num() >= start_timing_pos_index && bin.timing_pos_num()<=end_timing_pos_index) { // now check if we have its segment in memory if (bin.segment_num() >= start_segment_index && bin.segment_num()<=end_segment_index) @@ -946,7 +965,7 @@ actual_process_data_with_tof() bin.view_num(), bin.axial_pos_num(), bin.tangential_pos_num(), current_time, event_increment); else - (*segments[bin.segment_num()])[bin.view_num()][bin.axial_pos_num()][bin.tangential_pos_num()] += + (*segments[bin.timing_pos_num()][bin.segment_num()])[bin.view_num()][bin.axial_pos_num()][bin.tangential_pos_num()] += bin.get_bin_value() * event_increment; } @@ -968,7 +987,8 @@ actual_process_data_with_tof() if (!interactive) save_and_delete_segments(output, segments, - start_segment_index, end_segment_index, + start_timing_pos_index,end_timing_pos_index, + start_segment_index, end_segment_index, *proj_data_ptr); } // end of for loop for segment range @@ -994,7 +1014,10 @@ actual_process_data_with_tof() void LmToProjData::run_tof_test_function() { - VectorWithOffset +#if 1 + error("TOF test function disabled"); +#else + VectorWithOffset > segments (template_proj_data_info_ptr->get_min_segment_num(), template_proj_data_info_ptr->get_max_segment_num()); @@ -1031,7 +1054,9 @@ LmToProjData::run_tof_test_function() min( proj_data_ptr->get_max_segment_num()+1, start_segment_index + num_segments_in_memory) - 1; if (!interactive) - allocate_segments(segments, start_segment_index, end_segment_index, proj_data_ptr->get_proj_data_info_ptr(), current_timing_pos_index); + allocate_segments(segments, start_segment_index, end_segment_index, + start_timing_pos_index, end_timing_pos_index, + proj_data_ptr->get_proj_data_info_ptr(), current_timing_pos_index); for (int ax_num = proj_data_ptr->get_proj_data_info_ptr()->get_min_axial_pos_num(start_segment_index); ax_num <= proj_data_ptr->get_proj_data_info_ptr()->get_max_axial_pos_num(start_segment_index); @@ -1056,7 +1081,7 @@ LmToProjData::run_tof_test_function() } // end of for loop for segment range } // end of for loop for timing positions - +#endif } @@ -1065,20 +1090,22 @@ LmToProjData::run_tof_test_function() void -allocate_segments( VectorWithOffset& segments, +allocate_segments(VectorWithOffset > & segments, + const int start_timing_pos_index, + const int end_timing_pos_index, const int start_segment_index, const int end_segment_index, - const ProjDataInfo* proj_data_info_ptr, - const int timing_pos_num) + const ProjDataInfo* proj_data_info_ptr) { - for (int seg=start_segment_index ; seg<=end_segment_index; seg++) + for (int timing_pos_num=start_timing_pos_index ; timing_pos_num<=end_timing_pos_index; timing_pos_num++) + for (int seg=start_segment_index ; seg<=end_segment_index; seg++) { #ifdef USE_SegmentByView - segments[seg] = new SegmentByView( + segments[timing_pos_num][seg] = new SegmentByView( proj_data_info_ptr->get_empty_segment_by_view (seg, false, timing_pos_num)); #else - segments[seg] = + segments[timing_pos_num][seg] = new Array<3,elem_type>(IndexRange3D(0, proj_data_info_ptr->get_num_views()-1, 0, proj_data_info_ptr->get_num_axial_poss(seg)-1, -(proj_data_info_ptr->get_num_tangential_poss()/2), @@ -1089,24 +1116,23 @@ allocate_segments( VectorWithOffset& segments, void save_and_delete_segments(shared_ptr& output, - VectorWithOffset& segments, + VectorWithOffset >& segments, + const int start_timing_pos_index, + const int end_timing_pos_index, const int start_segment_index, - const int end_segment_index, - ProjData& proj_data) + const int end_segment_index, + ProjData& proj_data) { - - for (int seg=start_segment_index; seg<=end_segment_index; seg++) - { - { + for (int timing_pos_num=start_timing_pos_index ; timing_pos_num<=end_timing_pos_index; timing_pos_num++) + for (int seg=start_segment_index; seg<=end_segment_index; seg++) + { #ifdef USE_SegmentByView - proj_data.set_segment(*segments[seg]); + proj_data.set_segment(*segments[timing_pos_num][seg]); #else - (*segments[seg]).write_data(*output); + (*segments[timing_pos_num][seg]).write_data(*output); #endif - delete segments[seg]; - } - - } + delete segments[timing_pos_num][seg]; + } } From 74e2de4f40866a97fbde30c3bf75eaa8a7acd943 Mon Sep 17 00:00:00 2001 From: Nikos Efthimiou Date: Wed, 13 Sep 2017 16:56:38 +0100 Subject: [PATCH 086/509] Minor edits --- src/include/stir/ProjDataInfo.inl | 2 +- ...ithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/include/stir/ProjDataInfo.inl b/src/include/stir/ProjDataInfo.inl index c9ebc75bab..77ab6bad3b 100644 --- a/src/include/stir/ProjDataInfo.inl +++ b/src/include/stir/ProjDataInfo.inl @@ -97,7 +97,7 @@ ProjDataInfo::get_tof_bin(const double& delta) const return i; } // TODO handle differently - error(boost::format("TOF delta time %g out of range") % delta); + warning(boost::format("TOF delta time %g out of range") % delta); return 0; } diff --git a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx index ffd4736cd8..56087b3662 100644 --- a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx +++ b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx @@ -133,6 +133,9 @@ actual_subsets_are_approximately_balanced(std::string& warning_message) const Array<1,int> num_bins_in_subset(this->num_subsets); num_bins_in_subset.fill(0); + if (this->num_subsets == 1) + return true; + for (int subset_num=0; subset_numnum_subsets; ++subset_num) { From b091861efcb1c0446e41734b11bce30398175599 Mon Sep 17 00:00:00 2001 From: Elise Date: Fri, 15 Sep 2017 17:53:48 +0100 Subject: [PATCH 087/509] Adds a unit test to check the consistency between root listmode unlisting and STIR TOF projector. Also small fix to be able to use User_defined_scanners with TOF. --- src/IO/InterfileHeader.cxx | 3 +- src/recon_test/CMakeLists.txt | 13 + src/recon_test/test_consistency_root.cxx | 318 +++++++++++++++++++++++ 3 files changed, 333 insertions(+), 1 deletion(-) create mode 100644 src/recon_test/test_consistency_root.cxx diff --git a/src/IO/InterfileHeader.cxx b/src/IO/InterfileHeader.cxx index c556f75614..c7aa645add 100644 --- a/src/IO/InterfileHeader.cxx +++ b/src/IO/InterfileHeader.cxx @@ -1283,7 +1283,7 @@ bool InterfilePDFSHeader::post_processing() // data from the Interfile header (or the guessed scanner). shared_ptr scanner_sptr_from_file; - if (!guessed_scanner_ptr->is_tof_ready()) + if (false) // !guessed_scanner_ptr->is_tof_ready()) { scanner_sptr_from_file.reset( new Scanner(guessed_scanner_ptr->get_type(), @@ -1309,6 +1309,7 @@ bool InterfilePDFSHeader::post_processing() } else { + warning("ENERGY WINDOW INFO IGNORED"); scanner_sptr_from_file.reset( new Scanner(guessed_scanner_ptr->get_type(), get_exam_info_ptr()->originating_system, diff --git a/src/recon_test/CMakeLists.txt b/src/recon_test/CMakeLists.txt index 86852dcc28..497921ef48 100644 --- a/src/recon_test/CMakeLists.txt +++ b/src/recon_test/CMakeLists.txt @@ -33,6 +33,19 @@ set(${dir_INVOLVED_TEST_EXE_SOURCES} recontest ) +if (HAVE_CERN_ROOT) + list(APPEND ${dir_INVOLVED_TEST_EXE_SOURCES} test_consistency_root) + foreach(n RANGE 1 12 1) + set(test_name test_consistency_root_${n}) + ADD_TEST(NAME ${test_name} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/root_files_generation + COMMAND ${CMAKE_CURRENT_BINARY_DIR}/test_consistency_root + ${CMAKE_CURRENT_SOURCE_DIR}/root_files_generation/root_header_test${n}.hroot + ${CMAKE_CURRENT_SOURCE_DIR}/root_files_generation/stir_image${n}.hv + ) + endforeach() +endif() + include(stir_test_exe_targets) # a test that uses MPI diff --git a/src/recon_test/test_consistency_root.cxx b/src/recon_test/test_consistency_root.cxx new file mode 100644 index 0000000000..5e9970f281 --- /dev/null +++ b/src/recon_test/test_consistency_root.cxx @@ -0,0 +1,318 @@ +/* + Copyright (C) 2017, UCL + 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 +*/ +/*! + \ingroup recon_test + Implementation of stir::test_consistency_root +*/ +#include "stir/ProjDataInfoCylindricalNoArcCorr.h" +#include "stir/recon_buildblock/ProjMatrixByBin.h" +#include "stir/recon_buildblock/ProjMatrixByBinUsingRayTracing.h" +#include "stir/recon_buildblock/ProjMatrixElemsForOneBin.h" +#include "stir/centre_of_gravity.h" +#include "stir/listmode/LmToProjData.h" +#include "stir/listmode/CListModeDataROOT.h" +#include "stir/listmode/CListRecord.h" +#include "stir/IO/read_from_file.h" +#include "stir/HighResWallClockTimer.h" +#include "stir/DiscretisedDensity.h" +#include "stir/VoxelsOnCartesianGrid.h" +#include "stir/Succeeded.h" +#include "stir/shared_ptr.h" +#include "stir/RunTests.h" +#include "boost/lexical_cast.hpp" + +#include "stir/info.h" +#include "stir/warning.h" + +#include + +using std::cerr; +using std::ifstream; + +START_NAMESPACE_STIR + +/*! + * \ingroup recon_test + * \brief Test class to check the consistency between ROOT listmode and STIR backprojection for high resolution TOF + * \author Elise Emond + * + * This test currently uses Root listmodes of single point sources. Scatters are not + * considered. It could be extended to actual scanner data, for which we would need + * to exclude scatter events. A way to do so would be to compute the distance between + * the bin LOR and the (non-TOF?) LOR calculated from the original data and exclude + * the events for which the distance would be more than a chosen threshold. + * + */ + +class ROOTconsistency_Tests : public RunTests +{ +public: + ROOTconsistency_Tests(std::string in, std::string image) + : root_header_filename(in), image_filename(image) + {} + void run_tests(); + + // Class to store the coordinates and weights of the maxima of the Lines-of-Response + // used to calculate the centre of gravity (see below). + class LORMax{ + public: + LORMax() { voxel_centre = CartesianCoordinate3D(0.f,0.f,0.f); value = 0.f;} + CartesianCoordinate3D voxel_centre; + float value; + }; + +private: + //! Reads listmode event by event, computes the ProjMatrixElemsForOneBin (probabilities + //! along a bin LOR) and stores in a vector the coordinates and weights of the + //! LOR maxima (vector::LORMax) prior to computing the centre of mass of those. + void construct_list_of_LOR_max( + const shared_ptr >& test_discretised_density_sptr); + + //! Selects and stores the highest probability elements of ProjMatrixElemsForOneBin. + void get_LOR_of_max(const ProjMatrixElemsForOneBin& probabilities, + const shared_ptr >& test_discretised_density_sptr); + + //! Given a vector::LORMax, computes the centre of mass. + CartesianCoordinate3D compute_centre_of_mass(); + + //! Checks if original and calculated coordinates are close enough. + void compare_original_and_calculated_coordinates( + const CartesianCoordinate3D& original_coords, + const CartesianCoordinate3D& centre_of_mass, + const BasicCoordinate<3, float>& grid_spacing); + + //! Modified version of check_if_equal for this test + bool check_if_almost_equal(const double a, const double b, std::string str, const double tolerance); + + std::string root_header_filename; + std::string image_filename; + std::vector max_lor; +}; + +void +ROOTconsistency_Tests::run_tests() +{ + // DiscretisedDensity for original image + shared_ptr > discretised_density_sptr(DiscretisedDensity<3,float>::read_from_file(image_filename)); + + // needs to be cast to VoxelsOnCartesianGrid to be able to calculate the centre of gravity, + // hence the location of the original source, stored in test_original_coords. + const VoxelsOnCartesianGrid& discretised_cartesian_grid = + dynamic_cast &>(*discretised_density_sptr); + CartesianCoordinate3D original_coords = find_centre_of_gravity_in_mm(discretised_cartesian_grid); + + construct_list_of_LOR_max(discretised_density_sptr); + + CartesianCoordinate3D centreofmass = compute_centre_of_mass(); + + compare_original_and_calculated_coordinates(original_coords,centreofmass,discretised_cartesian_grid.get_grid_spacing()); +} + +void +ROOTconsistency_Tests:: +construct_list_of_LOR_max(const shared_ptr >& discretised_density_sptr) +{ + shared_ptr lm_data_sptr(read_from_file(root_header_filename)); + + shared_ptr proj_matrix_sptr(new ProjMatrixByBinUsingRayTracing()); + + proj_matrix_sptr.get()->set_up(lm_data_sptr->get_proj_data_info_sptr(), + discretised_density_sptr); + proj_matrix_sptr->enable_tof(lm_data_sptr->get_proj_data_info_sptr()); + + ProjMatrixElemsForOneBin proj_matrix_row; + + { + // loop over all events in the listmode file + shared_ptr record_sptr = lm_data_sptr->get_empty_record_sptr(); + CListRecord& record = *record_sptr; + while (lm_data_sptr->get_next_record(record) == Succeeded::yes) + { + // only stores prompts + if (record.is_event() && record.event().is_prompt()) + { + Bin bin; + bin.set_bin_value(1.f); + // gets the bin corresponding to the event + record.event().get_bin(bin, *lm_data_sptr->get_proj_data_info_sptr()); + if ( bin.get_bin_value()>0 ) + { + // computes the TOF probabilities along the bin LOR + proj_matrix_sptr->get_proj_matrix_elems_for_one_bin(proj_matrix_row, bin); + // adds coordinates and weights of the elements with highest probability along LOR + get_LOR_of_max(proj_matrix_row, discretised_density_sptr); + } + } + } + } + +} + +void +ROOTconsistency_Tests::get_LOR_of_max(const ProjMatrixElemsForOneBin& probabilities, const shared_ptr >& test_discretised_density_sptr) +{ + std::stack tmp_max_lor; + + CartesianCoordinate3D voxel_centre; + + float maxLOR = 0; + + ProjMatrixElemsForOneBin::const_iterator element_ptr = probabilities.begin(); + // iterative calculation of highest probability and corresponding elements along the LOR + while (element_ptr != probabilities.end()) + { + if (element_ptr->get_value() >= maxLOR) + { + maxLOR = element_ptr->get_value(); + voxel_centre = + test_discretised_density_sptr->get_physical_coordinates_for_indices(element_ptr->get_coords()); + LORMax tmp; + tmp.value = element_ptr->get_value(); + tmp.voxel_centre = voxel_centre; + tmp_max_lor.push(tmp); + } + ++element_ptr; + } + + // only selects the elements on top of the stack, corresponding to the highest probability + if (maxLOR !=0) + { + while(!tmp_max_lor.empty()) + { + if (tmp_max_lor.top().value == maxLOR) + { + max_lor.push_back(tmp_max_lor.top()); + tmp_max_lor.pop(); + } + else break; + } + } +} + +CartesianCoordinate3D ROOTconsistency_Tests::compute_centre_of_mass() +{ + + // creation of a file with all LOR maxima, to be able to plot them + std::ofstream myfile; + std::string file_name = image_filename.substr(0,image_filename.size()-3) + ".txt"; + myfile.open (file_name.c_str()); + + LORMax centreofmass; + + // computes centre of mass + for (std::vector::iterator lor_element_ptr=max_lor.begin(); + lor_element_ptr != max_lor.end();++lor_element_ptr) + { + centreofmass.voxel_centre.x() += lor_element_ptr->voxel_centre.x()*lor_element_ptr->value; + centreofmass.voxel_centre.y() += lor_element_ptr->voxel_centre.y()*lor_element_ptr->value; + centreofmass.voxel_centre.z() += lor_element_ptr->voxel_centre.z()*lor_element_ptr->value; + centreofmass.value += lor_element_ptr->value; + + myfile << lor_element_ptr->voxel_centre.x() << " " + << lor_element_ptr->voxel_centre.y() << " " + << lor_element_ptr->voxel_centre.z() << " " + << lor_element_ptr->value << std::endl; + + } + + // needs to divide by the weights + if (centreofmass.value != 0) + { + centreofmass.voxel_centre.x()=centreofmass.voxel_centre.x()/centreofmass.value; + centreofmass.voxel_centre.y()=centreofmass.voxel_centre.y()/centreofmass.value; + centreofmass.voxel_centre.z()=centreofmass.voxel_centre.z()/centreofmass.value; + } + else + { + warning("Total weight of the centre of mass equal to 0. Please check your data."); + centreofmass.voxel_centre.x()=0; + centreofmass.voxel_centre.y()=0; + centreofmass.voxel_centre.z()=0; + } + + cerr << "Centre of gravity coordinates: " << centreofmass.voxel_centre.x() << " " + << centreofmass.voxel_centre.y() << " " << centreofmass.voxel_centre.z() << std::endl; + + myfile.close(); + + return centreofmass.voxel_centre; + +} + +// TODO change this +void ROOTconsistency_Tests::compare_original_and_calculated_coordinates(const CartesianCoordinate3D& original_coords, + const CartesianCoordinate3D& centre_of_mass, const BasicCoordinate<3, float>& grid_spacing) +{ + check_if_almost_equal(static_cast(original_coords.x()),static_cast(centre_of_mass.x()),"x",grid_spacing[1]); + check_if_almost_equal(static_cast(original_coords.y()),static_cast(centre_of_mass.y()),"y",grid_spacing[2]); + check_if_almost_equal(static_cast(original_coords.z()),static_cast(centre_of_mass.z()),"z",grid_spacing[3]); + + cerr << "Original coordinates: " << original_coords.x() << " " + << original_coords.y() << " " << original_coords.z() << std::endl; +} + +bool +ROOTconsistency_Tests::check_if_almost_equal(const double a, const double b, std::string str, const double tolerance) +{ + if ((fabs(a-b) > tolerance)) + { + std::cerr << "Error : unequal values are " << a << " and " << b + << ". " << str << std::endl; + everything_ok = false; + return false; + } + else + return true; +} + +END_NAMESPACE_STIR + +int main(int argc, char **argv) +{ + USING_NAMESPACE_STIR + + if (argc != 3) + { + cerr << "Usage : " << argv[1] << " filename " + << argv[2] << "original image \n" + << "See source file for the format of this file.\n\n"; + return EXIT_FAILURE; + } + + + ifstream in(argv[1]); + if (!in) + { + cerr << argv[0] + << ": Error opening root file " << argv[1] << "\nExiting.\n"; + + return EXIT_FAILURE; + } + ifstream in2(argv[2]); + if (!in2) + { + cerr << argv[0] + << ": Error opening original image " << argv[2] << "\nExiting.\n"; + + return EXIT_FAILURE; + } + std::string filename(argv[1]); + std::string image(argv[2]); + ROOTconsistency_Tests tests(filename,image); + tests.run_tests(); + return tests.main_return_value(); +} From 89fc82b655c60fb7e2c84594b630a507ab4136a2 Mon Sep 17 00:00:00 2001 From: Nikos Efthimiou Date: Tue, 19 Sep 2017 12:01:40 +0100 Subject: [PATCH 088/509] Removed code from LM objective function for separate projectors Kris said that this should move to a new class. --- ...oodWithLinearModelForMeanAndListModeData.h | 2 +- ...orMeanAndListModeDataWithProjMatrixByBin.h | 3 - ...MeanAndListModeDataWithProjMatrixByBin.cxx | 171 +++++------------- 3 files changed, 45 insertions(+), 131 deletions(-) diff --git a/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeData.h b/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeData.h index 41a051045c..69b6e076a3 100644 --- a/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeData.h +++ b/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeData.h @@ -122,7 +122,7 @@ public PoissonLogLikelihoodWithLinearModelForMean //! \author Nikos Efthimiou //! \details This is part of some functionality I transfer from lm_to_projdata. //! The total number of events to be *STORED* not *PROCESSED*. - unsigned long int num_events_to_store; + int num_events_to_store; //! //! \brief do_time_frame diff --git a/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.h b/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.h index ea82529356..98ea3d6ecf 100644 --- a/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.h +++ b/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.h @@ -108,9 +108,6 @@ typedef RegisteredParsingObject PM_sptr; diff --git a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx index 56087b3662..126e8ca96b 100644 --- a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx +++ b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx @@ -92,7 +92,6 @@ set_defaults() this->normalisation_sptr.reset(new TrivialBinNormalisation); this->do_time_frame = false; this->use_tofsens = false; - this->use_projectors = false; } template @@ -106,8 +105,6 @@ initialise_keymap() this->parser.add_key("use time-of-flight sensitivities", &this->use_tofsens); this->parser.add_key("max ring difference num to process", &this->max_ring_difference_num_to_process); this->parser.add_parsing_key("Matrix type", &this->PM_sptr); - this->parser.add_parsing_key("Projector pair type", &this->projector_pair_ptr); - this->parser.add_key("use projectors", &use_projectors); this->parser.add_key("additive sinogram",&this->additive_projection_data_filename); this->parser.add_key("num_events_to_store",&this->num_events_to_store); @@ -214,28 +211,18 @@ set_up_before_sensitivity(shared_ptr const& target_sptr) distributed::send_int_value(100, -1); #endif - // Check if we have listmode projectors - if (!use_projectors) - { - // set projector to be used for the calculations - this->PM_sptr->set_up(proj_data_info_cyl_sptr->create_shared_clone(),target_sptr); + // set projector to be used for the calculations + this->PM_sptr->set_up(proj_data_info_cyl_sptr->create_shared_clone(),target_sptr); - this->PM_sptr->enable_tof(proj_data_info_cyl_sptr->create_shared_clone(), this->use_tof); + this->PM_sptr->enable_tof(proj_data_info_cyl_sptr->create_shared_clone(), this->use_tof); - shared_ptr forward_projector_ptr(new ForwardProjectorByBinUsingProjMatrixByBin(this->PM_sptr)); - shared_ptr back_projector_ptr(new BackProjectorByBinUsingProjMatrixByBin(this->PM_sptr)); + shared_ptr forward_projector_ptr(new ForwardProjectorByBinUsingProjMatrixByBin(this->PM_sptr)); + shared_ptr back_projector_ptr(new BackProjectorByBinUsingProjMatrixByBin(this->PM_sptr)); - this->projector_pair_ptr.reset( - new ProjectorByBinPairUsingSeparateProjectors(forward_projector_ptr, back_projector_ptr)); + this->projector_pair_ptr.reset( + new ProjectorByBinPairUsingSeparateProjectors(forward_projector_ptr, back_projector_ptr)); - this->projector_pair_ptr->set_up(proj_data_info_cyl_sptr->create_shared_clone(),target_sptr); - } - else - { - this->projector_pair_ptr->set_up(proj_data_info_cyl_sptr->create_shared_clone(),target_sptr); - - this->projector_pair_ptr->enable_tof(proj_data_info_cyl_sptr->create_shared_clone(), this->use_tof); - } + this->projector_pair_ptr->set_up(proj_data_info_cyl_sptr->create_shared_clone(),target_sptr); // sets non-tof backprojector for sensitivity calculation (clone of the back_projector + set projdatainfo to non-tof) this->sens_backprojector_sptr.reset(projector_pair_ptr->get_back_projector_sptr()->clone()); @@ -489,31 +476,9 @@ compute_sub_gradient_without_penalty_plus_sensitivity(TargetT& gradient, shared_ptr record_sptr = this->list_mode_data_sptr->get_empty_record_sptr(); CListRecord& record = *record_sptr; - unsigned long int more_events = + long int more_events = this->do_time_frame? 1 : (this->num_events_to_store / this->num_subsets); - if (use_projectors) - { - - PresmoothingForwardProjectorByBin* forward= - dynamic_cast (this->projector_pair_ptr->get_forward_projector_sptr().get()); - - if (!is_null_ptr(forward)) - forward->update_filtered_density_image(current_estimate); - - PostsmoothingBackProjectorByBin* back= - dynamic_cast (this->projector_pair_ptr->get_back_projector_sptr().get()); - - if (!is_null_ptr(back)) - back->init_filtered_density_image(gradient); - - if (this->use_tof) - { - projector_pair_ptr->set_tof_data(&lor_points.p1(), &lor_points.p2()); - } - - } - while (more_events)//this->list_mode_data_sptr->get_next_record(record) == Succeeded::yes) { @@ -536,8 +501,7 @@ compute_sub_gradient_without_penalty_plus_sensitivity(TargetT& gradient, { measured_bin.set_bin_value(1.0f); -// this->use_tof ? record.full_event(measured_bin, *proj_data_info_cyl_sptr): - record.event().get_bin(measured_bin, *proj_data_info_cyl_sptr); + record.event().get_bin(measured_bin, *proj_data_info_cyl_sptr); // In theory we have already done all these checks so we can // remove this if statement. @@ -570,94 +534,47 @@ compute_sub_gradient_without_penalty_plus_sensitivity(TargetT& gradient, } } - if(!use_projectors) - { - if(this->use_tof) - { - lor_points = record.event().get_LOR(); - this->PM_sptr->get_proj_matrix_elems_for_one_bin_with_tof(proj_matrix_row, - measured_bin, - lor_points.p1(), lor_points.p2()); - } - else - this->PM_sptr->get_proj_matrix_elems_for_one_bin(proj_matrix_row, measured_bin); - - //in_the_range++; - fwd_bin.set_bin_value(0.0f); - proj_matrix_row.forward_project(fwd_bin,current_estimate); - // additive sinogram - if (!is_null_ptr(this->additive_proj_data_sptr)) - { - float add_value = this->additive_proj_data_sptr->get_bin_value(measured_bin); - float value= fwd_bin.get_bin_value()+add_value; - fwd_bin.set_bin_value(value); - } - float measured_div_fwd = 0.0f; - - if(!this->do_time_frame) - more_events -=1 ; - - num_stored_events += 1; - - if (num_stored_events%200000L==0) - info( boost::format("Stored Events: %1% ") % num_stored_events); - - if ( measured_bin.get_bin_value() <= max_quotient *fwd_bin.get_bin_value()) - measured_div_fwd = 1.0f /fwd_bin.get_bin_value(); - else - continue; - - measured_bin.set_bin_value(measured_div_fwd); - proj_matrix_row.back_project(gradient, measured_bin); - } - else - { - measured_bin.set_bin_value(0.0f); - - - if(this->use_tof) - lor_points = record.event().get_LOR(); - - projector_pair_ptr->get_forward_projector_sptr()->forward_project(measured_bin, - current_estimate); - - if (!is_null_ptr(this->additive_proj_data_sptr)) - { - float add_value = this->additive_proj_data_sptr->get_bin_value(measured_bin); - float value= measured_bin.get_bin_value()+add_value; - measured_bin.set_bin_value(value); - } - - float measured_div_fwd = 0.0f; - - if(!this->do_time_frame) - more_events -=1 ; + if(this->use_tof) + { + lor_points = record.event().get_LOR(); + this->PM_sptr->get_proj_matrix_elems_for_one_bin_with_tof(proj_matrix_row, + measured_bin, + lor_points.p1(), lor_points.p2()); + } + else + this->PM_sptr->get_proj_matrix_elems_for_one_bin(proj_matrix_row, measured_bin); + + //in_the_range++; + fwd_bin.set_bin_value(0.0f); + proj_matrix_row.forward_project(fwd_bin,current_estimate); + // additive sinogram + if (!is_null_ptr(this->additive_proj_data_sptr)) + { + float add_value = this->additive_proj_data_sptr->get_bin_value(measured_bin); + float value= fwd_bin.get_bin_value()+add_value; + fwd_bin.set_bin_value(value); + } + float measured_div_fwd = 0.0f; - num_stored_events += 1; + if(!this->do_time_frame) + more_events -=1 ; - if (num_stored_events%200000L==0) - info( boost::format("Stored Events: %1% ") % num_stored_events); + num_stored_events += 1; - if ( measured_bin.get_bin_value() <= max_quotient *measured_bin.get_bin_value()) - measured_div_fwd = 1.0f /measured_bin.get_bin_value(); - else - continue; + if (num_stored_events%200000L==0) + info( boost::format("Stored Events: %1% ") % num_stored_events); - measured_bin.set_bin_value(measured_div_fwd); - projector_pair_ptr->get_back_projector_sptr()->back_project(gradient, measured_bin); - } - } - } - if (use_projectors) - { - PostsmoothingBackProjectorByBin* back= - dynamic_cast (this->projector_pair_ptr->get_back_projector_sptr().get()); + if ( measured_bin.get_bin_value() <= max_quotient *fwd_bin.get_bin_value()) + measured_div_fwd = 1.0f /fwd_bin.get_bin_value(); + else + continue; - if (!is_null_ptr(back)) - back->update_filtered_density_image(gradient); + measured_bin.set_bin_value(measured_div_fwd); + proj_matrix_row.back_project(gradient, measured_bin); + } } - info(boost::format("Number of used events: %1%") % num_stored_events); + info(boost::format("Number of used events: %1%") % num_stored_events); } # ifdef _MSC_VER From ba61f34771a71287e383dd7df7a187659bd79ec8 Mon Sep 17 00:00:00 2001 From: NikEfth Date: Tue, 19 Sep 2017 14:45:54 +0000 Subject: [PATCH 089/509] Changes to be able to reconstruct both TOF proj and lm data from ROOT files. --- src/listmode_buildblock/CListModeDataROOT.cxx | 4 ++-- src/recon_buildblock/BinNormalisationFromProjData.cxx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/listmode_buildblock/CListModeDataROOT.cxx b/src/listmode_buildblock/CListModeDataROOT.cxx index 8e28a823b0..484c38fac3 100644 --- a/src/listmode_buildblock/CListModeDataROOT.cxx +++ b/src/listmode_buildblock/CListModeDataROOT.cxx @@ -167,8 +167,8 @@ CListModeDataROOT(const std::string& hroot_filename) /* arc_correction*/false, tof_mash_factor)); - if (tof_mash_factor != 1) - error("TOF mashing factor for ROOT different from 1 not implemented yet."); +// if (tof_mash_factor != 1) + // error("TOF mashing factor for ROOT different from 1 not implemented yet."); } std::string diff --git a/src/recon_buildblock/BinNormalisationFromProjData.cxx b/src/recon_buildblock/BinNormalisationFromProjData.cxx index 8e8c38e45e..77734eb250 100644 --- a/src/recon_buildblock/BinNormalisationFromProjData.cxx +++ b/src/recon_buildblock/BinNormalisationFromProjData.cxx @@ -109,8 +109,8 @@ set_up(const shared_ptr& proj_data_info_ptr) ++segment_num) { ok = - norm_proj.get_min_axial_pos_num(segment_num) == proj.get_min_axial_pos_num(segment_num) && - norm_proj.get_max_axial_pos_num(segment_num) == proj.get_max_axial_pos_num(segment_num); + norm_proj.get_min_axial_pos_num(segment_num) <= proj.get_min_axial_pos_num(segment_num) && + norm_proj.get_max_axial_pos_num(segment_num) >= proj.get_max_axial_pos_num(segment_num); } if (ok) return Succeeded::yes; From 031e347ab79f29da620dd763fe9db9e4aadf1ae6 Mon Sep 17 00:00:00 2001 From: Nikos Efthimiou Date: Fri, 22 Sep 2017 17:36:49 +0100 Subject: [PATCH 090/509] Important bug fix and change in CListRecord and CListEventCylindricalScannerWithDiscreteDetectors Now they are initialised using ProjDataInfo because otherwise the tof_mash_factor was wrong! --- ...tCylindricalScannerWithDiscreteDetectors.h | 5 +- ...ylindricalScannerWithDiscreteDetectors.inl | 10 +- ...ricalScannerWithViewTangRingRingEncoding.h | 4 +- .../stir/listmode/CListRecordGESigna.h | 102 +++++++++--------- src/include/stir/listmode/CListRecordROOT.h | 6 +- ...orMeanAndListModeDataWithProjMatrixByBin.h | 2 + .../CListModeDataGESigna.cxx | 2 +- src/listmode_buildblock/CListModeDataROOT.cxx | 2 +- .../CListRecordECAT8_32bit.cxx | 12 +-- src/listmode_buildblock/CListRecordROOT.cxx | 14 +-- ...MeanAndListModeDataWithProjMatrixByBin.cxx | 4 +- 11 files changed, 80 insertions(+), 83 deletions(-) diff --git a/src/include/stir/listmode/CListEventCylindricalScannerWithDiscreteDetectors.h b/src/include/stir/listmode/CListEventCylindricalScannerWithDiscreteDetectors.h index c802f6575d..6125b4f728 100644 --- a/src/include/stir/listmode/CListEventCylindricalScannerWithDiscreteDetectors.h +++ b/src/include/stir/listmode/CListEventCylindricalScannerWithDiscreteDetectors.h @@ -43,10 +43,10 @@ class CListEventCylindricalScannerWithDiscreteDetectors : public CListEvent { public: inline explicit - CListEventCylindricalScannerWithDiscreteDetectors(const shared_ptr& scanner_sptr); + CListEventCylindricalScannerWithDiscreteDetectors(const shared_ptr& proj_data_info); const Scanner * get_scanner_ptr() const - { return this->scanner_sptr.get(); } + { return this->uncompressed_proj_data_info_sptr->get_scanner_ptr(); } //! This routine returns the corresponding detector pair virtual void get_detection_position(DetectionPositionPair<>&) const = 0; @@ -86,7 +86,6 @@ class CListEventCylindricalScannerWithDiscreteDetectors : public CListEvent shared_ptr scanner_sptr; - private: shared_ptr uncompressed_proj_data_info_sptr; diff --git a/src/include/stir/listmode/CListEventCylindricalScannerWithDiscreteDetectors.inl b/src/include/stir/listmode/CListEventCylindricalScannerWithDiscreteDetectors.inl index 6f0b3f45ea..640611db97 100644 --- a/src/include/stir/listmode/CListEventCylindricalScannerWithDiscreteDetectors.inl +++ b/src/include/stir/listmode/CListEventCylindricalScannerWithDiscreteDetectors.inl @@ -30,18 +30,12 @@ START_NAMESPACE_STIR CListEventCylindricalScannerWithDiscreteDetectors:: -CListEventCylindricalScannerWithDiscreteDetectors(const shared_ptr& scanner_sptr) - : scanner_sptr(scanner_sptr) +CListEventCylindricalScannerWithDiscreteDetectors(const shared_ptr& proj_data_info) { this->uncompressed_proj_data_info_sptr.reset (dynamic_cast ( - ProjDataInfo::ProjDataInfoCTI(scanner_sptr, - 1, scanner_sptr->get_num_rings()-1, - scanner_sptr->get_num_detectors_per_ring()/2, - scanner_sptr->get_default_num_arccorrected_bins(), - false, - /*TOF mashing factor*/1))); + proj_data_info.get())); } LORAs2Points diff --git a/src/include/stir/listmode/CListEventCylindricalScannerWithViewTangRingRingEncoding.h b/src/include/stir/listmode/CListEventCylindricalScannerWithViewTangRingRingEncoding.h index a0827e7c4e..9ec3f79955 100644 --- a/src/include/stir/listmode/CListEventCylindricalScannerWithViewTangRingRingEncoding.h +++ b/src/include/stir/listmode/CListEventCylindricalScannerWithViewTangRingRingEncoding.h @@ -71,8 +71,8 @@ class CListEventCylindricalScannerWithViewTangRingRingEncoding : public CListEventCylindricalScannerWithDiscreteDetectors { public: - CListEventCylindricalScannerWithViewTangRingRingEncoding(const shared_ptr& scanner_sptr) : - CListEventCylindricalScannerWithDiscreteDetectors(scanner_sptr) + CListEventCylindricalScannerWithViewTangRingRingEncoding(const shared_ptr& proj_data_info) : + CListEventCylindricalScannerWithDiscreteDetectors(proj_data_info) {} //! This routine returns the corresponding detector pair diff --git a/src/include/stir/listmode/CListRecordGESigna.h b/src/include/stir/listmode/CListRecordGESigna.h index d6b35efc82..146282e447 100644 --- a/src/include/stir/listmode/CListRecordGESigna.h +++ b/src/include/stir/listmode/CListRecordGESigna.h @@ -9,7 +9,7 @@ This file is based on GE proprietary information and can therefore not be distributed outside UCL without approval from GE. - + \author Kris Thielemans */ @@ -58,11 +58,11 @@ enum ExtendedEvtType */ class CListEventDataGESigna { - public: + public: inline bool is_prompt() const { return true; } // TODO - inline Succeeded set_prompt(const bool prompt = true) - { - //if (prompt) random=1; else random=0; return Succeeded::yes; + inline Succeeded set_prompt(const bool prompt = true) + { + //if (prompt) random=1; else random=0; return Succeeded::yes; return Succeeded::no; } inline void get_detection_position(DetectionPositionPair<>& det_pos) const @@ -85,13 +85,13 @@ class CListEventDataGESigna } } inline bool is_event() const - { - return (eventType==COINC_EVT)/* && eventTypeExt==COINC_COUNT_EVT)*/; + { + return (eventType==COINC_EVT)/* && eventTypeExt==COINC_COUNT_EVT)*/; } // TODO need to find out how to see if it's a coincidence event inline int get_tof_bin() const - { - return static_cast(deltaTime); - } + { + return static_cast(deltaTime); + } private: #if STIRIsNativeByteOrderBigEndian @@ -123,17 +123,17 @@ class CListTimeDataGESigna inline unsigned long get_time_in_millisecs() const { return (time_hi()<<16) | time_lo(); } inline Succeeded set_time_in_millisecs(const unsigned long time_in_millisecs) - { - data.timeMarkerLS = ((1UL<<16)-1) & (time_in_millisecs); - data.timeMarkerMS = (time_in_millisecs) >> 16; + { + data.timeMarkerLS = ((1UL<<16)-1) & (time_in_millisecs); + data.timeMarkerMS = (time_in_millisecs) >> 16; // TODO return more useful value return Succeeded::yes; } inline bool is_time() const { // TODO need to find out how to see if it's a timing event - return (data.eventType==EXTENDED_EVT) && (data.eventTypeExt==TIME_MARKER_EVT); + return (data.eventType==EXTENDED_EVT) && (data.eventTypeExt==TIME_MARKER_EVT); }// TODO - + private: typedef union{ struct { @@ -151,7 +151,7 @@ class CListTimeDataGESigna boost::uint16_t timeMarkerLS:16; /* Least Significant 16 bits of 32-bit Time Marker */ boost::uint16_t timeMarkerMS:16; /* Most Significant 16 bits of 32-bitTime Marker */ #endif - }; + }; } data_t; data_t data; @@ -173,9 +173,9 @@ class CListGatingDataGESigna inline unsigned long get_time_in_millisecs() const { return (time_hi()<<24) | time_lo(); } inline Succeeded set_time_in_millisecs(const unsigned long time_in_millisecs) - { - words[0].value = ((1UL<<24)-1) & (time_in_millisecs); - words[1].value = (time_in_millisecs) >> 24; + { + words[0].value = ((1UL<<24)-1) & (time_in_millisecs); + words[1].value = (time_in_millisecs) >> 24; // TODO return more useful value return Succeeded::yes; } @@ -184,9 +184,9 @@ class CListGatingDataGESigna { return (words[0].signature==21) && (words[1].signature==29); } inline unsigned int get_gating() const { return words[0].reserved; } // return "reserved" bits. might be something in there - inline Succeeded set_gating(unsigned int g) + inline Succeeded set_gating(unsigned int g) { words[0].reserved = g&7; return Succeeded::yes; } - + private: typedef union{ struct { @@ -199,7 +199,7 @@ class CListGatingDataGESigna boost::uint32_t reserved : 3; boost::uint32_t signature : 5; #endif - }; + }; boost::uint32_t raw; } oneword_t; oneword_t words[2]; @@ -213,7 +213,7 @@ class CListGatingDataGESigna This class essentially just forwards the work to the "basic" classes. A complication for GE Dimension data is that not all events are the same size: - coincidence events are 4 bytes, and others are 8 bytes. + coincidence events are 4 bytes, and others are 8 bytes. \todo Currently we always assume the data is from a DSTE. We should really read this from the RDF header. */ @@ -224,13 +224,13 @@ class CListRecordGESigna : public CListRecord, public CListTime, // public CList typedef CListTimeDataGESigna TimeType; //typedef CListGatingDataGESigna GatingType; - public: - CListRecordGESigna() : - CListEventCylindricalScannerWithDiscreteDetectors(shared_ptr(new Scanner(Scanner::PETMR_Signa))) + public: + CListRecordGESigna(const shared_ptr& proj_data_info_sptr) : + CListEventCylindricalScannerWithDiscreteDetectors(proj_data_info_sptr) {} bool is_time() const - { + { return this->time_data.is_time(); } #if 0 @@ -242,7 +242,7 @@ class CListRecordGESigna : public CListRecord, public CListTime, // public CList bool is_event() const { return this->event_data.is_event(); } - virtual CListEvent& event() + virtual CListEvent& event() { return *this; } virtual const CListEvent& event() const { return *this; } @@ -265,22 +265,22 @@ dynamic_cast(&e2) != 0 && raw[0] == static_cast(e2).raw[0] && (this->is_event() || (raw[1] == static_cast(e2).raw[1])); #endif - } + } - // time - inline unsigned long get_time_in_millisecs() const + // time + inline unsigned long get_time_in_millisecs() const { return time_data.get_time_in_millisecs(); } inline Succeeded set_time_in_millisecs(const unsigned long time_in_millisecs) { return time_data.set_time_in_millisecs(time_in_millisecs); } #if 0 inline unsigned int get_gating() const { return gating_data.get_gating(); } - inline Succeeded set_gating(unsigned int g) + inline Succeeded set_gating(unsigned int g) { return gating_data.set_gating(g); } #endif // event inline bool is_prompt() const { return event_data.is_prompt(); } - inline Succeeded set_prompt(const bool prompt = true) + inline Succeeded set_prompt(const bool prompt = true) { return event_data.set_prompt(prompt); } virtual void get_detection_position(DetectionPositionPair<>& det_pos) const @@ -292,15 +292,15 @@ dynamic_cast(&e2) != 0 && error("TODO"); } - virtual std::size_t size_of_record_at_ptr(const char * const data_ptr, const std::size_t /*size*/, + virtual std::size_t size_of_record_at_ptr(const char * const data_ptr, const std::size_t /*size*/, const bool do_byte_swap) const - { + { // TODO: get size of record from the file, whereas here I have hard-coded as being 6bytes (I know it's the case for the Orsay data) OtB 15/09 return std::size_t(6); // std::size_t(data_ptr[0]&0x80); } - virtual Succeeded init_from_data_ptr(const char * const data_ptr, + virtual Succeeded init_from_data_ptr(const char * const data_ptr, const std::size_t #ifndef NDEBUG size // only used within assert, so don't define otherwise to avoid compiler warning @@ -321,38 +321,38 @@ dynamic_cast(&e2) != 0 && { // std::cout << "This is an event \n" ; assert(size >= 6); - + std::copy(data_ptr+6, data_ptr+6, reinterpret_cast(&this->raw[1])); // std::cout << "after assert an event \n" ; } if (do_byte_swap) { - error("don't know how to byteswap"); + error("don't know how to byteswap"); ByteOrder::swap_order(this->raw[1]); } - - if (this->is_event()) - { - // set TOF info in ps - this->delta_time = this->event_data.get_tof_bin() *this-> get_scanner_ptr()->get_size_of_timing_bin(); - } - - - + + if (this->is_event()) + { + // set TOF info in ps + this->delta_time = this->event_data.get_tof_bin() *this-> get_scanner_ptr()->get_size_of_timing_bin(); + } + + + return Succeeded::yes; } private: union { DataType event_data; - TimeType time_data; + TimeType time_data; //GatingType gating_data; boost::int32_t raw[2]; }; BOOST_STATIC_ASSERT(sizeof(boost::int32_t)==4); - BOOST_STATIC_ASSERT(sizeof(DataType)==6); - BOOST_STATIC_ASSERT(sizeof(TimeType)==6); - //BOOST_STATIC_ASSERT(sizeof(GatingType)==8); + BOOST_STATIC_ASSERT(sizeof(DataType)==6); + BOOST_STATIC_ASSERT(sizeof(TimeType)==6); + //BOOST_STATIC_ASSERT(sizeof(GatingType)==8); }; diff --git a/src/include/stir/listmode/CListRecordROOT.h b/src/include/stir/listmode/CListRecordROOT.h index 6c2b10177e..173e1282e9 100644 --- a/src/include/stir/listmode/CListRecordROOT.h +++ b/src/include/stir/listmode/CListRecordROOT.h @@ -42,7 +42,7 @@ class CListEventROOT : public CListEventCylindricalScannerWithDiscreteDetectors { public: - CListEventROOT(const shared_ptr& scanner_sptr); + CListEventROOT(const shared_ptr& proj_data_info); //! This routine returns the corresponding detector pair virtual void get_detection_position(DetectionPositionPair<>&) const; @@ -145,8 +145,8 @@ class CListRecordROOT : public CListRecord // currently no gating yet raw[1] == dynamic_cast(e2).raw[1]; } - CListRecordROOT(const shared_ptr& scanner_sptr) : - event_data(scanner_sptr) + CListRecordROOT(const shared_ptr& proj_data_info_sptr) : + event_data(proj_data_info_sptr) {} virtual Succeeded init_from_data( const int& ring1, diff --git a/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.h b/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.h index 98ea3d6ecf..272463805e 100644 --- a/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.h +++ b/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.h @@ -125,6 +125,8 @@ typedef RegisteredParsingObject proj_data_info_cyl_sptr; + shared_ptr record_sptr; + //! sets any default values /*! Has to be called by set_defaults in the leaf-class */ virtual void set_defaults(); diff --git a/src/listmode_buildblock/CListModeDataGESigna.cxx b/src/listmode_buildblock/CListModeDataGESigna.cxx index 3ba7e3ab35..36c8ce6a8e 100644 --- a/src/listmode_buildblock/CListModeDataGESigna.cxx +++ b/src/listmode_buildblock/CListModeDataGESigna.cxx @@ -57,7 +57,7 @@ shared_ptr CListModeDataGESigna:: get_empty_record_sptr() const { - shared_ptr sptr(new CListRecordT); + shared_ptr sptr(new CListRecordT(this->proj_data_info_sptr)); return sptr; } diff --git a/src/listmode_buildblock/CListModeDataROOT.cxx b/src/listmode_buildblock/CListModeDataROOT.cxx index 484c38fac3..35be59d5cf 100644 --- a/src/listmode_buildblock/CListModeDataROOT.cxx +++ b/src/listmode_buildblock/CListModeDataROOT.cxx @@ -182,7 +182,7 @@ shared_ptr CListModeDataROOT:: get_empty_record_sptr() const { - shared_ptr sptr(new CListRecordROOT(this->scanner_sptr)); + shared_ptr sptr(new CListRecordROOT(this->proj_data_info_sptr)); return sptr; } diff --git a/src/listmode_buildblock/CListRecordECAT8_32bit.cxx b/src/listmode_buildblock/CListRecordECAT8_32bit.cxx index 274e08ff43..7b1857fdbc 100644 --- a/src/listmode_buildblock/CListRecordECAT8_32bit.cxx +++ b/src/listmode_buildblock/CListRecordECAT8_32bit.cxx @@ -36,16 +36,16 @@ namespace ecat { CListEventECAT8_32bit:: CListEventECAT8_32bit(const shared_ptr& proj_data_info_sptr) : - CListEventCylindricalScannerWithDiscreteDetectors(shared_ptr(new Scanner(*proj_data_info_sptr->get_scanner_ptr()))) + CListEventCylindricalScannerWithDiscreteDetectors(proj_data_info_sptr) { const ProjDataInfoCylindricalNoArcCorr * const proj_data_info_ptr = dynamic_cast(proj_data_info_sptr.get()); if (proj_data_info_ptr == 0) error("CListEventECAT8_32bit can only be initialised with cylindrical projection data without arc-correction"); - const int num_rings = this->scanner_sptr->get_num_rings(); - const int max_ring_diff=proj_data_info_ptr->get_max_ring_difference(proj_data_info_ptr->get_max_segment_num()); - if (proj_data_info_ptr->get_max_ring_difference(0) != proj_data_info_ptr->get_min_ring_difference(0)) + const int num_rings = this->uncompressed_proj_data_info_sptr->get_scanner_ptr()->get_num_rings(); + const int max_ring_diff=this->uncompressed_proj_data_info_sptr->get_max_ring_difference(proj_data_info_ptr->get_max_segment_num()); + if (this->uncompressed_proj_data_info_sptr->get_max_ring_difference(0) != this->uncompressed_proj_data_info_sptr->get_min_ring_difference(0)) error("CListEventECAT8_32bit can only handle axial compression==1"); this->segment_sequence.resize(2*max_ring_diff+1); this->sizes.resize(2*max_ring_diff+1); @@ -65,8 +65,8 @@ CListEventECAT8_32bit:: get_detection_position(DetectionPositionPair<>& det_pos) const { /* data is organised by segment, axial coordinate, view, tangential */ - const int num_tangential_poss = this->scanner_sptr->get_default_num_arccorrected_bins(); - const int num_views = this->scanner_sptr->get_num_detectors_per_ring()/2; + const int num_tangential_poss = this->uncompressed_proj_data_info_sptr->get_scanner_ptr()->get_default_num_arccorrected_bins(); + const int num_views = this->uncompressed_proj_data_info_sptr->get_scanner_ptr()->get_num_detectors_per_ring()/2; const int tang_pos_num = this->data.offset % num_tangential_poss;//(this->num_sinograms * this-> num_views); const int rest = this->data.offset / num_tangential_poss; diff --git a/src/listmode_buildblock/CListRecordROOT.cxx b/src/listmode_buildblock/CListRecordROOT.cxx index 8711fa3ae9..abbd3c4709 100644 --- a/src/listmode_buildblock/CListRecordROOT.cxx +++ b/src/listmode_buildblock/CListRecordROOT.cxx @@ -32,10 +32,10 @@ START_NAMESPACE_STIR CListEventROOT:: -CListEventROOT(const shared_ptr& scanner_sptr) : - CListEventCylindricalScannerWithDiscreteDetectors(scanner_sptr) +CListEventROOT(const shared_ptr &proj_data_info) : + CListEventCylindricalScannerWithDiscreteDetectors(proj_data_info) { - quarter_of_detectors = static_cast(scanner_sptr->get_num_detectors_per_ring()/4.f); + quarter_of_detectors = static_cast(proj_data_info->get_scanner_ptr()->get_num_detectors_per_ring()/4.f); } //! @@ -70,13 +70,13 @@ void CListEventROOT::init_from_data(const int& _ring1, const int& _ring2, if (det1 < 0 ) det1 = scanner_sptr->get_num_detectors_per_ring() + det1; - else if ( det1 >= scanner_sptr->get_num_detectors_per_ring()) - det1 = det1 - scanner_sptr->get_num_detectors_per_ring(); + else if ( det1 >= this->uncompressed_proj_data_info_sptr->get_scanner_ptr()->get_num_detectors_per_ring()) + det1 = det1 - this->uncompressed_proj_data_info_sptr->get_scanner_ptr()->get_num_detectors_per_ring(); if (det2 < 0 ) det2 = scanner_sptr->get_num_detectors_per_ring() + det2; - else if ( det2 >= scanner_sptr->get_num_detectors_per_ring()) - det2 = det2 - scanner_sptr->get_num_detectors_per_ring(); + else if ( det2 >= this->uncompressed_proj_data_info_sptr->get_scanner_ptr()->get_num_detectors_per_ring()) + det2 = det2 - this->uncompressed_proj_data_info_sptr->get_scanner_ptr()->get_num_detectors_per_ring(); ring1 = _ring1; ring2 = _ring2; diff --git a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx index 126e8ca96b..9b0a38f635 100644 --- a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx +++ b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx @@ -252,6 +252,8 @@ set_up_before_sensitivity(shared_ptr const& target_sptr) return Succeeded::no; } + record_sptr = this->list_mode_data_sptr->get_empty_record_sptr(); + return Succeeded::yes; } @@ -473,7 +475,7 @@ compute_sub_gradient_without_penalty_plus_sensitivity(TargetT& gradient, double current_time = 0.; ProjMatrixElemsForOneBin proj_matrix_row; - shared_ptr record_sptr = this->list_mode_data_sptr->get_empty_record_sptr(); + CListRecord& record = *record_sptr; long int more_events = From c75d71c8b460d6c25bdf98a3e61d6b5845e6ca7e Mon Sep 17 00:00:00 2001 From: Nikos Efthimiou Date: Fri, 22 Sep 2017 18:57:26 +0100 Subject: [PATCH 091/509] Bug fix ProjDataInfoCylindricalNoArcCorr: scanner_sptr should be deleted --- ...ylindricalScannerWithDiscreteDetectors.inl | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/include/stir/listmode/CListEventCylindricalScannerWithDiscreteDetectors.inl b/src/include/stir/listmode/CListEventCylindricalScannerWithDiscreteDetectors.inl index 640611db97..f26d81dc4f 100644 --- a/src/include/stir/listmode/CListEventCylindricalScannerWithDiscreteDetectors.inl +++ b/src/include/stir/listmode/CListEventCylindricalScannerWithDiscreteDetectors.inl @@ -4,9 +4,9 @@ \file \ingroup listmode \brief Implementations of class stir::CListEventCylindricalScannerWithDiscreteDetectors - + \author Kris Thielemans - + */ /* Copyright (C) 2003- 2011, Hammersmith Imanet Ltd @@ -36,6 +36,9 @@ CListEventCylindricalScannerWithDiscreteDetectors(const shared_ptr (dynamic_cast ( proj_data_info.get())); + + if (is_null_ptr(this->uncompressed_proj_data_info_sptr)) + error("CListEventCylindricalScannerWithDiscreteDetectors takes only ProjDataInfoCylindricalNoArcCorr. Abord."); } LORAs2Points @@ -51,7 +54,7 @@ get_LOR() const this->get_detection_position(det_pos); assert(det_pos.pos1().radial_coord()==0); assert(det_pos.pos2().radial_coord()==0); - + // TODO we're using an obsolete function here which uses a different coordinate system this->get_uncompressed_proj_data_info_sptr()-> find_cartesian_coordinates_given_scanner_coordinates(coord_1, coord_2, @@ -60,15 +63,15 @@ get_LOR() const det_pos.pos1().tangential_coord(), det_pos.pos2().tangential_coord()); // find shift in z - const float shift = this->scanner_sptr->get_ring_spacing()* - (this->scanner_sptr->get_num_rings()-1)/2.F; + const float shift = this->get_uncompressed_proj_data_info_sptr()->get_ring_spacing()* + (this->get_uncompressed_proj_data_info_sptr()->get_scanner_ptr()->get_num_rings()-1)/2.F; coord_1.z() -= shift; coord_2.z() -= shift; - + return lor; } -void +void CListEventCylindricalScannerWithDiscreteDetectors:: get_bin(Bin& bin, const ProjDataInfo& proj_data_info) const { @@ -88,10 +91,10 @@ bool CListEventCylindricalScannerWithDiscreteDetectors:: is_valid_template(const ProjDataInfo& proj_data_info) const { - if (dynamic_cast(&proj_data_info)!= 0) - return true; + if (dynamic_cast(&proj_data_info)!= 0) + return true; - return false; + return false; } END_NAMESPACE_STIR From aee5de758c8836a5088603f22b85acc1b0fe8078 Mon Sep 17 00:00:00 2001 From: Nikos Efthimiou Date: Wed, 27 Sep 2017 02:29:54 +0100 Subject: [PATCH 092/509] Remove tof_mash_factor from get_bin_for_det_pos_pair as ... applied in get_tof_bin() . --- src/include/stir/ProjDataInfoCylindricalNoArcCorr.inl | 2 +- .../CListEventCylindricalScannerWithDiscreteDetectors.h | 2 +- src/listmode_buildblock/CListRecordROOT.cxx | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/include/stir/ProjDataInfoCylindricalNoArcCorr.inl b/src/include/stir/ProjDataInfoCylindricalNoArcCorr.inl index b4ffa53c20..69f30f0522 100644 --- a/src/include/stir/ProjDataInfoCylindricalNoArcCorr.inl +++ b/src/include/stir/ProjDataInfoCylindricalNoArcCorr.inl @@ -164,7 +164,7 @@ get_bin_for_det_pos_pair(Bin& bin, dp.pos2().axial_coord(), this->get_tof_mash_factor()==0 ? 0 // use timing_pos==0 in the nonTOF case - : stir::round((float)dp.timing_pos()/this->get_tof_mash_factor())); + : dp.timing_pos()); } void ProjDataInfoCylindricalNoArcCorr:: diff --git a/src/include/stir/listmode/CListEventCylindricalScannerWithDiscreteDetectors.h b/src/include/stir/listmode/CListEventCylindricalScannerWithDiscreteDetectors.h index 6125b4f728..b5535c40eb 100644 --- a/src/include/stir/listmode/CListEventCylindricalScannerWithDiscreteDetectors.h +++ b/src/include/stir/listmode/CListEventCylindricalScannerWithDiscreteDetectors.h @@ -84,7 +84,7 @@ class CListEventCylindricalScannerWithDiscreteDetectors : public CListEvent return uncompressed_proj_data_info_sptr; } - shared_ptr scanner_sptr; +// shared_ptr scanner_sptr; shared_ptr uncompressed_proj_data_info_sptr; diff --git a/src/listmode_buildblock/CListRecordROOT.cxx b/src/listmode_buildblock/CListRecordROOT.cxx index abbd3c4709..5a526bb5e3 100644 --- a/src/listmode_buildblock/CListRecordROOT.cxx +++ b/src/listmode_buildblock/CListRecordROOT.cxx @@ -69,12 +69,12 @@ void CListEventROOT::init_from_data(const int& _ring1, const int& _ring2, det2 = crystal2 + quarter_of_detectors; if (det1 < 0 ) - det1 = scanner_sptr->get_num_detectors_per_ring() + det1; + det1 = this->uncompressed_proj_data_info_sptr->get_scanner_ptr()->get_num_detectors_per_ring() + det1; else if ( det1 >= this->uncompressed_proj_data_info_sptr->get_scanner_ptr()->get_num_detectors_per_ring()) det1 = det1 - this->uncompressed_proj_data_info_sptr->get_scanner_ptr()->get_num_detectors_per_ring(); if (det2 < 0 ) - det2 = scanner_sptr->get_num_detectors_per_ring() + det2; + det2 = this->uncompressed_proj_data_info_sptr->get_scanner_ptr()->get_num_detectors_per_ring() + det2; else if ( det2 >= this->uncompressed_proj_data_info_sptr->get_scanner_ptr()->get_num_detectors_per_ring()) det2 = det2 - this->uncompressed_proj_data_info_sptr->get_scanner_ptr()->get_num_detectors_per_ring(); From fc84e85d8247a9768fb644594cf1d2ab6ae02261 Mon Sep 17 00:00:00 2001 From: Nikos Efthimiou Date: Wed, 27 Sep 2017 18:03:12 +0100 Subject: [PATCH 093/509] fix for distributable computation at send_viewgrams() Error on the timing position. --- src/recon_buildblock/distributable.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/recon_buildblock/distributable.cxx b/src/recon_buildblock/distributable.cxx index 4ed12d0fe1..a256903aa0 100644 --- a/src/recon_buildblock/distributable.cxx +++ b/src/recon_buildblock/distributable.cxx @@ -241,7 +241,7 @@ void send_viewgrams(const shared_ptr >& y, const int next_receiver) { distributed::send_view_segment_numbers( y->get_basic_view_segment_num(), NEW_VIEWGRAM_TAG, next_receiver); - distributed::send_int_value( y->get_timing_pos_num(), next_receiver); + distributed::send_int_value( y->get_basic_timing_pos_num(), next_receiver); #ifndef NDEBUG //test sending related viegrams From 3dda018086273674153dacff3e71e0741628e8dc Mon Sep 17 00:00:00 2001 From: Nikos Efthimiou Date: Fri, 29 Sep 2017 19:14:18 +0100 Subject: [PATCH 094/509] Workaround for incomparable results between lm and proj recon. The TOF kernels from CListEventCylindricalScannerWithDiscreteDetectors::get_LOR() and ProjDataInfo::get_LOR() are shifted. The difference is small to be observed with simple numeric tests but the reconstructed images have distict differences and artifacts. In most cases they are not the same (by compare_image). --- src/include/stir/recon_buildblock/ProjMatrixByBin.inl | 3 ++- ...ModelForMeanAndListModeDataWithProjMatrixByBin.cxx | 11 +++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/include/stir/recon_buildblock/ProjMatrixByBin.inl b/src/include/stir/recon_buildblock/ProjMatrixByBin.inl index 9171ea05d2..4ad5875445 100644 --- a/src/include/stir/recon_buildblock/ProjMatrixByBin.inl +++ b/src/include/stir/recon_buildblock/ProjMatrixByBin.inl @@ -61,7 +61,8 @@ get_proj_matrix_elems_for_one_bin( // set to empty probabilities.erase(); - if (proj_data_info_sptr && proj_data_info_sptr->is_tof_data()) + if (proj_data_info_sptr && (proj_data_info_sptr->is_tof_data() || + this->tof_enabled)) { LORInAxialAndNoArcCorrSinogramCoordinates lor; proj_data_info_sptr->get_LOR(lor, bin); diff --git a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx index 9b0a38f635..2c09474d9d 100644 --- a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx +++ b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx @@ -538,10 +538,13 @@ compute_sub_gradient_without_penalty_plus_sensitivity(TargetT& gradient, if(this->use_tof) { - lor_points = record.event().get_LOR(); - this->PM_sptr->get_proj_matrix_elems_for_one_bin_with_tof(proj_matrix_row, - measured_bin, - lor_points.p1(), lor_points.p2()); +// lor_points = record.event().get_LOR(); +// this->PM_sptr->get_proj_matrix_elems_for_one_bin_with_tof(proj_matrix_row, +// measured_bin, +// lor_points.p1(), lor_points.p2()); + + this->PM_sptr->get_proj_matrix_elems_for_one_bin(proj_matrix_row, + measured_bin); } else this->PM_sptr->get_proj_matrix_elems_for_one_bin(proj_matrix_row, measured_bin); From 96a30a9daa49bccff60f0e59bb5b2a97381e031a Mon Sep 17 00:00:00 2001 From: Elise Date: Sat, 30 Sep 2017 18:05:58 +0100 Subject: [PATCH 095/509] Fix calculation of sensitivity for TOF projection data With the added use of distributable computation for sensitivity within the reconstruction, there was a inconsistency in the code when calculating non-TOF sensitivity for TOF projection data, which was previously using a TOF backprojector in one part and a non-TOF backprojector in another part, ending with the calculation of wrong sensitivity images. --- .../PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx index 23bd65cf48..637fdf2a10 100644 --- a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx +++ b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx @@ -778,7 +778,7 @@ add_subset_sensitivity(TargetT& sensitivity, const int subset_num) const sens_proj_data_sptr->fill(1.0F); distributable_sensitivity_computation(this->projector_pair_ptr->get_forward_projector_sptr(), - this->projector_pair_ptr->get_back_projector_sptr(), + this->sens_backprojector_sptr, this->symmetries_sptr, *sensitivity_this_subset_sptr, sensitivity, From e2647236cfa305a5c93613b603e93b30c5137c1f Mon Sep 17 00:00:00 2001 From: Elise Date: Tue, 3 Oct 2017 20:03:56 +0100 Subject: [PATCH 096/509] Fix segmentation fault due to double deletion of shared_ptr in CListEventCylindricalScannerWithDiscreteDetectors The pointer was deleted twice in list_lm_events or lm_to_projdata when reading the listmode data, which was causing a segmentation fault. A dynamic_pointer_cast replaced proj_data_info.get(). --- .../CListEventCylindricalScannerWithDiscreteDetectors.inl | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/include/stir/listmode/CListEventCylindricalScannerWithDiscreteDetectors.inl b/src/include/stir/listmode/CListEventCylindricalScannerWithDiscreteDetectors.inl index f26d81dc4f..1847be7cea 100644 --- a/src/include/stir/listmode/CListEventCylindricalScannerWithDiscreteDetectors.inl +++ b/src/include/stir/listmode/CListEventCylindricalScannerWithDiscreteDetectors.inl @@ -30,12 +30,10 @@ START_NAMESPACE_STIR CListEventCylindricalScannerWithDiscreteDetectors:: -CListEventCylindricalScannerWithDiscreteDetectors(const shared_ptr& proj_data_info) +CListEventCylindricalScannerWithDiscreteDetectors(const shared_ptr& proj_data_info_sptr) { - this->uncompressed_proj_data_info_sptr.reset - (dynamic_cast - ( - proj_data_info.get())); + this->uncompressed_proj_data_info_sptr = + dynamic_pointer_cast(proj_data_info_sptr); if (is_null_ptr(this->uncompressed_proj_data_info_sptr)) error("CListEventCylindricalScannerWithDiscreteDetectors takes only ProjDataInfoCylindricalNoArcCorr. Abord."); From a740037db62ea5aa536998ba433f863c9ef5d6d4 Mon Sep 17 00:00:00 2001 From: Elise Date: Wed, 4 Oct 2017 16:17:15 +0100 Subject: [PATCH 097/509] Rolling back to previous version-ProjData recons All the tests are successful with this version of the code and there should not be any reason to remove the tof mashing factor here for now. To be discussed. --- src/include/stir/ProjDataInfoCylindricalNoArcCorr.inl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/include/stir/ProjDataInfoCylindricalNoArcCorr.inl b/src/include/stir/ProjDataInfoCylindricalNoArcCorr.inl index 69f30f0522..b4ffa53c20 100644 --- a/src/include/stir/ProjDataInfoCylindricalNoArcCorr.inl +++ b/src/include/stir/ProjDataInfoCylindricalNoArcCorr.inl @@ -164,7 +164,7 @@ get_bin_for_det_pos_pair(Bin& bin, dp.pos2().axial_coord(), this->get_tof_mash_factor()==0 ? 0 // use timing_pos==0 in the nonTOF case - : dp.timing_pos()); + : stir::round((float)dp.timing_pos()/this->get_tof_mash_factor())); } void ProjDataInfoCylindricalNoArcCorr:: From 29dc9b45a53bdd0f311241c1cea830fc9f919b5d Mon Sep 17 00:00:00 2001 From: Elise Date: Thu, 5 Oct 2017 19:21:47 +0100 Subject: [PATCH 098/509] Fix for TOF sensitivity distributable computation Following change to using distributable computation for sensitivity calculation. This should allow the calculation of sensitivity for both TOF and non-TOF backprojectors. --- ...ogLikelihoodWithLinearModelForMeanAndProjData.cxx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx index 637fdf2a10..4f0277732b 100644 --- a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx +++ b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx @@ -793,8 +793,10 @@ add_subset_sensitivity(TargetT& sensitivity, const int subset_num) const 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 - ); + this->caching_info_ptr, + use_tofsens ? sens_proj_data_sptr->get_min_tof_pos_num() : 0, + use_tofsens ? sens_proj_data_sptr->get_max_tof_pos_num() : 0); + std::transform(sensitivity.begin_all(), sensitivity.end_all(), sensitivity_this_subset_sptr->begin_all(), sensitivity.begin_all(), std::plus()); @@ -1052,7 +1054,8 @@ void distributable_sensitivity_computation( shared_ptr const& normalisation_sptr, const double start_time_of_frame, const double end_time_of_frame, - DistributedCachingInformation* caching_info_ptr + DistributedCachingInformation* caching_info_ptr, + int min_timing_pos_num, int max_timing_pos_num ) { @@ -1070,7 +1073,8 @@ void distributable_sensitivity_computation( start_time_of_frame, end_time_of_frame, &RPC_process_related_viewgrams_sensitivity_computation, - caching_info_ptr + caching_info_ptr, + min_timing_pos_num, max_timing_pos_num ); } From 995bbdbbb885147ff7acf96aa5a75bbf3993cc5e Mon Sep 17 00:00:00 2001 From: Elise Date: Thu, 5 Oct 2017 22:54:13 +0100 Subject: [PATCH 099/509] Modification of last commit --- .../PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx index 4f0277732b..abcf46680b 100644 --- a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx +++ b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx @@ -794,8 +794,8 @@ add_subset_sensitivity(TargetT& sensitivity, const int subset_num) const 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, - use_tofsens ? sens_proj_data_sptr->get_min_tof_pos_num() : 0, - use_tofsens ? sens_proj_data_sptr->get_max_tof_pos_num() : 0); + use_tofsens ? -this->max_timing_pos_num_to_process : 0, + use_tofsens ? this->max_timing_pos_num_to_process : 0); std::transform(sensitivity.begin_all(), sensitivity.end_all(), sensitivity_this_subset_sptr->begin_all(), sensitivity.begin_all(), From e23620719b45de056f9a15e15d39890335f0518f Mon Sep 17 00:00:00 2001 From: Nikos Efthimiou Date: Tue, 10 Oct 2017 15:25:23 +0100 Subject: [PATCH 100/509] Display TOF sinograms simple patch on this --- src/utilities/display_projdata.cxx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/utilities/display_projdata.cxx b/src/utilities/display_projdata.cxx index f7f378f03f..09002a6b80 100644 --- a/src/utilities/display_projdata.cxx +++ b/src/utilities/display_projdata.cxx @@ -58,16 +58,19 @@ int main(int argc, char *argv[]) int segment_num = ask_num("Which segment number do you want to display", s3d->get_min_segment_num(), s3d->get_max_segment_num(), 0); + int tof_num = ask_num("Which timing pos number do you want to display", + s3d->get_min_tof_pos_num(), s3d->get_max_tof_pos_num(), 0); + if(ask_num("Display as SegmentByView (0) or BySinogram (1)?", 0,1,0)==0) { - SegmentByView segment= s3d->get_segment_by_view(segment_num); + SegmentByView segment= s3d->get_segment_by_view(segment_num, tof_num); const float maxi = ask_num("Maximum in color scale (default is actual max)",0.F,2*segment.find_max(),segment.find_max()); display(segment,maxi); } else { - SegmentBySinogram segment = s3d->get_segment_by_sinogram(segment_num); + SegmentBySinogram segment = s3d->get_segment_by_sinogram(segment_num, tof_num); const float maxi = ask_num("Maximum in color scale (default is actual max)",0.F,2*segment.find_max(),segment.find_max()); display(segment,maxi); From 11e84a74858f51809b5e8ae4d8cdd536470df9ad Mon Sep 17 00:00:00 2001 From: Elise Date: Wed, 11 Oct 2017 10:20:32 +0100 Subject: [PATCH 101/509] Allows the use of 4D sinograms with Swig --- src/buildblock/ProjData.cxx | 4 ++-- src/include/stir/ProjData.h | 8 ++++---- src/swig/stir.i | 36 ++++++++++++++++++++---------------- 3 files changed, 26 insertions(+), 22 deletions(-) diff --git a/src/buildblock/ProjData.cxx b/src/buildblock/ProjData.cxx index 91ad1489c0..e6afdb8976 100644 --- a/src/buildblock/ProjData.cxx +++ b/src/buildblock/ProjData.cxx @@ -386,9 +386,9 @@ ProjData::set_segment(const SegmentByView& segment) void ProjData::fill(const float value) { - for (int segment_num = this->get_min_segment_num(); segment_num <= this->get_max_segment_num(); ++segment_num) + for (int timing_pos_num = this->get_min_tof_pos_num(); timing_pos_num <= this->get_max_tof_pos_num(); ++timing_pos_num) { - for (int timing_pos_num = this->get_min_tof_pos_num(); timing_pos_num <= this->get_max_tof_pos_num(); ++timing_pos_num) + for (int segment_num = this->get_min_segment_num(); segment_num <= this->get_max_segment_num(); ++segment_num) { SegmentByView segment(this->get_empty_segment_by_view(segment_num, false, timing_pos_num)); segment.fill(value); diff --git a/src/include/stir/ProjData.h b/src/include/stir/ProjData.h index 174f98fde7..5c5237173d 100644 --- a/src/include/stir/ProjData.h +++ b/src/include/stir/ProjData.h @@ -280,11 +280,11 @@ class ProjData : public ExamData std::size_t copy_to(iterT array_iter) const { iterT init_pos = array_iter; - for (int s=0; s<= this->get_max_segment_num(); ++s) + for (int k = this->get_proj_data_info_ptr()->get_min_tof_pos_num(); + k <= this->get_proj_data_info_ptr()->get_max_tof_pos_num(); + ++k) { - for (int k=this->get_proj_data_info_ptr()->get_min_tof_pos_num(); - k<=this->get_proj_data_info_ptr()->get_max_tof_pos_num(); - ++k) + for (int s = 0; s <= this->get_max_segment_num(); ++s) { SegmentBySinogram segment= this->get_segment_by_sinogram(s,k); std::copy(segment.begin_all_const(), segment.end_all_const(), array_iter); diff --git a/src/swig/stir.i b/src/swig/stir.i index 34b0ac662c..1cd34b9f77 100644 --- a/src/swig/stir.i +++ b/src/swig/stir.i @@ -63,6 +63,7 @@ #include "stir/CartesianCoordinate3D.h" #include "stir/IndexRange.h" #include "stir/IndexRange3D.h" +#include "stir/IndexRange4D.h" #include "stir/Array.h" #include "stir/DiscretisedDensity.h" #include "stir/DiscretisedDensityOnCartesianGrid.h" @@ -607,7 +608,7 @@ namespace std { #endif - static Array<3,float> create_array_for_proj_data(const ProjData& proj_data) + static Array<4,float> create_array_for_proj_data(const ProjData& proj_data) { // int num_sinos=proj_data.get_num_axial_poss(0); // for (int s=1; s<= proj_data.get_max_segment_num(); ++s) @@ -616,15 +617,15 @@ namespace std { // } int num_sinos = proj_data.get_num_sinograms(); - Array<3,float> array(IndexRange3D(num_sinos, proj_data.get_num_views(), proj_data.get_num_tangential_poss())); + Array<4,float> array(IndexRange4D(proj_data.get_num_tof_poss(),num_sinos, proj_data.get_num_views(), proj_data.get_num_tangential_poss())); return array; } - // a function for converting ProjData to a 3D array as that's what is easy to use - static Array<3,float> projdata_to_3D(const ProjData& proj_data) + // a function for converting ProjData to a 4D array as that's what is easy to use + static Array<4,float> projdata_to_4D(const ProjData& proj_data) { - Array<3,float> array = create_array_for_proj_data(proj_data); - Array<3,float>::full_iterator array_iter = array.begin_all(); + Array<4,float> array = create_array_for_proj_data(proj_data); + Array<4,float>::full_iterator array_iter = array.begin_all(); // for (int s=0; s<= proj_data.get_max_segment_num(); ++s) // { // SegmentBySinogram segment=proj_data.get_segment_by_sinogram(s); @@ -642,7 +643,7 @@ namespace std { } // inverse of the above function - void fill_proj_data_from_3D(ProjData& proj_data, const Array<3,float>& array) + void fill_proj_data_from_4D(ProjData& proj_data, const Array<4,float>& array) { // int num_sinos=proj_data.get_num_axial_poss(0); // for (int s=1; s<= proj_data.get_max_segment_num(); ++s) @@ -655,7 +656,7 @@ namespace std { // { // throw std::runtime_error("Incorrect size for filling this projection data"); // } - Array<3,float>::const_full_iterator array_iter = array.begin_all(); + Array<4,float>::const_full_iterator array_iter = array.begin_all(); // // for (int s=0; s<= proj_data.get_max_segment_num(); ++s) // { @@ -828,6 +829,7 @@ namespace std { //%shared_ptr(stir::Array<1,float>); %shared_ptr(stir::Array<2,float>); %shared_ptr(stir::Array<3,float>); +%shared_ptr(stir::Array<4,float>); %shared_ptr(stir::DiscretisedDensity<3,float>); %shared_ptr(stir::DiscretisedDensityOnCartesianGrid<3,float>); %shared_ptr(stir::VoxelsOnCartesianGrid); @@ -1109,6 +1111,7 @@ namespace stir { %template(IndexRange2D) IndexRange<2>; //%template(IndexRange2DVectorWithOffset) VectorWithOffset >; %template(IndexRange3D) IndexRange<3>; + %template(IndexRange4D) IndexRange<4>; %ADD_indexaccess(int,T,VectorWithOffset); %template(FloatVectorWithOffset) VectorWithOffset; @@ -1254,6 +1257,7 @@ namespace stir { // TODO name %template (FloatNumericVectorWithOffset3D) stir::NumericVectorWithOffset, float>; %template(FloatArray3D) stir::Array<3,float>; + %template(FloatArray4D) stir::Array<4,float>; #if 0 %ADD_indexaccess(int,%arg(stir::Array<2,float>),%arg(stir::Array<3,float>)); #endif @@ -1351,11 +1355,11 @@ namespace stir { %extend ProjData { #ifdef SWIGPYTHON - %feature("autodoc", "create a stir 3D Array from the projection data (internal)") to_array; + %feature("autodoc", "create a stir 4D Array from the projection data (internal)") to_array; %newobject to_array; - Array<3,float> to_array() + Array<4,float> to_array() { - Array<3,float> array = swigstir::projdata_to_3D(*$self); + Array<4,float> array = swigstir::projdata_to_4D(*$self); return array; } @@ -1364,9 +1368,9 @@ namespace stir { { if (PyIter_Check(arg)) { - Array<3,float> array = swigstir::create_array_for_proj_data(*$self); + Array<4,float> array = swigstir::create_array_for_proj_data(*$self); swigstir::fill_Array_from_Python_iterator(&array, arg); - swigstir::fill_proj_data_from_3D(*$self, array); + swigstir::fill_proj_data_from_4D(*$self, array); } else { @@ -1381,15 +1385,15 @@ namespace stir { %newobject to_matlab; mxArray * to_matlab() { - Array<3,float> array = swigstir::projdata_to_3D(*$self); + Array<4,float> array = swigstir::projdata_to_4D(*$self); return swigstir::Array_to_matlab(array); } void fill(const mxArray *pm) { - Array<3,float> array; + Array<4,float> array; swigstir::fill_Array_from_matlab(array, pm, true); - swigstir::fill_proj_data_from_3D(*$self, array); + swigstir::fill_proj_data_from_4D(*$self, array); } #endif } From 69651e54492f465e87c9fb459ca6509ac2ab6ec9 Mon Sep 17 00:00:00 2001 From: Nikos Efthimiou Date: Mon, 15 Oct 2018 15:52:49 +0100 Subject: [PATCH 102/509] Corrections of errors from merging --- ...putStreamFromROOTFileForCylindricalPET.cxx | 2 +- src/IO/InputStreamFromROOTFileForECATPET.cxx | 2 +- src/buildblock/ProjDataFromStream.cxx | 18 +++--- .../stir/listmode/CListModeDataECAT8_32bit.h | 2 - src/include/stir/listmode/CListModeDataROOT.h | 4 +- .../stir/listmode/CListModeDataSAFIR.h | 3 - ...orMeanAndListModeDataWithProjMatrixByBin.h | 11 ++-- .../CListModeDataECAT8_32bit.cxx | 8 --- src/listmode_buildblock/CListModeDataROOT.cxx | 49 +++------------- .../CListModeDataSAFIR.cxx | 9 --- ...MeanAndListModeDataWithProjMatrixByBin.cxx | 58 +++++++++---------- 11 files changed, 54 insertions(+), 112 deletions(-) diff --git a/src/IO/InputStreamFromROOTFileForCylindricalPET.cxx b/src/IO/InputStreamFromROOTFileForCylindricalPET.cxx index a4a60269c1..c8417af6ae 100644 --- a/src/IO/InputStreamFromROOTFileForCylindricalPET.cxx +++ b/src/IO/InputStreamFromROOTFileForCylindricalPET.cxx @@ -124,7 +124,7 @@ get_next_record(CListRecordROOT& record) record.init_from_data(ring1, ring2, crystal1, crystal2, time1, delta_timing_bin, - event1, event2); + eventID1, eventID2); } std::string diff --git a/src/IO/InputStreamFromROOTFileForECATPET.cxx b/src/IO/InputStreamFromROOTFileForECATPET.cxx index e9a218dbc8..6028fdc1ca 100644 --- a/src/IO/InputStreamFromROOTFileForECATPET.cxx +++ b/src/IO/InputStreamFromROOTFileForECATPET.cxx @@ -113,7 +113,7 @@ get_next_record(CListRecordROOT& record) record.init_from_data(ring1, ring2, crystal1, crystal2, time1, delta_timing_bin, - event1, event2); + eventID1, eventID2); } std::string diff --git a/src/buildblock/ProjDataFromStream.cxx b/src/buildblock/ProjDataFromStream.cxx index 3cf916a808..851e7a1a2e 100644 --- a/src/buildblock/ProjDataFromStream.cxx +++ b/src/buildblock/ProjDataFromStream.cxx @@ -542,7 +542,7 @@ ProjDataFromStream::get_offsets_bin(const Bin this_bin) const num_axial_pos_offset += get_num_axial_poss(segment_sequence[i]); - const streamoff segment_offset = + streamoff segment_offset = offset + static_cast(num_axial_pos_offset* get_num_tangential_poss() * @@ -606,11 +606,11 @@ ProjDataFromStream::get_offsets_bin(const Bin this_bin) const { // The timing offset will be added to the segment offset. This approach we minimise the // changes - if (!(timing_pos_num >= get_min_tof_pos_num() && - timing_pos_num <= get_max_tof_pos_num())) - error("ProjDataFromStream::get_offsets_bin: timing_num out of range : %d", timing_pos_num); + if (!(this_bin.timing_pos_num() >= get_min_tof_pos_num() && + this_bin.timing_pos_num() <= get_max_tof_pos_num())) + error("ProjDataFromStream::get_offsets_bin: timing_num out of range : %d", this_bin.timing_pos_num()); - const int timing_index = static_cast(FIND(timing_poss_sequence.begin(), timing_poss_sequence.end(), timing_pos_num) - + const int timing_index = static_cast(FIND(timing_poss_sequence.begin(), timing_poss_sequence.end(), this_bin.timing_pos_num()) - timing_poss_sequence.begin()); assert(offset_3d_data > 0); @@ -618,21 +618,21 @@ ProjDataFromStream::get_offsets_bin(const Bin this_bin) const // Skip views const streamoff view_offset = - (view_num - get_min_view_num())* - get_num_axial_poss(segment_num) * + (this_bin.view_num() - get_min_view_num())* + get_num_axial_poss(this_bin.segment_num()) * get_num_tangential_poss()* on_disk_data_type.size_in_bytes(); // find axial pos const streamoff ax_pos_offset = - (ax_pos_num - get_min_axial_pos_num(segment_num)) * + (this_bin.axial_pos_num() - get_min_axial_pos_num(this_bin.segment_num())) * get_num_tangential_poss()* on_disk_data_type.size_in_bytes(); // find tang pos const streamoff tang_offset = - (tang_pos_num - get_min_tangential_pos_num()) * on_disk_data_type.size_in_bytes(); + (this_bin.tangential_pos_num() - get_min_tangential_pos_num()) * on_disk_data_type.size_in_bytes(); vector temp(1); temp[0] = segment_offset + ax_pos_offset +view_offset + tang_offset; diff --git a/src/include/stir/listmode/CListModeDataECAT8_32bit.h b/src/include/stir/listmode/CListModeDataECAT8_32bit.h index 280e872298..97fc66ee7d 100644 --- a/src/include/stir/listmode/CListModeDataECAT8_32bit.h +++ b/src/include/stir/listmode/CListModeDataECAT8_32bit.h @@ -74,8 +74,6 @@ class CListModeDataECAT8_32bit : public CListModeData /*! \todo this might depend on the acquisition parameters */ virtual bool has_delayeds() const { return true; } - virtual - shared_ptr get_proj_data_info_sptr() const; private: typedef CListRecordECAT8_32bit CListRecordT; std::string listmode_filename; diff --git a/src/include/stir/listmode/CListModeDataROOT.h b/src/include/stir/listmode/CListModeDataROOT.h index 635534a373..f8a6926925 100644 --- a/src/include/stir/listmode/CListModeDataROOT.h +++ b/src/include/stir/listmode/CListModeDataROOT.h @@ -175,11 +175,13 @@ class CListModeDataROOT : public CListModeData float ring_spacing; //! Bin size, set in the hroot file (optional) float bin_size; -//@} int max_num_timing_bins; + float size_timing_bin; + float timing_resolution; +//@} int tof_mash_factor; diff --git a/src/include/stir/listmode/CListModeDataSAFIR.h b/src/include/stir/listmode/CListModeDataSAFIR.h index 0e79332540..3d2117ebc6 100644 --- a/src/include/stir/listmode/CListModeDataSAFIR.h +++ b/src/include/stir/listmode/CListModeDataSAFIR.h @@ -85,9 +85,6 @@ template class CListModeDataSAFIR : public CListModeData */ virtual bool has_delayeds() const { return false; } - virtual - shared_ptr get_proj_data_info_sptr() const; - private: std::string listmode_filename; mutable shared_ptr > current_lm_data_ptr; diff --git a/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.h b/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.h index 272463805e..0fa1a6bb3a 100644 --- a/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.h +++ b/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.h @@ -35,12 +35,9 @@ #include "stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeData.h" #include "stir/recon_buildblock/ProjMatrixByBin.h" #include "stir/ProjDataInMemory.h" -#include "stir/recon_buildblock/ProjectorByBinPair.h" +#include "stir/recon_buildblock/ProjectorByBinPairUsingProjMatrixByBin.h" #include "stir/ExamInfo.h" -#include "stir/recon_buildblock/ForwardProjectorByBinUsingProjMatrixByBin.h" -#include "stir/recon_buildblock/BackProjectorByBinUsingProjMatrixByBin.h" -#include "stir/recon_buildblock/ProjMatrixByBinUsingRayTracing.h" -#include "stir/recon_buildblock/ProjectorByBinPairUsingSeparateProjectors.h" + START_NAMESPACE_STIR @@ -113,7 +110,7 @@ typedef RegisteredParsingObject PM_sptr; //! Stores the projectors that are used for the computations - shared_ptr projector_pair_ptr; + shared_ptr projector_pair_sptr; //! Backprojector used for sensitivity computation shared_ptr sens_backprojector_sptr; @@ -123,7 +120,7 @@ typedef RegisteredParsingObject proj_data_info_cyl_sptr; + shared_ptr proj_data_info_sptr; shared_ptr record_sptr; diff --git a/src/listmode_buildblock/CListModeDataECAT8_32bit.cxx b/src/listmode_buildblock/CListModeDataECAT8_32bit.cxx index ad25817442..4cfbc92350 100644 --- a/src/listmode_buildblock/CListModeDataECAT8_32bit.cxx +++ b/src/listmode_buildblock/CListModeDataECAT8_32bit.cxx @@ -141,13 +141,5 @@ set_get_position(const CListModeDataECAT8_32bit::SavedPosition& pos) current_lm_data_ptr->set_get_position(pos); } -shared_ptr -CListModeDataECAT8_32bit:: -get_proj_data_info_sptr() const -{ - assert(!is_null_ptr(proj_data_info_sptr)); - return proj_data_info_sptr; -} - } // namespace ecat END_NAMESPACE_STIR diff --git a/src/listmode_buildblock/CListModeDataROOT.cxx b/src/listmode_buildblock/CListModeDataROOT.cxx index c99a96cb63..4562660f0c 100644 --- a/src/listmode_buildblock/CListModeDataROOT.cxx +++ b/src/listmode_buildblock/CListModeDataROOT.cxx @@ -1,7 +1,7 @@ /* Copyright (C) 2015, 2016 University of Leeds Copyright (C) 2016, 2017 University College London - Copyright (C) 2018 University of Hull + Copyright (C) 2017, 2018 University of Hull This file is part of STIR. This file is free software; you can redistribute it and/or modify @@ -47,18 +47,6 @@ CListModeDataROOT(const std::string& hroot_filename) this->parser.add_start_key("ROOT header"); this->parser.add_stop_key("End ROOT header"); - // N.E.: Compression on ROOT listmode data is commented out until further testing is done. - // axial_compression = -1; - // maximum_ring_difference = -1; - // number_of_projections = -1; - // number_of_views = -1; - // number_of_segments = -1; - - max_num_timing_bins = -1; - size_timing_bin = -1.f; - timing_resolution = -1.f; - tof_mash_factor = 1; - // Scanner related & Physical dimensions. this->parser.add_key("originating system", &this->originating_system); @@ -71,14 +59,6 @@ CListModeDataROOT(const std::string& hroot_filename) this->parser.add_key("Maximum number of non-arc-corrected bins", &this->max_num_non_arccorrected_bins); // end Scanner and physical dimensions. - // Acquisition related - // N.E.: Compression on ROOT listmode data has been commented out until further testing is done. - // this->parser.add_key("%axial_compression", &axial_compression); - // this->parser.add_key("%maximum_ring_difference", &maximum_ring_difference); - // this->parser.add_key("%number_of_projections", &number_of_projections); - // this->parser.add_key("%number_of_views", &number_of_views); - // this->parser.add_key("%number_of_segments", &number_of_segments); - this->parser.add_key("number of TOF time bins", &this->max_num_timing_bins); this->parser.add_key("Size of timing bin (ps)", &this->size_timing_bin); this->parser.add_key("Timing resolution (ps)", &this->timing_resolution); @@ -163,8 +143,11 @@ CListModeDataROOT(const std::string& hroot_filename) /*num_transaxial_crystals_per_singles_unit_v*/ this->root_file_sptr->get_num_trans_crystals_per_singles_unit(), /*num_detector_layers_v*/ 1, + /* maximum number of timing bins */ max_num_timing_bins, + /* size of basic TOF bin */ size_timing_bin, + /* Scanner's timing resolution */ timing_resolution)); } @@ -179,31 +162,13 @@ CListModeDataROOT(const std::string& hroot_filename) this_scanner_sptr->get_num_rings()-1, this_scanner_sptr->get_num_detectors_per_ring()/2, this_scanner_sptr->get_max_num_non_arccorrected_bins(), - /* arc_correction*/false)); + /* arc_correction*/false, + tof_mash_factor)); this->set_proj_data_info_sptr(tmp); if (this->open_lm_file() == Succeeded::no) error("CListModeDataROOT: error opening ROOT file for filename '%s'", hroot_filename.c_str()); - - // N.E.: Compression on ROOT listmode data has been commented out until further testing is done. - // this->proj_data_info_sptr.reset(ProjDataInfo::ProjDataInfoCTI(this->scanner_sptr, - // std::max(axial_compression, 1), - // std::max(maximum_ring_difference, num_rings-1), - // std::max(number_of_views, num_detectors_per_ring/2), - // std::max(number_of_projections, max_num_non_arccorrected_bins), - // /* arc_correction*/false)); - - this->proj_data_info_sptr.reset(ProjDataInfo::ProjDataInfoCTI(this->scanner_sptr, - 1, - num_rings-1, - num_detectors_per_ring/2, - max_num_non_arccorrected_bins, - /* arc_correction*/false, - tof_mash_factor)); - -// if (tof_mash_factor != 1) - // error("TOF mashing factor for ROOT different from 1 not implemented yet."); } std::string @@ -217,7 +182,7 @@ shared_ptr CListModeDataROOT:: get_empty_record_sptr() const { - shared_ptr sptr(new CListRecordROOT(this->get_proj_data_info_sptr()->get_scanner_sptr())); + shared_ptr sptr(new CListRecordROOT(this->get_proj_data_info_sptr())); return sptr; } diff --git a/src/listmode_buildblock/CListModeDataSAFIR.cxx b/src/listmode_buildblock/CListModeDataSAFIR.cxx index 05365cb903..09de319fc1 100644 --- a/src/listmode_buildblock/CListModeDataSAFIR.cxx +++ b/src/listmode_buildblock/CListModeDataSAFIR.cxx @@ -119,15 +119,6 @@ open_lm_file() const ByteOrder::little_endian !=ByteOrder::get_native_order())); return Succeeded::yes; } - -template -shared_ptr -CListModeDataSAFIR:: -get_proj_data_info_sptr() const -{ - assert(!is_null_ptr(proj_data_info_sptr)); - return proj_data_info_sptr; -} template class CListModeDataSAFIR; diff --git a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx index d5dd0168cc..f1a900fe7b 100644 --- a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx +++ b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx @@ -135,26 +135,26 @@ actual_subsets_are_approximately_balanced(std::string& warning_message) const for (int subset_num=0; subset_numnum_subsets; ++subset_num) { - for (int timing_pos_num = proj_data_info_cyl_sptr->get_min_tof_pos_num(); - timing_pos_num <= proj_data_info_cyl_sptr->get_max_tof_pos_num(); + for (int timing_pos_num = proj_data_info_sptr->get_min_tof_pos_num(); + timing_pos_num <= proj_data_info_sptr->get_max_tof_pos_num(); ++timing_pos_num) { for (int segment_num = -this->max_ring_difference_num_to_process; segment_num <= this->max_ring_difference_num_to_process; ++segment_num) { - for (int axial_num = proj_data_info_cyl_sptr->get_min_axial_pos_num(segment_num); - axial_num < proj_data_info_cyl_sptr->get_max_axial_pos_num(segment_num); + for (int axial_num = proj_data_info_sptr->get_min_axial_pos_num(segment_num); + axial_num < proj_data_info_sptr->get_max_axial_pos_num(segment_num); axial_num ++) { // For debugging. // std::cout <get_min_tangential_pos_num(); - tang_num < proj_data_info_cyl_sptr->get_max_tangential_pos_num(); + for (int tang_num= proj_data_info_sptr->get_min_tangential_pos_num(); + tang_num < proj_data_info_sptr->get_max_tangential_pos_num(); tang_num ++ ) { - for(int view_num = proj_data_info_cyl_sptr->get_min_view_num() + subset_num; - view_num <= proj_data_info_cyl_sptr->get_max_view_num(); + for(int view_num = proj_data_info_sptr->get_min_view_num() + subset_num; + view_num <= proj_data_info_sptr->get_max_view_num(); view_num += this->num_subsets) { const Bin tmp_bin(segment_num, @@ -187,11 +187,11 @@ actual_subsets_are_approximately_balanced(std::string& warning_message) const << num_bins_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" - << proj_data_info_cyl_sptr->get_num_views() + << proj_data_info_sptr->get_num_views() << "/4 (or if that's not an integer, a divisor of " - << proj_data_info_cyl_sptr->get_num_views() + << proj_data_info_sptr->get_num_views() << "/2 or " - << proj_data_info_cyl_sptr->get_num_views() + << proj_data_info_sptr->get_num_views() << ").\n"; warning_message = str.str(); return false; @@ -213,7 +213,7 @@ set_up_before_sensitivity(shared_ptr const& target_sptr) // set projector to be used for the calculations this->PM_sptr->set_up(proj_data_info_sptr->create_shared_clone(),target_sptr); - this->PM_sptr->enable_tof(proj_data_info_cyl_sptr->create_shared_clone(), this->use_tof); + this->PM_sptr->enable_tof(proj_data_info_sptr->create_shared_clone(), this->use_tof); shared_ptr forward_projector_ptr(new ForwardProjectorByBinUsingProjMatrixByBin(this->PM_sptr)); shared_ptr back_projector_ptr(new BackProjectorByBinUsingProjMatrixByBin(this->PM_sptr)); @@ -223,9 +223,9 @@ set_up_before_sensitivity(shared_ptr const& target_sptr) this->projector_pair_sptr->set_up(proj_data_info_sptr->create_shared_clone(),target_sptr); // sets non-tof backprojector for sensitivity calculation (clone of the back_projector + set projdatainfo to non-tof) - this->sens_backprojector_sptr.reset(projector_pair_ptr->get_back_projector_sptr()->clone()); + this->sens_backprojector_sptr.reset(projector_pair_sptr->get_back_projector_sptr()->clone()); if (!this->use_tofsens) - this->sens_backprojector_sptr->set_up(proj_data_info_cyl_sptr->create_non_tof_clone(), target_sptr); + this->sens_backprojector_sptr->set_up(proj_data_info_sptr->create_non_tof_clone(), target_sptr); if (is_null_ptr(this->normalisation_sptr)) { @@ -394,15 +394,15 @@ void PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin:: add_view_seg_to_sensitivity(TargetT& sensitivity, const ViewSegmentNumbers& view_seg_nums) const { - int min_timing_pos_num = use_tofsens ? this->proj_data_info_cyl_sptr->get_min_tof_pos_num() : 0; - int max_timing_pos_num = use_tofsens ? this->proj_data_info_cyl_sptr->get_max_tof_pos_num() : 0; + int min_timing_pos_num = use_tofsens ? this->proj_data_info_sptr->get_min_tof_pos_num() : 0; + int max_timing_pos_num = use_tofsens ? this->proj_data_info_sptr->get_max_tof_pos_num() : 0; for (int timing_pos_num = min_timing_pos_num; timing_pos_num <= max_timing_pos_num; ++timing_pos_num) { shared_ptr symmetries_used - (this->projector_pair_ptr->get_symmetries_used()->clone()); + (this->projector_pair_sptr->get_symmetries_used()->clone()); RelatedViewgrams viewgrams = - proj_data_info_cyl_sptr->get_empty_related_viewgrams( + proj_data_info_sptr->get_empty_related_viewgrams( view_seg_nums, symmetries_used, false, timing_pos_num); viewgrams.fill(1.F); @@ -433,7 +433,7 @@ construct_target_ptr() const { return - new VoxelsOnCartesianGrid (*proj_data_info_cyl_sptr, + new VoxelsOnCartesianGrid (*proj_data_info_sptr, static_cast(this->zoom), CartesianCoordinate3D(static_cast(this->Zoffset), static_cast(this->Yoffset), @@ -476,7 +476,7 @@ compute_sub_gradient_without_penalty_plus_sensitivity(TargetT& gradient, CListRecord& record = *record_sptr; long int more_events = - this->do_time_frame? 1 : (this->num_events_to_store / this->num_subsets); + this->do_time_frame? 1 : (this->num_events_to_use / this->num_subsets); while (more_events)//this->list_mode_data_sptr->get_next_record(record) == Succeeded::yes) { @@ -500,19 +500,19 @@ compute_sub_gradient_without_penalty_plus_sensitivity(TargetT& gradient, { measured_bin.set_bin_value(1.0f); - record.event().get_bin(measured_bin, *proj_data_info_cyl_sptr); + record.event().get_bin(measured_bin, *proj_data_info_sptr); // In theory we have already done all these checks so we can // remove this if statement. if (measured_bin.get_bin_value() != 1.0f - || measured_bin.segment_num() < proj_data_info_cyl_sptr->get_min_segment_num() - || measured_bin.segment_num() > proj_data_info_cyl_sptr->get_max_segment_num() - || measured_bin.tangential_pos_num() < proj_data_info_cyl_sptr->get_min_tangential_pos_num() - || measured_bin.tangential_pos_num() > proj_data_info_cyl_sptr->get_max_tangential_pos_num() - || measured_bin.axial_pos_num() < proj_data_info_cyl_sptr->get_min_axial_pos_num(measured_bin.segment_num()) - || measured_bin.axial_pos_num() > proj_data_info_cyl_sptr->get_max_axial_pos_num(measured_bin.segment_num()) - || measured_bin.timing_pos_num() < proj_data_info_cyl_sptr->get_min_tof_pos_num() - || measured_bin.timing_pos_num() > proj_data_info_cyl_sptr->get_max_tof_pos_num()) + || measured_bin.segment_num() < proj_data_info_sptr->get_min_segment_num() + || measured_bin.segment_num() > proj_data_info_sptr->get_max_segment_num() + || measured_bin.tangential_pos_num() < proj_data_info_sptr->get_min_tangential_pos_num() + || measured_bin.tangential_pos_num() > proj_data_info_sptr->get_max_tangential_pos_num() + || measured_bin.axial_pos_num() < proj_data_info_sptr->get_min_axial_pos_num(measured_bin.segment_num()) + || measured_bin.axial_pos_num() > proj_data_info_sptr->get_max_axial_pos_num(measured_bin.segment_num()) + || measured_bin.timing_pos_num() < proj_data_info_sptr->get_min_tof_pos_num() + || measured_bin.timing_pos_num() > proj_data_info_sptr->get_max_tof_pos_num()) { continue; } From b26607d1a0cd1ad1f80abf3f06a07037cfb6ba13 Mon Sep 17 00:00:00 2001 From: Nikos Efthimiou Date: Wed, 17 Oct 2018 03:47:07 +0100 Subject: [PATCH 103/509] Significant speed-up on the application of the kernel Now the symmetries and the TOF kernel are applied with one loop --- src/include/stir/geometry/line_distances.h | 5 +- src/include/stir/listmode/CListModeDataROOT.h | 3 -- .../stir/recon_buildblock/ProjMatrixByBin.h | 6 +-- .../stir/recon_buildblock/ProjMatrixByBin.inl | 52 +++++++++---------- .../CListModeDataGESigna.cxx | 1 - 5 files changed, 31 insertions(+), 36 deletions(-) diff --git a/src/include/stir/geometry/line_distances.h b/src/include/stir/geometry/line_distances.h index 76331eb52f..0a89a103b4 100644 --- a/src/include/stir/geometry/line_distances.h +++ b/src/include/stir/geometry/line_distances.h @@ -178,14 +178,13 @@ project_point_on_a_line( CartesianCoordinate3D& r1 ) { - const CartesianCoordinate3D& r0 = p1; const CartesianCoordinate3D difference = p2 - p1; - const CartesianCoordinate3D r10 = r1-r0; + const CartesianCoordinate3D r10 = r1 - p1; float inner_prod = inner_product(difference, difference); - const coordT u = inner_product(r10,difference) / inner_prod ; + const float u = inner_product(r10, difference) / inner_prod ; r1.x() = p1.x() + u * difference.x(); r1.y() = p1.y() + u * difference.y(); diff --git a/src/include/stir/listmode/CListModeDataROOT.h b/src/include/stir/listmode/CListModeDataROOT.h index f8a6926925..f439d8a83c 100644 --- a/src/include/stir/listmode/CListModeDataROOT.h +++ b/src/include/stir/listmode/CListModeDataROOT.h @@ -140,9 +140,6 @@ class CListModeDataROOT : public CListModeData unsigned long int get_total_number_of_events() const ; - virtual - shared_ptr get_proj_data_info_sptr() const; - private: //! Check if the hroot contains a full scanner description Succeeded check_scanner_definition(std::string& ret); diff --git a/src/include/stir/recon_buildblock/ProjMatrixByBin.h b/src/include/stir/recon_buildblock/ProjMatrixByBin.h index 6e0b4faa33..d25688cd77 100644 --- a/src/include/stir/recon_buildblock/ProjMatrixByBin.h +++ b/src/include/stir/recon_buildblock/ProjMatrixByBin.h @@ -269,10 +269,10 @@ class ProjMatrixByBin : float r_sqrt2_gauss_sigma; //! The function which actually applies the TOF kernel on the LOR. - inline void apply_tof_kernel( ProjMatrixElemsForOneBin& nonTOF_probabilities, - ProjMatrixElemsForOneBin& tof_probabilities, + inline void apply_tof_kernel( ProjMatrixElemsForOneBin& tof_probabilities, const CartesianCoordinate3D& point1, - const CartesianCoordinate3D& point2) STIR_MUTABLE_CONST; + const CartesianCoordinate3D& point2, + const shared_ptr symm_ptr) STIR_MUTABLE_CONST; diff --git a/src/include/stir/recon_buildblock/ProjMatrixByBin.inl b/src/include/stir/recon_buildblock/ProjMatrixByBin.inl index 4ad5875445..5606981e41 100644 --- a/src/include/stir/recon_buildblock/ProjMatrixByBin.inl +++ b/src/include/stir/recon_buildblock/ProjMatrixByBin.inl @@ -34,7 +34,7 @@ #include "stir/Succeeded.h" #include "stir/recon_buildblock/SymmetryOperation.h" #include "stir/geometry/line_distances.h" -#include "stir/numerics/erf.h" +//#include "stir/numerics/erf.h" START_NAMESPACE_STIR @@ -145,35 +145,32 @@ get_proj_matrix_elems_for_one_bin_with_tof( // set to empty probabilities.erase(); - ProjMatrixElemsForOneBin tmp_probabilities; if (cache_stores_only_basic_bins) { // find basic bin Bin basic_bin = bin; - unique_ptr symm_ptr = + shared_ptr symm_ptr = symmetries_sptr->find_symmetry_operation_from_basic_bin(basic_bin); - tmp_probabilities.set_bin(basic_bin); - probabilities.set_bin(bin); + probabilities.set_bin(basic_bin); // check if basic bin is in cache - if (get_cached_proj_matrix_elems_for_one_bin(tmp_probabilities) == + if (get_cached_proj_matrix_elems_for_one_bin(probabilities) == Succeeded::no) { // call 'calculate' just for the basic bin - calculate_proj_matrix_elems_for_one_bin(tmp_probabilities); + calculate_proj_matrix_elems_for_one_bin(probabilities); #ifndef NDEBUG - tmp_probabilities.check_state(); + probabilities.check_state(); #endif - cache_proj_matrix_elems_for_one_bin(tmp_probabilities); + cache_proj_matrix_elems_for_one_bin(probabilities); } -// else -// int nikos = 0; -// tmp_probabilities.set_bin(bin); // now transform to original bin - symm_ptr->transform_proj_matrix_elems_for_one_bin(tmp_probabilities); - apply_tof_kernel(tmp_probabilities, probabilities, point1, point2); + // NE: I moved this operation in the apply_tof_kernel. This should increase the speed + //symm_ptr->transform_proj_matrix_elems_for_one_bin(tmp_probabilities); + apply_tof_kernel(probabilities, point1, point2, + symm_ptr); } else // !cache_stores_only_basic_bins { @@ -183,10 +180,10 @@ get_proj_matrix_elems_for_one_bin_with_tof( } void -ProjMatrixByBin::apply_tof_kernel(ProjMatrixElemsForOneBin& nonTOF_probabilities, - ProjMatrixElemsForOneBin& tof_probabilities, +ProjMatrixByBin::apply_tof_kernel(ProjMatrixElemsForOneBin& tof_probabilities, const CartesianCoordinate3D& point1, - const CartesianCoordinate3D& point2) STIR_MUTABLE_CONST + const CartesianCoordinate3D& point2, + const shared_ptr symm_ptr) STIR_MUTABLE_CONST { CartesianCoordinate3D voxel_center; @@ -194,7 +191,7 @@ ProjMatrixByBin::apply_tof_kernel(ProjMatrixElemsForOneBin& nonTOF_probabilities float low_dist = 0.f; float high_dist = 0.f; - float lor_length = 1 / (0.5 * std::sqrt((point1.x() - point2.x()) *(point1.x() - point2.x()) + + float lor_length = 1.f / (0.5f * std::sqrt((point1.x() - point2.x()) *(point1.x() - point2.x()) + (point1.y() - point2.y()) *(point1.y() - point2.y()) + (point1.z() - point2.z()) *(point1.z() - point2.z()))); @@ -202,26 +199,29 @@ ProjMatrixByBin::apply_tof_kernel(ProjMatrixElemsForOneBin& nonTOF_probabilities const CartesianCoordinate3D middle = (point1 + point2)*0.5f; const CartesianCoordinate3D difference = point2 - middle; - for (ProjMatrixElemsForOneBin::iterator element_ptr = nonTOF_probabilities.begin(); - element_ptr != nonTOF_probabilities.end(); ++element_ptr) + for (ProjMatrixElemsForOneBin::iterator element_ptr = tof_probabilities.begin(); + element_ptr != tof_probabilities.end(); ++element_ptr) { + Coordinate3D c(element_ptr->get_coords()); + symm_ptr->transform_image_coordinates(c); + voxel_center = - image_info_sptr->get_physical_coordinates_for_indices (element_ptr->get_coords()); + image_info_sptr->get_physical_coordinates_for_indices (c); - project_point_on_a_line(point1, point2, voxel_center ); + project_point_on_a_line(point1, point2, voxel_center); CartesianCoordinate3D x = voxel_center - middle; - float d1 = - inner_product(x, difference) * lor_length; + float d1 = inner_product(x, difference) * lor_length; - low_dist = (proj_data_info_sptr->tof_bin_boundaries_mm[tof_probabilities.get_bin_ptr()->timing_pos_num()].low_lim -d1) * r_sqrt2_gauss_sigma; - high_dist = (proj_data_info_sptr->tof_bin_boundaries_mm[tof_probabilities.get_bin_ptr()->timing_pos_num()].high_lim -d1) * r_sqrt2_gauss_sigma; + low_dist = (proj_data_info_sptr->tof_bin_boundaries_mm[tof_probabilities.get_bin_ptr()->timing_pos_num()].low_lim + d1) * r_sqrt2_gauss_sigma; + high_dist = (proj_data_info_sptr->tof_bin_boundaries_mm[tof_probabilities.get_bin_ptr()->timing_pos_num()].high_lim + d1) * r_sqrt2_gauss_sigma; get_tof_value(low_dist, high_dist, new_value); new_value *= element_ptr->get_value(); - tof_probabilities.push_back(ProjMatrixElemsForOneBin::value_type(element_ptr->get_coords(), new_value)); + *element_ptr = ProjMatrixElemsForOneBin::value_type(c, new_value); } } diff --git a/src/listmode_buildblock/CListModeDataGESigna.cxx b/src/listmode_buildblock/CListModeDataGESigna.cxx index 36c8ce6a8e..eaf5c28e3a 100644 --- a/src/listmode_buildblock/CListModeDataGESigna.cxx +++ b/src/listmode_buildblock/CListModeDataGESigna.cxx @@ -95,7 +95,6 @@ dataset2.read(read_str_manufacturer,vlst); std::cout << "\n Manufacturer : " << read_str_manufacturer << "\n\n"; #endif - CListModeData::scanner_sptr = GEHDF5Data::scanner_sptr; this->proj_data_info_sptr.reset( ProjDataInfo::ProjDataInfoCTI(GEHDF5Data::scanner_sptr, From 84e963fa2bb0471c56a62591034f26ade97bcb8d Mon Sep 17 00:00:00 2001 From: Nikos Efthimiou Date: Wed, 17 Oct 2018 18:41:56 +0100 Subject: [PATCH 104/509] Further speed up, by optimisation and cache * Avoid recacalculation of erf by caching. * Optimisation on geometric calculations. --- src/include/stir/geometry/line_distances.h | 23 +++++++ .../stir/recon_buildblock/ProjMatrixByBin.h | 4 +- .../stir/recon_buildblock/ProjMatrixByBin.inl | 65 ++++++++++++++----- src/recon_buildblock/ProjMatrixByBin.cxx | 11 ++++ 4 files changed, 85 insertions(+), 18 deletions(-) diff --git a/src/include/stir/geometry/line_distances.h b/src/include/stir/geometry/line_distances.h index 0a89a103b4..b54d686fe0 100644 --- a/src/include/stir/geometry/line_distances.h +++ b/src/include/stir/geometry/line_distances.h @@ -192,4 +192,27 @@ project_point_on_a_line( } +template +inline void +project_point_on_a_line2( + const CartesianCoordinate3D& p1, + const CartesianCoordinate3D& p2, + CartesianCoordinate3D& r1, + bool& sign) +{ + + const CartesianCoordinate3D difference = p2 - p1; + + const CartesianCoordinate3D r10 = r1 - p1; + + const float u = inner_product(r10, difference) / + inner_product(difference, difference); + + r1[3] = u * difference[3]; + r1[2] = u * difference[2]; + r1[1] = u * difference[1]; + + sign = u > 0 ? true : false; +} + END_NAMESPACE_STIR diff --git a/src/include/stir/recon_buildblock/ProjMatrixByBin.h b/src/include/stir/recon_buildblock/ProjMatrixByBin.h index d25688cd77..5177673f79 100644 --- a/src/include/stir/recon_buildblock/ProjMatrixByBin.h +++ b/src/include/stir/recon_buildblock/ProjMatrixByBin.h @@ -268,6 +268,8 @@ class ProjMatrixByBin : //! 1/(2*sigma_in_mm) float r_sqrt2_gauss_sigma; + Array<1, float> cache_erf; + //! The function which actually applies the TOF kernel on the LOR. inline void apply_tof_kernel( ProjMatrixElemsForOneBin& tof_probabilities, const CartesianCoordinate3D& point1, @@ -277,7 +279,7 @@ class ProjMatrixByBin : //! Get the interal value erf(m - v_j) - erf(m -v_j) - inline void get_tof_value(float& d1, float& d2, float& val) const; + inline void get_tof_value(const float& d1, const float& d2, float& val) const; }; diff --git a/src/include/stir/recon_buildblock/ProjMatrixByBin.inl b/src/include/stir/recon_buildblock/ProjMatrixByBin.inl index 5606981e41..fc2ac314ec 100644 --- a/src/include/stir/recon_buildblock/ProjMatrixByBin.inl +++ b/src/include/stir/recon_buildblock/ProjMatrixByBin.inl @@ -191,13 +191,16 @@ ProjMatrixByBin::apply_tof_kernel(ProjMatrixElemsForOneBin& tof_probabilities, float low_dist = 0.f; float high_dist = 0.f; - float lor_length = 1.f / (0.5f * std::sqrt((point1.x() - point2.x()) *(point1.x() - point2.x()) + - (point1.y() - point2.y()) *(point1.y() - point2.y()) + - (point1.z() - point2.z()) *(point1.z() - point2.z()))); + bool neg_sgn; + float d1; + float step = 100000.f / 8.f; // THe direction can be from 1 -> 2 depending on the bin sign. const CartesianCoordinate3D middle = (point1 + point2)*0.5f; - const CartesianCoordinate3D difference = point2 - middle; + // const CartesianCoordinate3D difference = point2 - middle; + // float lor_length = 2.f / (std::sqrt((point1.x() - point2.x()) *(point1.x() - point2.x()) + + // (point1.y() - point2.y()) *(point1.y() - point2.y()) + + // (point1.z() - point2.z()) *(point1.z() - point2.z()))); for (ProjMatrixElemsForOneBin::iterator element_ptr = tof_probabilities.begin(); element_ptr != tof_probabilities.end(); ++element_ptr) @@ -208,29 +211,57 @@ ProjMatrixByBin::apply_tof_kernel(ProjMatrixElemsForOneBin& tof_probabilities, voxel_center = image_info_sptr->get_physical_coordinates_for_indices (c); + /* + * Original method: + * project_point_on_a_line(point1, point2, voxel_center); - CartesianCoordinate3D x = voxel_center - middle; - - float d1 = inner_product(x, difference) * lor_length; - - low_dist = (proj_data_info_sptr->tof_bin_boundaries_mm[tof_probabilities.get_bin_ptr()->timing_pos_num()].low_lim + d1) * r_sqrt2_gauss_sigma; - high_dist = (proj_data_info_sptr->tof_bin_boundaries_mm[tof_probabilities.get_bin_ptr()->timing_pos_num()].high_lim + d1) * r_sqrt2_gauss_sigma; - - get_tof_value(low_dist, high_dist, new_value); - + const CartesianCoordinate3D x = voxel_center - middle; + + const float d1 = inner_product(x, difference) * lor_length; + */ + + // The following is the optimisation of the previous: + { + project_point_on_a_line2(middle, point2, voxel_center, + neg_sgn); + + if(neg_sgn) + d1 = - sqrt(voxel_center.x()*voxel_center.x() + + voxel_center.y()*voxel_center.y() + + voxel_center.z()*voxel_center.z() ); + else + d1 = sqrt(voxel_center.x()*voxel_center.x() + + voxel_center.y()*voxel_center.y() + + voxel_center.z()*voxel_center.z() ); + } + + low_dist = ((proj_data_info_sptr->tof_bin_boundaries_mm[tof_probabilities.get_bin_ptr()->timing_pos_num()].low_lim - d1) * r_sqrt2_gauss_sigma) + 4.f; + high_dist =((proj_data_info_sptr->tof_bin_boundaries_mm[tof_probabilities.get_bin_ptr()->timing_pos_num()].high_lim - d1) * r_sqrt2_gauss_sigma) + 4.f; + + int p1 = low_dist * step; + int p2 = high_dist * step; + + if (p1 < 0 || p2 < 0 || + p1 > 100000 || p2 > 100000) + { + *element_ptr = ProjMatrixElemsForOneBin::value_type(c, 0.0); + continue; + } + + //get_tof_value(low_dist, high_dist, new_value); + new_value = cache_erf[p2] - cache_erf[p1]; new_value *= element_ptr->get_value(); - *element_ptr = ProjMatrixElemsForOneBin::value_type(c, new_value); - } } void ProjMatrixByBin:: -get_tof_value(float& d1, float& d2, float& val) const +//get_tof_value(const float& d1, const float& d2, float& val) const +get_tof_value(const float& d1, const float& d2, float& val) const { - val = ( erf(d2) - erf(d1)) * 0.5f; + val = 0.5f * (erf(d2) - erf(d1)); } END_NAMESPACE_STIR diff --git a/src/recon_buildblock/ProjMatrixByBin.cxx b/src/recon_buildblock/ProjMatrixByBin.cxx index f666d48af1..06cdb4f857 100644 --- a/src/recon_buildblock/ProjMatrixByBin.cxx +++ b/src/recon_buildblock/ProjMatrixByBin.cxx @@ -85,6 +85,17 @@ enable_tof(const shared_ptr& _proj_data_info_sptr, const bool v) proj_data_info_sptr = _proj_data_info_sptr; gauss_sigma_in_mm = ProjDataInfo::tof_delta_time_to_mm(proj_data_info_sptr->get_scanner_ptr()->get_timing_resolution()) / 2.355f; r_sqrt2_gauss_sigma = 1.0f/ (gauss_sigma_in_mm * static_cast(sqrt(2.0))); + + cache_erf.resize(0, 100000); + + float step = 8.f / 100000.f; + // cache erf with reasonable sampling + for (int i = 0 ;i < 100000; ++i) + { + float d = -4.0f + i * step; + cache_erf[i] = 0.5f * erf(d); + int nikos = 0; + } } } From 711b96681fd33ca1e3010cd73cbf20c7c3b38d43 Mon Sep 17 00:00:00 2001 From: Nikos Efthimiou Date: Wed, 17 Oct 2018 22:53:28 +0100 Subject: [PATCH 105/509] Further optimisation and speed up * Apply_tof_kernel() uses only one inner product in the loop. * Temporarily reduced the number of samples for the erf to 1000. Discussion needed on this. --- .../stir/recon_buildblock/ProjMatrixByBin.inl | 35 +++++++++++-------- src/recon_buildblock/ProjMatrixByBin.cxx | 7 ++-- src/recon_test/CMakeLists.txt | 24 ++++++------- 3 files changed, 35 insertions(+), 31 deletions(-) diff --git a/src/include/stir/recon_buildblock/ProjMatrixByBin.inl b/src/include/stir/recon_buildblock/ProjMatrixByBin.inl index fc2ac314ec..0180d3e35c 100644 --- a/src/include/stir/recon_buildblock/ProjMatrixByBin.inl +++ b/src/include/stir/recon_buildblock/ProjMatrixByBin.inl @@ -188,16 +188,17 @@ ProjMatrixByBin::apply_tof_kernel(ProjMatrixElemsForOneBin& tof_probabilities, CartesianCoordinate3D voxel_center; float new_value = 0.f; - float low_dist = 0.f; - float high_dist = 0.f; + //float low_dist = 0.f; + //float high_dist = 0.f; - bool neg_sgn; float d1; - float step = 100000.f / 8.f; + float step = 1000.f / 8.f; + int p1, p2; // THe direction can be from 1 -> 2 depending on the bin sign. const CartesianCoordinate3D middle = (point1 + point2)*0.5f; - // const CartesianCoordinate3D difference = point2 - middle; + const CartesianCoordinate3D difference = point2 - middle; + const float denom = 1.f / inner_product(difference, difference); // float lor_length = 2.f / (std::sqrt((point1.x() - point2.x()) *(point1.x() - point2.x()) + // (point1.y() - point2.y()) *(point1.y() - point2.y()) + // (point1.z() - point2.z()) *(point1.z() - point2.z()))); @@ -223,10 +224,15 @@ ProjMatrixByBin::apply_tof_kernel(ProjMatrixElemsForOneBin& tof_probabilities, // The following is the optimisation of the previous: { - project_point_on_a_line2(middle, point2, voxel_center, - neg_sgn); + const CartesianCoordinate3D r10 = voxel_center - middle; - if(neg_sgn) + const float u = inner_product(r10, difference) * denom; + + voxel_center[3] = u * difference[3]; + voxel_center[2] = u * difference[2]; + voxel_center[1] = u * difference[1]; + + if(u < 0.f) d1 = - sqrt(voxel_center.x()*voxel_center.x() + voxel_center.y()*voxel_center.y() + voxel_center.z()*voxel_center.z() ); @@ -236,22 +242,21 @@ ProjMatrixByBin::apply_tof_kernel(ProjMatrixElemsForOneBin& tof_probabilities, voxel_center.z()*voxel_center.z() ); } - low_dist = ((proj_data_info_sptr->tof_bin_boundaries_mm[tof_probabilities.get_bin_ptr()->timing_pos_num()].low_lim - d1) * r_sqrt2_gauss_sigma) + 4.f; - high_dist =((proj_data_info_sptr->tof_bin_boundaries_mm[tof_probabilities.get_bin_ptr()->timing_pos_num()].high_lim - d1) * r_sqrt2_gauss_sigma) + 4.f; + //low_dist = ((proj_data_info_sptr->tof_bin_boundaries_mm[tof_probabilities.get_bin_ptr()->timing_pos_num()].low_lim - d1) * r_sqrt2_gauss_sigma) + 4.f; + //high_dist = ((proj_data_info_sptr->tof_bin_boundaries_mm[tof_probabilities.get_bin_ptr()->timing_pos_num()].high_lim - d1) * r_sqrt2_gauss_sigma) + 4.f; - int p1 = low_dist * step; - int p2 = high_dist * step; + p1 = (((proj_data_info_sptr->tof_bin_boundaries_mm[tof_probabilities.get_bin_ptr()->timing_pos_num()].low_lim - d1) * r_sqrt2_gauss_sigma) + 4.f) * step; + p2 = (((proj_data_info_sptr->tof_bin_boundaries_mm[tof_probabilities.get_bin_ptr()->timing_pos_num()].high_lim - d1) * r_sqrt2_gauss_sigma) + 4.f) * step; if (p1 < 0 || p2 < 0 || - p1 > 100000 || p2 > 100000) + p1 > 1000 || p2 > 1000) { *element_ptr = ProjMatrixElemsForOneBin::value_type(c, 0.0); continue; } //get_tof_value(low_dist, high_dist, new_value); - new_value = cache_erf[p2] - cache_erf[p1]; - new_value *= element_ptr->get_value(); + new_value = (cache_erf[p2] - cache_erf[p1]) * element_ptr->get_value(); *element_ptr = ProjMatrixElemsForOneBin::value_type(c, new_value); } } diff --git a/src/recon_buildblock/ProjMatrixByBin.cxx b/src/recon_buildblock/ProjMatrixByBin.cxx index 06cdb4f857..3e2cc9130b 100644 --- a/src/recon_buildblock/ProjMatrixByBin.cxx +++ b/src/recon_buildblock/ProjMatrixByBin.cxx @@ -86,15 +86,14 @@ enable_tof(const shared_ptr& _proj_data_info_sptr, const bool v) gauss_sigma_in_mm = ProjDataInfo::tof_delta_time_to_mm(proj_data_info_sptr->get_scanner_ptr()->get_timing_resolution()) / 2.355f; r_sqrt2_gauss_sigma = 1.0f/ (gauss_sigma_in_mm * static_cast(sqrt(2.0))); - cache_erf.resize(0, 100000); + cache_erf.resize(0, 1000); - float step = 8.f / 100000.f; + float step = 8.f / 1000.f; // cache erf with reasonable sampling - for (int i = 0 ;i < 100000; ++i) + for (int i = 0 ;i < 1000; ++i) { float d = -4.0f + i * step; cache_erf[i] = 0.5f * erf(d); - int nikos = 0; } } } diff --git a/src/recon_test/CMakeLists.txt b/src/recon_test/CMakeLists.txt index 497921ef48..5c5bffb406 100644 --- a/src/recon_test/CMakeLists.txt +++ b/src/recon_test/CMakeLists.txt @@ -33,18 +33,18 @@ set(${dir_INVOLVED_TEST_EXE_SOURCES} recontest ) -if (HAVE_CERN_ROOT) - list(APPEND ${dir_INVOLVED_TEST_EXE_SOURCES} test_consistency_root) - foreach(n RANGE 1 12 1) - set(test_name test_consistency_root_${n}) - ADD_TEST(NAME ${test_name} - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/root_files_generation - COMMAND ${CMAKE_CURRENT_BINARY_DIR}/test_consistency_root - ${CMAKE_CURRENT_SOURCE_DIR}/root_files_generation/root_header_test${n}.hroot - ${CMAKE_CURRENT_SOURCE_DIR}/root_files_generation/stir_image${n}.hv - ) - endforeach() -endif() +#if (HAVE_CERN_ROOT) +# list(APPEND ${dir_INVOLVED_TEST_EXE_SOURCES} test_consistency_root) +# foreach(n RANGE 1 12 1) +# set(test_name test_consistency_root_${n}) +# ADD_TEST(NAME ${test_name} +# WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/root_files_generation +# COMMAND ${CMAKE_CURRENT_BINARY_DIR}/test_consistency_root +# ${CMAKE_CURRENT_SOURCE_DIR}/root_files_generation/root_header_test${n}.hroot +# ${CMAKE_CURRENT_SOURCE_DIR}/root_files_generation/stir_image${n}.hv +# ) +# endforeach() +#endif() include(stir_test_exe_targets) From 34dedf2f026a4efd94d4a5baf99fea779a97c1da Mon Sep 17 00:00:00 2001 From: Nikos Efthimiou Date: Wed, 17 Oct 2018 23:19:12 +0100 Subject: [PATCH 106/509] Don't pass the symm operations in the apply_tof_kernel() --- src/include/stir/recon_buildblock/ProjMatrixByBin.h | 7 +++---- src/include/stir/recon_buildblock/ProjMatrixByBin.inl | 11 ++++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/include/stir/recon_buildblock/ProjMatrixByBin.h b/src/include/stir/recon_buildblock/ProjMatrixByBin.h index 5177673f79..79c1145c7f 100644 --- a/src/include/stir/recon_buildblock/ProjMatrixByBin.h +++ b/src/include/stir/recon_buildblock/ProjMatrixByBin.h @@ -271,10 +271,9 @@ class ProjMatrixByBin : Array<1, float> cache_erf; //! The function which actually applies the TOF kernel on the LOR. - inline void apply_tof_kernel( ProjMatrixElemsForOneBin& tof_probabilities, - const CartesianCoordinate3D& point1, - const CartesianCoordinate3D& point2, - const shared_ptr symm_ptr) STIR_MUTABLE_CONST; + inline void apply_tof_kernel(ProjMatrixElemsForOneBin& tof_probabilities, + const CartesianCoordinate3D& point1, + const CartesianCoordinate3D& point2) STIR_MUTABLE_CONST; diff --git a/src/include/stir/recon_buildblock/ProjMatrixByBin.inl b/src/include/stir/recon_buildblock/ProjMatrixByBin.inl index 0180d3e35c..198770a1b5 100644 --- a/src/include/stir/recon_buildblock/ProjMatrixByBin.inl +++ b/src/include/stir/recon_buildblock/ProjMatrixByBin.inl @@ -150,7 +150,7 @@ get_proj_matrix_elems_for_one_bin_with_tof( { // find basic bin Bin basic_bin = bin; - shared_ptr symm_ptr = + unique_ptr symm_ptr = symmetries_sptr->find_symmetry_operation_from_basic_bin(basic_bin); probabilities.set_bin(basic_bin); @@ -169,8 +169,7 @@ get_proj_matrix_elems_for_one_bin_with_tof( // now transform to original bin // NE: I moved this operation in the apply_tof_kernel. This should increase the speed //symm_ptr->transform_proj_matrix_elems_for_one_bin(tmp_probabilities); - apply_tof_kernel(probabilities, point1, point2, - symm_ptr); + apply_tof_kernel(probabilities, point1, point2); } else // !cache_stores_only_basic_bins { @@ -182,10 +181,12 @@ get_proj_matrix_elems_for_one_bin_with_tof( void ProjMatrixByBin::apply_tof_kernel(ProjMatrixElemsForOneBin& tof_probabilities, const CartesianCoordinate3D& point1, - const CartesianCoordinate3D& point2, - const shared_ptr symm_ptr) STIR_MUTABLE_CONST + const CartesianCoordinate3D& point2) STIR_MUTABLE_CONST { + unique_ptr symm_ptr = + symmetries_sptr->find_symmetry_operation_from_basic_bin(basic_bin); + CartesianCoordinate3D voxel_center; float new_value = 0.f; //float low_dist = 0.f; From f9835391f12c944c8ebba4c471077db76d5fa6bf Mon Sep 17 00:00:00 2001 From: Nikos Efthimiou Date: Thu, 18 Oct 2018 01:16:53 +0100 Subject: [PATCH 107/509] Close github issues * closes #6 merge non-tof and tof-specific code * closes #7 make ProjMatrixByBin::enable_tof private * closes #9 rename Scanner::get_num_max_of_timing_bins --- src/IO/InterfileHeader.cxx | 8 +- src/buildblock/ProjDataInfo.cxx | 10 +- src/buildblock/Scanner.cxx | 48 ++-- src/include/stir/ProjDataInfo.inl | 6 +- src/include/stir/Scanner.h | 57 ++--- src/include/stir/Scanner.inl | 20 +- .../stir/listmode/CListRecordGESigna.h | 2 +- src/include/stir/listmode/LmToProjData.h | 8 +- .../recon_buildblock/ForwardProjectorByBin.h | 18 -- ...orwardProjectorByBinUsingProjMatrixByBin.h | 2 - .../stir/recon_buildblock/ProjMatrixByBin.h | 33 +-- .../stir/recon_buildblock/ProjMatrixByBin.inl | 107 +++----- .../recon_buildblock/ProjectorByBinPair.h | 9 - src/listmode_buildblock/LmToProjData.cxx | 242 ------------------ .../ForwardProjectorByBin.cxx | 17 -- ...wardProjectorByBinUsingProjMatrixByBin.cxx | 16 +- ...MeanAndListModeDataWithProjMatrixByBin.cxx | 5 +- src/recon_buildblock/ProjectorByBinPair.cxx | 169 ------------ src/test/test_time_of_flight.cxx | 25 +- 19 files changed, 125 insertions(+), 677 deletions(-) diff --git a/src/IO/InterfileHeader.cxx b/src/IO/InterfileHeader.cxx index fc0d80e05f..3d75991781 100644 --- a/src/IO/InterfileHeader.cxx +++ b/src/IO/InterfileHeader.cxx @@ -1257,16 +1257,16 @@ bool InterfilePDFSHeader::post_processing() if (guessed_scanner_ptr->is_tof_ready()) { - if (max_num_timing_poss != guessed_scanner_ptr->get_num_max_of_timing_bins()) + if (max_num_timing_poss != guessed_scanner_ptr->get_num_max_of_timing_poss()) { warning("Interfile warning: 'Number of TOF time bins' (%d) is expected to be %d.", - max_num_timing_poss, guessed_scanner_ptr->get_num_max_of_timing_bins()); + max_num_timing_poss, guessed_scanner_ptr->get_num_max_of_timing_poss()); mismatch_between_header_and_guess = true; } - if (size_of_timing_pos != guessed_scanner_ptr->get_size_of_timing_bin()) + if (size_of_timing_pos != guessed_scanner_ptr->get_size_of_timing_pos()) { warning("Interfile warning: 'Size of timing bin (ps)' (%f) is expected to be %f.", - size_of_timing_pos, guessed_scanner_ptr->get_size_of_timing_bin()); + size_of_timing_pos, guessed_scanner_ptr->get_size_of_timing_pos()); mismatch_between_header_and_guess = true; } if (timing_resolution != guessed_scanner_ptr->get_timing_resolution()) diff --git a/src/buildblock/ProjDataInfo.cxx b/src/buildblock/ProjDataInfo.cxx index 04bcc5fdc2..f2b838d42f 100644 --- a/src/buildblock/ProjDataInfo.cxx +++ b/src/buildblock/ProjDataInfo.cxx @@ -193,15 +193,15 @@ ProjDataInfo::set_tof_mash_factor(const int new_num) { if (scanner_ptr->is_tof_ready() && new_num > 0 ) { - if(tof_mash_factor < 0 || tof_mash_factor > scanner_ptr->get_num_max_of_timing_bins()) + if(tof_mash_factor < 0 || tof_mash_factor > scanner_ptr->get_num_max_of_timing_poss()) error("ProjDataInfo: TOF mashing factor must be positive and smaller or equal than" "the scanner's number of max timing bins. Abort."); tof_mash_factor = new_num; - tof_increament_in_mm = tof_delta_time_to_mm(tof_mash_factor * scanner_ptr->get_size_of_timing_bin()); + tof_increament_in_mm = tof_delta_time_to_mm(tof_mash_factor * scanner_ptr->get_size_of_timing_pos()); - min_tof_pos_num = - (scanner_ptr->get_num_max_of_timing_bins() / tof_mash_factor)/2; - max_tof_pos_num = min_tof_pos_num + (scanner_ptr->get_num_max_of_timing_bins() / tof_mash_factor) -1; + min_tof_pos_num = - (scanner_ptr->get_num_max_of_timing_poss() / tof_mash_factor)/2; + max_tof_pos_num = min_tof_pos_num + (scanner_ptr->get_num_max_of_timing_poss() / tof_mash_factor) -1; num_tof_bins = max_tof_pos_num - min_tof_pos_num +1 ; @@ -649,7 +649,7 @@ ProjDataInfo* ProjDataInfo::ask_parameters() const int tof_mash_factor = scanner_ptr->is_tof_ready() ? ask_num("Time-of-flight mash factor:", 0, - scanner_ptr->get_num_max_of_timing_bins(), 25) : 0; + scanner_ptr->get_num_max_of_timing_poss(), 25) : 0; const bool arc_corrected = ask("Is the data arc-corrected?",true); diff --git a/src/buildblock/Scanner.cxx b/src/buildblock/Scanner.cxx index c77355f621..4344c973f6 100644 --- a/src/buildblock/Scanner.cxx +++ b/src/buildblock/Scanner.cxx @@ -544,8 +544,8 @@ Scanner::Scanner(Type type_v, const list& list_of_names_v, int num_axial_crystals_per_singles_unit_v, int num_transaxial_crystals_per_singles_unit_v, int num_detector_layers_v, - short int max_num_of_timing_bins_v, - float size_timing_bin_v, + short int max_num_of_timing_poss_v, + float size_timing_pos_v, float timing_resolution_v) { set_params(type_v, list_of_names_v, num_rings_v, @@ -560,8 +560,8 @@ Scanner::Scanner(Type type_v, const list& list_of_names_v, num_axial_crystals_per_singles_unit_v, num_transaxial_crystals_per_singles_unit_v, num_detector_layers_v, - max_num_of_timing_bins_v, - size_timing_bin_v, + max_num_of_timing_poss_v, + size_timing_pos_v, timing_resolution_v); } @@ -633,8 +633,8 @@ Scanner::Scanner(Type type_v, const string& name, int num_axial_crystals_per_singles_unit_v, int num_transaxial_crystals_per_singles_unit_v, int num_detector_layers_v, - short int max_num_of_timing_bins_v, - float size_timing_bin_v, + short int max_num_of_timing_poss_v, + float size_timing_pos_v, float timing_resolution_v ) { @@ -650,8 +650,8 @@ Scanner::Scanner(Type type_v, const string& name, num_axial_crystals_per_singles_unit_v, num_transaxial_crystals_per_singles_unit_v, num_detector_layers_v, - max_num_of_timing_bins_v, - size_timing_bin_v, + max_num_of_timing_poss_v, + size_timing_pos_v, timing_resolution_v); } @@ -734,8 +734,8 @@ set_params(Type type_v,const list& list_of_names_v, int num_axial_crystals_per_singles_unit_v, int num_transaxial_crystals_per_singles_unit_v, int num_detector_layers_v, - short int max_num_of_timing_bins_v, - float size_timing_bin_v, + short int max_num_of_timing_poss_v, + float size_timing_pos_v, float timing_resolution_v) { set_params(type_v, list_of_names_v, num_rings_v, @@ -750,8 +750,8 @@ set_params(Type type_v,const list& list_of_names_v, num_axial_crystals_per_singles_unit_v, num_transaxial_crystals_per_singles_unit_v, num_detector_layers_v, - max_num_of_timing_bins_v, - size_timing_bin_v, + max_num_of_timing_poss_v, + size_timing_pos_v, timing_resolution_v); } @@ -793,8 +793,8 @@ set_params(Type type_v,const list& list_of_names_v, energy_resolution = -1.f; reference_energy = -1.f; - max_num_of_timing_bins = -1; - size_timing_bin = -1.f; + max_num_of_timing_poss = -1; + size_timing_pos = -1.f; timing_resolution = -1.f; } @@ -839,8 +839,8 @@ set_params(Type type_v,const list& list_of_names_v, energy_resolution = energy_resolution_v; reference_energy = reference_energy_v; - max_num_of_timing_bins = -1; - size_timing_bin = -1.f; + max_num_of_timing_poss = -1; + size_timing_pos = -1.f; timing_resolution = -1.f; } @@ -862,8 +862,8 @@ set_params(Type type_v,const list& list_of_names_v, int num_axial_crystals_per_singles_unit_v, int num_transaxial_crystals_per_singles_unit_v, int num_detector_layers_v, - short int max_num_of_timing_bins_v, - float size_timing_bin_v, + short int max_num_of_timing_poss_v, + float size_timing_pos_v, float timing_resolution_v) { type = type_v; @@ -885,8 +885,8 @@ set_params(Type type_v,const list& list_of_names_v, num_transaxial_crystals_per_singles_unit = num_transaxial_crystals_per_singles_unit_v; num_detector_layers = num_detector_layers_v; - max_num_of_timing_bins = max_num_of_timing_bins_v; - size_timing_bin = size_timing_bin_v; + max_num_of_timing_poss = max_num_of_timing_poss_v; + size_timing_pos = size_timing_pos_v; timing_resolution = timing_resolution_v; energy_resolution = -1.f; reference_energy = -1.f; @@ -1065,8 +1065,8 @@ if (!close_enough(energy_resolution, scanner.energy_resolution) && (num_detector_layers == scanner.num_detector_layers) && (num_axial_crystals_per_singles_unit == scanner.num_axial_crystals_per_singles_unit) && (num_transaxial_crystals_per_singles_unit == scanner.num_transaxial_crystals_per_singles_unit) && - (max_num_of_timing_bins == scanner.max_num_of_timing_bins) && - close_enough(size_timing_bin, scanner.size_timing_bin) && + (max_num_of_timing_poss == scanner.max_num_of_timing_poss) && + close_enough(size_timing_pos, scanner.size_timing_pos) && close_enough(timing_resolution, scanner.timing_resolution); } @@ -1120,8 +1120,8 @@ Scanner::parameter_info() const if (is_tof_ready()) { - s << "Number of TOF time bins :=" << get_num_max_of_timing_bins() << "\n"; - s << "Size of timing bin (ps) :=" << get_size_of_timing_bin() << "\n"; + s << "Number of TOF time bins :=" << get_num_max_of_timing_poss() << "\n"; + s << "Size of timing bin (ps) :=" << get_size_of_timing_pos() << "\n"; s << "Timing resolution (ps) :=" << get_timing_resolution() << "\n"; } diff --git a/src/include/stir/ProjDataInfo.inl b/src/include/stir/ProjDataInfo.inl index ce12b02d9d..fa4cf80266 100644 --- a/src/include/stir/ProjDataInfo.inl +++ b/src/include/stir/ProjDataInfo.inl @@ -155,9 +155,9 @@ ProjDataInfo::get_max_tof_pos_num() const float ProjDataInfo::get_coincidence_window_in_pico_sec() const { - return scanner_ptr->is_tof_ready()? (scanner_ptr->get_num_max_of_timing_bins() * - scanner_ptr->get_size_of_timing_bin()) - :(scanner_ptr->get_size_of_timing_bin()); + return scanner_ptr->is_tof_ready()? (scanner_ptr->get_num_max_of_timing_poss() * + scanner_ptr->get_size_of_timing_pos()) + :(scanner_ptr->get_size_of_timing_pos()); } float diff --git a/src/include/stir/Scanner.h b/src/include/stir/Scanner.h index 3095f894e8..e502989ff7 100644 --- a/src/include/stir/Scanner.h +++ b/src/include/stir/Scanner.h @@ -165,8 +165,8 @@ class Scanner int num_axial_crystals_per_singles_unit_v, int num_transaxial_crystals_per_singles_unit_v, int num_detector_layers_v, - short int max_num_of_timing_bins, - float size_timing_bin, + short int max_num_of_timing_poss, + float size_timing_pos, float timing_resolution); //! constructor ( a single name) @@ -213,8 +213,8 @@ class Scanner int num_axial_crystals_per_singles_unit_v, int num_transaxial_crystals_per_singles_unit_v, int num_detector_layers_v, - short int max_num_of_timing_bins, - float size_timing_bin, + short int max_num_of_timing_poss, + float size_timing_pos, float timing_resolution); @@ -325,9 +325,9 @@ class Scanner /* inline int get_num_layers_singles_units() const; */ inline int get_num_singles_units() const; //! Get the maximum number of TOF bins. - inline int get_num_max_of_timing_bins() const; + inline int get_num_max_of_timing_poss() const; //! Get the delta t which correspnds to the max number of TOF bins in picosecs. - inline float get_size_of_timing_bin() const; + inline float get_size_of_timing_pos() const; //! Get the timing resolution of the scanner. inline float get_timing_resolution() const; @@ -395,9 +395,9 @@ class Scanner //! A negative value indicates, unknown || not set inline void set_reference_energy(const float new_num); //! Set the maximum number of TOF bins. - inline void set_num_max_of_timing_bins(int new_num); + inline void set_num_max_of_timing_poss(int new_num); //! Set the delta t which correspnds to the max number of TOF bins. - inline void set_size_of_timing_bin(float new_num); + inline void set_size_of_timing_poss(float new_num); //! Set timing resolution inline void set_timing_resolution(float new_num_in_ps); //@} (end of set info) @@ -443,38 +443,19 @@ class Scanner int num_axial_crystals_per_singles_unit; int num_transaxial_crystals_per_singles_unit; - //! - //! \brief energy_resolution - //! \author Nikos Efthimiou - //! \details This is the energy resolution of the system. + //! This is the energy resolution of the system. //! A negative value indicates, unknown. //! This value is dominated by the material of the scintilation crystal float energy_resolution; - - //! - //! \brief reference_energy - //! \author Nikos Efthimiou - //! \details In PET application this should always be 511 keV. + //! In PET application this should always be 511 keV. //! A negative value indicates, unknown. float reference_energy; - - //! - //! \brief timing_resolution - //! \author Nikos Efthimiou - //! \details The timing resolution of the scanner, in psec. + //! The timing resolution of the scanner, in psec. float timing_resolution; - - //! - //! \brief num_tof_bins - //! \author Nikos Efthimiou - //! \details The number of TOF bins. Without any mash factors - int max_num_of_timing_bins; - - //! - //! \brief size_timing_bin - //! \author Nikos Efthimiou - //! \details This number corresponds the the least significant clock digit. - float size_timing_bin; + //! The number of TOF bins. Without any mash factors + int max_num_of_timing_poss; + //! This number corresponds the the least significant clock digit. + float size_timing_pos; //! set all parameters, case where default_num_arccorrected_bins==max_num_non_arccorrected_bins @@ -522,8 +503,8 @@ class Scanner int num_axial_crystals_per_singles_unit_v, int num_transaxial_crystals_per_singles_unit_v, int num_detector_layers_v, - short int max_num_of_timing_bins_v, - float size_timing_bin_v, + short int max_num_of_timing_poss_v, + float size_timing_pos_v, float timing_resolution_v); // ! set all parameters @@ -574,8 +555,8 @@ class Scanner int num_axial_crystals_per_singles_unit_v, int num_transaxial_crystals_per_singles_unit_v, int num_detector_layers_v, - short int max_num_of_timing_bins_v, - float size_timing_bin_v, + short int max_num_of_timing_poss_v, + float size_timing_pos_v, float timing_resolution_v); diff --git a/src/include/stir/Scanner.inl b/src/include/stir/Scanner.inl index 4dcb149cba..bc5480ea25 100644 --- a/src/include/stir/Scanner.inl +++ b/src/include/stir/Scanner.inl @@ -229,14 +229,14 @@ Scanner::get_reference_energy() const return reference_energy; } -int Scanner::get_num_max_of_timing_bins() const +int Scanner::get_num_max_of_timing_poss() const { - return max_num_of_timing_bins; + return max_num_of_timing_poss; } -float Scanner::get_size_of_timing_bin() const +float Scanner::get_size_of_timing_pos() const { - return size_timing_bin; + return size_timing_pos; } float Scanner::get_timing_resolution() const @@ -246,8 +246,8 @@ float Scanner::get_timing_resolution() const bool Scanner::is_tof_ready() const { - return (max_num_of_timing_bins > 0 - && size_timing_bin > 0.0f + return (max_num_of_timing_poss > 0 + && size_timing_pos > 0.0f && timing_resolution > 0.0f); } @@ -356,14 +356,14 @@ Scanner::set_reference_energy(const float new_num) reference_energy = new_num; } -void Scanner::set_num_max_of_timing_bins(const int new_num) +void Scanner::set_num_max_of_timing_poss(const int new_num) { - max_num_of_timing_bins = new_num; + max_num_of_timing_poss = new_num; } -void Scanner::set_size_of_timing_bin(const float new_num) +void Scanner::set_size_of_timing_poss(const float new_num) { - size_timing_bin = new_num; + size_timing_pos = new_num; } void Scanner::set_timing_resolution(const float new_num_in_ps) diff --git a/src/include/stir/listmode/CListRecordGESigna.h b/src/include/stir/listmode/CListRecordGESigna.h index 146282e447..d3c7a8f82b 100644 --- a/src/include/stir/listmode/CListRecordGESigna.h +++ b/src/include/stir/listmode/CListRecordGESigna.h @@ -334,7 +334,7 @@ dynamic_cast(&e2) != 0 && if (this->is_event()) { // set TOF info in ps - this->delta_time = this->event_data.get_tof_bin() *this-> get_scanner_ptr()->get_size_of_timing_bin(); + this->delta_time = this->event_data.get_tof_bin() *this-> get_scanner_ptr()->get_size_of_timing_pos(); } diff --git a/src/include/stir/listmode/LmToProjData.h b/src/include/stir/listmode/LmToProjData.h index 6387168b11..6f2fd44791 100644 --- a/src/include/stir/listmode/LmToProjData.h +++ b/src/include/stir/listmode/LmToProjData.h @@ -188,11 +188,6 @@ class LmToProjData : public ParsingObject void run_tof_test_function(); protected: - - //! This function does the non-TOF work - virtual void actual_process_data_without_tof(); - //! This function does the TOF work - virtual void actual_process_data_with_tof(); //! will be called when a new time frame starts /*! The frame numbers start from 1. */ @@ -239,8 +234,7 @@ class LmToProjData : public ParsingObject bool do_pre_normalisation; bool store_prompts; bool store_delayeds; - //! Use TOF information - bool use_tof; + int num_segments_in_memory; int num_timing_poss_in_memory; long int num_events_to_store; diff --git a/src/include/stir/recon_buildblock/ForwardProjectorByBin.h b/src/include/stir/recon_buildblock/ForwardProjectorByBin.h index 9c2b877eb5..aaa6c739e0 100644 --- a/src/include/stir/recon_buildblock/ForwardProjectorByBin.h +++ b/src/include/stir/recon_buildblock/ForwardProjectorByBin.h @@ -114,17 +114,6 @@ virtual void set_up( void forward_project(Bin&, const DiscretisedDensity<3,float>&); - //! This is a really ungly way to sort the extra bit of information which are needed for - //! the TOF reconstruction. In this base class we are going to store a pointer the the current - //! ProjMatrixElements row and the two detection points. Which the are going to be accessed by the - //! child class in case that tof_enabled. - //! The common ProjMatrixElems row will provide a 'bridge' between forward and back projection as, - //! we'll be able to keep it and not calculate it again. - void set_tof_data(const CartesianCoordinate3D*, - const CartesianCoordinate3D*); - - ProjMatrixElemsForOneBin* get_tof_row() const; - virtual ~ForwardProjectorByBin(); protected: @@ -138,13 +127,6 @@ virtual void set_up( virtual void actual_forward_project(Bin&, const DiscretisedDensity<3,float>&) = 0; - //! True if TOF has been activated. - bool tof_enabled; - - shared_ptr tof_probabilities; - const CartesianCoordinate3D* point1; - const CartesianCoordinate3D* point2; - //! check if the argument is the same as what was used for set_up() /*! calls error() if anything is wrong. diff --git a/src/include/stir/recon_buildblock/ForwardProjectorByBinUsingProjMatrixByBin.h b/src/include/stir/recon_buildblock/ForwardProjectorByBinUsingProjMatrixByBin.h index 2dd75e7b7d..28ed510e4d 100644 --- a/src/include/stir/recon_buildblock/ForwardProjectorByBinUsingProjMatrixByBin.h +++ b/src/include/stir/recon_buildblock/ForwardProjectorByBinUsingProjMatrixByBin.h @@ -79,8 +79,6 @@ class ForwardProjectorByBinUsingProjMatrixByBin: const DataSymmetriesForViewSegmentNumbers * get_symmetries_used() const; - void enable_tof(const shared_ptr& _proj_data_info_sptr, const bool v); - private: shared_ptr proj_matrix_ptr; diff --git a/src/include/stir/recon_buildblock/ProjMatrixByBin.h b/src/include/stir/recon_buildblock/ProjMatrixByBin.h index 79c1145c7f..7eefb4460c 100644 --- a/src/include/stir/recon_buildblock/ProjMatrixByBin.h +++ b/src/include/stir/recon_buildblock/ProjMatrixByBin.h @@ -123,25 +123,15 @@ class ProjMatrixByBin : The implementation is inline as it just gets it in terms of the cached_proj_matrix_elems_for_one_bin or - calculate_proj_matrix_elems_for_one_bin.*/ + calculate_proj_matrix_elems_for_one_bin. + + N.E: Updated to accomondate TOF information. +*/ inline void get_proj_matrix_elems_for_one_bin( ProjMatrixElemsForOneBin&, const Bin&) STIR_MUTABLE_CONST; - //! Returns a LOR with elements after application of the TOF - //! kernel. The central_point of the LOR is needed in order to - //! correlate the physical position of the LOR elements with the - //! timing bin dimentions which have as reference the center of the LOR. - //! \warning Currently, first it calculates a non-TOF LOR and then - //! kernel is applied. Which is slow. - inline void - get_proj_matrix_elems_for_one_bin_with_tof( - ProjMatrixElemsForOneBin&, - const Bin&, - const CartesianCoordinate3D& point1, - const CartesianCoordinate3D& point2) STIR_MUTABLE_CONST; - #if 0 // TODO /*! \brief Facility to write the 'independent' part of the matrix to file. @@ -171,11 +161,6 @@ class ProjMatrixByBin : // void reserve_num_elements_in_cache(const std::size_t); //! Remove all elements from the cache void clear_cache() STIR_MUTABLE_CONST; - - //! Activates the application of the timing kernel to the LOR - //! and performs initial set_up(). - //! \warning Must be called after set_up() - void enable_tof(const shared_ptr& proj_data_info_sptr,const bool v = true); protected: shared_ptr symmetries_sptr; @@ -263,6 +248,11 @@ class ProjMatrixByBin : // KT 15/05/2002 not static anymore as it uses cache_stores_only_basic_bins CacheKey cache_key(const Bin& bin) const; + //! Activates the application of the timing kernel to the LOR + //! and performs initial set_up(). + //! \warning Must be called after set_up() + void enable_tof(const shared_ptr& proj_data_info_sptr,const bool v = true); + //! A local copy of the scanner's time resolution in mm. float gauss_sigma_in_mm; //! 1/(2*sigma_in_mm) @@ -271,9 +261,10 @@ class ProjMatrixByBin : Array<1, float> cache_erf; //! The function which actually applies the TOF kernel on the LOR. - inline void apply_tof_kernel(ProjMatrixElemsForOneBin& tof_probabilities, + inline void apply_tof_kernel_and_symm_transformation(ProjMatrixElemsForOneBin& tof_probabilities, const CartesianCoordinate3D& point1, - const CartesianCoordinate3D& point2) STIR_MUTABLE_CONST; + const CartesianCoordinate3D& point2, + const unique_ptr& symm_ptr) STIR_MUTABLE_CONST; diff --git a/src/include/stir/recon_buildblock/ProjMatrixByBin.inl b/src/include/stir/recon_buildblock/ProjMatrixByBin.inl index 198770a1b5..eb0c3ed304 100644 --- a/src/include/stir/recon_buildblock/ProjMatrixByBin.inl +++ b/src/include/stir/recon_buildblock/ProjMatrixByBin.inl @@ -57,24 +57,10 @@ get_proj_matrix_elems_for_one_bin( const Bin& bin) STIR_MUTABLE_CONST { // start_timers(); TODO, can't do this in a const member - + // set to empty probabilities.erase(); - if (proj_data_info_sptr && (proj_data_info_sptr->is_tof_data() || - this->tof_enabled)) - { - LORInAxialAndNoArcCorrSinogramCoordinates lor; - proj_data_info_sptr->get_LOR(lor, bin); - LORAs2Points lor2(lor); - this->get_proj_matrix_elems_for_one_bin_with_tof( - probabilities, - bin, - lor2.p1(), - lor2.p2()) ; - return; - } - if (cache_stores_only_basic_bins) { // find basic bin @@ -94,9 +80,21 @@ get_proj_matrix_elems_for_one_bin( #endif cache_proj_matrix_elems_for_one_bin(probabilities); } - - // now transform to original bin - symm_ptr->transform_proj_matrix_elems_for_one_bin(probabilities); + if ( proj_data_info_sptr->is_tof_data() && + this->tof_enabled) + { + LORInAxialAndNoArcCorrSinogramCoordinates lor; + proj_data_info_sptr->get_LOR(lor, bin); + LORAs2Points lor2(lor); + + // now apply TOF kernel and transform to original bin + apply_tof_kernel_and_symm_transformation(probabilities, lor2.p1(), lor2.p2(), symm_ptr); + } + else + { + // now transform to original bin + symm_ptr->transform_proj_matrix_elems_for_one_bin(probabilities); + } } else // !cache_stores_only_basic_bins { @@ -122,71 +120,34 @@ get_proj_matrix_elems_for_one_bin( #endif cache_proj_matrix_elems_for_one_bin(probabilities); } - symm_ptr->transform_proj_matrix_elems_for_one_bin(probabilities); + if ( proj_data_info_sptr->is_tof_data() && + this->tof_enabled) + { + LORInAxialAndNoArcCorrSinogramCoordinates lor; + proj_data_info_sptr->get_LOR(lor, bin); + LORAs2Points lor2(lor); + + // now apply TOF kernel and transform to original bin + apply_tof_kernel_and_symm_transformation(probabilities, lor2.p1(), lor2.p2(), symm_ptr); + } + else + { + // now transform to original bin + symm_ptr->transform_proj_matrix_elems_for_one_bin(probabilities); + } cache_proj_matrix_elems_for_one_bin(probabilities); } } // stop_timers(); TODO, can't do this in a const member } -inline void -ProjMatrixByBin:: -get_proj_matrix_elems_for_one_bin_with_tof( - ProjMatrixElemsForOneBin& probabilities, - const Bin& bin, - const CartesianCoordinate3D& point1, - const CartesianCoordinate3D& point2) STIR_MUTABLE_CONST -{ - // start_timers(); TODO, can't do this in a const member - - if (!tof_enabled) - error("The function get_proj_matrix_elems_for_one_bin_with_tof() needs proper timing " - "initialisation. Abort."); - - // set to empty - probabilities.erase(); - - if (cache_stores_only_basic_bins) - { - // find basic bin - Bin basic_bin = bin; - unique_ptr symm_ptr = - symmetries_sptr->find_symmetry_operation_from_basic_bin(basic_bin); - - probabilities.set_bin(basic_bin); - // check if basic bin is in cache - if (get_cached_proj_matrix_elems_for_one_bin(probabilities) == - Succeeded::no) - { - // call 'calculate' just for the basic bin - calculate_proj_matrix_elems_for_one_bin(probabilities); -#ifndef NDEBUG - probabilities.check_state(); -#endif - cache_proj_matrix_elems_for_one_bin(probabilities); - } - - // now transform to original bin - // NE: I moved this operation in the apply_tof_kernel. This should increase the speed - //symm_ptr->transform_proj_matrix_elems_for_one_bin(tmp_probabilities); - apply_tof_kernel(probabilities, point1, point2); - } - else // !cache_stores_only_basic_bins - { - error("This option has been deactivated as the amount of memory required is not realistic. Abort."); - } - // stop_timers(); TODO, can't do this in a const member -} - void -ProjMatrixByBin::apply_tof_kernel(ProjMatrixElemsForOneBin& tof_probabilities, +ProjMatrixByBin::apply_tof_kernel_and_symm_transformation(ProjMatrixElemsForOneBin& tof_probabilities, const CartesianCoordinate3D& point1, - const CartesianCoordinate3D& point2) STIR_MUTABLE_CONST + const CartesianCoordinate3D& point2, + const unique_ptr& symm_ptr) STIR_MUTABLE_CONST { - unique_ptr symm_ptr = - symmetries_sptr->find_symmetry_operation_from_basic_bin(basic_bin); - CartesianCoordinate3D voxel_center; float new_value = 0.f; //float low_dist = 0.f; diff --git a/src/include/stir/recon_buildblock/ProjectorByBinPair.h b/src/include/stir/recon_buildblock/ProjectorByBinPair.h index 42cabbc705..e9d04bac6b 100644 --- a/src/include/stir/recon_buildblock/ProjectorByBinPair.h +++ b/src/include/stir/recon_buildblock/ProjectorByBinPair.h @@ -84,15 +84,6 @@ public ParsingObject const shared_ptr get_back_projector_sptr() const; - ProjMatrixElemsForOneBin* get_current_tof_row() const; - - void enable_tof(const shared_ptr& _proj_data_info_sptr, const bool v); - - void - set_tof_data(const CartesianCoordinate3D* _point1, - const CartesianCoordinate3D* _point2); - - //! Provide access to the (minimal) symmetries used by the projectors /*! It is expected that the forward and back projector can handle the same symmetries. diff --git a/src/listmode_buildblock/LmToProjData.cxx b/src/listmode_buildblock/LmToProjData.cxx index f3c30d62bd..59eda4e8ea 100644 --- a/src/listmode_buildblock/LmToProjData.cxx +++ b/src/listmode_buildblock/LmToProjData.cxx @@ -163,7 +163,6 @@ set_defaults() post_normalisation_ptr.reset(new TrivialBinNormalisation); do_pre_normalisation =0; num_events_to_store = 0L; - use_tof = false; do_time_frame = false; } @@ -527,247 +526,6 @@ process_data() { assert(!is_null_ptr(template_proj_data_info_ptr)); - if (template_proj_data_info_ptr->is_tof_data()) - { - use_tof = true; - actual_process_data_with_tof(); - } - else - { - use_tof = false; - actual_process_data_without_tof(); - } -} - - -void -LmToProjData:: -actual_process_data_without_tof() -{ - CPUTimer timer; - timer.start(); - - // assume list mode data starts at time 0 - // we have to do this because the first time tag might occur only after a - // few coincidence events (as happens with ECAT scanners) - current_time = 0; - - double time_of_last_stored_event = 0; - long num_stored_events = 0; - VectorWithOffset > - segments (0,0); - for (int timing_pos_num=segments.get_min_index(); timing_pos_num<=segments.get_max_index(); ++timing_pos_num) - { - segments[timing_pos_num].resize(template_proj_data_info_ptr->get_min_segment_num(), - template_proj_data_info_ptr->get_max_segment_num()); - } - - VectorWithOffset - frame_start_positions(1, static_cast(frame_defs.get_num_frames())); - shared_ptr record_sptr = lm_data_ptr->get_empty_record_sptr(); - CListRecord& record = *record_sptr; - - if (!record.event().is_valid_template(*template_proj_data_info_ptr)) - error("The scanner template is not valid for LmToProjData. This might be because of unsupported arc correction."); - - - /* Here starts the main loop which will store the listmode data. */ - for (current_frame_num = 1; - current_frame_num<=frame_defs.get_num_frames(); - ++current_frame_num) - { - start_new_time_frame(current_frame_num); - - // construct ExamInfo appropriate for a single projdata with this time frame - ExamInfo this_frame_exam_info(*lm_data_ptr->get_exam_info_ptr()); - { - TimeFrameDefinitions this_time_frame_defs(frame_defs, current_frame_num); - this_frame_exam_info.set_time_frame_definitions(this_time_frame_defs); - } - - // *********** open output file - shared_ptr output; - shared_ptr proj_data_ptr; - - { - char rest[50]; - sprintf(rest, "_f%dg1d0b0", current_frame_num); - const string output_filename = output_filename_prefix + rest; - - proj_data_ptr = - construct_proj_data(output, output_filename, this_frame_exam_info, template_proj_data_info_ptr); - } - - long num_prompts_in_frame = 0; - long num_delayeds_in_frame = 0; - - const double start_time = frame_defs.get_start_time(current_frame_num); - const double end_time = frame_defs.get_end_time(current_frame_num); - - /* - For each start_segment_index, we check which events occur in the - segments between start_segment_index and - start_segment_index+num_segments_in_memory. - */ - for (int start_segment_index = proj_data_ptr->get_min_segment_num(); - start_segment_index <= proj_data_ptr->get_max_segment_num(); - start_segment_index += num_segments_in_memory) - { - const int end_segment_index = - min( proj_data_ptr->get_max_segment_num()+1, start_segment_index + num_segments_in_memory) - 1; - - if (!interactive) - allocate_segments(segments, 0,0,start_segment_index, end_segment_index, proj_data_ptr->get_proj_data_info_ptr()); - - // the next variable is used to see if there are more events to store for the current segments - // num_events_to_store-more_events will be the number of allowed coincidence events currently seen in the file - // ('allowed' independent on the fact of we have its segment in memory or not) - // When do_time_frame=true, the number of events is irrelevant, so we - // just set more_events to 1, and never change it - long more_events = - do_time_frame? 1 : num_events_to_store; - - if (start_segment_index != proj_data_ptr->get_min_segment_num()) - { - // we're going once more through the data (for the next batch of segments) - cerr << "\nProcessing next batch of segments\n"; - // go to the beginning of the listmode data for this frame - lm_data_ptr->set_get_position(frame_start_positions[current_frame_num]); - current_time = start_time; - } - else - { - cerr << "\nProcessing time frame " << current_frame_num << '\n'; - - // Note: we already have current_time from previous frame, so don't - // need to set it. In fact, setting it to start_time would be wrong - // as we first might have to skip some events before we get to start_time. - // So, let's do that now. - while (current_time < start_time && - lm_data_ptr->get_next_record(record) == Succeeded::yes) - { - if (record.is_time()) - current_time = record.time().get_time_in_secs(); - } - // now save position such that we can go back - frame_start_positions[current_frame_num] = - lm_data_ptr->save_get_position(); - } - { - // loop over all events in the listmode file - while (more_events) - { - if (lm_data_ptr->get_next_record(record) == Succeeded::no) - { - // no more events in file for some reason - break; //get out of while loop - } - if (record.is_time() && end_time > 0.01) // Direct comparison within doubles is unsafe. - { - current_time = record.time().get_time_in_secs(); - if (do_time_frame && current_time >= end_time) - break; // get out of while loop - assert(current_time>=start_time); - process_new_time_event(record.time()); - } - // note: could do "else if" here if we would be sure that - // a record can never be both timing and coincidence event - // and there might be a scanner around that has them both combined. - if (record.is_event()) - { - assert(start_time <= current_time); - Bin bin; - // set value in case the event decoder doesn't touch it - // otherwise it would be 0 and all events will be ignored - bin.set_bin_value(1); - get_bin_from_event(bin, record.event()); - - // check if it's inside the range we want to store - if (bin.get_bin_value()>0 - && bin.tangential_pos_num()>= proj_data_ptr->get_min_tangential_pos_num() - && bin.tangential_pos_num()<= proj_data_ptr->get_max_tangential_pos_num() - && bin.axial_pos_num()>=proj_data_ptr->get_min_axial_pos_num(bin.segment_num()) - && bin.axial_pos_num()<=proj_data_ptr->get_max_axial_pos_num(bin.segment_num()) - ) - { - assert(bin.view_num()>=proj_data_ptr->get_min_view_num()); - assert(bin.view_num()<=proj_data_ptr->get_max_view_num()); - - // see if we increment or decrement the value in the sinogram - const int event_increment = - record.event().is_prompt() - ? ( store_prompts ? 1 : 0 ) // it's a prompt - : delayed_increment;//it is a delayed-coincidence event - - if (event_increment==0) - continue; - - if (!do_time_frame) - more_events -= event_increment; - - // now check if we have its segment in memory - if (bin.segment_num() >= start_segment_index && bin.segment_num()<=end_segment_index) - { - do_post_normalisation(bin); - - num_stored_events += event_increment; - if (record.event().is_prompt()) - ++num_prompts_in_frame; - else - ++num_delayeds_in_frame; - - if (num_stored_events%500000L==0) cout << "\r" << num_stored_events << " events stored" << flush; - - if (interactive) - printf("Seg %4d view %4d ax_pos %4d tang_pos %4d time %8g stored with incr %d \n", - bin.segment_num(), bin.view_num(), bin.axial_pos_num(), bin.tangential_pos_num(), - current_time, event_increment); - else - (*segments[0][bin.segment_num()])[bin.view_num()][bin.axial_pos_num()][bin.tangential_pos_num()] += - bin.get_bin_value() * - event_increment; - } - } - else // event is rejected for some reason - { - if (interactive) - printf("Seg %4d view %4d ax_pos %4d tang_pos %4d time %8g ignored\n", - bin.segment_num(), bin.view_num(), bin.axial_pos_num(), bin.tangential_pos_num(), current_time); - } - } // end of spatial event processing - } // end of while loop over all events - - time_of_last_stored_event = - max(time_of_last_stored_event,current_time); - } - - if (!interactive) - save_and_delete_segments(output, segments, - 0,0, start_segment_index, end_segment_index, - *proj_data_ptr); - } // end of for loop for segment range - cerr << "\nNumber of prompts stored in this time period : " << num_prompts_in_frame - << "\nNumber of delayeds stored in this time period: " << num_delayeds_in_frame - << '\n'; - } // end of loop over frames - - timer.stop(); - - cerr << "Last stored event was recorded before time-tick at " << time_of_last_stored_event << " secs\n"; - if (!do_time_frame && - (num_stored_events<=0 || - /*static_cast*/(num_stored_events)* _point1, - const CartesianCoordinate3D* _point2) -{ - point1 = _point1; - point2 = _point2; -} - -ProjMatrixElemsForOneBin* -ForwardProjectorByBin:: -get_tof_row() const -{ - return tof_probabilities.get(); -} - END_NAMESPACE_STIR diff --git a/src/recon_buildblock/ForwardProjectorByBinUsingProjMatrixByBin.cxx b/src/recon_buildblock/ForwardProjectorByBinUsingProjMatrixByBin.cxx index 53a0806dc4..71604b6e98 100644 --- a/src/recon_buildblock/ForwardProjectorByBinUsingProjMatrixByBin.cxx +++ b/src/recon_buildblock/ForwardProjectorByBinUsingProjMatrixByBin.cxx @@ -239,29 +239,15 @@ ForwardProjectorByBinUsingProjMatrixByBin:: const DiscretisedDensity<3, float> &density) { - if (proj_matrix_ptr->is_cache_enabled() && !tof_enabled) + if (proj_matrix_ptr->is_cache_enabled()) { ProjMatrixElemsForOneBin proj_matrix_row; proj_matrix_ptr->get_proj_matrix_elems_for_one_bin(proj_matrix_row, this_bin); proj_matrix_row.forward_project(this_bin,density); } - else if (proj_matrix_ptr->is_cache_enabled() && tof_enabled) - { - proj_matrix_ptr->get_proj_matrix_elems_for_one_bin_with_tof(*tof_probabilities, this_bin, *point1, *point2); - tof_probabilities->forward_project(this_bin,density); - } else error("ForwardProjectorByBinUsingProjMatrixByBin: Symmetries should be handled by ProjMatrix. Abort. "); } -void -ForwardProjectorByBinUsingProjMatrixByBin:: -enable_tof(const shared_ptr& _proj_data_info_sptr, const bool v) -{ - proj_matrix_ptr->enable_tof(_proj_data_info_sptr, v); - tof_enabled = v; - tof_probabilities.reset(new ProjMatrixElemsForOneBin()); -} - END_NAMESPACE_STIR diff --git a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx index f1a900fe7b..c096621df6 100644 --- a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx +++ b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx @@ -213,7 +213,7 @@ set_up_before_sensitivity(shared_ptr const& target_sptr) // set projector to be used for the calculations this->PM_sptr->set_up(proj_data_info_sptr->create_shared_clone(),target_sptr); - this->PM_sptr->enable_tof(proj_data_info_sptr->create_shared_clone(), this->use_tof); + //this->PM_sptr->enable_tof(proj_data_info_sptr->create_shared_clone(), this->use_tof); shared_ptr forward_projector_ptr(new ForwardProjectorByBinUsingProjMatrixByBin(this->PM_sptr)); shared_ptr back_projector_ptr(new BackProjectorByBinUsingProjMatrixByBin(this->PM_sptr)); @@ -533,11 +533,8 @@ compute_sub_gradient_without_penalty_plus_sensitivity(TargetT& gradient, } } - if(this->use_tof) this->PM_sptr->get_proj_matrix_elems_for_one_bin(proj_matrix_row, measured_bin); - else - this->PM_sptr->get_proj_matrix_elems_for_one_bin(proj_matrix_row, measured_bin); //in_the_range++; fwd_bin.set_bin_value(0.0f); diff --git a/src/recon_buildblock/ProjectorByBinPair.cxx b/src/recon_buildblock/ProjectorByBinPair.cxx index 5575adee14..10fc37ac8f 100644 --- a/src/recon_buildblock/ProjectorByBinPair.cxx +++ b/src/recon_buildblock/ProjectorByBinPair.cxx @@ -62,175 +62,6 @@ set_up(const shared_ptr& proj_data_info_sptr, return Succeeded::yes; } -void -ProjectorByBinPair:: -enable_tof(const shared_ptr& _proj_data_info_sptr, const bool v) -{ - // Check if it is PresmoothingForwardProjectorByBin - PresmoothingForwardProjectorByBin* forward= - dynamic_cast (forward_projector_sptr.get()); - - if (!is_null_ptr(forward)) - { - ForwardProjectorByBinUsingProjMatrixByBin* original_forward = - dynamic_cast (forward->get_original_forward_projector_ptr()); - - if (is_null_ptr(original_forward)) - error("Currently only ForwardProjectorByBinUsingProjMatrixByBin supports TOF reconstruction. Abort."); - else - original_forward->enable_tof(_proj_data_info_sptr, v); - } - else // if is ForwardProjectorByBinUsingProjMatrixByBin directly. - { - ForwardProjectorByBinUsingProjMatrixByBin* original_forward = - dynamic_cast (forward_projector_sptr.get()); - - if (is_null_ptr(original_forward)) - error("Currently only ForwardProjectorByBinUsingProjMatrixByBin supports TOF reconstruction. Abort."); - else - original_forward->enable_tof(_proj_data_info_sptr, v); - } - -// // Check if it is PostSmoothingBackProjectorByBin is used. -// PostsmoothingBackProjectorByBin* back= -// dynamic_cast (back_projector_sptr.get()); - -// if (!is_null_ptr(back)) -// { -// BackProjectorByBinUsingProjMatrixByBin* original_back = -// dynamic_cast (back->get_original_back_projector_ptr()); - -// if (is_null_ptr(original_back)) -// error("Currently only ForwardProjectorByBinUsingProjMatrixByBin supports TOF reconstruction. Abort."); -// else -// original_back->enable_tof(_proj_data_info_sptr, v); -// } -// else // if is ForwardProjectorByBinUsingProjMatrixByBin directly. -// { -// BackProjectorByBinUsingProjMatrixByBin* original_back = -// dynamic_cast (back_projector_sptr.get()); - -// if (is_null_ptr(original_back)) -// error("Currently only BackProjectorByBinUsingProjMatrixByBin supports TOF reconstruction. Abort."); -// else -// original_back->enable_tof(_proj_data_info_sptr, v); -// } - -} - -void -ProjectorByBinPair:: -set_tof_data(const CartesianCoordinate3D* _point1, - const CartesianCoordinate3D* _point2) -{ - // Check if it is PresmoothingForwardProjectorByBin - PresmoothingForwardProjectorByBin* forward= - dynamic_cast (forward_projector_sptr.get()); - - if (!is_null_ptr(forward)) - { - ForwardProjectorByBinUsingProjMatrixByBin* original_forward = - dynamic_cast (forward->get_original_forward_projector_ptr()); - - if (is_null_ptr(original_forward)) - error("Currently only ForwardProjectorByBinUsingProjMatrixByBin supports TOF reconstruction. Abort."); - else - { - original_forward->set_tof_data(_point1, _point2); - - PostsmoothingBackProjectorByBin* back= - dynamic_cast (back_projector_sptr.get()); - - if (!is_null_ptr(back)) - { - BackProjectorByBinUsingProjMatrixByBin* original_back = - dynamic_cast (back->get_original_back_projector_ptr()); - - if (is_null_ptr(original_back)) - error("Currently only ForwardProjectorByBinUsingProjMatrixByBin supports TOF reconstruction. Abort."); - else - original_back->enable_tof(original_forward->get_tof_row()); - } - else // if is ForwardProjectorByBinUsingProjMatrixByBin directly. - { - BackProjectorByBinUsingProjMatrixByBin* original_back = - dynamic_cast (back_projector_sptr.get()); - - if (is_null_ptr(original_back)) - error("Currently only BackProjectorByBinUsingProjMatrixByBin supports TOF reconstruction. Abort."); - else - original_back->enable_tof(original_forward->get_tof_row()); - } - } - } - else // if is ForwardProjectorByBinUsingProjMatrixByBin directly. - { - ForwardProjectorByBinUsingProjMatrixByBin* original_forward = - dynamic_cast (forward_projector_sptr.get()); - - if (is_null_ptr(original_forward)) - error("Currently only ForwardProjectorByBinUsingProjMatrixByBin supports TOF reconstruction. Abort."); - else - { - original_forward->set_tof_data(_point1, _point2); - - PostsmoothingBackProjectorByBin* back= - dynamic_cast (back_projector_sptr.get()); - - if (!is_null_ptr(back)) - { - BackProjectorByBinUsingProjMatrixByBin* original_back = - dynamic_cast (back->get_original_back_projector_ptr()); - - if (is_null_ptr(original_back)) - error("Currently only ForwardProjectorByBinUsingProjMatrixByBin supports TOF reconstruction. Abort."); - else - original_back->enable_tof(original_forward->get_tof_row()); - } - else // if is ForwardProjectorByBinUsingProjMatrixByBin directly. - { - BackProjectorByBinUsingProjMatrixByBin* original_back = - dynamic_cast (back_projector_sptr.get()); - - if (is_null_ptr(original_back)) - error("Currently only BackProjectorByBinUsingProjMatrixByBin supports TOF reconstruction. Abort."); - else - original_back->enable_tof(original_forward->get_tof_row()); - } - } - } -} - -ProjMatrixElemsForOneBin* -ProjectorByBinPair:: -get_current_tof_row() const -{ - // Check if it is PresmoothingForwardProjectorByBin - PresmoothingForwardProjectorByBin* forward= - dynamic_cast (forward_projector_sptr.get()); - - if (!is_null_ptr(forward)) - { - ForwardProjectorByBinUsingProjMatrixByBin* original_forward = - dynamic_cast (forward->get_original_forward_projector_ptr()); - - if (is_null_ptr(original_forward)) - error("Currently only ForwardProjectorByBinUsingProjMatrixByBin supports TOF reconstruction. Abort."); - else - return original_forward->get_tof_row(); - } - else // if is ForwardProjectorByBinUsingProjMatrixByBin directly. - { - ForwardProjectorByBinUsingProjMatrixByBin* original_forward = - dynamic_cast (forward_projector_sptr.get()); - - if (is_null_ptr(original_forward)) - error("Currently only ForwardProjectorByBinUsingProjMatrixByBin supports TOF reconstruction. Abort."); - else - return original_forward->get_tof_row(); - } -} - void ProjectorByBinPair:: check(const ProjDataInfo& proj_data_info, const DiscretisedDensity<3,float>& density_info) const diff --git a/src/test/test_time_of_flight.cxx b/src/test/test_time_of_flight.cxx index c70ce53322..128160a0b9 100644 --- a/src/test/test_time_of_flight.cxx +++ b/src/test/test_time_of_flight.cxx @@ -150,7 +150,7 @@ TOF_Tests::run_tests() test_proj_matrix_sptr.reset(new ProjMatrixByBinUsingRayTracing()); dynamic_cast(test_proj_matrix_sptr.get())->set_num_tangential_LORs(1); dynamic_cast(test_proj_matrix_sptr.get())->set_up(test_proj_data_info_sptr, test_discretised_density_sptr); - test_proj_matrix_sptr->enable_tof(test_proj_data_info_sptr); +// test_proj_matrix_sptr->enable_tof(test_proj_data_info_sptr); shared_ptr forward_projector_ptr( new ForwardProjectorByBinUsingProjMatrixByBin(test_proj_matrix_sptr)); @@ -174,7 +174,7 @@ TOF_Tests::test_tof_proj_data_info() { const int correct_tof_mashing_factor = 39; const int num_timing_positions = 9; - float correct_width_of_tof_bin = test_scanner_sptr->get_size_of_timing_bin() * + float correct_width_of_tof_bin = test_scanner_sptr->get_size_of_timing_pos() * test_proj_data_info_sptr->get_tof_mash_factor() * 0.299792458f/2; float correct_timing_locations[num_timing_positions] = {-360.201f/2, -280.156f/2, -200.111f/2, -120.067f/2, -40.022f/2, 40.022f/2, 120.067f/2, 200.111f/2, 280.156f/2}; @@ -317,7 +317,7 @@ TOF_Tests::test_tof_kernel_application() int view_num = 0; int axial_num = 0; int tang_num = 0; - CartesianCoordinate3D lor_point_1, lor_point_2; + ProjMatrixElemsForOneBin proj_matrix_row; HighResWallClockTimer t; std::vector times_of_tofing; @@ -326,17 +326,13 @@ TOF_Tests::test_tof_kernel_application() dynamic_cast (test_proj_data_info_sptr.get()); Bin this_bin(seg_num, view_num, axial_num, tang_num, 1.f); - proj_data_ptr->get_LOR_as_two_points(lor_point_1, lor_point_2, this_bin); - - std::cerr<< lor_point_1.x() << " " << lor_point_1.y() << " " << lor_point_1.z() << " " << - lor_point_2.x() << " " << lor_point_2.y() << " " << lor_point_2.z() << std::endl; t.reset(); t.start(); test_proj_matrix_sptr->get_proj_matrix_elems_for_one_bin(proj_matrix_row, this_bin); t.stop(); std::cerr<<"Execution time for nonTOF: "<get_min_tof_pos_num(); timing_num <= test_proj_data_info_sptr->get_max_tof_pos_num(); ++ timing_num) @@ -345,14 +341,13 @@ TOF_Tests::test_tof_kernel_application() Bin bin(seg_num, view_num, axial_num, tang_num, timing_num, 1.f); t.reset(); t.start(); - test_proj_matrix_sptr->get_proj_matrix_elems_for_one_bin_with_tof(new_proj_matrix_row, - bin, - lor_point_1, lor_point_2); + test_proj_matrix_sptr->get_proj_matrix_elems_for_one_bin(new_proj_matrix_row, + bin); t.stop(); times_of_tofing.push_back(t.value()); - export_lor(new_proj_matrix_row, - lor_point_1, lor_point_2, timing_num, - proj_matrix_row); +// export_lor(new_proj_matrix_row, +// lor_point_1, lor_point_2, timing_num, +// proj_matrix_row); } double mean = 0.0; From c996e126eb2b9544bcf5c03c20d1cd846900af62 Mon Sep 17 00:00:00 2001 From: Nikos Efthimiou Date: Thu, 18 Oct 2018 02:00:58 +0100 Subject: [PATCH 108/509] Reduce test time in test_DataSYmmetriesForBins_PET_CartesianGrid increase the tof mashing factor until the test run on Travis --- .../stir/recon_buildblock/ProjMatrixByBin.inl | 12 ++++++------ .../test_DataSymmetriesForBins_PET_CartesianGrid.cxx | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/include/stir/recon_buildblock/ProjMatrixByBin.inl b/src/include/stir/recon_buildblock/ProjMatrixByBin.inl index eb0c3ed304..1f27e9620c 100644 --- a/src/include/stir/recon_buildblock/ProjMatrixByBin.inl +++ b/src/include/stir/recon_buildblock/ProjMatrixByBin.inl @@ -195,13 +195,13 @@ ProjMatrixByBin::apply_tof_kernel_and_symm_transformation(ProjMatrixElemsForOneB voxel_center[1] = u * difference[1]; if(u < 0.f) - d1 = - sqrt(voxel_center.x()*voxel_center.x() + - voxel_center.y()*voxel_center.y() + - voxel_center.z()*voxel_center.z() ); + d1 = - sqrt( voxel_center[3] * voxel_center[3] + + voxel_center[2] * voxel_center[2] + + voxel_center[1] * voxel_center[1]); else - d1 = sqrt(voxel_center.x()*voxel_center.x() + - voxel_center.y()*voxel_center.y() + - voxel_center.z()*voxel_center.z() ); + d1 = sqrt( voxel_center[3] * voxel_center[3] + + voxel_center[2] * voxel_center[2] + + voxel_center[1] * voxel_center[1]); } //low_dist = ((proj_data_info_sptr->tof_bin_boundaries_mm[tof_probabilities.get_bin_ptr()->timing_pos_num()].low_lim - d1) * r_sqrt2_gauss_sigma) + 4.f; diff --git a/src/recon_test/test_DataSymmetriesForBins_PET_CartesianGrid.cxx b/src/recon_test/test_DataSymmetriesForBins_PET_CartesianGrid.cxx index 1ca8acba3d..e6edd0f879 100644 --- a/src/recon_test/test_DataSymmetriesForBins_PET_CartesianGrid.cxx +++ b/src/recon_test/test_DataSymmetriesForBins_PET_CartesianGrid.cxx @@ -727,7 +727,7 @@ DataSymmetriesForBins_PET_CartesianGridTests::run_tests() /*num_views=*/scanner_sptr->get_num_detectors_per_ring()/8, /*num_tang_poss=*/64, /*arc_corrected*/false, - /*tof_mashing*/39)); + /*tof_mashing*/100)); run_tests_for_1_projdata(proj_data_info_sptr); From 81d2f507849153cee8ba246f0c870f088b63fcf9 Mon Sep 17 00:00:00 2001 From: Nikos Efthimiou Date: Sat, 20 Oct 2018 02:17:41 +0100 Subject: [PATCH 109/509] Revert kernel caching, ProjDataInfo comparison improvement * Comprare the timing positions of the ProjDataInfo only if both are tof_enabled --- src/buildblock/ProjDataInfo.cxx | 2 +- .../stir/recon_buildblock/ProjMatrixByBin.inl | 32 +++++++++++-------- src/recon_buildblock/ProjMatrixByBin.cxx | 18 +++++------ 3 files changed, 29 insertions(+), 23 deletions(-) diff --git a/src/buildblock/ProjDataInfo.cxx b/src/buildblock/ProjDataInfo.cxx index f2b838d42f..c15d97f087 100644 --- a/src/buildblock/ProjDataInfo.cxx +++ b/src/buildblock/ProjDataInfo.cxx @@ -746,7 +746,7 @@ operator>=(const ProjDataInfo& proj_data_info) const proj_data_info.get_min_tangential_pos_num() < larger_proj_data_info.get_min_tangential_pos_num() || ((proj_data_info.get_min_tof_pos_num() < larger_proj_data_info.get_min_tof_pos_num() || proj_data_info.get_max_tof_pos_num() > larger_proj_data_info.get_max_tof_pos_num()) && - proj_data_info.is_tof_data())) + (proj_data_info.is_tof_data() && larger_proj_data_info.is_tof_data()))) return false; for (int segment_num=proj_data_info.get_min_segment_num(); diff --git a/src/include/stir/recon_buildblock/ProjMatrixByBin.inl b/src/include/stir/recon_buildblock/ProjMatrixByBin.inl index 1f27e9620c..8b1288facd 100644 --- a/src/include/stir/recon_buildblock/ProjMatrixByBin.inl +++ b/src/include/stir/recon_buildblock/ProjMatrixByBin.inl @@ -34,7 +34,6 @@ #include "stir/Succeeded.h" #include "stir/recon_buildblock/SymmetryOperation.h" #include "stir/geometry/line_distances.h" -//#include "stir/numerics/erf.h" START_NAMESPACE_STIR @@ -154,7 +153,7 @@ ProjMatrixByBin::apply_tof_kernel_and_symm_transformation(ProjMatrixElemsForOneB //float high_dist = 0.f; float d1; - float step = 1000.f / 8.f; + //float step = 100000.f / 8.f; int p1, p2; // THe direction can be from 1 -> 2 depending on the bin sign. @@ -195,30 +194,37 @@ ProjMatrixByBin::apply_tof_kernel_and_symm_transformation(ProjMatrixElemsForOneB voxel_center[1] = u * difference[1]; if(u < 0.f) - d1 = - sqrt( voxel_center[3] * voxel_center[3] + + d1 = std::sqrt( voxel_center[3] * voxel_center[3] + voxel_center[2] * voxel_center[2] + voxel_center[1] * voxel_center[1]); else - d1 = sqrt( voxel_center[3] * voxel_center[3] + + d1 = -std::sqrt( voxel_center[3] * voxel_center[3] + voxel_center[2] * voxel_center[2] + voxel_center[1] * voxel_center[1]); } - //low_dist = ((proj_data_info_sptr->tof_bin_boundaries_mm[tof_probabilities.get_bin_ptr()->timing_pos_num()].low_lim - d1) * r_sqrt2_gauss_sigma) + 4.f; - //high_dist = ((proj_data_info_sptr->tof_bin_boundaries_mm[tof_probabilities.get_bin_ptr()->timing_pos_num()].high_lim - d1) * r_sqrt2_gauss_sigma) + 4.f; + float low_dist = ((proj_data_info_sptr->tof_bin_boundaries_mm[tof_probabilities.get_bin_ptr()->timing_pos_num()].low_lim - d1) * r_sqrt2_gauss_sigma); + float high_dist = ((proj_data_info_sptr->tof_bin_boundaries_mm[tof_probabilities.get_bin_ptr()->timing_pos_num()].high_lim - d1) * r_sqrt2_gauss_sigma); - p1 = (((proj_data_info_sptr->tof_bin_boundaries_mm[tof_probabilities.get_bin_ptr()->timing_pos_num()].low_lim - d1) * r_sqrt2_gauss_sigma) + 4.f) * step; - p2 = (((proj_data_info_sptr->tof_bin_boundaries_mm[tof_probabilities.get_bin_ptr()->timing_pos_num()].high_lim - d1) * r_sqrt2_gauss_sigma) + 4.f) * step; + //p1 = (((proj_data_info_sptr->tof_bin_boundaries_mm[tof_probabilities.get_bin_ptr()->timing_pos_num()].low_lim - d1) * r_sqrt2_gauss_sigma) + 4.f) * step; + //p2 = (((proj_data_info_sptr->tof_bin_boundaries_mm[tof_probabilities.get_bin_ptr()->timing_pos_num()].high_lim - d1) * r_sqrt2_gauss_sigma) + 4.f) * step; - if (p1 < 0 || p2 < 0 || - p1 > 1000 || p2 > 1000) +// if (p1 < 0 || p2 < 0 || +// p1 >= 100000 || p2 >= 100000) +// { +// *element_ptr = ProjMatrixElemsForOneBin::value_type(c, 0.0f); +// continue; +// } + + if (low_dist >= 4.f || high_dist >= 4.f || + low_dist <= -4.f || high_dist <= -4.f) { - *element_ptr = ProjMatrixElemsForOneBin::value_type(c, 0.0); + *element_ptr = ProjMatrixElemsForOneBin::value_type(c, 0.0f); continue; } - //get_tof_value(low_dist, high_dist, new_value); - new_value = (cache_erf[p2] - cache_erf[p1]) * element_ptr->get_value(); + get_tof_value(low_dist, high_dist, new_value); + new_value *= element_ptr->get_value();//*(cache_erf[p2] - cache_erf[p1]); // *element_ptr = ProjMatrixElemsForOneBin::value_type(c, new_value); } } diff --git a/src/recon_buildblock/ProjMatrixByBin.cxx b/src/recon_buildblock/ProjMatrixByBin.cxx index 3e2cc9130b..c62d78de87 100644 --- a/src/recon_buildblock/ProjMatrixByBin.cxx +++ b/src/recon_buildblock/ProjMatrixByBin.cxx @@ -86,15 +86,15 @@ enable_tof(const shared_ptr& _proj_data_info_sptr, const bool v) gauss_sigma_in_mm = ProjDataInfo::tof_delta_time_to_mm(proj_data_info_sptr->get_scanner_ptr()->get_timing_resolution()) / 2.355f; r_sqrt2_gauss_sigma = 1.0f/ (gauss_sigma_in_mm * static_cast(sqrt(2.0))); - cache_erf.resize(0, 1000); - - float step = 8.f / 1000.f; - // cache erf with reasonable sampling - for (int i = 0 ;i < 1000; ++i) - { - float d = -4.0f + i * step; - cache_erf[i] = 0.5f * erf(d); - } +// cache_erf.resize(0, 100000); + +// float step = 8.f / 100000.f; +// // cache erf with reasonable sampling +// for (int i = 0 ;i < 100000; ++i) +// { +// float d = -4.0f + i * step; +// cache_erf[i] = 0.5f * erf(d); +// } } } From 460173d28b594caf070df0a24ce019ce3a1d3462 Mon Sep 17 00:00:00 2001 From: Nikos Efthimiou Date: Sat, 20 Oct 2018 09:54:34 +0100 Subject: [PATCH 110/509] correction of the LOR bin after symmetry --- .../stir/recon_buildblock/ProjMatrixByBin.h | 2 +- .../stir/recon_buildblock/ProjMatrixByBin.inl | 16 ++++++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/include/stir/recon_buildblock/ProjMatrixByBin.h b/src/include/stir/recon_buildblock/ProjMatrixByBin.h index 7eefb4460c..bb54907502 100644 --- a/src/include/stir/recon_buildblock/ProjMatrixByBin.h +++ b/src/include/stir/recon_buildblock/ProjMatrixByBin.h @@ -269,7 +269,7 @@ class ProjMatrixByBin : //! Get the interal value erf(m - v_j) - erf(m -v_j) - inline void get_tof_value(const float& d1, const float& d2, float& val) const; + inline void get_tof_value(const float d1, const float d2, float& val) const; }; diff --git a/src/include/stir/recon_buildblock/ProjMatrixByBin.inl b/src/include/stir/recon_buildblock/ProjMatrixByBin.inl index 8b1288facd..93cdb6ff6f 100644 --- a/src/include/stir/recon_buildblock/ProjMatrixByBin.inl +++ b/src/include/stir/recon_buildblock/ProjMatrixByBin.inl @@ -86,6 +86,10 @@ get_proj_matrix_elems_for_one_bin( proj_data_info_sptr->get_LOR(lor, bin); LORAs2Points lor2(lor); + Bin fbin = probabilities.get_bin(); + symm_ptr->transform_bin_coordinates(fbin); + probabilities.set_bin(fbin); + // now apply TOF kernel and transform to original bin apply_tof_kernel_and_symm_transformation(probabilities, lor2.p1(), lor2.p2(), symm_ptr); } @@ -127,6 +131,10 @@ get_proj_matrix_elems_for_one_bin( LORAs2Points lor2(lor); // now apply TOF kernel and transform to original bin + + Bin fbin = probabilities.get_bin(); + symm_ptr->transform_bin_coordinates(fbin); + probabilities.set_bin(fbin); apply_tof_kernel_and_symm_transformation(probabilities, lor2.p1(), lor2.p2(), symm_ptr); } else @@ -154,7 +162,7 @@ ProjMatrixByBin::apply_tof_kernel_and_symm_transformation(ProjMatrixElemsForOneB float d1; //float step = 100000.f / 8.f; - int p1, p2; + //int p1, p2; // THe direction can be from 1 -> 2 depending on the bin sign. const CartesianCoordinate3D middle = (point1 + point2)*0.5f; @@ -223,8 +231,8 @@ ProjMatrixByBin::apply_tof_kernel_and_symm_transformation(ProjMatrixElemsForOneB continue; } - get_tof_value(low_dist, high_dist, new_value); - new_value *= element_ptr->get_value();//*(cache_erf[p2] - cache_erf[p1]); // +// get_tof_value(low_dist, high_dist, new_value); + new_value = element_ptr->get_value() * 0.5f * (erf(high_dist) - erf(low_dist));//*(cache_erf[p2] - cache_erf[p1]); // *element_ptr = ProjMatrixElemsForOneBin::value_type(c, new_value); } } @@ -232,7 +240,7 @@ ProjMatrixByBin::apply_tof_kernel_and_symm_transformation(ProjMatrixElemsForOneB void ProjMatrixByBin:: //get_tof_value(const float& d1, const float& d2, float& val) const -get_tof_value(const float& d1, const float& d2, float& val) const +get_tof_value(const float d1, const float d2, float& val) const { val = 0.5f * (erf(d2) - erf(d1)); } From 2056815a7562c7d1ea0be3deaea6daf64dfd172e Mon Sep 17 00:00:00 2001 From: Nikos Efthimiou Date: Wed, 24 Oct 2018 01:02:16 +0100 Subject: [PATCH 111/509] Revert some changes made in previous commits: Some modifications in 711b96681fd33ca1e3010cd73cbf20c7c3b38d43 and 711b96681fd33ca1e3010cd73cbf20c7c3b38d43 led to small benefits and potential problems. --- .../stir/recon_buildblock/ProjMatrixByBin.h | 2 +- .../stir/recon_buildblock/ProjMatrixByBin.inl | 90 +++++++++---------- 2 files changed, 42 insertions(+), 50 deletions(-) diff --git a/src/include/stir/recon_buildblock/ProjMatrixByBin.h b/src/include/stir/recon_buildblock/ProjMatrixByBin.h index bb54907502..8a438121eb 100644 --- a/src/include/stir/recon_buildblock/ProjMatrixByBin.h +++ b/src/include/stir/recon_buildblock/ProjMatrixByBin.h @@ -261,7 +261,7 @@ class ProjMatrixByBin : Array<1, float> cache_erf; //! The function which actually applies the TOF kernel on the LOR. - inline void apply_tof_kernel_and_symm_transformation(ProjMatrixElemsForOneBin& tof_probabilities, + inline void apply_tof_kernel_and_symm_transformation(ProjMatrixElemsForOneBin& probabilities, const CartesianCoordinate3D& point1, const CartesianCoordinate3D& point2, const unique_ptr& symm_ptr) STIR_MUTABLE_CONST; diff --git a/src/include/stir/recon_buildblock/ProjMatrixByBin.inl b/src/include/stir/recon_buildblock/ProjMatrixByBin.inl index 93cdb6ff6f..79c607d155 100644 --- a/src/include/stir/recon_buildblock/ProjMatrixByBin.inl +++ b/src/include/stir/recon_buildblock/ProjMatrixByBin.inl @@ -59,7 +59,7 @@ get_proj_matrix_elems_for_one_bin( // set to empty probabilities.erase(); - + if (cache_stores_only_basic_bins) { // find basic bin @@ -77,7 +77,7 @@ get_proj_matrix_elems_for_one_bin( #ifndef NDEBUG probabilities.check_state(); #endif - cache_proj_matrix_elems_for_one_bin(probabilities); + cache_proj_matrix_elems_for_one_bin(probabilities); } if ( proj_data_info_sptr->is_tof_data() && this->tof_enabled) @@ -85,11 +85,7 @@ get_proj_matrix_elems_for_one_bin( LORInAxialAndNoArcCorrSinogramCoordinates lor; proj_data_info_sptr->get_LOR(lor, bin); LORAs2Points lor2(lor); - - Bin fbin = probabilities.get_bin(); - symm_ptr->transform_bin_coordinates(fbin); - probabilities.set_bin(fbin); - + probabilities.set_bin(bin); // now apply TOF kernel and transform to original bin apply_tof_kernel_and_symm_transformation(probabilities, lor2.p1(), lor2.p2(), symm_ptr); } @@ -129,13 +125,10 @@ get_proj_matrix_elems_for_one_bin( LORInAxialAndNoArcCorrSinogramCoordinates lor; proj_data_info_sptr->get_LOR(lor, bin); LORAs2Points lor2(lor); - + probabilities.set_bin(bin); // now apply TOF kernel and transform to original bin - - Bin fbin = probabilities.get_bin(); - symm_ptr->transform_bin_coordinates(fbin); - probabilities.set_bin(fbin); apply_tof_kernel_and_symm_transformation(probabilities, lor2.p1(), lor2.p2(), symm_ptr); + } else { @@ -149,7 +142,7 @@ get_proj_matrix_elems_for_one_bin( } void -ProjMatrixByBin::apply_tof_kernel_and_symm_transformation(ProjMatrixElemsForOneBin& tof_probabilities, +ProjMatrixByBin::apply_tof_kernel_and_symm_transformation(ProjMatrixElemsForOneBin& probabilities, const CartesianCoordinate3D& point1, const CartesianCoordinate3D& point2, const unique_ptr& symm_ptr) STIR_MUTABLE_CONST @@ -157,8 +150,8 @@ ProjMatrixByBin::apply_tof_kernel_and_symm_transformation(ProjMatrixElemsForOneB CartesianCoordinate3D voxel_center; float new_value = 0.f; - //float low_dist = 0.f; - //float high_dist = 0.f; + float low_dist = 0.f; + float high_dist = 0.f; float d1; //float step = 100000.f / 8.f; @@ -166,53 +159,53 @@ ProjMatrixByBin::apply_tof_kernel_and_symm_transformation(ProjMatrixElemsForOneB // THe direction can be from 1 -> 2 depending on the bin sign. const CartesianCoordinate3D middle = (point1 + point2)*0.5f; - const CartesianCoordinate3D difference = point2 - middle; + const CartesianCoordinate3D difference = point2 - point1; + +// const CartesianCoordinate3D diff = point2 - middle; + const float denom = 1.f / inner_product(difference, difference); - // float lor_length = 2.f / (std::sqrt((point1.x() - point2.x()) *(point1.x() - point2.x()) + - // (point1.y() - point2.y()) *(point1.y() - point2.y()) + - // (point1.z() - point2.z()) *(point1.z() - point2.z()))); - for (ProjMatrixElemsForOneBin::iterator element_ptr = tof_probabilities.begin(); - element_ptr != tof_probabilities.end(); ++element_ptr) + const float lor_length = 1.f / (std::sqrt(difference.x() * difference.x() + + difference.y() * difference.y() + + difference.z() * difference.z())); + + for (ProjMatrixElemsForOneBin::iterator element_ptr = probabilities.begin(); + element_ptr != probabilities.end(); ++element_ptr) { Coordinate3D c(element_ptr->get_coords()); symm_ptr->transform_image_coordinates(c); - voxel_center = - image_info_sptr->get_physical_coordinates_for_indices (c); +// voxel_center = +// image_info_sptr->get_physical_coordinates_for_indices (c); + +// project_point_on_a_line(point1, point2, voxel_center); - /* - * Original method: - * - project_point_on_a_line(point1, point2, voxel_center); +// const CartesianCoordinate3D x = voxel_center - middle; - const CartesianCoordinate3D x = voxel_center - middle; +// const float d2 = -inner_product(x, diff) * lor_length; - const float d1 = inner_product(x, difference) * lor_length; - */ // The following is the optimisation of the previous: { - const CartesianCoordinate3D r10 = voxel_center - middle; - - const float u = inner_product(r10, difference) * denom; - - voxel_center[3] = u * difference[3]; - voxel_center[2] = u * difference[2]; - voxel_center[1] = u * difference[1]; - - if(u < 0.f) - d1 = std::sqrt( voxel_center[3] * voxel_center[3] + - voxel_center[2] * voxel_center[2] + - voxel_center[1] * voxel_center[1]); - else - d1 = -std::sqrt( voxel_center[3] * voxel_center[3] + - voxel_center[2] * voxel_center[2] + - voxel_center[1] * voxel_center[1]); + voxel_center = + image_info_sptr->get_physical_coordinates_for_indices (c); + + const CartesianCoordinate3D x = point2 - voxel_center; + + const float u = inner_product(x, difference) * denom; + + voxel_center[3] = point1[3] + u * difference[3]; + voxel_center[2] = point1[2] + u * difference[2]; + voxel_center[1] = point1[1] + u * difference[1]; + + const CartesianCoordinate3D x_dim = voxel_center - middle; + + d1 = inner_product(x_dim, difference) * lor_length; + } - float low_dist = ((proj_data_info_sptr->tof_bin_boundaries_mm[tof_probabilities.get_bin_ptr()->timing_pos_num()].low_lim - d1) * r_sqrt2_gauss_sigma); - float high_dist = ((proj_data_info_sptr->tof_bin_boundaries_mm[tof_probabilities.get_bin_ptr()->timing_pos_num()].high_lim - d1) * r_sqrt2_gauss_sigma); + low_dist = ((proj_data_info_sptr->tof_bin_boundaries_mm[probabilities.get_bin_ptr()->timing_pos_num()].low_lim - d1) * r_sqrt2_gauss_sigma); + high_dist = ((proj_data_info_sptr->tof_bin_boundaries_mm[probabilities.get_bin_ptr()->timing_pos_num()].high_lim - d1) * r_sqrt2_gauss_sigma); //p1 = (((proj_data_info_sptr->tof_bin_boundaries_mm[tof_probabilities.get_bin_ptr()->timing_pos_num()].low_lim - d1) * r_sqrt2_gauss_sigma) + 4.f) * step; //p2 = (((proj_data_info_sptr->tof_bin_boundaries_mm[tof_probabilities.get_bin_ptr()->timing_pos_num()].high_lim - d1) * r_sqrt2_gauss_sigma) + 4.f) * step; @@ -239,7 +232,6 @@ ProjMatrixByBin::apply_tof_kernel_and_symm_transformation(ProjMatrixElemsForOneB void ProjMatrixByBin:: -//get_tof_value(const float& d1, const float& d2, float& val) const get_tof_value(const float d1, const float d2, float& val) const { val = 0.5f * (erf(d2) - erf(d1)); From ccb6a24054a490ea5c0d4cf6db469ef0d49011e8 Mon Sep 17 00:00:00 2001 From: Nikos Efthimiou Date: Mon, 12 Nov 2018 09:13:09 +1100 Subject: [PATCH 112/509] Bug fixes close #16 : BinNormalisationFromProjData::is_trivial needs TOF loop close #13 : use member record_sptr in PoissonLogLikelihoodWithLinearModelForMeanAndListModeData close #5: consistent naming of timing/tof bin/pos close #4: change get_k to be centre of bin. rename to be consistent in TOF variable --- src/buildblock/ProjDataInfo.cxx | 26 +++++++++---------- ...orMeanAndListModeDataWithProjMatrixByBin.h | 2 -- .../stir/recon_buildblock/ProjMatrixByBin.inl | 8 +++--- .../BinNormalisationFromProjData.cxx | 7 ++++- ...MeanAndListModeDataWithProjMatrixByBin.cxx | 14 +++++----- 5 files changed, 29 insertions(+), 28 deletions(-) diff --git a/src/buildblock/ProjDataInfo.cxx b/src/buildblock/ProjDataInfo.cxx index c15d97f087..117a671ff8 100644 --- a/src/buildblock/ProjDataInfo.cxx +++ b/src/buildblock/ProjDataInfo.cxx @@ -78,15 +78,15 @@ float ProjDataInfo::get_k(const Bin& bin) const { if (!(num_tof_bins%2)) - return bin.timing_pos_num() * tof_increament_in_mm; + return bin.timing_pos_num() * tof_increament_in_mm + tof_increament_in_mm / 2.f; else - return (bin.timing_pos_num() * tof_increament_in_mm) - tof_increament_in_mm/2.f; + return (bin.timing_pos_num() * tof_increament_in_mm); } double ProjDataInfo::get_tof_delta_time(const Bin& bin) const { - return mm_to_tof_delta_time(get_k(bin) + tof_increament_in_mm / 2.f); // get_k gives "left" edge + return mm_to_tof_delta_time(get_k(bin)); // get_k gives "left" edge N.E: corrected returns the center. } float @@ -214,21 +214,21 @@ ProjDataInfo::set_tof_mash_factor(const int new_num) tof_bin_boundaries_ps.grow(min_tof_pos_num, max_tof_pos_num); - for (int i = min_tof_pos_num; i <= max_tof_pos_num; ++i ) + for (int k = min_tof_pos_num; k <= max_tof_pos_num; ++k ) { Bin bin; - bin.timing_pos_num() = i; + bin.timing_pos_num() = k; - float cur_low = get_k(bin); - float cur_high = get_k(bin) + get_sampling_in_k(bin); + float cur_low = get_k(bin) - get_sampling_in_k(bin)/2.f; + float cur_high = get_k(bin) + get_sampling_in_k(bin)/2.f; - tof_bin_boundaries_mm[i].low_lim = cur_low; - tof_bin_boundaries_mm[i].high_lim = cur_high; - tof_bin_boundaries_ps[i].low_lim = static_cast(mm_to_tof_delta_time(tof_bin_boundaries_mm[i].low_lim)); - tof_bin_boundaries_ps[i].high_lim = static_cast(mm_to_tof_delta_time(tof_bin_boundaries_mm[i].high_lim)); + tof_bin_boundaries_mm[k].low_lim = cur_low; + tof_bin_boundaries_mm[k].high_lim = cur_high; + tof_bin_boundaries_ps[k].low_lim = static_cast(mm_to_tof_delta_time(tof_bin_boundaries_mm[k].low_lim)); + tof_bin_boundaries_ps[k].high_lim = static_cast(mm_to_tof_delta_time(tof_bin_boundaries_mm[k].high_lim)); // I could imagine a better printing. - info(boost::format("Tbin %1%: %2% - %3% mm (%4% - %5% ps) = %6%") %i % tof_bin_boundaries_mm[i].low_lim % tof_bin_boundaries_mm[i].high_lim - % tof_bin_boundaries_ps[i].low_lim % tof_bin_boundaries_ps[i].high_lim % get_sampling_in_k(bin)); + info(boost::format("Tbin %1%: %2% - %3% mm (%4% - %5% ps) = %6%") %k % tof_bin_boundaries_mm[k].low_lim % tof_bin_boundaries_mm[k].high_lim + % tof_bin_boundaries_ps[k].low_lim % tof_bin_boundaries_ps[k].high_lim % get_sampling_in_k(bin)); } } else if ((scanner_ptr->is_tof_ready() && new_num <= 0) diff --git a/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.h b/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.h index 0fa1a6bb3a..4628cb4005 100644 --- a/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.h +++ b/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.h @@ -122,8 +122,6 @@ typedef RegisteredParsingObject proj_data_info_sptr; - shared_ptr record_sptr; - //! sets any default values /*! Has to be called by set_defaults in the leaf-class */ virtual void set_defaults(); diff --git a/src/include/stir/recon_buildblock/ProjMatrixByBin.inl b/src/include/stir/recon_buildblock/ProjMatrixByBin.inl index 79c607d155..41c7351036 100644 --- a/src/include/stir/recon_buildblock/ProjMatrixByBin.inl +++ b/src/include/stir/recon_buildblock/ProjMatrixByBin.inl @@ -165,7 +165,7 @@ ProjMatrixByBin::apply_tof_kernel_and_symm_transformation(ProjMatrixElemsForOneB const float denom = 1.f / inner_product(difference, difference); - const float lor_length = 1.f / (std::sqrt(difference.x() * difference.x() + + const float lor_length = 2.f / (std::sqrt(difference.x() * difference.x() + difference.y() * difference.y() + difference.z() * difference.z())); @@ -200,7 +200,7 @@ ProjMatrixByBin::apply_tof_kernel_and_symm_transformation(ProjMatrixElemsForOneB const CartesianCoordinate3D x_dim = voxel_center - middle; - d1 = inner_product(x_dim, difference) * lor_length; + d1 = -inner_product(x_dim, difference) * lor_length; } @@ -224,8 +224,8 @@ ProjMatrixByBin::apply_tof_kernel_and_symm_transformation(ProjMatrixElemsForOneB continue; } -// get_tof_value(low_dist, high_dist, new_value); - new_value = element_ptr->get_value() * 0.5f * (erf(high_dist) - erf(low_dist));//*(cache_erf[p2] - cache_erf[p1]); // + get_tof_value(low_dist, high_dist, new_value); + //new_value = element_ptr->get_value() *(cache_erf[p2] - cache_erf[p1]); *element_ptr = ProjMatrixElemsForOneBin::value_type(c, new_value); } } diff --git a/src/recon_buildblock/BinNormalisationFromProjData.cxx b/src/recon_buildblock/BinNormalisationFromProjData.cxx index 59aaa52d13..fa4b503fa6 100644 --- a/src/recon_buildblock/BinNormalisationFromProjData.cxx +++ b/src/recon_buildblock/BinNormalisationFromProjData.cxx @@ -113,6 +113,10 @@ bool BinNormalisationFromProjData:: is_trivial() const { + for (int tof_pos = this->norm_proj_data_ptr->get_min_tof_pos_num(); + tof_pos <= this->norm_proj_data_ptr->get_max_tof_pos_num(); + ++tof_pos) + { // check if all data is 1 (up to a tolerance of 1e-4) for (int segment_num = this->norm_proj_data_ptr->get_min_segment_num(); segment_num <= this->norm_proj_data_ptr->get_max_segment_num(); @@ -123,11 +127,12 @@ is_trivial() const ++view_num) { const Viewgram viewgram = - this->norm_proj_data_ptr->get_viewgram(view_num, segment_num); + this->norm_proj_data_ptr->get_viewgram(view_num, segment_num, tof_pos); if (fabs(viewgram.find_min()-1)>.0001 || fabs(viewgram.find_max()-1)>.0001) return false; // return from function as we know not all data is 1 } } + } // if we get here. they were all 1 return true; } diff --git a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx index c096621df6..4f7637b6fa 100644 --- a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx +++ b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx @@ -250,8 +250,6 @@ set_up_before_sensitivity(shared_ptr const& target_sptr) return Succeeded::no; } - record_sptr = this->list_mode_data_sptr->get_empty_record_sptr(); - return Succeeded::yes; } @@ -473,7 +471,7 @@ compute_sub_gradient_without_penalty_plus_sensitivity(TargetT& gradient, double current_time = 0.; ProjMatrixElemsForOneBin proj_matrix_row; - CListRecord& record = *record_sptr; + shared_ptr record_sptr = this->list_mode_data_sptr->get_empty_record_sptr(); long int more_events = this->do_time_frame? 1 : (this->num_events_to_use / this->num_subsets); @@ -481,26 +479,26 @@ compute_sub_gradient_without_penalty_plus_sensitivity(TargetT& gradient, while (more_events)//this->list_mode_data_sptr->get_next_record(record) == Succeeded::yes) { - if (this->list_mode_data_sptr->get_next_record(record) == Succeeded::no) + if (this->list_mode_data_sptr->get_next_record(*record_sptr) == Succeeded::no) { info("End of file!"); break; //get out of while loop } - if(record.is_time() && end_time > 0.01) + if(record_sptr->is_time() && end_time > 0.01) { - current_time = record.time().get_time_in_secs(); + current_time = record_sptr->time().get_time_in_secs(); if (this->do_time_frame && current_time >= end_time) break; // get out of while loop if (current_time < start_time) continue; } - if (record.is_event() && record.event().is_prompt()) + if (record_sptr->is_event() && record_sptr->event().is_prompt()) { measured_bin.set_bin_value(1.0f); - record.event().get_bin(measured_bin, *proj_data_info_sptr); + record_sptr->event().get_bin(measured_bin, *proj_data_info_sptr); // In theory we have already done all these checks so we can // remove this if statement. From b2778ed062496766745fb2c6c134d4bca6d71239 Mon Sep 17 00:00:00 2001 From: Nikos Efthimiou Date: Mon, 12 Nov 2018 11:17:59 +1100 Subject: [PATCH 113/509] Keep only two set_params and constructors for Scanner With the addition of the energy resolution and timing resolution the initialisation of the scanner became very ambiguous. I unified all version in two cases in which all parameters have to set by the user, even if they are not used. --- src/IO/InterfileHeader.cxx | 74 ++-- src/IO/InterfilePDFSHeaderSPECT.cxx | 46 ++- src/buildblock/Scanner.cxx | 595 +++++++--------------------- src/include/stir/Scanner.h | 153 +------ 4 files changed, 202 insertions(+), 666 deletions(-) diff --git a/src/IO/InterfileHeader.cxx b/src/IO/InterfileHeader.cxx index 3d75991781..a6c3ce0896 100644 --- a/src/IO/InterfileHeader.cxx +++ b/src/IO/InterfileHeader.cxx @@ -1321,57 +1321,31 @@ bool InterfilePDFSHeader::post_processing() // data from the Interfile header (or the guessed scanner). shared_ptr scanner_sptr_from_file; - if (false) // !guessed_scanner_ptr->is_tof_ready()) - { - scanner_sptr_from_file.reset( - new Scanner(guessed_scanner_ptr->get_type(), - get_exam_info_ptr()->originating_system, - num_detectors_per_ring, - num_rings, - max_num_non_arccorrected_bins, - default_num_arccorrected_bins, - static_cast(inner_ring_diameter_in_cm*10./2), - static_cast(average_depth_of_interaction_in_cm*10), - static_cast(distance_between_rings_in_cm*10.), - static_cast(default_bin_size_in_cm*10), - static_cast(view_offset_in_degrees*_PI/180), - num_axial_blocks_per_bucket, - num_transaxial_blocks_per_bucket, - num_axial_crystals_per_block, - num_transaxial_crystals_per_block, - num_axial_crystals_per_singles_unit, - num_transaxial_crystals_per_singles_unit, - num_detector_layers, - energy_resolution, - reference_energy)); - } - else - { - warning("ENERGY WINDOW INFO IGNORED"); - scanner_sptr_from_file.reset( - new Scanner(guessed_scanner_ptr->get_type(), - get_exam_info_ptr()->originating_system, - num_detectors_per_ring, - num_rings, - max_num_non_arccorrected_bins, - default_num_arccorrected_bins, - static_cast(inner_ring_diameter_in_cm*10./2), - static_cast(average_depth_of_interaction_in_cm*10), - static_cast(distance_between_rings_in_cm*10.), - static_cast(default_bin_size_in_cm*10), - static_cast(view_offset_in_degrees*_PI/180), - num_axial_blocks_per_bucket, - num_transaxial_blocks_per_bucket, - num_axial_crystals_per_block, - num_transaxial_crystals_per_block, - num_axial_crystals_per_singles_unit, - num_transaxial_crystals_per_singles_unit, - num_detector_layers, - max_num_timing_poss, - size_of_timing_pos, - timing_resolution)); - } + scanner_sptr_from_file.reset( + new Scanner(guessed_scanner_ptr->get_type(), + get_exam_info_ptr()->originating_system, + num_detectors_per_ring, + num_rings, + max_num_non_arccorrected_bins, + default_num_arccorrected_bins, + static_cast(inner_ring_diameter_in_cm*10./2), + static_cast(average_depth_of_interaction_in_cm*10), + static_cast(distance_between_rings_in_cm*10.), + static_cast(default_bin_size_in_cm*10), + static_cast(view_offset_in_degrees*_PI/180), + num_axial_blocks_per_bucket, + num_transaxial_blocks_per_bucket, + num_axial_crystals_per_block, + num_transaxial_crystals_per_block, + num_axial_crystals_per_singles_unit, + num_transaxial_crystals_per_singles_unit, + num_detector_layers, + energy_resolution, + reference_energy, + max_num_timing_poss, + size_of_timing_pos, + timing_resolution)); bool is_consistent = scanner_sptr_from_file->check_consistency() == Succeeded::yes; diff --git a/src/IO/InterfilePDFSHeaderSPECT.cxx b/src/IO/InterfilePDFSHeaderSPECT.cxx index bc043232d1..c13392ba18 100644 --- a/src/IO/InterfilePDFSHeaderSPECT.cxx +++ b/src/IO/InterfilePDFSHeaderSPECT.cxx @@ -163,27 +163,37 @@ bool InterfilePDFSHeaderSPECT::post_processing() const int num_axial_crystals_per_singles_unit = -1; const int num_transaxial_crystals_per_singles_unit = -1; const int num_detector_layers = 1; + const float energy_resolution = -1.f; + const float reference_energy = -1.f; + const short int max_num_of_timing_poss = 1; + const float size_timing_pos = -1.f; + const float timing_resolution = -1.f; shared_ptr guessed_scanner_ptr(Scanner::get_scanner_from_name(get_exam_info_ptr()->originating_system)); shared_ptr scanner_ptr_from_file( - new Scanner(guessed_scanner_ptr->get_type(), - get_exam_info_ptr()->originating_system, - num_detectors_per_ring, - num_rings, - max_num_non_arccorrected_bins, - default_num_arccorrected_bins, - static_cast(radii[0]), - static_cast(average_depth_of_interaction_in_cm*10), - static_cast(distance_between_rings_in_cm*10.), - static_cast(default_bin_size_in_cm*10), - static_cast(view_offset_in_degrees*_PI/180), - num_axial_blocks_per_bucket, - num_transaxial_blocks_per_bucket, - num_axial_crystals_per_block, - num_transaxial_crystals_per_block, - num_axial_crystals_per_singles_unit, - num_transaxial_crystals_per_singles_unit, - num_detector_layers)); + new Scanner(guessed_scanner_ptr->get_type(), + get_exam_info_ptr()->originating_system, + num_detectors_per_ring, + num_rings, + max_num_non_arccorrected_bins, + default_num_arccorrected_bins, + static_cast(radii[0]), + static_cast(average_depth_of_interaction_in_cm*10), + static_cast(distance_between_rings_in_cm*10.), + static_cast(default_bin_size_in_cm*10), + static_cast(view_offset_in_degrees*_PI/180), + num_axial_blocks_per_bucket, + num_transaxial_blocks_per_bucket, + num_axial_crystals_per_block, + num_transaxial_crystals_per_block, + num_axial_crystals_per_singles_unit, + num_transaxial_crystals_per_singles_unit, + num_detector_layers, + energy_resolution, + reference_energy, + max_num_of_timing_poss, + size_timing_pos, + timing_resolution)); #if 0 if (default_bin_size_in_cm <= 0) default_bin_size_in_cm = diff --git a/src/buildblock/Scanner.cxx b/src/buildblock/Scanner.cxx index 9bb456fe6d..756ee8e0d3 100644 --- a/src/buildblock/Scanner.cxx +++ b/src/buildblock/Scanner.cxx @@ -120,9 +120,11 @@ Scanner::Scanner(Type scanner_type) // KT 25/01/2002 corrected ring_spacing set_params(E931, string_list("ECAT 931"), - 8, 192, 2 * 256, + 8, 192, 2 * 256, 2*256, 510.0F, 7.0F, 13.5F, 3.129F, 0.0F, - 2, 4, 4, 8, 4, 8 * 4, 1); + 2, 4, 4, 8, 4, 8 * 4, 1, + 0.0F, 511.F, + 1, 1.F, 1.F); // 16 BUCKETS per ring in TWO rings - i.e. 32 buckets in total break; @@ -130,66 +132,82 @@ Scanner::Scanner(Type scanner_type) case E951: set_params(E951, string_list("ECAT 951"), - 16, 192, 2 * 256, + 16, 192, 2 * 256, 2*256, 510.0F, 7.0F, 6.75F, 3.12932F, 0.0F, - 1, 4, 8, 8, 8, 8 * 4, 1); + 1, 4, 8, 8, 8, 8 * 4, 1, + 0.0F, 511.F, + 1, 1.F, 1.F); break; case E953: set_params(E953, string_list("ECAT 953"), - 16, 160, 2 * 192, + 16, 160, 2 * 192, 2*192, 382.5F, 7.0F, 6.75F, 3.12932F, static_cast(15.*_PI/180), - 1, 4, 8, 8, 8, 8 * 4, 1); + 1, 4, 8, 8, 8, 8 * 4, 1, + 0.0F, 511.F, + 1, 1.F, 1.F); break; case E921: set_params(E921, string_list("ECAT 921", "ECAT EXACT", "EXACT"), - 24, 192, 2* 192, + 24, 192, 2* 192, 2 * 192, 412.5F, 7.0F, 6.75F, 3.375F, static_cast(15.*_PI/180), - 1, 4, 8, 8, 8, 8 * 4, 1); + 1, 4, 8, 8, 8, 8 * 4, 1, + 0.0F, 511.F, + 1, 1.F, 1.F); break; case E925: set_params(E925, string_list("ECAT 925", "ECAT ART"), - 24, 192, 2* 192, + 24, 192, 2* 192, 2* 192, 412.5F, 7.0F, 6.75F, 3.375F, static_cast(15.*_PI/180), - 3, 4, 8, 8, 8, 8 * 4, 1); + 3, 4, 8, 8, 8, 8 * 4, 1, + 0.0F, 511.F, + 1, 1.F, 1.F); break; case E961: set_params(E961,string_list("ECAT 961", "ECAT HR"), - 24, 336, 2* 392, + 24, 336, 2* 392, 2*392, 412.0F, 7.0F, 6.25F, 1.650F, static_cast(13.*_PI/180), - 1, 8, 8, 7, 8, 7 * 8, 1); + 1, 8, 8, 7, 8, 7 * 8, 1, + 0.0F, 511.F, + 1, 1.F, 1.F); break; case E962: set_params(E962,string_list("ECAT 962","ECAT HR+"), - 32, 288, 2* 288, + 32, 288, 2* 288, 2*288, 412.0F, 7.0F, 4.85F, 2.25F, 0.0F, - 4, 3, 8, 8, 8, 8 * 3, 1); + 4, 3, 8, 8, 8, 8 * 3, 1, + 0.0F, 511.F, + 1, 1.F, 1.F); break; case E966: set_params(E966, string_list("ECAT EXACT 3D", "EXACT 3D", "ECAT HR++","ECAT 966"), - 48, 288, 2* 288, + 48, 288, 2* 288, 2*288, 412.0F, 7.0F, 4.850F, 2.250F, 0.0, - 6, 2, 8, 8, 2 * 8, 8 * 2, 1); + 6, 2, 8, 8, 2 * 8, 8 * 2, 1, + 0.0F, 511.F, + 1, 1.F, 1.F); break; case E1080: // data added by Robert Barnett, Westmead Hospital, Sydney set_params(E1080, string_list("ECAT 1080", "Biograph 16", "1080"), - 41, 336, 2* 336, + 41, 336, 2* 336, 2*336, 412.0F, 7.0F, 4.0F, 2.000F, 0.0F, - 1, 2, 41, 14, 41, 14, 1); + 1, 2, 41, 14, 41, 14, 1, + 0.0F, 511.F, + 1, 1.F, 1.F); // Transaxial blocks have 13 physical crystals and a gap at the // 140th crystal where the counts are zero. // There are 39 rings with 13 axial crystals per block. Two virtual @@ -201,17 +219,20 @@ Scanner::Scanner(Type scanner_type) // Transaxial blocks have 8 physical crystals and a gap at the // 9th crystal where the counts are zero. set_params(Siemens_mMR, string_list("Siemens mMR", "mMR", "2008"), - 64, 344, 2* 252, + 64, 344, 2* 252, 2*252, 328.0F, 7.0F, 4.0625F, 2.08626F, 0.0F, - 2, 1, 8, 9, 16, 9, 1 ); // TODO bucket/singles info incorrect? 224 buckets in total, but not sure how distributed + 2, 1, 8, 9, 16, 9, 1, + 0.0F, 511.F, + 1, 1.F, 1.F ); // TODO bucket/singles info incorrect? 224 buckets in total, but not sure how distributed break; case test_scanner: // This is a relatively small scanner for test purposes. set_params(test_scanner, string_list("test_scanner"), - 4, 344, 2*252, + 4, 344, 2*252,2*252, 328.0F, 7.0F, 4.0625F, 2.08626F, 0.0F, 1, 1, 4, 1, 4, 1, 1, + 0.0F, 511.F, (short int)(410), (float)(10.0F), (float)(400.0F) ); @@ -220,9 +241,11 @@ Scanner::Scanner(Type scanner_type) case RPT: set_params(RPT, string_list("PRT-1", "RPT"), - 16, 128, 2 * 192, + 16, 128, 2 * 192, 2*192, 380.0F - 7.0F, 7.0F, 6.75F, 3.1088F, 0.0F, - 1, 4, 8, 8, 8, 32, 1); + 1, 4, 8, 8, 8, 32, 1, + 0.0F, 511.F, + 1, 1.F, 1.F); // Default 7.0mm average interaction depth. // This 7mm taken off the inner ring radius so that the effective radius remains 380mm @@ -231,9 +254,11 @@ Scanner::Scanner(Type scanner_type) case RATPET: set_params(RATPET, string_list("RATPET"), - 8, 56, 2 * 56, + 8, 56, 2 * 56, 2*56, 115 / 2.F, 7.0F, 6.25F, 1.65F, 0.0F, - 1, 16, 8, 7, 8, 0, 1); // HR block, 4 buckets per ring + 1, 16, 8, 7, 8, 0, 1, + 0.0F, 511.F, + 1, 1.F, 1.F); // HR block, 4 buckets per ring // Default 7.0mm average interaction depth. // 8 x 0 crystals per singles unit because not known @@ -245,7 +270,9 @@ Scanner::Scanner(Type scanner_type) set_params(PANDA, string_list("PANDA"), 1 /*NumRings*/, 512 /*MaxBinsNonArcCor*/, 512 /*MaxBinsArcCor*/, 2048 /*NumDetPerRing*/, /*MeanInnerRadius*/ 75.5/2.F, /*AverageDoI*/ 10.F, /*Ring Spacing*/ 3.F, /*BinSize*/ 0.1F, /*IntrinsicTilt*/ 0.F, - 1, 1, 1, 1, 0, 0, 1); + 1, 1, 1, 1, 0, 0, 1, + 0.0F, 511.F, + 1, 1.F, 1.F); break; case nanoPET: @@ -254,7 +281,9 @@ Scanner::Scanner(Type scanner_type) 81, 39*3, /* We could also model gaps in the future as one detector so 39->39+1, while 1 (point source), 3 (mouse) or 5 (rats) */ 39*3, /* Just put the same with NonArcCor for now*/ 12 * 39, 174.F, 5.0F, 1.17F, 1.17F, /* Actual size is 1.12 and 0.05 is the thickness of the optical reflector */ 0.0F, /* not sure for this */ - 0,0,0,0,0,0, 1); + 0,0,0,0,0,0, 1, + 0.0F, 511.F, + 1, 1.F, 1.F); break; case HYPERimage: @@ -262,7 +291,9 @@ Scanner::Scanner(Type scanner_type) set_params(HYPERimage, string_list("HYPERimage"), /*Modelling the gap with one fake crystal */ 22, 239, 245, 490, 103.97F, 3.0F, 1.4F, 1.4F, /* Actual size is 1.3667 and assume 0.0333 is the thickness of the optical reflector */ 0.F, - 0,0,0,0,0,0,1); + 0,0,0,0,0,0,1, + 0.0F, 511.F, + 1, 1.F, 1.F); break; @@ -274,7 +305,9 @@ Scanner::Scanner(Type scanner_type) set_params(Advance, string_list("GE Advance", "Advance"), 18, 283, 281, 2 * 336, 471.875F - 8.4F, 8.4F, 8.5F, 1.970177F, 0.0F, //TODO view offset shouldn't be zero - 3, 2, 6, 6, 1, 1, 1); + 3, 2, 6, 6, 1, 1, 1, + 0.0F, 511.F, + 1, 1.F, 1.F); break; case DiscoveryLS: @@ -282,7 +315,9 @@ Scanner::Scanner(Type scanner_type) set_params(DiscoveryLS, string_list("GE Discovery LS", "Discovery LS"), 18, 283, 281, 2 * 336, 471.875F - 8.4F, 8.4F, 8.5F, 1.970177F, 0.0F, //TODO view offset shouldn't be zero - 3, 2, 6, 6, 1, 1, 1); + 3, 2, 6, 6, 1, 1, 1, + 0.0F, 511.F, + 1, 1.F, 1.F); break; case DiscoveryST: @@ -293,7 +328,9 @@ Scanner::Scanner(Type scanner_type) 24, 249, 221, 2 * 210, 886.2F/2.F, 8.4F, 6.54F, 3.195F, static_cast(-4.54224*_PI/180),//sign? - 4, 2, 6, 6, 1, 1, 1);// TODO not sure about sign of view_offset + 4, 2, 6, 6, 1, 1, 1, + 0.0F, 511.F, + 1, 1.F, 1.F);// TODO not sure about sign of view_offset break; case DiscoverySTE: @@ -303,6 +340,7 @@ Scanner::Scanner(Type scanner_type) 886.2F/2.F, 8.4F, 6.54F, 2.397F, static_cast(-4.5490*_PI/180),//sign? 4, 2, 6, 8, 1, 1, 1, + 0.0F, 511.F, (short int)(410), (float)(10.0F), (float)(400.0F) );// TODO not sure about sign of view_offset @@ -321,7 +359,9 @@ Scanner::Scanner(Type scanner_type) static_cast(-4.5950*_PI/180),//sign? 4, 2, - 6, 9, 1, 1, 1);// TODO not sure about sign of view_offset + 6, 9, 1, 1, 1, + 0.0F, 511.F, + 1, 1.F, 1.F);// TODO not sure about sign of view_offset break; case Discovery600: @@ -338,7 +378,9 @@ Scanner::Scanner(Type scanner_type) static_cast(-4.5490*_PI/180),//sign? TODO value 4, 2, - 6, 8, 1, 1, 1); + 6, 8, 1, 1, 1, + 0.0F, 511.F, + 1, 1.F, 1.F); break; case PETMR_Signa: @@ -355,7 +397,8 @@ case PETMR_Signa: static_cast(-5.23*_PI/180),//sign? TODO value 5, 4, - 9, 4, 1, 1, 1, + 9, 4, 1, 1, 1, + 0.0F, 511.F, (short int)(351), (float)(89.0F/13.0F), //TODO (float)(390.0F) ); @@ -376,22 +419,22 @@ case PETMR_Signa: static_cast(-5.021*_PI/180),//sign? TODO value 4, 2, - 6, 9, 1, 1, 1 -#ifdef STIR_TOF - , + 6, 9, 1, 1, 1, + 0.0F, 511.F, (short int)(55), (float)(89.0F), (float)(550.0F) -#endif ); break; case HZLR: set_params(HZLR, string_list("Positron HZL/R"), - 32, 256, 2 * 192, + 32, 256, 2 * 192, 2*192, 780.0F, 7.0F, 5.1875F, 2.F, 0.0F, - 0, 0, 0, 0, 0,0, 1); + 0, 0, 0, 0, 0,0, 1, + 0.0F, 511.F, + 1, 1.F, 1.F); // Default 7.0mm average interaction depth. // crystals per singles unit etc unknown break; @@ -399,9 +442,11 @@ case PETMR_Signa: case HRRT: set_params(HRRT, string_list("HRRT"), - 104, 288, 2 * 288, + 104, 288, 2 * 288, 2*288, 234.765F, 7.0F, 2.4375F, 1.21875F, 0.0F, - 0, 0, 0, 0, 0, 0, 2); // added by Dylan Togane + 0, 0, 0, 0, 0, 0, 2, + 0.0F, 511.F, + 1, 1.F, 1.F); // added by Dylan Togane // warning: used 7.0mm average interaction depth. // crystals per singles unit etc unknown break; @@ -430,13 +475,15 @@ case PETMR_Signa: scanner. */ set_params(Allegro,string_list("Allegro", "Philips Allegro"), - 29, 295, 28*23, + 29, 295, 28*23, 28*23, 430.05F, 12.F, 6.3F, 4.3F, 0.0F, 1, 0, 29, 0 /* 23* or 22*/, 29, 0 /* all detectors in a ring? */, - 1); + 1, + 0.0F, 511.F, + 1, 1.F, 1.F); break; case GeminiTF: @@ -448,32 +495,40 @@ case PETMR_Signa: 0, 0, 0, 0, // Not considering any gap, but this is per module 28 flat modules in total, while 420 PMTs 0, 0 /* Not sure about these, but shouldn't be important */, - 1); + 1, + 0.0F, 511.F, + 1, 1.F, 1.F); break; case HiDAC: // all of these don't make any sense for the HiDAC set_params(HiDAC, string_list("HiDAC"), - 0, 0, 0, + 0, 0, 0,0, 0.F, 0.F, 0.F, 0.F, 0.F, - 0, 0, 0, 0, 0, 0, 0); + 0, 0, 0, 0, 0, 0, 0, + 0.0F, 511.F, + 1, 1.F, 1.F); break; case User_defined_scanner: // zlong, 08-04-2004, Userdefined support set_params(User_defined_scanner, string_list("Userdefined"), - 0, 0, 0, + 0, 0, 0,0, 0.F, 0.F, 0.F, 0.F, 0.F, - 0, 0, 0, 0, 0, 0, 0); + 0, 0, 0, 0, 0, 0, 0, + 0.0F, 511.F, + 1, 1.F, 1.F); break; default: // warning("Unknown scanner type used for initialisation of Scanner\n"); set_params(Unknown_scanner, string_list("Unknown"), - 0, 0, 0, + 0, 0, 0,0, 0.F, 0.F, 0.F, 0.F, 0.F, - 0, 0, 0, 0, 0, 0, 0); + 0, 0, 0, 0, 0, 0, 0, + 0.0F, 511.F, + 1, 1.F, 1.F); break; @@ -481,33 +536,6 @@ case PETMR_Signa: } - -Scanner::Scanner(Type type_v, const list& list_of_names_v, - int num_detectors_per_ring_v, int num_rings_v, - int max_num_non_arccorrected_bins_v, - int default_num_arccorrected_bins_v, - float inner_ring_radius_v, float average_depth_of_interaction_v, - float ring_spacing_v, float bin_size_v, float intrinsic_tilt_v, - int num_axial_blocks_per_bucket_v, int num_transaxial_blocks_per_bucket_v, - int num_axial_crystals_per_block_v, int num_transaxial_crystals_per_block_v, - int num_axial_crystals_per_singles_unit_v, - int num_transaxial_crystals_per_singles_unit_v, - int num_detector_layers_v) -{ - set_params(type_v, list_of_names_v, num_rings_v, - max_num_non_arccorrected_bins_v, - default_num_arccorrected_bins_v, - num_detectors_per_ring_v, - inner_ring_radius_v, - average_depth_of_interaction_v, - ring_spacing_v, bin_size_v, intrinsic_tilt_v, - num_axial_blocks_per_bucket_v, num_transaxial_blocks_per_bucket_v, - num_axial_crystals_per_block_v, num_transaxial_crystals_per_block_v, - num_axial_crystals_per_singles_unit_v, - num_transaxial_crystals_per_singles_unit_v, - num_detector_layers_v); -} - Scanner::Scanner(Type type_v, const list& list_of_names_v, int num_detectors_per_ring_v, int num_rings_v, int max_num_non_arccorrected_bins_v, @@ -520,308 +548,51 @@ Scanner::Scanner(Type type_v, const list& list_of_names_v, int num_transaxial_crystals_per_singles_unit_v, int num_detector_layers_v, float energy_resolution_v, - float reference_energy_v) -{ - set_params(type_v, list_of_names_v, num_rings_v, - max_num_non_arccorrected_bins_v, - default_num_arccorrected_bins_v, - num_detectors_per_ring_v, - inner_ring_radius_v, - average_depth_of_interaction_v, - ring_spacing_v, bin_size_v, intrinsic_tilt_v, - energy_resolution_v, - reference_energy_v, - num_axial_blocks_per_bucket_v, num_transaxial_blocks_per_bucket_v, - num_axial_crystals_per_block_v, num_transaxial_crystals_per_block_v, - num_axial_crystals_per_singles_unit_v, - num_transaxial_crystals_per_singles_unit_v, - num_detector_layers_v); -} - -Scanner::Scanner(Type type_v, const list& list_of_names_v, - int num_detectors_per_ring_v, int num_rings_v, - int max_num_non_arccorrected_bins_v, - int default_num_arccorrected_bins_v, - float inner_ring_radius_v, float average_depth_of_interaction_v, - float ring_spacing_v, float bin_size_v, float intrinsic_tilt_v, - int num_axial_blocks_per_bucket_v, int num_transaxial_blocks_per_bucket_v, - int num_axial_crystals_per_block_v, int num_transaxial_crystals_per_block_v, - int num_axial_crystals_per_singles_unit_v, - int num_transaxial_crystals_per_singles_unit_v, - int num_detector_layers_v, + float reference_energy_v, short int max_num_of_timing_poss_v, float size_timing_pos_v, float timing_resolution_v) { - set_params(type_v, list_of_names_v, num_rings_v, - max_num_non_arccorrected_bins_v, - default_num_arccorrected_bins_v, - num_detectors_per_ring_v, - inner_ring_radius_v, - average_depth_of_interaction_v, - ring_spacing_v, bin_size_v, intrinsic_tilt_v, - num_axial_blocks_per_bucket_v, num_transaxial_blocks_per_bucket_v, - num_axial_crystals_per_block_v, num_transaxial_crystals_per_block_v, - num_axial_crystals_per_singles_unit_v, - num_transaxial_crystals_per_singles_unit_v, - num_detector_layers_v, - max_num_of_timing_poss_v, - size_timing_pos_v, - timing_resolution_v); -} - - -Scanner::Scanner(Type type_v, const string& name, - int num_detectors_per_ring_v, int num_rings_v, - int max_num_non_arccorrected_bins_v, - int default_num_arccorrected_bins_v, - float inner_ring_radius_v, float average_depth_of_interaction_v, - float ring_spacing_v, float bin_size_v, float intrinsic_tilt_v, - int num_axial_blocks_per_bucket_v, int num_transaxial_blocks_per_bucket_v, - int num_axial_crystals_per_block_v, int num_transaxial_crystals_per_block_v, - int num_axial_crystals_per_singles_unit_v, - int num_transaxial_crystals_per_singles_unit_v, - int num_detector_layers_v) -{ - set_params(type_v, string_list(name), num_rings_v, - max_num_non_arccorrected_bins_v, - default_num_arccorrected_bins_v, - num_detectors_per_ring_v, - inner_ring_radius_v, - average_depth_of_interaction_v, - ring_spacing_v, bin_size_v, intrinsic_tilt_v, - num_axial_blocks_per_bucket_v, num_transaxial_blocks_per_bucket_v, - num_axial_crystals_per_block_v, num_transaxial_crystals_per_block_v, - num_axial_crystals_per_singles_unit_v, - num_transaxial_crystals_per_singles_unit_v, - num_detector_layers_v); -} - -Scanner::Scanner(Type type_v, const string& name, - int num_detectors_per_ring_v, int num_rings_v, - int max_num_non_arccorrected_bins_v, - int default_num_arccorrected_bins_v, - float inner_ring_radius_v, float average_depth_of_interaction_v, - float ring_spacing_v, float bin_size_v, float intrinsic_tilt_v, - int num_axial_blocks_per_bucket_v, int num_transaxial_blocks_per_bucket_v, - int num_axial_crystals_per_block_v, int num_transaxial_crystals_per_block_v, - int num_axial_crystals_per_singles_unit_v, - int num_transaxial_crystals_per_singles_unit_v, - int num_detector_layers_v, - float energy_resolution_v, - float reference_energy_v) -{ - set_params(type_v, string_list(name), num_rings_v, + set_params(type_v, list_of_names_v, num_rings_v, max_num_non_arccorrected_bins_v, default_num_arccorrected_bins_v, num_detectors_per_ring_v, inner_ring_radius_v, average_depth_of_interaction_v, ring_spacing_v, bin_size_v, intrinsic_tilt_v, - energy_resolution_v, - reference_energy_v, num_axial_blocks_per_bucket_v, num_transaxial_blocks_per_bucket_v, num_axial_crystals_per_block_v, num_transaxial_crystals_per_block_v, num_axial_crystals_per_singles_unit_v, num_transaxial_crystals_per_singles_unit_v, - num_detector_layers_v); -} - -Scanner::Scanner(Type type_v, const string& name, - int num_detectors_per_ring_v, int num_rings_v, - int max_num_non_arccorrected_bins_v, - int default_num_arccorrected_bins_v, - float inner_ring_radius_v, float average_depth_of_interaction_v, - float ring_spacing_v, float bin_size_v, float intrinsic_tilt_v, - int num_axial_blocks_per_bucket_v, int num_transaxial_blocks_per_bucket_v, - int num_axial_crystals_per_block_v, int num_transaxial_crystals_per_block_v, - int num_axial_crystals_per_singles_unit_v, - int num_transaxial_crystals_per_singles_unit_v, - int num_detector_layers_v, - short int max_num_of_timing_poss_v, - float size_timing_pos_v, - float timing_resolution_v - ) -{ - set_params(type_v, string_list(name), num_rings_v, - max_num_non_arccorrected_bins_v, - default_num_arccorrected_bins_v, - num_detectors_per_ring_v, - inner_ring_radius_v, - average_depth_of_interaction_v, - ring_spacing_v, bin_size_v, intrinsic_tilt_v, - num_axial_blocks_per_bucket_v, num_transaxial_blocks_per_bucket_v, - num_axial_crystals_per_block_v, num_transaxial_crystals_per_block_v, - num_axial_crystals_per_singles_unit_v, - num_transaxial_crystals_per_singles_unit_v, - num_detector_layers_v, - max_num_of_timing_poss_v, - size_timing_pos_v, - timing_resolution_v); -} - -void -Scanner:: -set_params(Type type_v,const list& list_of_names_v, - int num_rings_v, - int max_num_non_arccorrected_bins_v, - int num_detectors_per_ring_v, - float inner_ring_radius_v, - float average_depth_of_interaction_v, - float ring_spacing_v, - float bin_size_v, float intrinsic_tilt_v, - int num_axial_blocks_per_bucket_v, int num_transaxial_blocks_per_bucket_v, - int num_axial_crystals_per_block_v, int num_transaxial_crystals_per_block_v, - int num_axial_crystals_per_singles_unit_v, - int num_transaxial_crystals_per_singles_unit_v, - int num_detector_layers_v) -{ - set_params(type_v, list_of_names_v, num_rings_v, - max_num_non_arccorrected_bins_v, - max_num_non_arccorrected_bins_v, - num_detectors_per_ring_v, - inner_ring_radius_v, - average_depth_of_interaction_v, - ring_spacing_v, bin_size_v, intrinsic_tilt_v, - num_axial_blocks_per_bucket_v, num_transaxial_blocks_per_bucket_v, - num_axial_crystals_per_block_v, num_transaxial_crystals_per_block_v, - num_axial_crystals_per_singles_unit_v, - num_transaxial_crystals_per_singles_unit_v, - num_detector_layers_v); -} - -void -Scanner:: -set_params(Type type_v,const list& list_of_names_v, - int num_rings_v, - int max_num_non_arccorrected_bins_v, - int num_detectors_per_ring_v, - float inner_ring_radius_v, - float average_depth_of_interaction_v, - float ring_spacing_v, - float bin_size_v, float intrinsic_tilt_v, - float energy_resolution_v, - float reference_energy_v, - int num_axial_blocks_per_bucket_v, int num_transaxial_blocks_per_bucket_v, - int num_axial_crystals_per_block_v, int num_transaxial_crystals_per_block_v, - int num_axial_crystals_per_singles_unit_v, - int num_transaxial_crystals_per_singles_unit_v, - int num_detector_layers_v) -{ - set_params(type_v, list_of_names_v, num_rings_v, - max_num_non_arccorrected_bins_v, - max_num_non_arccorrected_bins_v, - num_detectors_per_ring_v, - inner_ring_radius_v, - average_depth_of_interaction_v, - ring_spacing_v, bin_size_v, intrinsic_tilt_v, + num_detector_layers_v, energy_resolution_v, reference_energy_v, - num_axial_blocks_per_bucket_v, num_transaxial_blocks_per_bucket_v, - num_axial_crystals_per_block_v, num_transaxial_crystals_per_block_v, - num_axial_crystals_per_singles_unit_v, - num_transaxial_crystals_per_singles_unit_v, - num_detector_layers_v); + max_num_of_timing_poss_v, + size_timing_pos_v, + timing_resolution_v); } void Scanner:: -set_params(Type type_v,const list& list_of_names_v, - int num_rings_v, - int max_num_non_arccorrected_bins_v, - int num_detectors_per_ring_v, - float inner_ring_radius_v, - float average_depth_of_interaction_v, - float ring_spacing_v, - float bin_size_v, float intrinsic_tilt_v, - int num_axial_blocks_per_bucket_v, int num_transaxial_blocks_per_bucket_v, - int num_axial_crystals_per_block_v, int num_transaxial_crystals_per_block_v, - int num_axial_crystals_per_singles_unit_v, - int num_transaxial_crystals_per_singles_unit_v, - int num_detector_layers_v, - short int max_num_of_timing_poss_v, - float size_timing_pos_v, - float timing_resolution_v) -{ - set_params(type_v, list_of_names_v, num_rings_v, - max_num_non_arccorrected_bins_v, - max_num_non_arccorrected_bins_v, - num_detectors_per_ring_v, - inner_ring_radius_v, - average_depth_of_interaction_v, - ring_spacing_v, bin_size_v, intrinsic_tilt_v, - num_axial_blocks_per_bucket_v, num_transaxial_blocks_per_bucket_v, - num_axial_crystals_per_block_v, num_transaxial_crystals_per_block_v, - num_axial_crystals_per_singles_unit_v, - num_transaxial_crystals_per_singles_unit_v, - num_detector_layers_v, - max_num_of_timing_poss_v, - size_timing_pos_v, - timing_resolution_v); -} - -void -Scanner:: -set_params(Type type_v,const list& list_of_names_v, - int num_rings_v, - int max_num_non_arccorrected_bins_v, - int default_num_arccorrected_bins_v, - int num_detectors_per_ring_v, - float inner_ring_radius_v, - float average_depth_of_interaction_v, - float ring_spacing_v, - float bin_size_v, float intrinsic_tilt_v, - int num_axial_blocks_per_bucket_v, int num_transaxial_blocks_per_bucket_v, - int num_axial_crystals_per_block_v, int num_transaxial_crystals_per_block_v, - int num_axial_crystals_per_singles_unit_v, - int num_transaxial_crystals_per_singles_unit_v, - int num_detector_layers_v) -{ - type = type_v; - list_of_names = list_of_names_v; - num_rings = num_rings_v; - max_num_non_arccorrected_bins = max_num_non_arccorrected_bins_v; - default_num_arccorrected_bins = default_num_arccorrected_bins_v; - num_detectors_per_ring = num_detectors_per_ring_v; - inner_ring_radius = inner_ring_radius_v; - average_depth_of_interaction = average_depth_of_interaction_v; - ring_spacing = ring_spacing_v; - bin_size = bin_size_v; - intrinsic_tilt = intrinsic_tilt_v; - num_transaxial_blocks_per_bucket = num_transaxial_blocks_per_bucket_v; - num_axial_blocks_per_bucket = num_axial_blocks_per_bucket_v; - num_axial_crystals_per_block= num_axial_crystals_per_block_v; - num_transaxial_crystals_per_block= num_transaxial_crystals_per_block_v; - num_axial_crystals_per_singles_unit = num_axial_crystals_per_singles_unit_v; - num_transaxial_crystals_per_singles_unit = num_transaxial_crystals_per_singles_unit_v; - num_detector_layers = num_detector_layers_v; - - energy_resolution = -1.f; - reference_energy = -1.f; - max_num_of_timing_poss = -1; - size_timing_pos = -1.f; - timing_resolution = -1.f; - -} - -void -Scanner:: -set_params(Type type_v,const list& list_of_names_v, - int num_rings_v, - int max_num_non_arccorrected_bins_v, - int default_num_arccorrected_bins_v, - int num_detectors_per_ring_v, - float inner_ring_radius_v, - float average_depth_of_interaction_v, - float ring_spacing_v, - float bin_size_v, float intrinsic_tilt_v, - float energy_resolution_v, - float reference_energy_v, - int num_axial_blocks_per_bucket_v, int num_transaxial_blocks_per_bucket_v, - int num_axial_crystals_per_block_v, int num_transaxial_crystals_per_block_v, - int num_axial_crystals_per_singles_unit_v, - int num_transaxial_crystals_per_singles_unit_v, - int num_detector_layers_v) +set_params(Type type_v, const std::list& list_of_names_v, + int num_rings_v, + int max_num_non_arccorrected_bins_v, + int default_num_arccorrected_bins_v, + int num_detectors_per_ring_v, + float inner_ring_radius_v, + float average_depth_of_interaction_v, + float ring_spacing_v, + float bin_size_v, float intrinsic_tilt_v, + int num_axial_blocks_per_bucket_v, int num_transaxial_blocks_per_bucket_v, + int num_axial_crystals_per_block_v, int num_transaxial_crystals_per_block_v, + int num_axial_crystals_per_singles_unit_v, + int num_transaxial_crystals_per_singles_unit_v, + int num_detector_layers_v, + float energy_resolution_v, + float reference_energy_v, + short int max_num_of_timing_poss_v, + float size_timing_pos_v, + float timing_resolution_v) { type = type_v; list_of_names = list_of_names_v; @@ -844,61 +615,12 @@ set_params(Type type_v,const list& list_of_names_v, energy_resolution = energy_resolution_v; reference_energy = reference_energy_v; - max_num_of_timing_poss = -1; - size_timing_pos = -1.f; - timing_resolution = -1.f; - -} - + max_num_of_timing_poss = max_num_of_timing_poss_v; + size_timing_pos = size_timing_pos_v; + timing_resolution = timing_resolution_v; -void -Scanner:: -set_params(Type type_v,const list& list_of_names_v, - int num_rings_v, - int max_num_non_arccorrected_bins_v, - int default_num_arccorrected_bins_v, - int num_detectors_per_ring_v, - float inner_ring_radius_v, - float average_depth_of_interaction_v, - float ring_spacing_v, - float bin_size_v, float intrinsic_tilt_v, - int num_axial_blocks_per_bucket_v, int num_transaxial_blocks_per_bucket_v, - int num_axial_crystals_per_block_v, int num_transaxial_crystals_per_block_v, - int num_axial_crystals_per_singles_unit_v, - int num_transaxial_crystals_per_singles_unit_v, - int num_detector_layers_v, - short int max_num_of_timing_poss_v, - float size_timing_pos_v, - float timing_resolution_v) -{ - type = type_v; - list_of_names = list_of_names_v; - num_rings = num_rings_v; - max_num_non_arccorrected_bins = max_num_non_arccorrected_bins_v; - default_num_arccorrected_bins = default_num_arccorrected_bins_v; - num_detectors_per_ring = num_detectors_per_ring_v; - inner_ring_radius = inner_ring_radius_v; - average_depth_of_interaction = average_depth_of_interaction_v; - ring_spacing = ring_spacing_v; - bin_size = bin_size_v; - intrinsic_tilt = intrinsic_tilt_v; - num_transaxial_blocks_per_bucket = num_transaxial_blocks_per_bucket_v; - num_axial_blocks_per_bucket = num_axial_blocks_per_bucket_v; - num_axial_crystals_per_block= num_axial_crystals_per_block_v; - num_transaxial_crystals_per_block= num_transaxial_crystals_per_block_v; - num_axial_crystals_per_singles_unit = num_axial_crystals_per_singles_unit_v; - num_transaxial_crystals_per_singles_unit = num_transaxial_crystals_per_singles_unit_v; - num_detector_layers = num_detector_layers_v; - - max_num_of_timing_poss = max_num_of_timing_poss_v; - size_timing_pos = size_timing_pos_v; - timing_resolution = timing_resolution_v; - energy_resolution = -1.f; - reference_energy = -1.f; } - - Succeeded Scanner:: check_consistency() const @@ -1246,8 +968,8 @@ Scanner* Scanner::ask_parameters() int TransaxialCrystalsPerSinglesUnit = ask_num("Enter number of transaxial crystals per singles unit: ", 0, num_detectors_per_ring, 1); - int Num_TOF_bins = - ask_num("Number of TOF time bins :", 0.0f, 800.0f, 0.0f); + short int Num_TOF_bins = + ask_num("Number of TOF time bins :", 0, 800, 0); float Size_TOF_bin = ask_num("Size of timing bin (ps) :", 0.0f, 100.0f, 0.0f); float TOF_resolution = @@ -1263,23 +985,7 @@ Scanner* Scanner::ask_parameters() ask_num("Enter number of detector layers per block: ",1,100,1); Type type = User_defined_scanner; - bool make_tof_scanner = false; - if (Num_TOF_bins > 0 && Size_TOF_bin > 0 && TOF_resolution > 0) - make_tof_scanner = true; - - Scanner* scanner_ptr = - new Scanner(type, string_list(name), - num_detectors_per_ring, NoRings, - NoBins, NoBins, - InnerRingRadius, AverageDepthOfInteraction, - RingSpacing, BinSize,intrTilt*float(_PI)/180, - AxialBlocksPerBucket,TransBlocksPerBucket, - AxialCrystalsPerBlock,TransaxialCrystalsPerBlock, - AxialCrstalsPerSinglesUnit, TransaxialCrystalsPerSinglesUnit, - num_detector_layers); - - if (EnergyResolution > -1 && ReferenceEnergy > -1) - scanner_ptr = + scanner_ptr = new Scanner(type, string_list(name), num_detectors_per_ring, NoRings, NoBins, NoBins, @@ -1290,29 +996,10 @@ Scanner* Scanner::ask_parameters() AxialCrstalsPerSinglesUnit, TransaxialCrystalsPerSinglesUnit, num_detector_layers, EnergyResolution, - ReferenceEnergy ); - else - scanner_ptr = make_tof_scanner ? new Scanner(type, string_list(name), - num_detectors_per_ring, NoRings, - NoBins, NoBins, - InnerRingRadius, AverageDepthOfInteraction, - RingSpacing, BinSize,intrTilt*float(_PI)/180, - AxialBlocksPerBucket,TransBlocksPerBucket, - AxialCrystalsPerBlock,TransaxialCrystalsPerBlock, - AxialCrstalsPerSinglesUnit, TransaxialCrystalsPerSinglesUnit, - num_detector_layers, - Num_TOF_bins, - Size_TOF_bin, - TOF_resolution) : - new Scanner(type, string_list(name), - num_detectors_per_ring, NoRings, - NoBins, NoBins, - InnerRingRadius, AverageDepthOfInteraction, - RingSpacing, BinSize,intrTilt*float(_PI)/180, - AxialBlocksPerBucket,TransBlocksPerBucket, - AxialCrystalsPerBlock,TransaxialCrystalsPerBlock, - AxialCrstalsPerSinglesUnit, TransaxialCrystalsPerSinglesUnit, - num_detector_layers); + ReferenceEnergy, + Num_TOF_bins, + Size_TOF_bin, + TOF_resolution ); if (scanner_ptr->check_consistency()==Succeeded::yes || !ask("Ask questions again?",true)) diff --git a/src/include/stir/Scanner.h b/src/include/stir/Scanner.h index cba4a8e647..0bb651c3ea 100644 --- a/src/include/stir/Scanner.h +++ b/src/include/stir/Scanner.h @@ -126,19 +126,6 @@ class Scanner \param intrinsic_tilt_v value in radians, \see get_default_intrinsic_tilt() \warning calls error() when block/bucket info are inconsistent */ - Scanner(Type type_v, const std::list& list_of_names_v, - int num_detectors_per_ring_v, int num_rings_v, - int max_num_non_arccorrected_bins_v, - int default_num_arccorrected_bins_v, - float inner_ring_radius_v, float average_depth_of_interaction_v, - float ring_spacing_v, float bin_size_v, float intrinsic_tilt_v, - int num_axial_blocks_per_bucket_v, int num_transaxial_blocks_per_bucket_v, - int num_axial_crystals_per_block_v, int num_transaxial_crystals_per_block_v, - int num_axial_crystals_per_singles_unit_v, - int num_transaxial_crystals_per_singles_unit_v, - int num_detector_layers_v); - - //! Overloaded for energy resolution Scanner(Type type_v, const std::list& list_of_names_v, int num_detectors_per_ring_v, int num_rings_v, int max_num_non_arccorrected_bins_v, @@ -151,42 +138,17 @@ class Scanner int num_transaxial_crystals_per_singles_unit_v, int num_detector_layers_v, float energy_resolution_v, - float reference_energy_v); + float reference_energy_v, + short int max_num_of_timing_poss, + float size_timing_pos, + float timing_resolution); - //! Overloaded constructed with TOF information - Scanner(Type type_v, const std::list& list_of_names_v, - int num_detectors_per_ring_v, int num_rings_v, - int max_num_non_arccorrected_bins_v, - int default_num_arccorrected_bins_v, - float inner_ring_radius_v, float average_depth_of_interaction_v, - float ring_spacing_v, float bin_size_v, float intrinsic_tilt_v, - int num_axial_blocks_per_bucket_v, int num_transaxial_blocks_per_bucket_v, - int num_axial_crystals_per_block_v, int num_transaxial_crystals_per_block_v, - int num_axial_crystals_per_singles_unit_v, - int num_transaxial_crystals_per_singles_unit_v, - int num_detector_layers_v, - short int max_num_of_timing_poss, - float size_timing_pos, - float timing_resolution); //! constructor ( a single name) /*! size info is in mm \param intrinsic_tilt value in radians, \see get_default_intrinsic_tilt() \warning calls error() when block/bucket info are inconsistent */ - Scanner(Type type_v, const std::string& name, - int num_detectors_per_ring_v, int num_rings_v, - int max_num_non_arccorrected_bins_v, - int default_num_arccorrected_bins_v, - float inner_ring_radius_v, float average_depth_of_interaction_v, - float ring_spacing_v, float bin_size_v, float intrinsic_tilt_v, - int num_axial_blocks_per_bucket_v, int num_transaxial_blocks_per_bucket_v, - int num_axial_crystals_per_block_v, int num_transaxial_crystals_per_block_v, - int num_axial_crystals_per_singles_unit_v, - int num_transaxial_crystals_per_singles_unit_v, - int num_detector_layers_v); - - //! Overloaded for energy resolution Scanner(Type type_v, const std::string& name, int num_detectors_per_ring_v, int num_rings_v, int max_num_non_arccorrected_bins_v, @@ -199,24 +161,10 @@ class Scanner int num_transaxial_crystals_per_singles_unit_v, int num_detector_layers_v, float energy_resolution_v, - float reference_energy_v); - - //! Overloaded constructor with TOF information ( a single name) - Scanner(Type type_v, const std::string& name, - int num_detectors_per_ring_v, int num_rings_v, - int max_num_non_arccorrected_bins_v, - int default_num_arccorrected_bins_v, - float inner_ring_radius_v, float average_depth_of_interaction_v, - float ring_spacing_v, float bin_size_v, float intrinsic_tilt_v, - int num_axial_blocks_per_bucket_v, int num_transaxial_blocks_per_bucket_v, - int num_axial_crystals_per_block_v, int num_transaxial_crystals_per_block_v, - int num_axial_crystals_per_singles_unit_v, - int num_transaxial_crystals_per_singles_unit_v, - int num_detector_layers_v, - short int max_num_of_timing_poss, - float size_timing_pos, - float timing_resolution); - + float reference_energy_v, + short int max_num_of_timing_poss, + float size_timing_pos, + float timing_resolution); //! get scanner parameters as a std::string @@ -457,57 +405,6 @@ class Scanner //! This number corresponds the the least significant clock digit. float size_timing_pos; - - //! set all parameters, case where default_num_arccorrected_bins==max_num_non_arccorrected_bins - void set_params(Type type_v, const std::list& list_of_names_v, - int num_rings_v, - int max_num_non_arccorrected_bins_v, - int num_detectors_per_ring_v, - float inner_ring_radius_v, - float average_depth_of_interaction_v, - float ring_spacing_v, - float bin_size_v, float intrinsic_tilt_v, - int num_axial_blocks_per_bucket_v, int num_transaxial_blocks_per_bucket_v, - int num_axial_crystals_per_block_v, int num_transaxial_crystals_per_block_v, - int num_axial_crystals_per_singles_unit_v, - int num_transaxial_crystals_per_singles_unit_v, - int num_detector_layers_v); - - void set_params(Type type_v, const std::list& list_of_names_v, - int num_rings_v, - int max_num_non_arccorrected_bins_v, - int num_detectors_per_ring_v, - float inner_ring_radius_v, - float average_depth_of_interaction_v, - float ring_spacing_v, - float bin_size_v, float intrinsic_tilt_v, - float energy_resolution_v, - float reference_energy, - int num_axial_blocks_per_bucket_v, int num_transaxial_blocks_per_bucket_v, - int num_axial_crystals_per_block_v, int num_transaxial_crystals_per_block_v, - int num_axial_crystals_per_singles_unit_v, - int num_transaxial_crystals_per_singles_unit_v, - int num_detector_layers_v); - - //! Overloaded with TOF stuff. - void set_params(Type type_v, const std::list& list_of_names_v, - int num_rings_v, - int max_num_non_arccorrected_bins_v, - int num_detectors_per_ring_v, - float inner_ring_radius_v, - float average_depth_of_interaction_v, - float ring_spacing_v, - float bin_size_v, float intrinsic_tilt_v, - int num_axial_blocks_per_bucket_v, int num_transaxial_blocks_per_bucket_v, - int num_axial_crystals_per_block_v, int num_transaxial_crystals_per_block_v, - int num_axial_crystals_per_singles_unit_v, - int num_transaxial_crystals_per_singles_unit_v, - int num_detector_layers_v, - short int max_num_of_timing_poss_v, - float size_timing_pos_v, - float timing_resolution_v); - - // ! set all parameters void set_params(Type type_v, const std::list& list_of_names_v, int num_rings_v, int max_num_non_arccorrected_bins_v, @@ -521,45 +418,13 @@ class Scanner int num_axial_crystals_per_block_v, int num_transaxial_crystals_per_block_v, int num_axial_crystals_per_singles_unit_v, int num_transaxial_crystals_per_singles_unit_v, - int num_detector_layers_v); - - void set_params(Type type_v, const std::list& list_of_names_v, - int num_rings_v, - int max_num_non_arccorrected_bins_v, - int default_num_arccorrected_bins_v, - int num_detectors_per_ring_v, - float inner_ring_radius_v, - float average_depth_of_interaction_v, - float ring_spacing_v, - float bin_size_v, float intrinsic_tilt_v, + int num_detector_layers_v, float energy_resolution_v, float reference_energy, - int num_axial_blocks_per_bucket_v, int num_transaxial_blocks_per_bucket_v, - int num_axial_crystals_per_block_v, int num_transaxial_crystals_per_block_v, - int num_axial_crystals_per_singles_unit_v, - int num_transaxial_crystals_per_singles_unit_v, - int num_detector_layers_v); - - //! Overloaded with TOF stuff. - void set_params(Type type_v, const std::list& list_of_names_v, - int num_rings_v, - int max_num_non_arccorrected_bins_v, - int default_num_arccorrected_bins_v, - int num_detectors_per_ring_v, - float inner_ring_radius_v, - float average_depth_of_interaction_v, - float ring_spacing_v, - float bin_size_v, float intrinsic_tilt_v, - int num_axial_blocks_per_bucket_v, int num_transaxial_blocks_per_bucket_v, - int num_axial_crystals_per_block_v, int num_transaxial_crystals_per_block_v, - int num_axial_crystals_per_singles_unit_v, - int num_transaxial_crystals_per_singles_unit_v, - int num_detector_layers_v, short int max_num_of_timing_poss_v, float size_timing_pos_v, float timing_resolution_v); - }; END_NAMESPACE_STIR From 0d58b914c78a0822a04482fe458515385bec41e1 Mon Sep 17 00:00:00 2001 From: Nikos Efthimiou Date: Thu, 6 Dec 2018 14:53:04 +0000 Subject: [PATCH 114/509] Fix tests and clean up *. test_ArcCorrection shorter execution time *. Further work on simpler Scanner *. clean up *. Tests for TOF corrected and documented --- scripts/plot_TOF_bins.m | 70 +++ src/buildblock/Scanner.cxx | 121 ++++- src/include/stir/Scanner.h | 7 +- src/include/stir/listmode/CListModeDataROOT.h | 4 + .../stir/recon_buildblock/ProjMatrixByBin.inl | 65 +-- src/listmode_buildblock/CListModeDataROOT.cxx | 5 + src/recon_buildblock/ProjMatrixByBin.cxx | 10 - src/test/test_ArcCorrection.cxx | 2 +- src/test/test_time_of_flight.cxx | 474 +++++++++--------- 9 files changed, 435 insertions(+), 323 deletions(-) create mode 100644 scripts/plot_TOF_bins.m diff --git a/scripts/plot_TOF_bins.m b/scripts/plot_TOF_bins.m new file mode 100644 index 0000000000..b7ed40758e --- /dev/null +++ b/scripts/plot_TOF_bins.m @@ -0,0 +1,70 @@ +%% +%Plot TOF bins. +% Nikos Efthimiou. 2018/11/01 +% University of Hull + +%This scripts loads LOR files, exported by test_time_of_flight from the disk and plots them. +%% +clc; clear all; +%Path to TOF files. +path_name ='/home/nikos/Desktop/conv_LOR/' + +pre_sort_files_in_path = dir(path_name) +nums = [] +names = [] + +for i = 1: size(pre_sort_files_in_path) + cur_file = pre_sort_files_in_path(i).name + if strfind (cur_file, 'glor') + num = sscanf(cur_file,'glor_%d') + % The following number can change accordingly. + if ((mod(num,1)==0) || num == 500000000) + nums{end+1} = int32(num); + names{end+1} = cur_file; + end + + end +end + +clear cur_file +sorted_filenames = cell(numel(nums),2); +[Sorted_A, Index_A] = sort(cell2mat(nums)); +sorted_filenames(:,2) = names(Index_A); + +% hold x values +x_values = []; +% hold the tof bins. +y_tf_values = []; +% hold the non tof LOR +y__ntf_values = []; + +for i = 1 : size(sorted_filenames,1) + cur_file = sorted_filenames{i,2}; + + if strfind (cur_file, 'glor') + + if strfind(cur_file, '500000000') + cur_full_path = fullfile(path_name, cur_file); + + A = importdata(cur_full_path); + y_ntf_values = A(:,2); + else + cur_full_path = fullfile(path_name, cur_file); + + A = importdata(cur_full_path); + + if size(x_values) == 0 + x_values = A(:,1); + end + + y_tf_values = [y_tf_values A(:,2)]; + + end + end +end + +sum_of_all_bins = sum(y_tf_values,2); +x_v = x_values/0.299; + +%% Create Plot +plot(x_v,y_tf_values(:,:), x_v, sum_of_all_bins, x_v, y_ntf_values) diff --git a/src/buildblock/Scanner.cxx b/src/buildblock/Scanner.cxx index 756ee8e0d3..88169ea932 100644 --- a/src/buildblock/Scanner.cxx +++ b/src/buildblock/Scanner.cxx @@ -124,7 +124,7 @@ Scanner::Scanner(Type scanner_type) 510.0F, 7.0F, 13.5F, 3.129F, 0.0F, 2, 4, 4, 8, 4, 8 * 4, 1, 0.0F, 511.F, - 1, 1.F, 1.F); + 0, 0.F, 0.F); // 16 BUCKETS per ring in TWO rings - i.e. 32 buckets in total break; @@ -136,7 +136,7 @@ Scanner::Scanner(Type scanner_type) 510.0F, 7.0F, 6.75F, 3.12932F, 0.0F, 1, 4, 8, 8, 8, 8 * 4, 1, 0.0F, 511.F, - 1, 1.F, 1.F); + 0, 0.F, 0.F); break; case E953: @@ -146,7 +146,7 @@ Scanner::Scanner(Type scanner_type) 382.5F, 7.0F, 6.75F, 3.12932F, static_cast(15.*_PI/180), 1, 4, 8, 8, 8, 8 * 4, 1, 0.0F, 511.F, - 1, 1.F, 1.F); + 0, 0.F, 0.F); break; case E921: @@ -156,7 +156,7 @@ Scanner::Scanner(Type scanner_type) 412.5F, 7.0F, 6.75F, 3.375F, static_cast(15.*_PI/180), 1, 4, 8, 8, 8, 8 * 4, 1, 0.0F, 511.F, - 1, 1.F, 1.F); + 0, 0.F, 0.F); break; case E925: @@ -166,7 +166,7 @@ Scanner::Scanner(Type scanner_type) 412.5F, 7.0F, 6.75F, 3.375F, static_cast(15.*_PI/180), 3, 4, 8, 8, 8, 8 * 4, 1, 0.0F, 511.F, - 1, 1.F, 1.F); + 0, 0.F, 0.F); break; @@ -177,7 +177,7 @@ Scanner::Scanner(Type scanner_type) 412.0F, 7.0F, 6.25F, 1.650F, static_cast(13.*_PI/180), 1, 8, 8, 7, 8, 7 * 8, 1, 0.0F, 511.F, - 1, 1.F, 1.F); + 0, 0.F, 0.F); break; case E962: @@ -187,7 +187,7 @@ Scanner::Scanner(Type scanner_type) 412.0F, 7.0F, 4.85F, 2.25F, 0.0F, 4, 3, 8, 8, 8, 8 * 3, 1, 0.0F, 511.F, - 1, 1.F, 1.F); + 0, 0.F, 0.F); break; case E966: @@ -197,7 +197,7 @@ Scanner::Scanner(Type scanner_type) 412.0F, 7.0F, 4.850F, 2.250F, 0.0, 6, 2, 8, 8, 2 * 8, 8 * 2, 1, 0.0F, 511.F, - 1, 1.F, 1.F); + 0, 0.F, 0.F); break; case E1080: @@ -207,7 +207,7 @@ Scanner::Scanner(Type scanner_type) 412.0F, 7.0F, 4.0F, 2.000F, 0.0F, 1, 2, 41, 14, 41, 14, 1, 0.0F, 511.F, - 1, 1.F, 1.F); + 0, 0.F, 0.F); // Transaxial blocks have 13 physical crystals and a gap at the // 140th crystal where the counts are zero. // There are 39 rings with 13 axial crystals per block. Two virtual @@ -223,7 +223,7 @@ Scanner::Scanner(Type scanner_type) 328.0F, 7.0F, 4.0625F, 2.08626F, 0.0F, 2, 1, 8, 9, 16, 9, 1, 0.0F, 511.F, - 1, 1.F, 1.F ); // TODO bucket/singles info incorrect? 224 buckets in total, but not sure how distributed + 0, 0.F, 0.F); // TODO bucket/singles info incorrect? 224 buckets in total, but not sure how distributed break; case test_scanner: @@ -245,7 +245,7 @@ Scanner::Scanner(Type scanner_type) 380.0F - 7.0F, 7.0F, 6.75F, 3.1088F, 0.0F, 1, 4, 8, 8, 8, 32, 1, 0.0F, 511.F, - 1, 1.F, 1.F); + 0, 0.F, 0.F); // Default 7.0mm average interaction depth. // This 7mm taken off the inner ring radius so that the effective radius remains 380mm @@ -258,7 +258,7 @@ Scanner::Scanner(Type scanner_type) 115 / 2.F, 7.0F, 6.25F, 1.65F, 0.0F, 1, 16, 8, 7, 8, 0, 1, 0.0F, 511.F, - 1, 1.F, 1.F); // HR block, 4 buckets per ring + 0, 0.F, 0.F); // HR block, 4 buckets per ring // Default 7.0mm average interaction depth. // 8 x 0 crystals per singles unit because not known @@ -272,7 +272,7 @@ Scanner::Scanner(Type scanner_type) /*MeanInnerRadius*/ 75.5/2.F, /*AverageDoI*/ 10.F, /*Ring Spacing*/ 3.F, /*BinSize*/ 0.1F, /*IntrinsicTilt*/ 0.F, 1, 1, 1, 1, 0, 0, 1, 0.0F, 511.F, - 1, 1.F, 1.F); + 0, 0.F, 0.F); break; case nanoPET: @@ -283,7 +283,7 @@ Scanner::Scanner(Type scanner_type) 12 * 39, 174.F, 5.0F, 1.17F, 1.17F, /* Actual size is 1.12 and 0.05 is the thickness of the optical reflector */ 0.0F, /* not sure for this */ 0,0,0,0,0,0, 1, 0.0F, 511.F, - 1, 1.F, 1.F); + 0, 0.F, 0.F); break; case HYPERimage: @@ -293,7 +293,7 @@ Scanner::Scanner(Type scanner_type) 490, 103.97F, 3.0F, 1.4F, 1.4F, /* Actual size is 1.3667 and assume 0.0333 is the thickness of the optical reflector */ 0.F, 0,0,0,0,0,0,1, 0.0F, 511.F, - 1, 1.F, 1.F); + 0, 0.F, 0.F); break; @@ -307,7 +307,7 @@ Scanner::Scanner(Type scanner_type) 471.875F - 8.4F, 8.4F, 8.5F, 1.970177F, 0.0F, //TODO view offset shouldn't be zero 3, 2, 6, 6, 1, 1, 1, 0.0F, 511.F, - 1, 1.F, 1.F); + 0, 0.F, 0.F); break; case DiscoveryLS: @@ -317,7 +317,7 @@ Scanner::Scanner(Type scanner_type) 471.875F - 8.4F, 8.4F, 8.5F, 1.970177F, 0.0F, //TODO view offset shouldn't be zero 3, 2, 6, 6, 1, 1, 1, 0.0F, 511.F, - 1, 1.F, 1.F); + 0, 0.F, 0.F); break; case DiscoveryST: @@ -330,7 +330,7 @@ Scanner::Scanner(Type scanner_type) static_cast(-4.54224*_PI/180),//sign? 4, 2, 6, 6, 1, 1, 1, 0.0F, 511.F, - 1, 1.F, 1.F);// TODO not sure about sign of view_offset + 0, 0.F, 0.F);// TODO not sure about sign of view_offset break; case DiscoverySTE: @@ -346,6 +346,20 @@ Scanner::Scanner(Type scanner_type) (float)(400.0F) );// TODO not sure about sign of view_offset break; + case ntest_TOF_50: // dummy + // 8x8 blocks, 1 virtual "crystal", 56 blocks along the ring, 8 blocks in axial direction + // Transaxial blocks have 8 physical crystals and a gap at the + // 9th crystal where the counts are zero. + set_params(ntest_TOF_50, string_list("ntest_TOF_50"), + 24, 320, 320,666, + 424.5F, 7.0F, 4.16F, 2.0F, 0.0F, + 1, 1, 24, 1, 24, 1, 1, + 0.0f, 511.f, + (short int)(2999), + (float)(1.0F), + (float)(81.2) ); // TODO bucket/singles info incorrect? 224 buckets in total, but not sure how di$ + break; + case DiscoveryRX: set_params(DiscoveryRX, string_list("GE Discovery RX", "Discovery RX"), @@ -404,6 +418,27 @@ case PETMR_Signa: (float)(390.0F) ); break; + case PETMR_Signa_nonTOF: + + set_params(PETMR_Signa_nonTOF, string_list("GE PET/MR Signa nonTOF", "GE PET/MR Signa nonTOF"), + 45, + 357, + 331, // TODO + 2 * 224, + 317.0F, + 9.4F, + 5.55F, + 2.1306F, // TO CHECK + static_cast(-5.23*_PI/180),//sign? TODO value + 5, + 4, + 9, 4, 1, 1, 1, + 0.0F, 511.F, + (short int)(0), + (float)(0), //TODO + (float)(0) ); + break; + case Discovery690: // same as 710 set_params(Discovery690, string_list("GE Discovery 690", "Discovery 690", @@ -434,7 +469,7 @@ case PETMR_Signa: 780.0F, 7.0F, 5.1875F, 2.F, 0.0F, 0, 0, 0, 0, 0,0, 1, 0.0F, 511.F, - 1, 1.F, 1.F); + 0, 0.F, 0.F); // Default 7.0mm average interaction depth. // crystals per singles unit etc unknown break; @@ -446,7 +481,7 @@ case PETMR_Signa: 234.765F, 7.0F, 2.4375F, 1.21875F, 0.0F, 0, 0, 0, 0, 0, 0, 2, 0.0F, 511.F, - 1, 1.F, 1.F); // added by Dylan Togane + 0, 0.F, 0.F); // added by Dylan Togane // warning: used 7.0mm average interaction depth. // crystals per singles unit etc unknown break; @@ -483,7 +518,7 @@ case PETMR_Signa: 29, 0 /* all detectors in a ring? */, 1, 0.0F, 511.F, - 1, 1.F, 1.F); + 0, 0.F, 0.F); break; case GeminiTF: @@ -497,7 +532,7 @@ case PETMR_Signa: 0, 0 /* Not sure about these, but shouldn't be important */, 1, 0.0F, 511.F, - 1, 1.F, 1.F); + 0, 0.F, 0.F); break; case HiDAC: // all of these don't make any sense for the HiDAC @@ -506,7 +541,7 @@ case PETMR_Signa: 0.F, 0.F, 0.F, 0.F, 0.F, 0, 0, 0, 0, 0, 0, 0, 0.0F, 511.F, - 1, 1.F, 1.F); + 0, 0.F, 0.F); break; @@ -517,7 +552,7 @@ case PETMR_Signa: 0.F, 0.F, 0.F, 0.F, 0.F, 0, 0, 0, 0, 0, 0, 0, 0.0F, 511.F, - 1, 1.F, 1.F); + 0, 0.F, 0.F); break; @@ -528,7 +563,7 @@ case PETMR_Signa: 0.F, 0.F, 0.F, 0.F, 0.F, 0, 0, 0, 0, 0, 0, 0, 0.0F, 511.F, - 1, 1.F, 1.F); + 0, 0.F, 0.F); break; @@ -572,6 +607,42 @@ Scanner::Scanner(Type type_v, const list& list_of_names_v, timing_resolution_v); } +Scanner::Scanner(Type type_v, const string& name, + int num_detectors_per_ring_v, int num_rings_v, + int max_num_non_arccorrected_bins_v, + int default_num_arccorrected_bins_v, + float inner_ring_radius_v, float average_depth_of_interaction_v, + float ring_spacing_v, float bin_size_v, float intrinsic_tilt_v, + int num_axial_blocks_per_bucket_v, int num_transaxial_blocks_per_bucket_v, + int num_axial_crystals_per_block_v, int num_transaxial_crystals_per_block_v, + int num_axial_crystals_per_singles_unit_v, + int num_transaxial_crystals_per_singles_unit_v, + int num_detector_layers_v, + float energy_resolution_v, + float reference_energy_v, + short int max_num_of_timing_poss_v, + float size_timing_pos_v, + float timing_resolution_v) +{ + set_params(type_v, string_list(name), num_rings_v, + max_num_non_arccorrected_bins_v, + default_num_arccorrected_bins_v, + num_detectors_per_ring_v, + inner_ring_radius_v, + average_depth_of_interaction_v, + ring_spacing_v, bin_size_v, intrinsic_tilt_v, + num_axial_blocks_per_bucket_v, num_transaxial_blocks_per_bucket_v, + num_axial_crystals_per_block_v, num_transaxial_crystals_per_block_v, + num_axial_crystals_per_singles_unit_v, + num_transaxial_crystals_per_singles_unit_v, + num_detector_layers_v, + energy_resolution_v, + reference_energy_v, + max_num_of_timing_poss_v, + size_timing_pos_v, + timing_resolution_v); +} + void Scanner:: set_params(Type type_v, const std::list& list_of_names_v, diff --git a/src/include/stir/Scanner.h b/src/include/stir/Scanner.h index 0bb651c3ea..4f3f67a6b2 100644 --- a/src/include/stir/Scanner.h +++ b/src/include/stir/Scanner.h @@ -82,6 +82,9 @@ class Succeeded; \warning This information is only sensible for discrete detector-based scanners. \warning Currently, in a TOF compatible scanner template, the last three types have to be explicitly defined to avoid ambiguity. + \warning The energy resolution has to be specified but it is used only for scatter correction. + \warning In order to define a nonTOF scanner the timing resolution has to be set to 0 or 1. + Anything else will trigger a TOF reconstruction. \todo Some scanners do not have all info filled in at present. Values are then set to 0. @@ -113,8 +116,8 @@ class Scanner any given parameters. */ enum Type {E931, E951, E953, E921, E925, E961, E962, E966, E1080, test_scanner, Siemens_mMR, RPT,HiDAC, - Advance, DiscoveryLS, DiscoveryST, DiscoverySTE, DiscoveryRX, Discovery600,Discovery690,PETMR_Signa, - HZLR, RATPET, PANDA, HYPERimage, nanoPET, HRRT, Allegro, GeminiTF, User_defined_scanner, + Advance, DiscoveryLS, DiscoveryST, DiscoverySTE, DiscoveryRX, Discovery600,Discovery690,PETMR_Signa, PETMR_Signa_nonTOF, + HZLR, RATPET, PANDA, HYPERimage, nanoPET, HRRT, Allegro, GeminiTF, User_defined_scanner,ntest_TOF_50, Unknown_scanner}; //! constructor that takes scanner type as an input argument diff --git a/src/include/stir/listmode/CListModeDataROOT.h b/src/include/stir/listmode/CListModeDataROOT.h index f439d8a83c..85884df7d5 100644 --- a/src/include/stir/listmode/CListModeDataROOT.h +++ b/src/include/stir/listmode/CListModeDataROOT.h @@ -178,6 +178,10 @@ class CListModeDataROOT : public CListModeData float size_timing_bin; float timing_resolution; + + float energy_resolution; + + float reference_energy; //@} int tof_mash_factor; diff --git a/src/include/stir/recon_buildblock/ProjMatrixByBin.inl b/src/include/stir/recon_buildblock/ProjMatrixByBin.inl index 41c7351036..259e6e4a61 100644 --- a/src/include/stir/recon_buildblock/ProjMatrixByBin.inl +++ b/src/include/stir/recon_buildblock/ProjMatrixByBin.inl @@ -154,20 +154,14 @@ ProjMatrixByBin::apply_tof_kernel_and_symm_transformation(ProjMatrixElemsForOneB float high_dist = 0.f; float d1; - //float step = 100000.f / 8.f; - //int p1, p2; - // THe direction can be from 1 -> 2 depending on the bin sign. + // The direction can be from 1 -> 2 depending on the bin sign. const CartesianCoordinate3D middle = (point1 + point2)*0.5f; - const CartesianCoordinate3D difference = point2 - point1; + const CartesianCoordinate3D diff = point2 - middle; -// const CartesianCoordinate3D diff = point2 - middle; - - const float denom = 1.f / inner_product(difference, difference); - - const float lor_length = 2.f / (std::sqrt(difference.x() * difference.x() + - difference.y() * difference.y() + - difference.z() * difference.z())); + const float lor_length = 1.f / (std::sqrt(diff.x() * diff.x() + + diff.y() * diff.y() + + diff.z() * diff.z())); for (ProjMatrixElemsForOneBin::iterator element_ptr = probabilities.begin(); element_ptr != probabilities.end(); ++element_ptr) @@ -175,57 +169,28 @@ ProjMatrixByBin::apply_tof_kernel_and_symm_transformation(ProjMatrixElemsForOneB Coordinate3D c(element_ptr->get_coords()); symm_ptr->transform_image_coordinates(c); -// voxel_center = -// image_info_sptr->get_physical_coordinates_for_indices (c); - -// project_point_on_a_line(point1, point2, voxel_center); - -// const CartesianCoordinate3D x = voxel_center - middle; + voxel_center = + image_info_sptr->get_physical_coordinates_for_indices (c); -// const float d2 = -inner_product(x, diff) * lor_length; - - - // The following is the optimisation of the previous: - { - voxel_center = - image_info_sptr->get_physical_coordinates_for_indices (c); + project_point_on_a_line(point1, point2, voxel_center); - const CartesianCoordinate3D x = point2 - voxel_center; - - const float u = inner_product(x, difference) * denom; - - voxel_center[3] = point1[3] + u * difference[3]; - voxel_center[2] = point1[2] + u * difference[2]; - voxel_center[1] = point1[1] + u * difference[1]; - - const CartesianCoordinate3D x_dim = voxel_center - middle; - - d1 = -inner_product(x_dim, difference) * lor_length; - - } + const CartesianCoordinate3D x = voxel_center - middle; - low_dist = ((proj_data_info_sptr->tof_bin_boundaries_mm[probabilities.get_bin_ptr()->timing_pos_num()].low_lim - d1) * r_sqrt2_gauss_sigma); - high_dist = ((proj_data_info_sptr->tof_bin_boundaries_mm[probabilities.get_bin_ptr()->timing_pos_num()].high_lim - d1) * r_sqrt2_gauss_sigma); + const float d2 = -inner_product(x, diff) * lor_length; - //p1 = (((proj_data_info_sptr->tof_bin_boundaries_mm[tof_probabilities.get_bin_ptr()->timing_pos_num()].low_lim - d1) * r_sqrt2_gauss_sigma) + 4.f) * step; - //p2 = (((proj_data_info_sptr->tof_bin_boundaries_mm[tof_probabilities.get_bin_ptr()->timing_pos_num()].high_lim - d1) * r_sqrt2_gauss_sigma) + 4.f) * step; + low_dist = ((proj_data_info_sptr->tof_bin_boundaries_mm[probabilities.get_bin_ptr()->timing_pos_num()].low_lim - d2) * r_sqrt2_gauss_sigma); + high_dist = ((proj_data_info_sptr->tof_bin_boundaries_mm[probabilities.get_bin_ptr()->timing_pos_num()].high_lim - d2) * r_sqrt2_gauss_sigma); -// if (p1 < 0 || p2 < 0 || -// p1 >= 100000 || p2 >= 100000) -// { -// *element_ptr = ProjMatrixElemsForOneBin::value_type(c, 0.0f); -// continue; -// } - if (low_dist >= 4.f || high_dist >= 4.f || - low_dist <= -4.f || high_dist <= -4.f) + if ((low_dist >= 4.f && high_dist >= 4.f) || + (low_dist <= -4.f && high_dist <= -4.f)) { *element_ptr = ProjMatrixElemsForOneBin::value_type(c, 0.0f); continue; } get_tof_value(low_dist, high_dist, new_value); - //new_value = element_ptr->get_value() *(cache_erf[p2] - cache_erf[p1]); + new_value *= element_ptr->get_value(); *element_ptr = ProjMatrixElemsForOneBin::value_type(c, new_value); } } diff --git a/src/listmode_buildblock/CListModeDataROOT.cxx b/src/listmode_buildblock/CListModeDataROOT.cxx index 4562660f0c..e4baaee9e4 100644 --- a/src/listmode_buildblock/CListModeDataROOT.cxx +++ b/src/listmode_buildblock/CListModeDataROOT.cxx @@ -59,6 +59,9 @@ CListModeDataROOT(const std::string& hroot_filename) this->parser.add_key("Maximum number of non-arc-corrected bins", &this->max_num_non_arccorrected_bins); // end Scanner and physical dimensions. + this->parser.add_key("energy resolution", &this->energy_resolution); + this->parser.add_key("reference energy", &this->reference_energy); + this->parser.add_key("number of TOF time bins", &this->max_num_timing_bins); this->parser.add_key("Size of timing bin (ps)", &this->size_timing_bin); this->parser.add_key("Timing resolution (ps)", &this->timing_resolution); @@ -143,6 +146,8 @@ CListModeDataROOT(const std::string& hroot_filename) /*num_transaxial_crystals_per_singles_unit_v*/ this->root_file_sptr->get_num_trans_crystals_per_singles_unit(), /*num_detector_layers_v*/ 1, + this->energy_resolution, + this->reference_energy, /* maximum number of timing bins */ max_num_timing_bins, /* size of basic TOF bin */ diff --git a/src/recon_buildblock/ProjMatrixByBin.cxx b/src/recon_buildblock/ProjMatrixByBin.cxx index c62d78de87..f666d48af1 100644 --- a/src/recon_buildblock/ProjMatrixByBin.cxx +++ b/src/recon_buildblock/ProjMatrixByBin.cxx @@ -85,16 +85,6 @@ enable_tof(const shared_ptr& _proj_data_info_sptr, const bool v) proj_data_info_sptr = _proj_data_info_sptr; gauss_sigma_in_mm = ProjDataInfo::tof_delta_time_to_mm(proj_data_info_sptr->get_scanner_ptr()->get_timing_resolution()) / 2.355f; r_sqrt2_gauss_sigma = 1.0f/ (gauss_sigma_in_mm * static_cast(sqrt(2.0))); - -// cache_erf.resize(0, 100000); - -// float step = 8.f / 100000.f; -// // cache erf with reasonable sampling -// for (int i = 0 ;i < 100000; ++i) -// { -// float d = -4.0f + i * step; -// cache_erf[i] = 0.5f * erf(d); -// } } } diff --git a/src/test/test_ArcCorrection.cxx b/src/test/test_ArcCorrection.cxx index ea9885bb0d..71e0354839 100644 --- a/src/test/test_ArcCorrection.cxx +++ b/src/test/test_ArcCorrection.cxx @@ -189,7 +189,7 @@ ArcCorrectionTests::run_tests_tof() shared_ptr proj_data_info_ptr( ProjDataInfo::ProjDataInfoGE(scanner_ptr, - /*max_delta*/ 10,/*views*/ 224, /*tang_pos*/ 357, /*arc_corrected*/ false, /*tof_mashing_factor*/ 39)); + /*max_delta*/ 10,/*views*/ 224, /*tang_pos*/ 357, /*arc_corrected*/ false, /*tof_mashing_factor*/ 116)); cerr << "Using default range and bin-size\n"; { diff --git a/src/test/test_time_of_flight.cxx b/src/test/test_time_of_flight.cxx index 128160a0b9..f20449e6cd 100644 --- a/src/test/test_time_of_flight.cxx +++ b/src/test/test_time_of_flight.cxx @@ -14,11 +14,7 @@ GNU Lesser General Public License for more details. See STIR/LICENSE.txt for details */ -/*! - \ingroup test - \brief Test class for Time-Of-Flight - \author Nikos Efthimiou -*/ + #include "stir/ProjDataInfoCylindricalNoArcCorr.h" #include "stir/recon_buildblock/ProjMatrixByBin.h" #include "stir/recon_buildblock/ProjMatrixByBinUsingRayTracing.h" @@ -48,6 +44,7 @@ START_NAMESPACE_STIR //! A helper class to keep the combination of a view, a segment and //! a key tight. //! \author Nikos Efthimiou +//! class cache_index{ public: cache_index() { @@ -88,6 +85,21 @@ class FloatFloat{ float float2; }; +/*! + \ingroup test + \brief Test class for Time Of Flight + \author Nikos Efthimiou + + + The following 2 tests are performed: + + *. Compare the ProjDataInfo of the GE Signa scanner to known values. + + *. Check that the sum of the TOF LOR is the same as the non TOF. + + \warning If you change the mashing factor the test_tof_proj_data_info() will fail. + \warning The execution time strongly depends on the value of the TOF mashing factor +*/ class TOF_Tests : public RunTests { public: @@ -97,19 +109,21 @@ class TOF_Tests : public RunTests void test_tof_proj_data_info(); - void test_tof_geometry_1(); - - void test_tof_geometry_2(); - //! This checks peaks a specific bin, finds the LOR and applies all the - //! kernels of all available timing positions. - void test_tof_kernel_application(); + //! kernels of all available timing positions. Then check if the sum + //! of the TOF bins is equal to the non-TOF LOR. + void test_tof_kernel_application(bool export_to_file); + //! Exports the nonTOF LOR to a file indicated by the current_id value + //! in the filename. void export_lor(ProjMatrixElemsForOneBin& probabilities, const CartesianCoordinate3D& point1, const CartesianCoordinate3D& point2,int current_id); + //! Exports the TOF LOR. The TOFid is indicated in the fileName. + //! Only the common elements with the nonTOF LOR will be written in the file. + //! Although changing that is straight forward. void export_lor(ProjMatrixElemsForOneBin& probabilities, const CartesianCoordinate3D& point1, @@ -118,8 +132,14 @@ class TOF_Tests : public RunTests shared_ptr test_scanner_sptr; shared_ptr test_proj_data_info_sptr; + + shared_ptr test_nonTOF_scanner_sptr; + shared_ptr test_nonTOF_proj_data_info_sptr; + shared_ptr > test_discretised_density_sptr; shared_ptr test_proj_matrix_sptr; + shared_ptr test_nonTOF_proj_matrix_sptr; + shared_ptr projector_pair_sptr; shared_ptr symmetries_used_sptr; }; @@ -129,6 +149,7 @@ TOF_Tests::run_tests() { // New Scanner test_scanner_sptr.reset(new Scanner(Scanner::PETMR_Signa)); + test_nonTOF_scanner_sptr.reset(new Scanner(Scanner::PETMR_Signa_nonTOF)); // New Proj_Data_Info const int test_tof_mashing_factor = 39; // to have 9 TOF bins (381/39=9) @@ -139,8 +160,14 @@ TOF_Tests::run_tests() /* arc_correction*/false)); test_proj_data_info_sptr->set_tof_mash_factor(test_tof_mashing_factor); + test_nonTOF_proj_data_info_sptr.reset(ProjDataInfo::ProjDataInfoCTI(test_nonTOF_scanner_sptr, + 1,test_scanner_sptr->get_num_rings() -1, + test_scanner_sptr->get_num_detectors_per_ring()/2, + test_scanner_sptr->get_max_num_non_arccorrected_bins(), + /* arc_correction*/false)); + test_tof_proj_data_info(); -// test_tof_geometry_1(); + // test_tof_geometry_1(); // New Discretised Density test_discretised_density_sptr.reset( new VoxelsOnCartesianGrid (*test_proj_data_info_sptr, 1.f, @@ -150,23 +177,13 @@ TOF_Tests::run_tests() test_proj_matrix_sptr.reset(new ProjMatrixByBinUsingRayTracing()); dynamic_cast(test_proj_matrix_sptr.get())->set_num_tangential_LORs(1); dynamic_cast(test_proj_matrix_sptr.get())->set_up(test_proj_data_info_sptr, test_discretised_density_sptr); -// test_proj_matrix_sptr->enable_tof(test_proj_data_info_sptr); - - shared_ptr forward_projector_ptr( - new ForwardProjectorByBinUsingProjMatrixByBin(test_proj_matrix_sptr)); - shared_ptr back_projector_ptr( - new BackProjectorByBinUsingProjMatrixByBin(test_proj_matrix_sptr)); - projector_pair_sptr.reset( - new ProjectorByBinPairUsingSeparateProjectors(forward_projector_ptr, back_projector_ptr)); - projector_pair_sptr->set_up(test_proj_data_info_sptr, test_discretised_density_sptr); + test_nonTOF_proj_matrix_sptr.reset(new ProjMatrixByBinUsingRayTracing()); + dynamic_cast(test_nonTOF_proj_matrix_sptr.get())->set_num_tangential_LORs(1); + dynamic_cast(test_nonTOF_proj_matrix_sptr.get())->set_up(test_nonTOF_proj_data_info_sptr, test_discretised_density_sptr); - symmetries_used_sptr.reset(projector_pair_sptr->get_symmetries_used()->clone()); - - // Deactivated it now because it takes a long time to finish. - // test_cache(); - - test_tof_kernel_application(); + // Switch to true in order to export the LORs at files in the current directory + test_tof_kernel_application(false); } void @@ -176,8 +193,11 @@ TOF_Tests::test_tof_proj_data_info() const int num_timing_positions = 9; float correct_width_of_tof_bin = test_scanner_sptr->get_size_of_timing_pos() * test_proj_data_info_sptr->get_tof_mash_factor() * 0.299792458f/2; - float correct_timing_locations[num_timing_positions] = {-360.201f/2, -280.156f/2, -200.111f/2, -120.067f/2, -40.022f/2, 40.022f/2, - 120.067f/2, 200.111f/2, 280.156f/2}; + float correct_timing_locations[num_timing_positions] = {-360.201f/2 + correct_width_of_tof_bin/2, -280.156f/2 + correct_width_of_tof_bin/2, + -200.111f/2 + correct_width_of_tof_bin/2, -120.067f/2 + correct_width_of_tof_bin/2, + 0.0f, 40.022f/2 + correct_width_of_tof_bin/2, + 120.067f/2 + correct_width_of_tof_bin/2, 200.111f/2 + correct_width_of_tof_bin/2, + 280.156f/2+ correct_width_of_tof_bin/2}; check_if_equal(correct_tof_mashing_factor, test_proj_data_info_sptr->get_tof_mash_factor(), "Different TOF mashing factor."); @@ -208,131 +228,43 @@ TOF_Tests::test_tof_proj_data_info() } void -TOF_Tests::test_tof_geometry_1() -{ - - float correct_scanner_length = test_scanner_sptr->get_ring_spacing() * - test_scanner_sptr->get_num_rings() - test_proj_data_info_sptr->get_sampling_in_m(Bin(0,0,0,0,0)); - - CartesianCoordinate3D ez1_coord0, ez2_coord0, ez3_coord0, ez4_coord0, ez5_coord0; - //ProjDataInfoCylindrical* proj_data_ptr = - // dynamic_cast (test_proj_data_info_sptr.get()); - - int mid_seg = test_proj_data_info_sptr->get_num_segments()/2; - - int mid_axial_0 = (test_proj_data_info_sptr->get_min_axial_pos_num(0) + - test_proj_data_info_sptr->get_max_axial_pos_num(0)) /2; - - int mid_axial_mid_seg = (test_proj_data_info_sptr->get_min_axial_pos_num(mid_seg) + - test_proj_data_info_sptr->get_max_axial_pos_num(mid_seg)) /2; - - // Some easy to validate bins: - Bin ez1_bin(0,0,0,0,0,1.f); - Bin ez2_bin(0,0,mid_axial_0,0,0,1.f); - Bin ez3_bin(mid_seg,0,mid_axial_mid_seg,0,0,1.f); - Bin ez4_bin(0,0,test_proj_data_info_sptr->get_min_axial_pos_num(0),0,0,1.f); - Bin ez5_bin(0,0,test_proj_data_info_sptr->get_max_axial_pos_num(0),0,0,1.f); - - // Get middle points -// proj_data_ptr->get_LOR_middle_point(ez1_coord0, ez1_bin); -// proj_data_ptr->get_LOR_middle_point(ez2_coord0, ez2_bin); -// proj_data_ptr->get_LOR_middle_point(ez3_coord0, ez3_bin); -// proj_data_ptr->get_LOR_middle_point(ez4_coord0, ez4_bin); -// proj_data_ptr->get_LOR_middle_point(ez5_coord0, ez5_bin); - - // axial ez1 && ez4 should be -scanner_length/2.f - check_if_equal(static_cast(ez1_coord0.z()), - static_cast(-correct_scanner_length/2.f), - "Min axial positions of mid-points don't look " - "reasonable."); - - check_if_equal(static_cast(ez4_coord0.z()), - static_cast(-correct_scanner_length/2.f), - "Min axial positions of mid-points don't look " - "reasonable."); - - // axial ez2 should be -ring_spacing/2 - check_if_equal(static_cast(ez2_coord0.z()), - static_cast(-test_scanner_sptr->get_ring_spacing()/2.f), - "[1]Central axial positions of mid-points don't look " - "reasonable."); - - // axial ez3 should be 0 - check_if_equal(static_cast(ez3_coord0.z()), - static_cast(-test_proj_data_info_sptr->get_m(Bin(mid_seg,0,0,0,0))), - "[2]Central axial positions of mid-points don't look " - "reasonable."); - - // axial ez5 should be scanner_length/2.f - check_if_equal(static_cast(ez5_coord0.z()), - static_cast(correct_scanner_length/2.f), - "Max axial positions of mid-points don't look " - "reasonable."); - - //TODO: more tests for X and Y -} - -void -TOF_Tests::test_tof_geometry_2() -{ - //float correct_scanner_length = test_scanner_sptr->get_ring_spacing() * - // test_scanner_sptr->get_num_rings() - test_proj_data_info_sptr->get_sampling_in_m(Bin(0,0,0,0,0)); - - CartesianCoordinate3D ez1_coord1, ez2_coord1, ez3_coord1, ez4_coord1, ez5_coord1; - CartesianCoordinate3D ez1_coord2, ez2_coord2, ez3_coord2, ez4_coord2, ez5_coord2; - //ProjDataInfoCylindrical* proj_data_ptr = - // dynamic_cast (test_proj_data_info_sptr.get()); - - int mid_seg = test_proj_data_info_sptr->get_num_segments()/2; - - int mid_axial_0 = (test_proj_data_info_sptr->get_min_axial_pos_num(0) + - test_proj_data_info_sptr->get_max_axial_pos_num(0)) /2; - - int mid_axial_mid_seg = (test_proj_data_info_sptr->get_min_axial_pos_num(mid_seg) + - test_proj_data_info_sptr->get_max_axial_pos_num(mid_seg)) /2; - - // Some easy to validate bins: - Bin ez1_bin(0,0,0,0,0,1.f); - Bin ez2_bin(0,0,mid_axial_0,0,0,1.f); - Bin ez3_bin(mid_seg,0,mid_axial_mid_seg,0,0,1.f); - Bin ez4_bin(0,0,test_proj_data_info_sptr->get_min_axial_pos_num(0),0,0,1.f); - Bin ez5_bin(0,0,test_proj_data_info_sptr->get_max_axial_pos_num(0),0,0,1.f); - - // Get middle points -// proj_data_ptr->get_LOR_as_two_points(ez1_coord1,ez1_coord2, ez1_bin); -// proj_data_ptr->get_LOR_as_two_points(ez2_coord1,ez2_coord2, ez2_bin); -// proj_data_ptr->get_LOR_as_two_points(ez3_coord1,ez3_coord2, ez3_bin); -// proj_data_ptr->get_LOR_as_two_points(ez4_coord1,ez4_coord2, ez4_bin); -// proj_data_ptr->get_LOR_as_two_points(ez5_coord1,ez5_coord2, ez5_bin); - - // TESTS TO COME. - - // TEST IF THE FLIPING IS OK. -} - -void -TOF_Tests::test_tof_kernel_application() +TOF_Tests::test_tof_kernel_application(bool print_to_file) { int seg_num = 0; int view_num = 0; int axial_num = 0; int tang_num = 0; + float nonTOF_val = 0.0; + float TOF_val = 0.0; + ProjMatrixElemsForOneBin proj_matrix_row; + ProjMatrixElemsForOneBin sum_tof_proj_matrix_row; + HighResWallClockTimer t; std::vector times_of_tofing; ProjDataInfoCylindrical* proj_data_ptr = dynamic_cast (test_proj_data_info_sptr.get()); + ProjDataInfoCylindrical* proj_data_nonTOF_ptr = + dynamic_cast (test_nonTOF_proj_data_info_sptr.get()); + + LORInAxialAndNoArcCorrSinogramCoordinates lor; + Bin this_bin(seg_num, view_num, axial_num, tang_num, 1.f); t.reset(); t.start(); - test_proj_matrix_sptr->get_proj_matrix_elems_for_one_bin(proj_matrix_row, this_bin); + test_nonTOF_proj_matrix_sptr->get_proj_matrix_elems_for_one_bin(proj_matrix_row, this_bin); t.stop(); + std::cerr<<"Execution time for nonTOF: "<get_LOR(lor, this_bin); + LORAs2Points lor2(lor); + + if (print_to_file) + export_lor(proj_matrix_row, + lor2.p1(), lor2.p2(), 500000000); for (int timing_num = test_proj_data_info_sptr->get_min_tof_pos_num(); timing_num <= test_proj_data_info_sptr->get_max_tof_pos_num(); ++ timing_num) @@ -342,27 +274,101 @@ TOF_Tests::test_tof_kernel_application() t.reset(); t.start(); test_proj_matrix_sptr->get_proj_matrix_elems_for_one_bin(new_proj_matrix_row, - bin); + bin); t.stop(); times_of_tofing.push_back(t.value()); -// export_lor(new_proj_matrix_row, -// lor_point_1, lor_point_2, timing_num, -// proj_matrix_row); + + if (print_to_file) + export_lor(new_proj_matrix_row, + lor2.p1(), lor2.p2(), timing_num, + proj_matrix_row); + + + if (sum_tof_proj_matrix_row.size() > 0) + { + ProjMatrixElemsForOneBin::iterator element_ptr = new_proj_matrix_row.begin(); + while (element_ptr != new_proj_matrix_row.end()) + { + + ProjMatrixElemsForOneBin::iterator sum_element_ptr = sum_tof_proj_matrix_row.begin(); + bool found = false; + while(sum_element_ptr != sum_tof_proj_matrix_row.end()) + { + if(element_ptr->get_coords() == sum_element_ptr->get_coords()) + { + float new_value = element_ptr->get_value() + sum_element_ptr->get_value(); + *sum_element_ptr = ProjMatrixElemsForOneBin::value_type(element_ptr->get_coords(), new_value); + found = true; + break; + } + ++sum_element_ptr; + } + if (!found) + { + sum_tof_proj_matrix_row.push_back(ProjMatrixElemsForOneBin::value_type(element_ptr->get_coords(), + element_ptr->get_value())); + break; + } + ++element_ptr; + } + + } + else + { + ProjMatrixElemsForOneBin::iterator element_ptr = new_proj_matrix_row.begin(); + while (element_ptr != new_proj_matrix_row.end()) + { + sum_tof_proj_matrix_row.push_back(ProjMatrixElemsForOneBin::value_type(element_ptr->get_coords(), + element_ptr->get_value())); + ++element_ptr; + } + } + + } + + // Get value of nonTOF LOR, for central voxels only + + { + ProjMatrixElemsForOneBin::iterator element_ptr = proj_matrix_row.begin(); + while (element_ptr != proj_matrix_row.end()) + { + if (element_ptr->get_value() > nonTOF_val) + nonTOF_val = element_ptr->get_value(); + ++element_ptr; + } + } + + // Get value of TOF LOR, for central voxels only + + { + ProjMatrixElemsForOneBin::iterator element_ptr = sum_tof_proj_matrix_row.begin(); + while (element_ptr != sum_tof_proj_matrix_row.end()) + { + if (element_ptr->get_value() > TOF_val) + TOF_val = element_ptr->get_value(); + ++element_ptr; + } } - double mean = 0.0; - for (unsigned i = 0; i < times_of_tofing.size(); i++) - mean += times_of_tofing.at(i); - mean /= (times_of_tofing.size()); + check_if_equal(static_cast(nonTOF_val), static_cast(TOF_val), + "Sum over nonTOF LOR does not match sum over TOF LOR."); - double s=0.0; - for (unsigned i = 0; i < times_of_tofing.size(); i++) - s += (times_of_tofing.at(i) - mean) * (times_of_tofing.at(i) - mean) / (times_of_tofing.size()-1); + { + double mean = 0.0; + for (unsigned i = 0; i < times_of_tofing.size(); i++) + mean += times_of_tofing.at(i); + + mean /= (times_of_tofing.size()); + + double s=0.0; + for (unsigned i = 0; i < times_of_tofing.size(); i++) + s += (times_of_tofing.at(i) - mean) * (times_of_tofing.at(i) - mean) / (times_of_tofing.size()-1); - s = std::sqrt(s); + s = std::sqrt(s); + std::cerr<<"Execution time for TOF: "<& point1, - const CartesianCoordinate3D& point2, int current_id) + const CartesianCoordinate3D& point2, + int current_id) { - std::ofstream myfile; - std::string file_name = "glor_" + boost::lexical_cast(current_id) + ".txt"; - myfile.open (file_name.c_str()); + std::ofstream myfile; + std::string file_name = "glor_" + boost::lexical_cast(current_id) + ".txt"; + myfile.open (file_name.c_str()); - CartesianCoordinate3D voxel_center; + CartesianCoordinate3D voxel_center; - std::vector lor_to_export; - lor_to_export.reserve(probabilities.size()); + std::vector lor_to_export; + lor_to_export.reserve(probabilities.size()); - ProjMatrixElemsForOneBin::iterator element_ptr = probabilities.begin(); - while (element_ptr != probabilities.end()) - { - voxel_center = - test_discretised_density_sptr->get_physical_coordinates_for_indices (element_ptr->get_coords()); + const CartesianCoordinate3D middle = (point1 + point2)*0.5f; + const CartesianCoordinate3D diff = point2 - middle; - if(voxel_center.z() == 0.f) - { - project_point_on_a_line(point1, point2, voxel_center ); + const float lor_length = 1.f / (std::sqrt(diff.x() * diff.x() + + diff.y() * diff.y() + + diff.z() * diff.z())); - float d1 = std::sqrt((point1.x() - voxel_center.x()) *(point1.x() - voxel_center.x()) + - (point1.y() - voxel_center.y()) *(point1.y() - voxel_center.y()) + - (point1.z() - voxel_center.z()) *(point1.z() - voxel_center.z())); + ProjMatrixElemsForOneBin::iterator element_ptr = probabilities.begin(); + while (element_ptr != probabilities.end()) + { + voxel_center = + test_discretised_density_sptr->get_physical_coordinates_for_indices (element_ptr->get_coords()); - float d2 = std::sqrt( (point2.x() - voxel_center.x()) *(point2.x() - voxel_center.x()) + - (point2.y() - voxel_center.y()) *(point2.y() - voxel_center.y()) + - (point2.z() - voxel_center.z()) *(point2.z() - voxel_center.z())); +// if(voxel_center.z() == 0.f) + { + project_point_on_a_line(point1, point2, voxel_center ); + const CartesianCoordinate3D x = voxel_center - middle; - float d12 = (d2 - d1) * 0.5f; + const float d2 = -inner_product(x, diff) * lor_length; - std::cerr<< voxel_center.x() << " " << voxel_center.y() << " " << voxel_center.z() << " " << - d1 << " " << d2 << " " << d12 <get_value(); - lor_to_export.push_back(tmp); -} - ++element_ptr; - } +// std::cerr<< voxel_center.x() << " " << voxel_center.y() << " " << voxel_center.z() << " " << +// d1 << " " << d2 << " " << d12 << " " << element_ptr->get_value() <get_value(); + lor_to_export.push_back(tmp); + } + ++element_ptr; + } for (unsigned int i = 0; i < lor_to_export.size(); i++) - myfile << lor_to_export.at(i).float1 << " " << lor_to_export.at(i).float2 << std::endl; + myfile << lor_to_export.at(i).float1 << " " << lor_to_export.at(i).float2 << std::endl; - myfile << std::endl; - myfile.close(); + myfile << std::endl; + myfile.close(); } void @@ -427,65 +435,61 @@ export_lor(ProjMatrixElemsForOneBin& probabilities, const CartesianCoordinate3D& point2, int current_id, ProjMatrixElemsForOneBin& template_probabilities) { - std::ofstream myfile; - std::string file_name = "glor_" + boost::lexical_cast(current_id) + ".txt"; - myfile.open (file_name.c_str()); - - CartesianCoordinate3D voxel_center; + std::ofstream myfile; + std::string file_name = "glor_" + boost::lexical_cast(current_id) + ".txt"; + myfile.open (file_name.c_str()); - std::vector lor_to_export; - lor_to_export.reserve(template_probabilities.size()); + const CartesianCoordinate3D middle = (point1 + point2)*0.5f; + const CartesianCoordinate3D diff = point2 - middle; - ProjMatrixElemsForOneBin::iterator tmpl_element_ptr = template_probabilities.begin(); - while (tmpl_element_ptr != template_probabilities.end()) - { - voxel_center = - test_discretised_density_sptr->get_physical_coordinates_for_indices (tmpl_element_ptr->get_coords()); - if(voxel_center.z() == 0.f) - { - project_point_on_a_line(point1, point2, voxel_center ); - - float d1 = std::sqrt((point1.x() - voxel_center.x()) *(point1.x() - voxel_center.x()) + - (point1.y() - voxel_center.y()) *(point1.y() - voxel_center.y()) + - (point1.z() - voxel_center.z()) *(point1.z() - voxel_center.z())); - - float d2 = std::sqrt( (point2.x() - voxel_center.x()) *(point2.x() - voxel_center.x()) + - (point2.y() - voxel_center.y()) *(point2.y() - voxel_center.y()) + - (point2.z() - voxel_center.z()) *(point2.z() - voxel_center.z())); - - float d12 = (d2 - d1) * 0.5f; - - FloatFloat tmp; - tmp.float1 = d12; - - ProjMatrixElemsForOneBin::iterator element_ptr = probabilities.begin(); - bool found = false; + const float lor_length = 1.f / (std::sqrt(diff.x() * diff.x() + + diff.y() * diff.y() + + diff.z() * diff.z())); - while (element_ptr != probabilities.end()) - { - if (element_ptr->get_coords() == tmpl_element_ptr->get_coords()) - { - tmp.float2 = element_ptr->get_value(); - found = true; - break; - } - ++element_ptr; - } + CartesianCoordinate3D voxel_center; - if (!found) - tmp.float2 = 0.f; + std::vector lor_to_export; + lor_to_export.reserve(template_probabilities.size()); - - lor_to_export.push_back(tmp); -} - ++tmpl_element_ptr; - } + ProjMatrixElemsForOneBin::iterator tmpl_element_ptr = template_probabilities.begin(); + while (tmpl_element_ptr != template_probabilities.end()) + { + voxel_center = + test_discretised_density_sptr->get_physical_coordinates_for_indices (tmpl_element_ptr->get_coords()); +// if(voxel_center.z() == 0.f) + { + project_point_on_a_line(point1, point2, voxel_center ); + + const CartesianCoordinate3D x = voxel_center - middle; + + const float d2 = -inner_product(x, diff) * lor_length; + + FloatFloat tmp; + tmp.float1 = d2; + + ProjMatrixElemsForOneBin::iterator element_ptr = probabilities.begin(); + bool found = false; + + while (element_ptr != probabilities.end()) + { + if (element_ptr->get_coords() == tmpl_element_ptr->get_coords()) + { + tmp.float2 = element_ptr->get_value(); + found = true; + lor_to_export.push_back(tmp); + break; + } + ++element_ptr; + } + } + ++tmpl_element_ptr; + } for (unsigned int i = 0; i < lor_to_export.size(); i++) - myfile << lor_to_export.at(i).float1 << " " << lor_to_export.at(i).float2 << std::endl; + myfile << lor_to_export.at(i).float1 << " " << lor_to_export.at(i).float2 << std::endl; - myfile << std::endl; - myfile.close(); + myfile << std::endl; + myfile.close(); } END_NAMESPACE_STIR @@ -493,7 +497,7 @@ END_NAMESPACE_STIR int main() { USING_NAMESPACE_STIR - TOF_Tests tests; + TOF_Tests tests; tests.run_tests(); return tests.main_return_value(); } From f9d87a50e24ed972a2855207e92e7513ad1275b2 Mon Sep 17 00:00:00 2001 From: Nikos Efthimiou Date: Thu, 6 Dec 2018 16:08:12 +0000 Subject: [PATCH 115/509] fix application of tof mashing TOF mashing can be applied in listmode and projection reconstruction. --- src/buildblock/ProjDataInfo.cxx | 3 +++ src/include/stir/ProjDataInfo.h | 7 ++++++ src/include/stir/ProjDataInfo.inl | 24 ++++++++++++++++++++- src/listmode_buildblock/CListRecordROOT.cxx | 3 ++- 4 files changed, 35 insertions(+), 2 deletions(-) diff --git a/src/buildblock/ProjDataInfo.cxx b/src/buildblock/ProjDataInfo.cxx index 117a671ff8..7f996fddbc 100644 --- a/src/buildblock/ProjDataInfo.cxx +++ b/src/buildblock/ProjDataInfo.cxx @@ -203,6 +203,9 @@ ProjDataInfo::set_tof_mash_factor(const int new_num) min_tof_pos_num = - (scanner_ptr->get_num_max_of_timing_poss() / tof_mash_factor)/2; max_tof_pos_num = min_tof_pos_num + (scanner_ptr->get_num_max_of_timing_poss() / tof_mash_factor) -1; + min_unmashed_tof_pos_num = - (scanner_ptr->get_num_max_of_timing_poss())/2; + max_unmashed_tof_pos_num = min_tof_pos_num + (scanner_ptr->get_num_max_of_timing_poss()) -1; + num_tof_bins = max_tof_pos_num - min_tof_pos_num +1 ; // Ensure that we have a central tof bin. diff --git a/src/include/stir/ProjDataInfo.h b/src/include/stir/ProjDataInfo.h index 94e562d19b..3a94c12677 100644 --- a/src/include/stir/ProjDataInfo.h +++ b/src/include/stir/ProjDataInfo.h @@ -221,6 +221,9 @@ class ProjDataInfo inline int get_num_tangential_poss() const; //! Get number of tof bins inline int get_tof_bin(const double& delta) const; + + inline int get_unmashed_tof_bin(const double& delta) const; + //! Get number of TOF bins inline int get_num_tof_poss() const; //! Get minimum segment number @@ -450,6 +453,10 @@ class ProjDataInfo int min_tof_pos_num; //! Maximum TOF pos int max_tof_pos_num; + //! Minimum TOF pos regardless of the mashing factor + int min_unmashed_tof_pos_num; + //! Maximum TOF pos regardless of the mashing factor + int max_unmashed_tof_pos_num; //! TOF mash factor. int tof_mash_factor; //! Finally (with any mashing factor) TOF bin increament. diff --git a/src/include/stir/ProjDataInfo.inl b/src/include/stir/ProjDataInfo.inl index fa4cf80266..fce8226496 100644 --- a/src/include/stir/ProjDataInfo.inl +++ b/src/include/stir/ProjDataInfo.inl @@ -90,7 +90,7 @@ ProjDataInfo::get_tof_bin(const double& delta) const if (!is_tof_data()) return 0; - for (int i = min_tof_pos_num; i <= max_tof_pos_num; i++) + for (int i = min_tof_pos_num; i <= max_tof_pos_num; ++i) { if (delta >= tof_bin_boundaries_ps[i].low_lim && delta < tof_bin_boundaries_ps[i].high_lim) @@ -101,6 +101,28 @@ ProjDataInfo::get_tof_bin(const double& delta) const return 0; } +int +ProjDataInfo::get_unmashed_tof_bin(const double& delta) const +{ + if (!is_tof_data()) + return 0; + + float d = tof_bin_boundaries_ps[min_tof_pos_num].low_lim; + float dd = tof_bin_boundaries_ps[max_tof_pos_num].high_lim; + + if (delta < tof_bin_boundaries_ps[min_tof_pos_num].low_lim && + delta > tof_bin_boundaries_ps[max_tof_pos_num].high_lim) + { + // TODO handle differently + warning(boost::format("TOF delta time %g out of range") % delta); + return 0; + } + + return delta * get_scanner_ptr()->get_num_max_of_timing_poss() / + get_coincidence_window_in_pico_sec(); + +} + int ProjDataInfo::get_tof_mash_factor() const { return tof_mash_factor; } diff --git a/src/listmode_buildblock/CListRecordROOT.cxx b/src/listmode_buildblock/CListRecordROOT.cxx index 5a526bb5e3..387c32454e 100644 --- a/src/listmode_buildblock/CListRecordROOT.cxx +++ b/src/listmode_buildblock/CListRecordROOT.cxx @@ -51,7 +51,8 @@ void CListEventROOT::get_detection_position(DetectionPositionPair<>& _det_pos) c _det_pos.pos1() = det1; _det_pos.pos2() = det2; - _det_pos.timing_pos() = this->get_uncompressed_proj_data_info_sptr()->get_tof_bin(delta_time); +// _det_pos.timing_pos() = this->get_uncompressed_proj_data_info_sptr()->get_tof_bin(delta_time); + _det_pos.timing_pos() = this->get_uncompressed_proj_data_info_sptr()->get_unmashed_tof_bin(delta_time); } void CListEventROOT::set_detection_position(const DetectionPositionPair<>&) From 4236a05a02f76bdda81625d7a646b50e48f78b2a Mon Sep 17 00:00:00 2001 From: Nikos Efthimiou Date: Thu, 6 Dec 2018 16:26:05 +0000 Subject: [PATCH 116/509] remove two control variables. --- src/include/stir/ProjDataInfo.inl | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/include/stir/ProjDataInfo.inl b/src/include/stir/ProjDataInfo.inl index fce8226496..415aee28b0 100644 --- a/src/include/stir/ProjDataInfo.inl +++ b/src/include/stir/ProjDataInfo.inl @@ -107,9 +107,6 @@ ProjDataInfo::get_unmashed_tof_bin(const double& delta) const if (!is_tof_data()) return 0; - float d = tof_bin_boundaries_ps[min_tof_pos_num].low_lim; - float dd = tof_bin_boundaries_ps[max_tof_pos_num].high_lim; - if (delta < tof_bin_boundaries_ps[min_tof_pos_num].low_lim && delta > tof_bin_boundaries_ps[max_tof_pos_num].high_lim) { From 0d0db6f481625dabfd27ce12628620ed37ab9bc1 Mon Sep 17 00:00:00 2001 From: Nikos Efthimiou Date: Thu, 6 Dec 2018 16:41:12 +0000 Subject: [PATCH 117/509] Reduce the scanner in test_ArcCorrection This test fails due to time out --- src/test/test_ArcCorrection.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/test_ArcCorrection.cxx b/src/test/test_ArcCorrection.cxx index 71e0354839..57d9e1ac57 100644 --- a/src/test/test_ArcCorrection.cxx +++ b/src/test/test_ArcCorrection.cxx @@ -189,7 +189,7 @@ ArcCorrectionTests::run_tests_tof() shared_ptr proj_data_info_ptr( ProjDataInfo::ProjDataInfoGE(scanner_ptr, - /*max_delta*/ 10,/*views*/ 224, /*tang_pos*/ 357, /*arc_corrected*/ false, /*tof_mashing_factor*/ 116)); + /*max_delta*/ 5,/*views*/ 112, /*tang_pos*/ 357, /*arc_corrected*/ false, /*tof_mashing_factor*/ 116)); cerr << "Using default range and bin-size\n"; { From 728699d0abfdcf72148cfb57f8ad5d0bf29279ce Mon Sep 17 00:00:00 2001 From: Nikos Efthimiou Date: Thu, 6 Dec 2018 16:47:33 +0000 Subject: [PATCH 118/509] Reduced scanner in DataSymmetriesForBins_PET_CartesianGridTests This test was prone to timeouts --- .../test_DataSymmetriesForBins_PET_CartesianGrid.cxx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/recon_test/test_DataSymmetriesForBins_PET_CartesianGrid.cxx b/src/recon_test/test_DataSymmetriesForBins_PET_CartesianGrid.cxx index e6edd0f879..6127c18b46 100644 --- a/src/recon_test/test_DataSymmetriesForBins_PET_CartesianGrid.cxx +++ b/src/recon_test/test_DataSymmetriesForBins_PET_CartesianGrid.cxx @@ -723,11 +723,11 @@ DataSymmetriesForBins_PET_CartesianGridTests::run_tests() proj_data_info_sptr.reset( ProjDataInfo::ProjDataInfoCTI(scanner_sptr, /*span=*/11, - /*max_delta=*/scanner_sptr->get_num_rings()-1, + /*max_delta=*/5, /*num_views=*/scanner_sptr->get_num_detectors_per_ring()/8, /*num_tang_poss=*/64, /*arc_corrected*/false, - /*tof_mashing*/100)); + /*tof_mashing*/116)); run_tests_for_1_projdata(proj_data_info_sptr); From 2a6d383cbb2b7064fa8b2c4ab114df348a762943 Mon Sep 17 00:00:00 2001 From: Nikos Efthimiou Date: Tue, 29 Jan 2019 04:12:43 +0000 Subject: [PATCH 119/509] comments1 --- ..._test_time_of_fligh.sh => run_test_time_of_flight.sh} | 0 scripts/plot_TOF_bins.m | 2 +- src/include/stir/ProjDataInfo.h | 9 ++++----- src/include/stir/ProjDataInfo.inl | 4 ++-- 4 files changed, 7 insertions(+), 8 deletions(-) rename recon_test_pack/{run_test_time_of_fligh.sh => run_test_time_of_flight.sh} (100%) diff --git a/recon_test_pack/run_test_time_of_fligh.sh b/recon_test_pack/run_test_time_of_flight.sh similarity index 100% rename from recon_test_pack/run_test_time_of_fligh.sh rename to recon_test_pack/run_test_time_of_flight.sh diff --git a/scripts/plot_TOF_bins.m b/scripts/plot_TOF_bins.m index b7ed40758e..d566be56db 100644 --- a/scripts/plot_TOF_bins.m +++ b/scripts/plot_TOF_bins.m @@ -3,7 +3,7 @@ % Nikos Efthimiou. 2018/11/01 % University of Hull -%This scripts loads LOR files, exported by test_time_of_flight from the disk and plots them. +%This scripts loads LOR files, exported by test_time_of_flight to the disk and plots them. %% clc; clear all; %Path to TOF files. diff --git a/src/include/stir/ProjDataInfo.h b/src/include/stir/ProjDataInfo.h index 3a94c12677..486fdd7edf 100644 --- a/src/include/stir/ProjDataInfo.h +++ b/src/include/stir/ProjDataInfo.h @@ -219,11 +219,10 @@ class ProjDataInfo inline int get_num_views() const; //! Get number of tangential positions inline int get_num_tangential_poss() const; - //! Get number of tof bins - inline int get_tof_bin(const double& delta) const; - - inline int get_unmashed_tof_bin(const double& delta) const; - + //! Get number of tof bin for a given time difference + inline int get_tof_bin(const double delta) const; + //! Get number of tof bin for a given time difference, ignoring the TOF mashing factor + inline int get_unmashed_tof_bin(const double delta) const; //! Get number of TOF bins inline int get_num_tof_poss() const; //! Get minimum segment number diff --git a/src/include/stir/ProjDataInfo.inl b/src/include/stir/ProjDataInfo.inl index 415aee28b0..3e7749762d 100644 --- a/src/include/stir/ProjDataInfo.inl +++ b/src/include/stir/ProjDataInfo.inl @@ -85,7 +85,7 @@ ProjDataInfo::get_num_tof_poss() const { return num_tof_bins; } int -ProjDataInfo::get_tof_bin(const double& delta) const +ProjDataInfo::get_tof_bin(const double delta) const { if (!is_tof_data()) return 0; @@ -102,7 +102,7 @@ ProjDataInfo::get_tof_bin(const double& delta) const } int -ProjDataInfo::get_unmashed_tof_bin(const double& delta) const +ProjDataInfo::get_unmashed_tof_bin(const double delta) const { if (!is_tof_data()) return 0; From 3dae02c84c7059f5abcd195a76a2a050565ea2a1 Mon Sep 17 00:00:00 2001 From: Nikos Efthimiou Date: Fri, 8 Feb 2019 04:09:15 +0000 Subject: [PATCH 120/509] Application of comments --- src/CMakeLists.txt | 12 ++------- ...putStreamFromROOTFileForCylindricalPET.cxx | 5 ++-- src/IO/InterfileHeader.cxx | 20 +++++++-------- src/buildblock/ML_norm.cxx | 22 +++++++++++----- src/buildblock/ProjDataInfo.cxx | 25 +++++++++++++++++++ src/include/stir/ProjDataInfo.h | 6 ++++- src/include/stir/ProjDataInfo.inl | 20 +++++++-------- src/include/stir/common.h | 5 ++++ 8 files changed, 75 insertions(+), 40 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 90e262948c..381407c7a5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -33,6 +33,8 @@ option(BUILD_EXECUTABLES option(BUILD_SHARED_LIBS "Use shared libraries" OFF) +### Settings for external libraries + if (LLN_FOUND) set(HAVE_ECAT ON) message(STATUS "ECAT support enabled.") @@ -66,16 +68,6 @@ else() message(STATUS "RDF support disabled.") endif() -if (HDF5_FOUND) - set(HAVE_HDF5 ON) - message(STATUS "HDF5 support enabled.") - add_definitions(-D HAVE_HDF5) - include_directories(${HDF5_INCLUDE_DIRS}) - -else() - message(STATUS "HDF5 support disabled.") -endif() - if (ITK_FOUND) message(STATUS "ITK libraries added.") diff --git a/src/IO/InputStreamFromROOTFileForCylindricalPET.cxx b/src/IO/InputStreamFromROOTFileForCylindricalPET.cxx index c8417af6ae..c2fb22df77 100644 --- a/src/IO/InputStreamFromROOTFileForCylindricalPET.cxx +++ b/src/IO/InputStreamFromROOTFileForCylindricalPET.cxx @@ -56,7 +56,7 @@ InputStreamFromROOTFileForCylindricalPET(std::string _filename, up_energy_window = _up_energy_window; offset_dets = _offset_dets; - half_block = static_cast( (module_repeater_y * submodule_repeater_y * crystal_repeater_y) / 2); + half_block = (module_repeater_y * submodule_repeater_y * crystal_repeater_y) / 2; if (half_block < 0 ) half_block = 0; } @@ -207,8 +207,7 @@ set_up(const std::string & header_path) if (nentries == 0) error("InputStreamFromROOTFileForCylindricalPET: The total number of entries in the ROOT file is zero. Abort."); - return Succeeded::yes; - half_block = static_cast( (module_repeater_y * submodule_repeater_y * crystal_repeater_y) / 2); + half_block = (module_repeater_y * submodule_repeater_y * crystal_repeater_y) / 2; if (half_block < 0 ) half_block = 0; diff --git a/src/IO/InterfileHeader.cxx b/src/IO/InterfileHeader.cxx index a6c3ce0896..2202b9cb93 100644 --- a/src/IO/InterfileHeader.cxx +++ b/src/IO/InterfileHeader.cxx @@ -672,18 +672,21 @@ int InterfilePDFSHeader::find_storage_order() // If TOF information is in there if (matrix_labels.size() > 4) { - num_timing_poss = matrix_size[4][0]; - storage_order =ProjDataFromStream::Timing_Segment_View_AxialPos_TangPos; - num_views = matrix_size[2][0]; + if (matrix_labels[4] == "timing positions") + { + num_timing_poss = matrix_size[4][0]; + storage_order = ProjDataFromStream::Timing_Segment_View_AxialPos_TangPos; + num_views = matrix_size[2][0]; #ifdef _MSC_VER - num_rings_per_segment.assign(matrix_size[1].begin(), matrix_size[1].end()); + num_rings_per_segment.assign(matrix_size[1].begin(), matrix_size[1].end()); #else - num_rings_per_segment = matrix_size[1]; + num_rings_per_segment = matrix_size[1]; #endif + } } else { - storage_order =ProjDataFromStream::Segment_View_AxialPos_TangPos; + storage_order = ProjDataFromStream::Segment_View_AxialPos_TangPos; num_views = matrix_size[2][0]; #ifdef _MSC_VER num_rings_per_segment.assign(matrix_size[1].begin(), matrix_size[1].end()); @@ -1360,10 +1363,7 @@ bool InterfilePDFSHeader::post_processing() // float azimuthal_angle_sampling =_PI/num_views; - - - //TODO: TOF ProjDataInfo - + if (is_arccorrected) { if (effective_central_bin_size_in_cm <= 0) diff --git a/src/buildblock/ML_norm.cxx b/src/buildblock/ML_norm.cxx index a4290eb521..5e7cf2cc9b 100644 --- a/src/buildblock/ML_norm.cxx +++ b/src/buildblock/ML_norm.cxx @@ -479,6 +479,7 @@ FanProjData(const int num_rings, const int num_detectors_per_ring, const int max assert(num_detectors_per_ring%2 == 0); assert(max_ring_diff fan_indices; fan_indices.grow(0,num_rings-1); @@ -756,6 +757,9 @@ void make_fan_data(FanProjData& fan_data, int num_detectors_per_ring; int fan_size; int max_delta; + if(proj_data.get_proj_data_info_sptr()->is_tof_data()) + error("make_fan_data: Incompatible with TOF data. Abort."); + shared_ptr proj_data_info_ptr = get_fan_info(num_rings, num_detectors_per_ring, max_delta, fan_size, *proj_data.get_proj_data_info_ptr()); @@ -768,9 +772,9 @@ void make_fan_data(FanProjData& fan_data, for (bin.segment_num() = proj_data.get_min_segment_num(); bin.segment_num() <= proj_data.get_max_segment_num(); ++ bin.segment_num()) { - for (bin.timing_pos_num() = proj_data.get_min_tof_pos_num(); bin.timing_pos_num() <= proj_data.get_max_tof_pos_num(); ++ bin.timing_pos_num()) +// for (bin.timing_pos_num() = proj_data.get_min_tof_pos_num(); bin.timing_pos_num() <= proj_data.get_max_tof_pos_num(); ++ bin.timing_pos_num()) { - segment_ptr.reset(new SegmentBySinogram(proj_data.get_segment_by_sinogram(bin.segment_num(),bin.timing_pos_num()))); + segment_ptr.reset(new SegmentBySinogram(proj_data.get_segment_by_sinogram(bin.segment_num()/*,bin.timing_pos_num()*/))); for (bin.axial_pos_num() = proj_data.get_min_axial_pos_num(bin.segment_num()); bin.axial_pos_num() <= proj_data.get_max_axial_pos_num(bin.segment_num()); @@ -800,6 +804,9 @@ void set_fan_data(ProjData& proj_data, int num_detectors_per_ring; int fan_size; int max_delta; + if(proj_data.get_proj_data_info_sptr()->is_tof_data()) + error("make_fan_data: Incompatible with TOF data. Abort."); + shared_ptr proj_data_info_ptr = get_fan_info(num_rings, num_detectors_per_ring, max_delta, fan_size, *proj_data.get_proj_data_info_ptr()); @@ -814,9 +821,9 @@ void set_fan_data(ProjData& proj_data, for (bin.segment_num() = proj_data.get_min_segment_num(); bin.segment_num() <= proj_data.get_max_segment_num(); ++ bin.segment_num()) { - for (bin.timing_pos_num() = proj_data.get_min_tof_pos_num(); bin.timing_pos_num() <= proj_data.get_max_tof_pos_num(); ++ bin.timing_pos_num()) +// for (bin.timing_pos_num() = proj_data.get_min_tof_pos_num(); bin.timing_pos_num() <= proj_data.get_max_tof_pos_num(); ++ bin.timing_pos_num()) { - segment_ptr.reset(new SegmentBySinogram(proj_data.get_empty_segment_by_sinogram(bin.segment_num(),bin.timing_pos_num()))); + segment_ptr.reset(new SegmentBySinogram(proj_data.get_empty_segment_by_sinogram(bin.segment_num()/*,bin.timing_pos_num()*/))); for (bin.axial_pos_num() = proj_data.get_min_axial_pos_num(bin.segment_num()); bin.axial_pos_num() <= proj_data.get_max_axial_pos_num(bin.segment_num()); @@ -935,6 +942,9 @@ void make_fan_sum_data(Array<2,float>& data_fan_sums, int num_detectors_per_ring; int fan_size; int max_delta; + if(proj_data.get_proj_data_info_sptr()->is_tof_data()) + error("make_fan_data: Incompatible with TOF data. Abort."); + shared_ptr proj_data_info_ptr = get_fan_info(num_rings, num_detectors_per_ring, max_delta, fan_size, *proj_data.get_proj_data_info_ptr()); @@ -946,9 +956,9 @@ void make_fan_sum_data(Array<2,float>& data_fan_sums, for (bin.segment_num() = proj_data.get_min_segment_num(); bin.segment_num() <= proj_data.get_max_segment_num(); ++ bin.segment_num()) { - for (bin.timing_pos_num() = proj_data.get_min_tof_pos_num(); bin.timing_pos_num() <= proj_data.get_max_tof_pos_num(); ++ bin.timing_pos_num()) +// for (bin.timing_pos_num() = proj_data.get_min_tof_pos_num(); bin.timing_pos_num() <= proj_data.get_max_tof_pos_num(); ++ bin.timing_pos_num()) { - segment_ptr.reset(new SegmentBySinogram(proj_data.get_segment_by_sinogram(bin.segment_num(),bin.timing_pos_num()))); + segment_ptr.reset(new SegmentBySinogram(proj_data.get_segment_by_sinogram(bin.segment_num()/*,bin.timing_pos_num()*/))); for (bin.axial_pos_num() = proj_data.get_min_axial_pos_num(bin.segment_num()); bin.axial_pos_num() <= proj_data.get_max_axial_pos_num(bin.segment_num()); diff --git a/src/buildblock/ProjDataInfo.cxx b/src/buildblock/ProjDataInfo.cxx index 7f996fddbc..007770d723 100644 --- a/src/buildblock/ProjDataInfo.cxx +++ b/src/buildblock/ProjDataInfo.cxx @@ -198,6 +198,31 @@ ProjDataInfo::set_tof_mash_factor(const int new_num) "the scanner's number of max timing bins. Abort."); tof_mash_factor = new_num; + tof_increament_in_mm = tof_delta_time_to_mm(scanner_ptr->get_size_of_timing_pos()); + min_unmashed_tof_pos_num = - (scanner_ptr->get_num_max_of_timing_poss())/2; + max_unmashed_tof_pos_num = min_tof_pos_num + (scanner_ptr->get_num_max_of_timing_poss()) -1; + + // Upper and lower boundaries of the timing poss; + tof_bin_unmashed_boundaries_mm.grow(min_unmashed_tof_pos_num, max_unmashed_tof_pos_num); + tof_bin_unmashed_boundaries_ps.grow(min_unmashed_tof_pos_num, max_unmashed_tof_pos_num); + + // Silently intialise the unmashed TOF bins. + for (int k = min_unmashed_tof_pos_num; k <= max_unmashed_tof_pos_num; ++k ) + { + Bin bin; + bin.timing_pos_num() = k; + + float cur_low = get_k(bin) - get_sampling_in_k(bin)/2.f; + float cur_high = get_k(bin) + get_sampling_in_k(bin)/2.f; + + tof_bin_unmashed_boundaries_mm[k].low_lim = cur_low; + tof_bin_unmashed_boundaries_mm[k].high_lim = cur_high; + tof_bin_unmashed_boundaries_ps[k].low_lim = static_cast(mm_to_tof_delta_time(tof_bin_boundaries_mm[k].low_lim)); + tof_bin_unmashed_boundaries_ps[k].high_lim = static_cast(mm_to_tof_delta_time(tof_bin_boundaries_mm[k].high_lim)); + + } + + // Now, initialise the mashed TOF bins. tof_increament_in_mm = tof_delta_time_to_mm(tof_mash_factor * scanner_ptr->get_size_of_timing_pos()); min_tof_pos_num = - (scanner_ptr->get_num_max_of_timing_poss() / tof_mash_factor)/2; diff --git a/src/include/stir/ProjDataInfo.h b/src/include/stir/ProjDataInfo.h index 486fdd7edf..592cbb57ac 100644 --- a/src/include/stir/ProjDataInfo.h +++ b/src/include/stir/ProjDataInfo.h @@ -424,7 +424,11 @@ class ProjDataInfo mutable VectorWithOffset tof_bin_boundaries_mm; //! Vector which holds the lower and higher boundary for each TOF position in ps`, for faster access. mutable VectorWithOffset tof_bin_boundaries_ps; - + //! Vector which holds the lower and higher boundary for each TOF position, without the application of TOF mashing, in mm, for faster access. + mutable VectorWithOffset tof_bin_unmashed_boundaries_mm; + //! Vector which holds the lower and higher boundary for each TOF position, without the application of TOF mashing, in ps`, for faster access. + mutable VectorWithOffset tof_bin_unmashed_boundaries_ps; + //! Set horizontal bed position void set_bed_position_horizontal(const float bed_position_horizontal_arg) { bed_position_horizontal = bed_position_horizontal_arg; } diff --git a/src/include/stir/ProjDataInfo.inl b/src/include/stir/ProjDataInfo.inl index 3e7749762d..887bf993d6 100644 --- a/src/include/stir/ProjDataInfo.inl +++ b/src/include/stir/ProjDataInfo.inl @@ -38,12 +38,13 @@ START_NAMESPACE_STIR double ProjDataInfo::mm_to_tof_delta_time(const float dist) { - return dist / (0.299792458 / 2); + return dist / _c_light_div2; } + float ProjDataInfo::tof_delta_time_to_mm(const double delta_time) { - return static_cast(delta_time * (0.299792458 / 2)); + return static_cast(delta_time * _c_light_div2); } shared_ptr @@ -107,16 +108,15 @@ ProjDataInfo::get_unmashed_tof_bin(const double delta) const if (!is_tof_data()) return 0; - if (delta < tof_bin_boundaries_ps[min_tof_pos_num].low_lim && - delta > tof_bin_boundaries_ps[max_tof_pos_num].high_lim) + for (int i = min_unmashed_tof_pos_num; i <= max_unmashed_tof_pos_num; ++i) { - // TODO handle differently - warning(boost::format("TOF delta time %g out of range") % delta); - return 0; + if (delta >= tof_bin_boundaries_ps[i].low_lim && + delta < tof_bin_boundaries_ps[i].high_lim) + return i; } - - return delta * get_scanner_ptr()->get_num_max_of_timing_poss() / - get_coincidence_window_in_pico_sec(); + // TODO handle differently + warning(boost::format("TOF delta time %g out of range") % delta); + return 0; } diff --git a/src/include/stir/common.h b/src/include/stir/common.h index d5b9949a36..2ad5ce2d4c 100644 --- a/src/include/stir/common.h +++ b/src/include/stir/common.h @@ -299,6 +299,11 @@ START_NAMESPACE_STIR #define _PI boost::math::constants::pi() #endif +//! Define the speed of light in mm / ps +const double _c_light = 0.299792458; +//! This ratio is used often. +const double _c_light_div2 = _c_light * 0.5; + //! returns the square of a number, templated. /*! \ingroup buildblock */ template From f29e92a2b37b9595a5437f43057eff184dc5ac06 Mon Sep 17 00:00:00 2001 From: NikEfth Date: Wed, 13 Feb 2019 12:57:09 +0000 Subject: [PATCH 121/509] Somehow the option to disable HDF5 got disappeared. I bring it back. --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index e3e7f1807a..38c7ac0933 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -86,6 +86,7 @@ option(DISABLE_AVW "disable use of AVW library" OFF) option(DISABLE_RDF "disable use of GE RDF library" OFF) option(DISABLE_STIR_LOCAL "disable use of LOCAL extensions to STIR" OFF) option(DISABLE_CERN_ROOT "disable use of Cern ROOT libraries" OFF) +option(DISABLE_HDF5_SUPPORT "disable use of HDF5 libraries" OFF) option(STIR_ENABLE_EXPERIMENTAL "disable use of STIR experimental code" OFF) # disable by default if(NOT DISABLE_ITK) From 442eaf3ffa8a599591f2c5c05d234c2024790084 Mon Sep 17 00:00:00 2001 From: Nikos Efthimiou Date: Sun, 3 Mar 2019 04:23:45 +0000 Subject: [PATCH 122/509] Corrections: all recon_test_pack should pass *. Introduction of nonTOF Discovery STE. fixes problems in ./run_test_simulate_and_recon *. In Scanner comparison the TOF information is taken into account only when the two scanners are TOFready. *. Bug fix in ProjDataInfo --- recon_test_pack/Siemens_mMR_seg2.hs | 2 + .../lm_generate_atten_cylinder.par | 8 +-- recon_test_pack/root_header.hroot | 4 +- recon_test_pack/run_test_listmode_recon.sh | 2 +- .../run_test_simulate_and_recon.sh | 2 +- recon_test_pack/template_for_ROOT_scanner.hs | 5 ++ src/buildblock/ProjDataInfo.cxx | 4 +- src/buildblock/Scanner.cxx | 55 +++++++++++++------ src/include/stir/Scanner.h | 2 +- 9 files changed, 55 insertions(+), 29 deletions(-) diff --git a/recon_test_pack/Siemens_mMR_seg2.hs b/recon_test_pack/Siemens_mMR_seg2.hs index f419f744ec..6337e33864 100644 --- a/recon_test_pack/Siemens_mMR_seg2.hs +++ b/recon_test_pack/Siemens_mMR_seg2.hs @@ -44,4 +44,6 @@ Number of crystals per singles unit in transaxial direction := 9 end scanner parameters:= effective central bin size (cm) := 0.208815 number of time frames := 1 +start vertical bed position (mm) := 0 +start horizontal bed position (mm) := 0 !END OF INTERFILE := diff --git a/recon_test_pack/lm_generate_atten_cylinder.par b/recon_test_pack/lm_generate_atten_cylinder.par index 0aa3511bfb..c7227c749c 100644 --- a/recon_test_pack/lm_generate_atten_cylinder.par +++ b/recon_test_pack/lm_generate_atten_cylinder.par @@ -1,8 +1,8 @@ generate_image Parameters := output filename:=my_atten_image -X output image size (in pixels):=111 -Y output image size (in pixels):=111 -Z output image size (in pixels):=65 +X output image size (in pixels):=100 +Y output image size (in pixels):=100 +Z output image size (in pixels):=127 X voxel size (in mm):= 3 Y voxel size (in mm):= 3 Z voxel size (in mm) := 0.40625 @@ -16,7 +16,7 @@ Ellipsoidal Cylinder Parameters:= radius-x (in mm):=100 radius-y (in mm):=100 length-z (in mm):=110 - origin (in mm):={70,10,20} + origin (in mm):={70,0,0} END:= value := 0.096 diff --git a/recon_test_pack/root_header.hroot b/recon_test_pack/root_header.hroot index f8af3f7559..51abcc463e 100644 --- a/recon_test_pack/root_header.hroot +++ b/recon_test_pack/root_header.hroot @@ -9,8 +9,8 @@ Distance between rings (cm) := 0.40625 Default bin size (cm) := 0.208626 Maximum number of non-arc-corrected bins := 344 Number of TOF time bins := 410 -Size of timing bin (in picoseconds) := 10.00 -Timing resolution (in picoseconds) := 400.0 +Size of timing bin (ps) := 10.00 +Timing resolution (ps) := 400.0 %TOF mashing factor:= 82 diff --git a/recon_test_pack/run_test_listmode_recon.sh b/recon_test_pack/run_test_listmode_recon.sh index d9c5c460e9..d5bf2f44b1 100755 --- a/recon_test_pack/run_test_listmode_recon.sh +++ b/recon_test_pack/run_test_listmode_recon.sh @@ -76,7 +76,7 @@ rm -f my_*v my_*s my_*S echo "=== Simulate normalisation data" # For normalisation data we are going to use a cylinder in the center, # with water attenuation values -echo "=== Gnerete fake emission image" +echo "=== Generate fake emission image" generate_image lm_generate_atten_cylinder.par echo "=== Calculate ACFs" calculate_attenuation_coefficients --ACF my_acfs.hs my_atten_image.hv Siemens_mMR_seg2.hs > my_create_acfs.log 2>&1 diff --git a/recon_test_pack/run_test_simulate_and_recon.sh b/recon_test_pack/run_test_simulate_and_recon.sh index 9f68e837fb..5654d6cd61 100755 --- a/recon_test_pack/run_test_simulate_and_recon.sh +++ b/recon_test_pack/run_test_simulate_and_recon.sh @@ -86,7 +86,7 @@ generate_image generate_atten_cylinder.par echo "=== create template sinogram (DSTE in 3D with max ring diff 2 to save time)" template_sino=my_DSTE_3D_rd2_template.hs cat > my_input.txt <(mm_to_tof_delta_time(tof_bin_boundaries_mm[k].low_lim)); - tof_bin_unmashed_boundaries_ps[k].high_lim = static_cast(mm_to_tof_delta_time(tof_bin_boundaries_mm[k].high_lim)); + tof_bin_unmashed_boundaries_ps[k].low_lim = static_cast(mm_to_tof_delta_time(tof_bin_unmashed_boundaries_mm[k].low_lim)); + tof_bin_unmashed_boundaries_ps[k].high_lim = static_cast(mm_to_tof_delta_time(tof_bin_unmashed_boundaries_mm[k].high_lim)); } diff --git a/src/buildblock/Scanner.cxx b/src/buildblock/Scanner.cxx index 88169ea932..e564765433 100644 --- a/src/buildblock/Scanner.cxx +++ b/src/buildblock/Scanner.cxx @@ -120,7 +120,7 @@ Scanner::Scanner(Type scanner_type) // KT 25/01/2002 corrected ring_spacing set_params(E931, string_list("ECAT 931"), - 8, 192, 2 * 256, 2*256, + 8, 192, 192, 2*256, 510.0F, 7.0F, 13.5F, 3.129F, 0.0F, 2, 4, 4, 8, 4, 8 * 4, 1, 0.0F, 511.F, @@ -132,7 +132,7 @@ Scanner::Scanner(Type scanner_type) case E951: set_params(E951, string_list("ECAT 951"), - 16, 192, 2 * 256, 2*256, + 16, 192, 192, 2*256, 510.0F, 7.0F, 6.75F, 3.12932F, 0.0F, 1, 4, 8, 8, 8, 8 * 4, 1, 0.0F, 511.F, @@ -142,7 +142,7 @@ Scanner::Scanner(Type scanner_type) case E953: set_params(E953, string_list("ECAT 953"), - 16, 160, 2 * 192, 2*192, + 16, 160, 160, 2*192, 382.5F, 7.0F, 6.75F, 3.12932F, static_cast(15.*_PI/180), 1, 4, 8, 8, 8, 8 * 4, 1, 0.0F, 511.F, @@ -152,7 +152,7 @@ Scanner::Scanner(Type scanner_type) case E921: set_params(E921, string_list("ECAT 921", "ECAT EXACT", "EXACT"), - 24, 192, 2* 192, 2 * 192, + 24, 192, 192, 2 * 192, 412.5F, 7.0F, 6.75F, 3.375F, static_cast(15.*_PI/180), 1, 4, 8, 8, 8, 8 * 4, 1, 0.0F, 511.F, @@ -162,7 +162,7 @@ Scanner::Scanner(Type scanner_type) case E925: set_params(E925, string_list("ECAT 925", "ECAT ART"), - 24, 192, 2* 192, 2* 192, + 24, 192, 192, 2* 192, 412.5F, 7.0F, 6.75F, 3.375F, static_cast(15.*_PI/180), 3, 4, 8, 8, 8, 8 * 4, 1, 0.0F, 511.F, @@ -173,7 +173,7 @@ Scanner::Scanner(Type scanner_type) case E961: set_params(E961,string_list("ECAT 961", "ECAT HR"), - 24, 336, 2* 392, 2*392, + 24, 336, 336, 2*392, 412.0F, 7.0F, 6.25F, 1.650F, static_cast(13.*_PI/180), 1, 8, 8, 7, 8, 7 * 8, 1, 0.0F, 511.F, @@ -183,7 +183,7 @@ Scanner::Scanner(Type scanner_type) case E962: set_params(E962,string_list("ECAT 962","ECAT HR+"), - 32, 288, 2* 288, 2*288, + 32, 288, 288, 2*288, 412.0F, 7.0F, 4.85F, 2.25F, 0.0F, 4, 3, 8, 8, 8, 8 * 3, 1, 0.0F, 511.F, @@ -193,7 +193,7 @@ Scanner::Scanner(Type scanner_type) case E966: set_params(E966, string_list("ECAT EXACT 3D", "EXACT 3D", "ECAT HR++","ECAT 966"), - 48, 288, 2* 288, 2*288, + 48, 288, 288, 2*288, 412.0F, 7.0F, 4.850F, 2.250F, 0.0, 6, 2, 8, 8, 2 * 8, 8 * 2, 1, 0.0F, 511.F, @@ -203,7 +203,7 @@ Scanner::Scanner(Type scanner_type) case E1080: // data added by Robert Barnett, Westmead Hospital, Sydney set_params(E1080, string_list("ECAT 1080", "Biograph 16", "1080"), - 41, 336, 2* 336, 2*336, + 41, 336, 336, 2*336, 412.0F, 7.0F, 4.0F, 2.000F, 0.0F, 1, 2, 41, 14, 41, 14, 1, 0.0F, 511.F, @@ -219,7 +219,7 @@ Scanner::Scanner(Type scanner_type) // Transaxial blocks have 8 physical crystals and a gap at the // 9th crystal where the counts are zero. set_params(Siemens_mMR, string_list("Siemens mMR", "mMR", "2008"), - 64, 344, 2* 252, 2*252, + 64, 344, 344, 2*252, 328.0F, 7.0F, 4.0625F, 2.08626F, 0.0F, 2, 1, 8, 9, 16, 9, 1, 0.0F, 511.F, @@ -229,7 +229,7 @@ Scanner::Scanner(Type scanner_type) case test_scanner: // This is a relatively small scanner for test purposes. set_params(test_scanner, string_list("test_scanner"), - 4, 344, 2*252,2*252, + 4, 344, 344,2*252, 328.0F, 7.0F, 4.0625F, 2.08626F, 0.0F, 1, 1, 4, 1, 4, 1, 1, 0.0F, 511.F, @@ -241,7 +241,7 @@ Scanner::Scanner(Type scanner_type) case RPT: set_params(RPT, string_list("PRT-1", "RPT"), - 16, 128, 2 * 192, 2*192, + 16, 128, 128, 2*192, 380.0F - 7.0F, 7.0F, 6.75F, 3.1088F, 0.0F, 1, 4, 8, 8, 8, 32, 1, 0.0F, 511.F, @@ -254,7 +254,7 @@ Scanner::Scanner(Type scanner_type) case RATPET: set_params(RATPET, string_list("RATPET"), - 8, 56, 2 * 56, 2*56, + 8, 56, 56, 2*56, 115 / 2.F, 7.0F, 6.25F, 1.65F, 0.0F, 1, 16, 8, 7, 8, 0, 1, 0.0F, 511.F, @@ -346,6 +346,19 @@ Scanner::Scanner(Type scanner_type) (float)(400.0F) );// TODO not sure about sign of view_offset break; + case DiscoverySTE_nonTOF: + + set_params(DiscoverySTE_nonTOF, string_list("GE Discovery STE nonTOF", "Discovery STE nonTOF"), + 24, 329, 293, 2 * 280, + 886.2F/2.F, 8.4F, 6.54F, 2.397F, + static_cast(-4.5490*_PI/180),//sign? + 4, 2, 6, 8, 1, 1, 1, + 0.0F, 511.F, + (short int)(0.F), + (float)(0.F), + (float)(0.F) );// TODO not sure about sign of view_offset + break; + case ntest_TOF_50: // dummy // 8x8 blocks, 1 virtual "crystal", 56 blocks along the ring, 8 blocks in axial direction // Transaxial blocks have 8 physical crystals and a gap at the @@ -846,7 +859,7 @@ if (!close_enough(energy_resolution, scanner.energy_resolution) && " %d opposed to %d" "This only affects scatter simulation. \n", energy_resolution, scanner.energy_resolution); - return + bool ok = (num_rings == scanner.num_rings) && (max_num_non_arccorrected_bins == scanner.max_num_non_arccorrected_bins) && (default_num_arccorrected_bins == scanner.default_num_arccorrected_bins) && @@ -862,10 +875,16 @@ if (!close_enough(energy_resolution, scanner.energy_resolution) && (num_transaxial_crystals_per_block == scanner.num_transaxial_crystals_per_block) && (num_detector_layers == scanner.num_detector_layers) && (num_axial_crystals_per_singles_unit == scanner.num_axial_crystals_per_singles_unit) && - (num_transaxial_crystals_per_singles_unit == scanner.num_transaxial_crystals_per_singles_unit) && - (max_num_of_timing_poss == scanner.max_num_of_timing_poss) && - close_enough(size_timing_pos, scanner.size_timing_pos) && - close_enough(timing_resolution, scanner.timing_resolution); + (num_transaxial_crystals_per_singles_unit == scanner.num_transaxial_crystals_per_singles_unit); + + if (this->is_tof_ready() && scanner.is_tof_ready()) + { + ok = (max_num_of_timing_poss == scanner.max_num_of_timing_poss) && + close_enough(size_timing_pos, scanner.size_timing_pos) && + close_enough(timing_resolution, scanner.timing_resolution); + } + + return ok; } diff --git a/src/include/stir/Scanner.h b/src/include/stir/Scanner.h index 4f3f67a6b2..63f64c8837 100644 --- a/src/include/stir/Scanner.h +++ b/src/include/stir/Scanner.h @@ -116,7 +116,7 @@ class Scanner any given parameters. */ enum Type {E931, E951, E953, E921, E925, E961, E962, E966, E1080, test_scanner, Siemens_mMR, RPT,HiDAC, - Advance, DiscoveryLS, DiscoveryST, DiscoverySTE, DiscoveryRX, Discovery600,Discovery690,PETMR_Signa, PETMR_Signa_nonTOF, + Advance, DiscoveryLS, DiscoveryST, DiscoverySTE, DiscoverySTE_nonTOF, DiscoveryRX, Discovery600,Discovery690,PETMR_Signa, PETMR_Signa_nonTOF, HZLR, RATPET, PANDA, HYPERimage, nanoPET, HRRT, Allegro, GeminiTF, User_defined_scanner,ntest_TOF_50, Unknown_scanner}; From 63d3848fe19d2aaf09df3eeb4f72ed6f2e9e169c Mon Sep 17 00:00:00 2001 From: Nikos Efthimiou Date: Sun, 3 Mar 2019 04:30:17 +0000 Subject: [PATCH 123/509] Fix run_test_simulate_and_recon_with_motion Use of nonTOF Discovery STE. fixes problems in run_test_simulate_and_recon_with_motion --- recon_test_pack/run_test_simulate_and_recon_with_motion.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/recon_test_pack/run_test_simulate_and_recon_with_motion.sh b/recon_test_pack/run_test_simulate_and_recon_with_motion.sh index 5b9b09c7bb..af66a78b95 100755 --- a/recon_test_pack/run_test_simulate_and_recon_with_motion.sh +++ b/recon_test_pack/run_test_simulate_and_recon_with_motion.sh @@ -124,7 +124,7 @@ fi echo "=== create template sinogram (DSTE in 3D with max ring diff 2 to save time)" template_sino=my_DSTE_3D_rd2_template.hs cat > my_input.txt < Date: Sun, 3 Mar 2019 11:39:59 +0000 Subject: [PATCH 124/509] Fix double free error in SPECT reconstruction --- src/recon_buildblock/ProjMatrixByBinSPECTUB.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/recon_buildblock/ProjMatrixByBinSPECTUB.cxx b/src/recon_buildblock/ProjMatrixByBinSPECTUB.cxx index 4ce6ef8c94..3de387f7f9 100644 --- a/src/recon_buildblock/ProjMatrixByBinSPECTUB.cxx +++ b/src/recon_buildblock/ProjMatrixByBinSPECTUB.cxx @@ -584,7 +584,7 @@ ProjMatrixByBinSPECTUB::clone() const ProjMatrixByBinSPECTUB:: ~ProjMatrixByBinSPECTUB() { - delete_UB_SPECT_arrays(); + // delete_UB_SPECT_arrays(); } void From b4651a6e9e06e587b1666d99fed1d67e55a0b68b Mon Sep 17 00:00:00 2001 From: Nikos Efthimiou Date: Sun, 3 Mar 2019 14:28:15 +0000 Subject: [PATCH 125/509] Minor fixes to make Codacy happy(-ier). --- recon_test_pack/run_test_time_of_flight.sh | 4 ++-- src/recon_buildblock/BinNormalisationFromGEHDF5.cxx | 4 ++-- .../ForwardProjectorByBinUsingRayTracing.cxx | 2 +- src/recon_buildblock/distributed_functions.cxx | 2 +- src/recon_test/test_consistency_root.cxx | 4 ++-- src/test/test_time_of_flight.cxx | 6 +++--- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/recon_test_pack/run_test_time_of_flight.sh b/recon_test_pack/run_test_time_of_flight.sh index 9d2d5ecd4d..2bff60c52a 100755 --- a/recon_test_pack/run_test_time_of_flight.sh +++ b/recon_test_pack/run_test_time_of_flight.sh @@ -90,8 +90,8 @@ echo "Data maxs:" $Data_maxs for i in $(seq 5) do - if [ $(( $(($(($i-1)) - $((TOF_bins/2)))) - $((Data_mins[$i])))) -ne 0 ]; then - echo "Wrong values in TOF sinogram. Error. $(( $(($(($i-1)) - $((TOF_bins/2)))) - $((Data_mins[$i]))))" + if [ $(( $((i-1 - TOF_bins/2)) - Data_mins[i])) -ne 0 ]; then + echo "Wrong values in TOF sinogram. Error. $(( $(($((i-1)) -TOF_bins/2)) - Data_mins[i]))" exit 1 fi done diff --git a/src/recon_buildblock/BinNormalisationFromGEHDF5.cxx b/src/recon_buildblock/BinNormalisationFromGEHDF5.cxx index c0185264ef..d140e3b87a 100644 --- a/src/recon_buildblock/BinNormalisationFromGEHDF5.cxx +++ b/src/recon_buildblock/BinNormalisationFromGEHDF5.cxx @@ -290,8 +290,8 @@ read_norm_data(const string& filename) normalisation data. */ - const int min_tang_pos_num = -(scanner_ptr->get_max_num_non_arccorrected_bins())/2; - const int max_tang_pos_num = min_tang_pos_num +scanner_ptr->get_max_num_non_arccorrected_bins()- 1; + //const int min_tang_pos_num = -(scanner_ptr->get_max_num_non_arccorrected_bins())/2; + //const int max_tang_pos_num = min_tang_pos_num +scanner_ptr->get_max_num_non_arccorrected_bins()- 1; //geometric_factors = // Array<2,float>(IndexRange2D(0,127-1, //XXXXnrm_subheader_ptr->num_geo_corr_planes-1, diff --git a/src/recon_buildblock/ForwardProjectorByBinUsingRayTracing.cxx b/src/recon_buildblock/ForwardProjectorByBinUsingRayTracing.cxx index 961cf67c11..24cc08eed5 100644 --- a/src/recon_buildblock/ForwardProjectorByBinUsingRayTracing.cxx +++ b/src/recon_buildblock/ForwardProjectorByBinUsingRayTracing.cxx @@ -1129,7 +1129,7 @@ forward_project_all_symmetries_2D(Viewgram & pos_view, const int nviews = pos_view.get_proj_data_info_ptr()->get_num_views(); const int segment_num = pos_view.get_segment_num(); - const int timing_pos_num = pos_view.get_timing_pos_num(); + //const int timing_pos_num = pos_view.get_timing_pos_num(); const float delta = proj_data_info_ptr->get_average_ring_difference(segment_num); const int view = pos_view.get_view_num(); diff --git a/src/recon_buildblock/distributed_functions.cxx b/src/recon_buildblock/distributed_functions.cxx index ced7944f50..08f9fe1a95 100644 --- a/src/recon_buildblock/distributed_functions.cxx +++ b/src/recon_buildblock/distributed_functions.cxx @@ -353,7 +353,7 @@ namespace distributed void send_viewgram(const stir::Viewgram& viewgram, int destination) { //send dimensions of viewgram (axial and tangential positions and the view and segment numbers) - int viewgram_values[6]; + int viewgram_values[7]; viewgram_values[0] = viewgram.get_min_axial_pos_num(); viewgram_values[1] = viewgram.get_max_axial_pos_num(); viewgram_values[2] = viewgram.get_min_tangential_pos_num(); diff --git a/src/recon_test/test_consistency_root.cxx b/src/recon_test/test_consistency_root.cxx index 5e9970f281..a7f705950d 100644 --- a/src/recon_test/test_consistency_root.cxx +++ b/src/recon_test/test_consistency_root.cxx @@ -60,7 +60,7 @@ START_NAMESPACE_STIR class ROOTconsistency_Tests : public RunTests { public: - ROOTconsistency_Tests(std::string in, std::string image) + ROOTconsistency_Tests(std::string in, const std::string& image) : root_header_filename(in), image_filename(image) {} void run_tests(); @@ -95,7 +95,7 @@ class ROOTconsistency_Tests : public RunTests const BasicCoordinate<3, float>& grid_spacing); //! Modified version of check_if_equal for this test - bool check_if_almost_equal(const double a, const double b, std::string str, const double tolerance); + bool check_if_almost_equal(const double a, const double b, const std::string& str, const double tolerance); std::string root_header_filename; std::string image_filename; diff --git a/src/test/test_time_of_flight.cxx b/src/test/test_time_of_flight.cxx index f20449e6cd..1f04db82ad 100644 --- a/src/test/test_time_of_flight.cxx +++ b/src/test/test_time_of_flight.cxx @@ -287,11 +287,11 @@ TOF_Tests::test_tof_kernel_application(bool print_to_file) if (sum_tof_proj_matrix_row.size() > 0) { ProjMatrixElemsForOneBin::iterator element_ptr = new_proj_matrix_row.begin(); + bool found = false; while (element_ptr != new_proj_matrix_row.end()) { - ProjMatrixElemsForOneBin::iterator sum_element_ptr = sum_tof_proj_matrix_row.begin(); - bool found = false; + found = false; while(sum_element_ptr != sum_tof_proj_matrix_row.end()) { if(element_ptr->get_coords() == sum_element_ptr->get_coords()) @@ -309,7 +309,7 @@ TOF_Tests::test_tof_kernel_application(bool print_to_file) element_ptr->get_value())); break; } - ++element_ptr; + ++element_ptr; } } From 929e01f529531e72ac22648acd15bc9cbca6f185 Mon Sep 17 00:00:00 2001 From: Nikos Efthimiou Date: Mon, 4 Mar 2019 09:58:10 +0000 Subject: [PATCH 126/509] Modification for Codacy --- src/recon_test/test_consistency_root.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/recon_test/test_consistency_root.cxx b/src/recon_test/test_consistency_root.cxx index a7f705950d..09dbb8b671 100644 --- a/src/recon_test/test_consistency_root.cxx +++ b/src/recon_test/test_consistency_root.cxx @@ -60,7 +60,7 @@ START_NAMESPACE_STIR class ROOTconsistency_Tests : public RunTests { public: - ROOTconsistency_Tests(std::string in, const std::string& image) + ROOTconsistency_Tests(const std::string& in, const std::string& image) : root_header_filename(in), image_filename(image) {} void run_tests(); From ad906823bbc111bc59865436e10a4e2e17059abc Mon Sep 17 00:00:00 2001 From: Nikos Efthimiou Date: Tue, 5 Mar 2019 10:01:12 +0000 Subject: [PATCH 127/509] AppVeyor failed test_proj_dat_in_memory, prob. due to memory limitations I reduced the size of the proj data --- src/test/test_proj_data_in_memory.cxx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/test_proj_data_in_memory.cxx b/src/test/test_proj_data_in_memory.cxx index 024885b394..fde6c2983e 100644 --- a/src/test/test_proj_data_in_memory.cxx +++ b/src/test/test_proj_data_in_memory.cxx @@ -168,7 +168,7 @@ run_tests_tof() shared_ptr proj_data_info_sptr (ProjDataInfo::ProjDataInfoCTI(scanner_sptr, - /*span*/1, 10,/*views*/ 96, /*tang_pos*/128, /*arc_corrected*/ true, 11) + /*span*/1, 5,/*views*/ 96, /*tang_pos*/64, /*arc_corrected*/ true, 70) ); shared_ptr exam_info_sptr(new ExamInfo); @@ -176,7 +176,7 @@ run_tests_tof() // construct with filling to 0 ProjDataInMemory proj_data(exam_info_sptr, proj_data_info_sptr); { - check_if_equal( proj_data.get_sinogram(0,0,false,-2).get_timing_pos_num(), + check_if_equal( proj_data.get_sinogram(0,0,false,-2).get_timing_pos_num(), -2, "test get_sinogram timing position index"); Sinogram sinogram = proj_data.get_sinogram(0,0,false,-2); From cb2c46cae88ad3b7339f4e28c4ba0b4d639ee56f Mon Sep 17 00:00:00 2001 From: Nikos Efthimiou Date: Thu, 11 Apr 2019 17:15:01 +0100 Subject: [PATCH 128/509] Two more changes asked by Codacy --- src/recon_buildblock/ForwardProjectorByBinUsingRayTracing.cxx | 2 +- src/test/test_time_of_flight.cxx | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/recon_buildblock/ForwardProjectorByBinUsingRayTracing.cxx b/src/recon_buildblock/ForwardProjectorByBinUsingRayTracing.cxx index 24cc08eed5..d9fcba5db2 100644 --- a/src/recon_buildblock/ForwardProjectorByBinUsingRayTracing.cxx +++ b/src/recon_buildblock/ForwardProjectorByBinUsingRayTracing.cxx @@ -425,7 +425,7 @@ forward_project_all_symmetries( const int nviews = pos_view.get_proj_data_info_ptr()->get_num_views(); const int segment_num = pos_view.get_segment_num(); - const int timing_pos_num = pos_view.get_timing_pos_num(); + //const int timing_pos_num = pos_view.get_timing_pos_num(); const float delta = proj_data_info_ptr->get_average_ring_difference(segment_num); const int view = pos_view.get_view_num(); diff --git a/src/test/test_time_of_flight.cxx b/src/test/test_time_of_flight.cxx index 1f04db82ad..67d89100c2 100644 --- a/src/test/test_time_of_flight.cxx +++ b/src/test/test_time_of_flight.cxx @@ -287,11 +287,10 @@ TOF_Tests::test_tof_kernel_application(bool print_to_file) if (sum_tof_proj_matrix_row.size() > 0) { ProjMatrixElemsForOneBin::iterator element_ptr = new_proj_matrix_row.begin(); - bool found = false; while (element_ptr != new_proj_matrix_row.end()) { ProjMatrixElemsForOneBin::iterator sum_element_ptr = sum_tof_proj_matrix_row.begin(); - found = false; + bool found = false; while(sum_element_ptr != sum_tof_proj_matrix_row.end()) { if(element_ptr->get_coords() == sum_element_ptr->get_coords()) From a4b815e9c7ae19e5202cb25ef88376d10d63d797 Mon Sep 17 00:00:00 2001 From: Alexander Whitehead Date: Mon, 22 Apr 2019 21:30:31 +0100 Subject: [PATCH 129/509] Number of time frames and function declaration (#12) Fix a missing function declaration. --- src/include/stir/recon_buildblock/ProjMatrixByBinSPECTUB.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/include/stir/recon_buildblock/ProjMatrixByBinSPECTUB.h b/src/include/stir/recon_buildblock/ProjMatrixByBinSPECTUB.h index f8c608c5c4..fe080d6b6a 100644 --- a/src/include/stir/recon_buildblock/ProjMatrixByBinSPECTUB.h +++ b/src/include/stir/recon_buildblock/ProjMatrixByBinSPECTUB.h @@ -154,6 +154,10 @@ class ProjMatrixByBinSPECTUB : void set_resolution_model(const float collimator_sigma_0_in_mm, const float collimator_slope_in_mm, const bool full_3D = true); + + //Alex + //Fix to compile, missing function definition in header + ProjMatrixByBinSPECTUB * clone() const; private: From 7c5ec7b9f2688e9f4774140586346abe7e6606d8 Mon Sep 17 00:00:00 2001 From: Nikos Efthimiou Date: Thu, 6 Jun 2019 00:24:57 +0100 Subject: [PATCH 130/509] Fix for TOF-PSF reconstruction --- ...issonLogLikelihoodWithLinearModelForMeanAndProjData.cxx | 7 +++++-- src/recon_buildblock/PostsmoothingBackProjectorByBin.cxx | 4 +++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx index 20334f1731..41328daf89 100644 --- a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx +++ b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx @@ -751,9 +751,12 @@ add_subset_sensitivity(TargetT& sensitivity, const int subset_num) const #if 1 shared_ptr sensitivity_this_subset_sptr(sensitivity.clone()); - + shared_ptr sens_proj_data_sptr; // have to create a ProjData object filled with 1 here because otherwise zero_seg0_endplanes will not be effective - shared_ptr sens_proj_data_sptr(new ProjDataInMemory(this->proj_data_sptr->get_exam_info_sptr(), this->proj_data_sptr->get_proj_data_info_sptr())); + if (!this->use_tofsens) + sens_proj_data_sptr.reset(new ProjDataInMemory(this->proj_data_sptr->get_exam_info_sptr(), this->proj_data_sptr->get_proj_data_info_sptr()->create_non_tof_clone())); + else + sens_proj_data_sptr.reset(new ProjDataInMemory(this->proj_data_sptr->get_exam_info_sptr(), this->proj_data_sptr->get_proj_data_info_sptr())); sens_proj_data_sptr->fill(1.0F); distributable_sensitivity_computation(this->projector_pair_ptr->get_forward_projector_sptr(), diff --git a/src/recon_buildblock/PostsmoothingBackProjectorByBin.cxx b/src/recon_buildblock/PostsmoothingBackProjectorByBin.cxx index 62b9eb9321..a89036aabe 100644 --- a/src/recon_buildblock/PostsmoothingBackProjectorByBin.cxx +++ b/src/recon_buildblock/PostsmoothingBackProjectorByBin.cxx @@ -88,7 +88,9 @@ PostsmoothingBackProjectorByBin::get_original_back_projector_ptr() const PostsmoothingBackProjectorByBin* PostsmoothingBackProjectorByBin::clone() const { - return new PostsmoothingBackProjectorByBin(*this); + PostsmoothingBackProjectorByBin* sptr(new PostsmoothingBackProjectorByBin(*this)); + sptr->original_back_projector_ptr.reset(this->original_back_projector_ptr->clone()); + return sptr; } void PostsmoothingBackProjectorByBin:: From 2c068c27ad6187f3ddffb56dd9b645e06669cf44 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Wed, 29 Jan 2020 16:17:16 +0000 Subject: [PATCH 131/509] fix ProjData::get_num_sinograms() for TOF --- src/include/stir/ProjData.inl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/include/stir/ProjData.inl b/src/include/stir/ProjData.inl index 37f55eb13c..ee4b183582 100644 --- a/src/include/stir/ProjData.inl +++ b/src/include/stir/ProjData.inl @@ -110,7 +110,7 @@ int ProjData::get_num_sinograms() const for (int s=1; s<= this->get_max_segment_num(); ++s) num_sinos += 2* this->get_num_axial_poss(s); - return num_sinos; + return num_sinos*this->get_num_tof_poss(); } std::size_t ProjData::size_all() const From 8fa31698a818a19e9e7c444e1ad92433878f1fd9 Mon Sep 17 00:00:00 2001 From: Nikos Efthimiou Date: Tue, 4 Feb 2020 14:37:38 +0000 Subject: [PATCH 132/509] fix sensitivity calculation for lm recon --- ...nearModelForMeanAndListModeDataWithProjMatrixByBin.cxx | 4 ++-- src/test/test_time_of_flight.cxx | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx index 4141862623..3ea159a3d5 100644 --- a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx +++ b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx @@ -371,7 +371,7 @@ add_subset_sensitivity(TargetT& sensitivity, const int subset_num) const const int min_segment_num = proj_data_info_sptr->get_min_segment_num(); const int max_segment_num = proj_data_info_sptr->get_max_segment_num(); - this->projector_pair_sptr->get_back_projector_sptr()-> + this->sens_backprojector_sptr-> start_accumulating_in_new_target(); // warning: has to be same as subset scheme used as in distributable_computation @@ -388,7 +388,7 @@ add_subset_sensitivity(TargetT& sensitivity, const int subset_num) const this->add_view_seg_to_sensitivity(view_segment_num); } } - this->projector_pair_sptr->get_back_projector_sptr()-> + this->sens_backprojector_sptr-> get_output(sensitivity); } diff --git a/src/test/test_time_of_flight.cxx b/src/test/test_time_of_flight.cxx index 67d89100c2..439fddcfa2 100644 --- a/src/test/test_time_of_flight.cxx +++ b/src/test/test_time_of_flight.cxx @@ -47,10 +47,10 @@ START_NAMESPACE_STIR //! class cache_index{ public: - cache_index() { + cache_index(): + key(0){ view_num = 0; seg_num = 0; - key = 0; } inline bool operator==(const cache_index& Y) const @@ -247,8 +247,8 @@ TOF_Tests::test_tof_kernel_application(bool print_to_file) ProjDataInfoCylindrical* proj_data_ptr = dynamic_cast (test_proj_data_info_sptr.get()); - ProjDataInfoCylindrical* proj_data_nonTOF_ptr = - dynamic_cast (test_nonTOF_proj_data_info_sptr.get()); +// ProjDataInfoCylindrical* proj_data_nonTOF_ptr = +// dynamic_cast (test_nonTOF_proj_data_info_sptr.get()); LORInAxialAndNoArcCorrSinogramCoordinates lor; From 53085f0814855a1429c57fc65626981d1490a1a3 Mon Sep 17 00:00:00 2001 From: Nikos Efthimiou Date: Tue, 4 Feb 2020 15:28:11 +0000 Subject: [PATCH 133/509] Fixed test_zoom_images --- recon_test_pack/run_test_zoom_image.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/recon_test_pack/run_test_zoom_image.sh b/recon_test_pack/run_test_zoom_image.sh index e70a570a0c..3f38e52aef 100755 --- a/recon_test_pack/run_test_zoom_image.sh +++ b/recon_test_pack/run_test_zoom_image.sh @@ -156,6 +156,7 @@ template_sino=my_DSTE_3D_rd2_template.hs cat > my_input.txt < Date: Tue, 4 Feb 2020 16:13:29 +0000 Subject: [PATCH 134/509] fix: python cannot test right now. Let's see what travis will say. --- src/swig/stir.i | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/swig/stir.i b/src/swig/stir.i index 5925c6406e..43456f05bd 100644 --- a/src/swig/stir.i +++ b/src/swig/stir.i @@ -640,9 +640,7 @@ namespace std { // { // num_sinos += 2*proj_data.get_num_axial_poss(s); // } - int num_sinos = proj_data.get_num_sinograms(); - - Array<4,float> array(IndexRange4D(proj_data.get_num_tof_poss(),num_sinos, proj_data.get_num_views(), proj_data.get_num_tangential_poss())); + Array<4,float> array(IndexRange4D(proj_data.get_num_tof_poss(),proj_data.get_num_segments(), proj_data.get_num_views(), proj_data.get_num_tangential_poss())); return array; } From 3f4c94d74f69074a398450a03019dca71bfd42fe Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Tue, 4 Feb 2020 21:26:04 +0000 Subject: [PATCH 135/509] added ProjData::get_num_non_tof_sinograms and fixed SWIG for tof --- src/include/stir/ProjData.h | 8 ++++++++ src/include/stir/ProjData.inl | 9 +++++++-- src/swig/stir.i | 8 ++------ 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/include/stir/ProjData.h b/src/include/stir/ProjData.h index e73374d73f..0a760891fe 100644 --- a/src/include/stir/ProjData.h +++ b/src/include/stir/ProjData.h @@ -333,7 +333,15 @@ class ProjData : public ExamData //! Get maximum tangential position number inline int get_max_tangential_pos_num() const; //! Get the number of sinograms + /*! Note that this will count TOF sinograms as well. + \see get_num_non_tof_sinograms() + */ inline int get_num_sinograms() const; + //! Get the number of sinograms + /*! Note that this is the sum of the number of axial poss over all segments. + \see get_num_sinograms() + */ + inline int get_num_non_tof_sinograms() const; //! Get the total size of the data inline std::size_t size_all() const; //! writes data to a file in Interfile format diff --git a/src/include/stir/ProjData.inl b/src/include/stir/ProjData.inl index ee4b183582..72c945ac6d 100644 --- a/src/include/stir/ProjData.inl +++ b/src/include/stir/ProjData.inl @@ -104,13 +104,18 @@ int ProjData::get_min_tof_pos_num() const int ProjData::get_max_tof_pos_num() const { return proj_data_info_ptr->get_max_tof_pos_num(); } -int ProjData::get_num_sinograms() const +int ProjData::get_num_non_tof_sinograms() const { int num_sinos = proj_data_info_ptr->get_num_axial_poss(0); for (int s=1; s<= this->get_max_segment_num(); ++s) num_sinos += 2* this->get_num_axial_poss(s); - return num_sinos*this->get_num_tof_poss(); + return num_sinos; +} + +int ProjData::get_num_sinograms() const +{ + return this->get_num_non_tof_sinograms()*this->get_num_tof_poss(); } std::size_t ProjData::size_all() const diff --git a/src/swig/stir.i b/src/swig/stir.i index 43456f05bd..9451268290 100644 --- a/src/swig/stir.i +++ b/src/swig/stir.i @@ -635,12 +635,8 @@ namespace std { #endif static Array<4,float> create_array_for_proj_data(const ProjData& proj_data) { - // int num_sinos=proj_data.get_num_axial_poss(0); - // for (int s=1; s<= proj_data.get_max_segment_num(); ++s) - // { - // num_sinos += 2*proj_data.get_num_axial_poss(s); - // } - Array<4,float> array(IndexRange4D(proj_data.get_num_tof_poss(),proj_data.get_num_segments(), proj_data.get_num_views(), proj_data.get_num_tangential_poss())); + const int num_non_tof_sinos = proj_data.get_num_non_tof_sinograms(); + Array<4,float> array(IndexRange4D(proj_data.get_num_tof_poss(),num_non_tof_sinos, proj_data.get_num_views(), proj_data.get_num_tangential_poss())); return array; } From f22abd6921625022242b9536f4067bf723224973 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Fri, 7 Feb 2020 23:09:58 +0000 Subject: [PATCH 136/509] removed unused ForwardProjectorByBin::forward_project(Bin&,...) The implementation in terms of actual_forward_project got lost in the previous merge, but it would need to be rewritten in terms of the new style (not passing the image) anyway. In any case, most implementations were calling error anyway. As it is currently not used, it seemed easiest to just comment it out... --- src/include/stir/recon_buildblock/ForwardProjectorByBin.h | 8 ++++++-- .../ForwardProjectorByBinUsingProjMatrixByBin.h | 2 ++ .../ForwardProjectorByBinUsingRayTracing.h | 4 ++-- .../recon_buildblock/PresmoothingForwardProjectorByBin.h | 2 ++ .../ForwardProjectorByBinUsingProjMatrixByBin.cxx | 4 +++- .../ForwardProjectorByBinUsingRayTracing.cxx | 4 +++- .../PresmoothingForwardProjectorByBin.cxx | 4 +++- 7 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/include/stir/recon_buildblock/ForwardProjectorByBin.h b/src/include/stir/recon_buildblock/ForwardProjectorByBin.h index 1beec05025..2c28b86a1c 100644 --- a/src/include/stir/recon_buildblock/ForwardProjectorByBin.h +++ b/src/include/stir/recon_buildblock/ForwardProjectorByBin.h @@ -127,10 +127,12 @@ virtual void set_up( const int min_axial_pos_num, const int max_axial_pos_num, const int min_tangential_pos_num, const int max_tangential_pos_num); - //! Overloaded function mainly used in ListMode reconstruction. +#if 0 // disabled as currently not used. needs to be written in the new style anyway + //! function mainly used in ListMode reconstruction. + /*! Calls actual_forward_project */ void forward_project(Bin&, const DiscretisedDensity<3,float>&); - +#endif virtual ~ForwardProjectorByBin(); /// Set input @@ -150,9 +152,11 @@ virtual void set_up( const int min_axial_pos_num, const int max_axial_pos_num, const int min_tangential_pos_num, const int max_tangential_pos_num); +#if 0 // disabled as currently not used. needs to be written in the new style anyway //! This virtual function has to be implemented by the derived class. virtual void actual_forward_project(Bin&, const DiscretisedDensity<3,float>&) = 0; +#endif //! check if the argument is the same as what was used for set_up() /*! calls error() if anything is wrong. diff --git a/src/include/stir/recon_buildblock/ForwardProjectorByBinUsingProjMatrixByBin.h b/src/include/stir/recon_buildblock/ForwardProjectorByBinUsingProjMatrixByBin.h index 28ed510e4d..289d10cc41 100644 --- a/src/include/stir/recon_buildblock/ForwardProjectorByBinUsingProjMatrixByBin.h +++ b/src/include/stir/recon_buildblock/ForwardProjectorByBinUsingProjMatrixByBin.h @@ -88,7 +88,9 @@ class ForwardProjectorByBinUsingProjMatrixByBin: const int min_axial_pos_num, const int max_axial_pos_num, const int min_tangential_pos_num, const int max_tangential_pos_num); +#if 0 // disabled as currently not used. needs to be written in the new style anyway void actual_forward_project(Bin&, const DiscretisedDensity<3,float>&); +#endif virtual void set_defaults(); virtual void initialise_keymap(); diff --git a/src/include/stir/recon_buildblock/ForwardProjectorByBinUsingRayTracing.h b/src/include/stir/recon_buildblock/ForwardProjectorByBinUsingRayTracing.h index 96af693885..e4c6a56421 100644 --- a/src/include/stir/recon_buildblock/ForwardProjectorByBinUsingRayTracing.h +++ b/src/include/stir/recon_buildblock/ForwardProjectorByBinUsingRayTracing.h @@ -112,10 +112,10 @@ class ForwardProjectorByBinUsingRayTracing : const DiscretisedDensity<3,float>&, const int min_axial_pos_num, const int max_axial_pos_num, const int min_tangential_pos_num, const int max_tangential_pos_num); - +#if 0 // disabled as currently not used. needs to be written in the new style anyway void actual_forward_project(Bin&, const DiscretisedDensity<3,float>&); - +#endif // KT 20/06/2001 changed type from 'const DataSymmetriesForViewSegmentNumbers *' shared_ptr symmetries_ptr; diff --git a/src/include/stir/recon_buildblock/PresmoothingForwardProjectorByBin.h b/src/include/stir/recon_buildblock/PresmoothingForwardProjectorByBin.h index e102170185..dc09bdb7b7 100644 --- a/src/include/stir/recon_buildblock/PresmoothingForwardProjectorByBin.h +++ b/src/include/stir/recon_buildblock/PresmoothingForwardProjectorByBin.h @@ -101,8 +101,10 @@ class PresmoothingForwardProjectorByBin : const int min_axial_pos_num, const int max_axial_pos_num, const int min_tangential_pos_num, const int max_tangential_pos_num); +#if 0 // disabled as currently not used. needs to be written in the new style anyway void actual_forward_project(Bin&, const DiscretisedDensity<3,float>&); +#endif virtual void set_defaults(); virtual void initialise_keymap(); diff --git a/src/recon_buildblock/ForwardProjectorByBinUsingProjMatrixByBin.cxx b/src/recon_buildblock/ForwardProjectorByBinUsingProjMatrixByBin.cxx index 35aabc50f8..1fa202b6f9 100644 --- a/src/recon_buildblock/ForwardProjectorByBinUsingProjMatrixByBin.cxx +++ b/src/recon_buildblock/ForwardProjectorByBinUsingProjMatrixByBin.cxx @@ -233,12 +233,13 @@ ForwardProjectorByBinUsingProjMatrixByBin:: } } +#if 0 // disabled as currently not used. needs to be written in the new style anyway void ForwardProjectorByBinUsingProjMatrixByBin:: actual_forward_project(Bin& this_bin, const DiscretisedDensity<3, float> &density) { - + // KT does not understand this `if` statement. The cache has nothing to do with symmetries etc. if (proj_matrix_ptr->is_cache_enabled()) { ProjMatrixElemsForOneBin proj_matrix_row; @@ -249,5 +250,6 @@ ForwardProjectorByBinUsingProjMatrixByBin:: else error("ForwardProjectorByBinUsingProjMatrixByBin: Symmetries should be handled by ProjMatrix. Abort. "); } +#endif END_NAMESPACE_STIR diff --git a/src/recon_buildblock/ForwardProjectorByBinUsingRayTracing.cxx b/src/recon_buildblock/ForwardProjectorByBinUsingRayTracing.cxx index d9fcba5db2..9558bbf590 100644 --- a/src/recon_buildblock/ForwardProjectorByBinUsingRayTracing.cxx +++ b/src/recon_buildblock/ForwardProjectorByBinUsingRayTracing.cxx @@ -1451,12 +1451,14 @@ forward_project_all_symmetries_2D(Viewgram & pos_view, stop_timers(); } +#if 0 // disabled as currently not used. needs to be written in the new style anyway void ForwardProjectorByBinUsingRayTracing:: actual_forward_project(Bin& this_bin, const DiscretisedDensity<3,float>& density) { - error("ForwardProjectorByBinUsingRayTracing is not supported for list-mode data. Abort."); + error("ForwardProjectorByBinUsingRayTracing does not support single-bin forward projection. Abort."); } +#endif END_NAMESPACE_STIR diff --git a/src/recon_buildblock/PresmoothingForwardProjectorByBin.cxx b/src/recon_buildblock/PresmoothingForwardProjectorByBin.cxx index e90a0a20cf..0393bcd5ba 100644 --- a/src/recon_buildblock/PresmoothingForwardProjectorByBin.cxx +++ b/src/recon_buildblock/PresmoothingForwardProjectorByBin.cxx @@ -148,11 +148,13 @@ actual_forward_project(RelatedViewgrams& viewgrams, min_tangential_pos_num, max_tangential_pos_num); } +#if 0 // disabled as currently not used. needs to be written in the new style anyway void PresmoothingForwardProjectorByBin::actual_forward_project(Bin&, const DiscretisedDensity<3,float>&) { - + error("TODO"); } +#endif END_NAMESPACE_STIR From 8683ef71c6909f27aefcf44920a2d3f8edc24697 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Fri, 7 Feb 2020 23:13:52 +0000 Subject: [PATCH 137/509] removed unused variable --- src/include/stir/recon_buildblock/ProjMatrixByBin.inl | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/include/stir/recon_buildblock/ProjMatrixByBin.inl b/src/include/stir/recon_buildblock/ProjMatrixByBin.inl index 259e6e4a61..939c545073 100644 --- a/src/include/stir/recon_buildblock/ProjMatrixByBin.inl +++ b/src/include/stir/recon_buildblock/ProjMatrixByBin.inl @@ -153,8 +153,6 @@ ProjMatrixByBin::apply_tof_kernel_and_symm_transformation(ProjMatrixElemsForOneB float low_dist = 0.f; float high_dist = 0.f; - float d1; - // The direction can be from 1 -> 2 depending on the bin sign. const CartesianCoordinate3D middle = (point1 + point2)*0.5f; const CartesianCoordinate3D diff = point2 - middle; From fafeefdd0b2f4512a885c1f6b94e1203f48c6770 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sat, 8 Feb 2020 07:47:49 +0000 Subject: [PATCH 138/509] implemented codacy recommendations --- src/recon_test/test_consistency_root.cxx | 3 ++- src/test/test_time_of_flight.cxx | 2 -- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/recon_test/test_consistency_root.cxx b/src/recon_test/test_consistency_root.cxx index 09dbb8b671..e34201c38c 100644 --- a/src/recon_test/test_consistency_root.cxx +++ b/src/recon_test/test_consistency_root.cxx @@ -69,7 +69,8 @@ class ROOTconsistency_Tests : public RunTests // used to calculate the centre of gravity (see below). class LORMax{ public: - LORMax() { voxel_centre = CartesianCoordinate3D(0.f,0.f,0.f); value = 0.f;} + LORMax() : voxel_centre(CartesianCoordinate3D(0.f,0.f,0.f)), value(0.f) + {} CartesianCoordinate3D voxel_centre; float value; }; diff --git a/src/test/test_time_of_flight.cxx b/src/test/test_time_of_flight.cxx index 439fddcfa2..3ddd24922a 100644 --- a/src/test/test_time_of_flight.cxx +++ b/src/test/test_time_of_flight.cxx @@ -467,14 +467,12 @@ export_lor(ProjMatrixElemsForOneBin& probabilities, tmp.float1 = d2; ProjMatrixElemsForOneBin::iterator element_ptr = probabilities.begin(); - bool found = false; while (element_ptr != probabilities.end()) { if (element_ptr->get_coords() == tmpl_element_ptr->get_coords()) { tmp.float2 = element_ptr->get_value(); - found = true; lor_to_export.push_back(tmp); break; } From 2b0691e25e3e5d4c963093ff25ec8d4be49aa399 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sat, 8 Feb 2020 21:56:34 +0000 Subject: [PATCH 139/509] enable tests for filling TOF data --- src/test/test_proj_data_in_memory.cxx | 61 ++++++++++++++------------- 1 file changed, 32 insertions(+), 29 deletions(-) diff --git a/src/test/test_proj_data_in_memory.cxx b/src/test/test_proj_data_in_memory.cxx index 92d145286a..3c074d825f 100644 --- a/src/test/test_proj_data_in_memory.cxx +++ b/src/test/test_proj_data_in_memory.cxx @@ -64,7 +64,7 @@ void ProjDataInMemoryTests:: run_tests_no_tof() { - std::cerr << "-------- Testing ProjDataInMemory --------\n"; + std::cerr << "-------- Testing ProjDataInMemory without TOF --------\n"; shared_ptr scanner_sptr(new Scanner(Scanner::E953)); shared_ptr proj_data_info_sptr @@ -153,6 +153,7 @@ run_tests_no_tof() // this should call error, so we'll catch it try { + std::cout << "\nthis test should throw an error (which we will catch)\n"; proj_data2.fill(proj_data); check(false, "test fill wtih too small proj_data should have thrown"); } @@ -167,12 +168,12 @@ void ProjDataInMemoryTests:: run_tests_tof() { - std::cerr << "-------- Testing ProjDataInMemory --------\n"; + std::cerr << "-------- Testing ProjDataInMemory with TOF --------\n"; shared_ptr scanner_sptr(new Scanner(Scanner::PETMR_Signa)); shared_ptr proj_data_info_sptr (ProjDataInfo::ProjDataInfoCTI(scanner_sptr, - /*span*/1, 5,/*views*/ 96, /*tang_pos*/64, /*arc_corrected*/ true, 70) + /*span*/1, 10,/*views*/ 96, /*tang_pos*/64, /*arc_corrected*/ true, 70) ); shared_ptr exam_info_sptr(new ExamInfo); @@ -253,41 +254,43 @@ run_tests_tof() { shared_ptr proj_data_info_sptr2 (ProjDataInfo::ProjDataInfoCTI(scanner_sptr, - /*span*/1, 8,/*views*/ 96, /*tang_pos*/128, /*arc_corrected*/ true) + /*span*/1, 8,/*views*/ 96, /*tang_pos*/64, /*arc_corrected*/ true, proj_data.get_tof_mash_factor()) ); // construct without filling ProjDataInMemory proj_data2(exam_info_sptr, proj_data_info_sptr2, false); -// proj_data2.fill(proj_data); -// check_if_equal(proj_data2.get_viewgram(0,0).find_max(), -// proj_data.get_viewgram(0,0).find_max(), -// "test 1 for copy-constructor and get_viewgram"); -// check_if_equal(proj_data2.get_viewgram(1,1).find_max(), -// proj_data.get_viewgram(1,1).find_max(), -// "test 1 for copy-constructor and get_viewgram"); + proj_data2.fill(proj_data); + check_if_equal(proj_data2.get_viewgram(0,0,false,-2).find_max(), + proj_data.get_viewgram(0,0,false,-2).find_max(), + "test 1 for copy-constructor and get_viewgram(0,0,-2)"); + check_if_equal(proj_data2.get_viewgram(1,1,false,2).find_max(), + proj_data.get_viewgram(1,1,false,2).find_max(), + "test 1 for copy-constructor and get_viewgram(1,1,2)"); + } // test fill with smaller input { -// shared_ptr proj_data_info_sptr2 -// (ProjDataInfo::ProjDataInfoCTI(scanner_sptr, -// /*span*/1, 12,/*views*/ 96, /*tang_pos*/128, /*arc_corrected*/ true) -// ); - - -// // construct without filling -// ProjDataInMemory proj_data2(exam_info_sptr, proj_data_info_sptr2, false); -// // this should call error, so we'll catch it -// try -// { -// proj_data2.fill(proj_data); -// check(false, "test fill wtih too small proj_data should have thrown"); -// } -// catch (...) -// { -// // ok -// } + shared_ptr proj_data_info_sptr2 + (ProjDataInfo::ProjDataInfoCTI(scanner_sptr, + /*span*/1, 20,/*views*/ 96, /*tang_pos*/64, /*arc_corrected*/ true, 70) + ); + + // construct without filling + ProjDataInMemory proj_data2(exam_info_sptr, proj_data_info_sptr2, false); + // this should call error, so we'll catch it + try + { + std::cout << "\nthis test should throw an error (which we will catch)\n"; + proj_data2.fill(proj_data); + check(false, "test fill with too small proj_data should have thrown"); + } + catch (...) + { + // ok + } + } } From 4645c1571dd034b9de34ba15f6cd190f2dc7ed11 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sat, 8 Feb 2020 22:00:00 +0000 Subject: [PATCH 140/509] corrected some diagnostic messages in test_proj_data_in_memory --- src/test/test_proj_data_in_memory.cxx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/test/test_proj_data_in_memory.cxx b/src/test/test_proj_data_in_memory.cxx index 3c074d825f..ade318c1e5 100644 --- a/src/test/test_proj_data_in_memory.cxx +++ b/src/test/test_proj_data_in_memory.cxx @@ -134,10 +134,10 @@ run_tests_no_tof() proj_data2.fill(proj_data); check_if_equal(proj_data2.get_viewgram(0,0).find_max(), proj_data.get_viewgram(0,0).find_max(), - "test 1 for copy-constructor and get_viewgram"); + "test 1 for constructor, fill and get_viewgram(0,0)"); check_if_equal(proj_data2.get_viewgram(1,1).find_max(), proj_data.get_viewgram(1,1).find_max(), - "test 1 for copy-constructor and get_viewgram"); + "test 1 for constructor, fill and get_viewgram(1,1)"); } // test fill with smaller input @@ -155,7 +155,7 @@ run_tests_no_tof() { std::cout << "\nthis test should throw an error (which we will catch)\n"; proj_data2.fill(proj_data); - check(false, "test fill wtih too small proj_data should have thrown"); + check(false, "test fill with too small proj_data should have thrown"); } catch (...) { @@ -263,10 +263,10 @@ run_tests_tof() proj_data2.fill(proj_data); check_if_equal(proj_data2.get_viewgram(0,0,false,-2).find_max(), proj_data.get_viewgram(0,0,false,-2).find_max(), - "test 1 for copy-constructor and get_viewgram(0,0,-2)"); + "test 1 for constructor, fill and get_viewgram(0,0,-2)"); check_if_equal(proj_data2.get_viewgram(1,1,false,2).find_max(), proj_data.get_viewgram(1,1,false,2).find_max(), - "test 1 for copy-constructor and get_viewgram(1,1,2)"); + "test 1 for constructor, fill and get_viewgram(1,1,2)"); } From 99c8f7ff686a5d1d70a029986e2a55094ea97f82 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sat, 8 Feb 2020 22:04:25 +0000 Subject: [PATCH 141/509] check TOF range in ProjDataInfo::operator== --- src/buildblock/ProjDataInfo.cxx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/buildblock/ProjDataInfo.cxx b/src/buildblock/ProjDataInfo.cxx index e156ba988e..c08f2d2dbb 100644 --- a/src/buildblock/ProjDataInfo.cxx +++ b/src/buildblock/ProjDataInfo.cxx @@ -724,6 +724,8 @@ blindly_equals(const root_type * const that) const (get_max_view_num()==proj.get_max_view_num()) && (get_min_tangential_pos_num() ==proj.get_min_tangential_pos_num())&& (get_max_tangential_pos_num() ==proj.get_max_tangential_pos_num())&& + (get_min_tof_pos_num() ==proj.get_min_tof_pos_num())&& + (get_max_tof_pos_num() ==proj.get_max_tof_pos_num())&& equal(min_axial_pos_per_seg.begin(), min_axial_pos_per_seg.end(), proj.min_axial_pos_per_seg.begin())&& equal(max_axial_pos_per_seg.begin(), max_axial_pos_per_seg.end(), proj.max_axial_pos_per_seg.begin())&& (*get_scanner_ptr()== *(proj.get_scanner_ptr()))&& @@ -752,7 +754,7 @@ operator !=(const root_type& that) const /*! \return \c true only if the types are the same, they are equal, or the range for the - segments, axial and tangential positions is at least as large. + TOF, segments, axial and tangential positions is at least as large. \warning Currently view ranges have to be identical. */ From e4e58048b0a3b4134adca693b50341171e2d0daf Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sat, 8 Feb 2020 22:52:28 +0000 Subject: [PATCH 142/509] [TOF] fixed bug in max_unmashed_tof_pos_num It was set via min_tof_bin, as opposed to min_unmashed_tof_bin, and was therefore generally too large. This seems harmless though (too small would have been a disaster). Also added some tests on min/max/num functions in ProjDataInfo --- src/buildblock/ProjDataInfo.cxx | 2 +- src/test/test_proj_data_info.cxx | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/buildblock/ProjDataInfo.cxx b/src/buildblock/ProjDataInfo.cxx index c08f2d2dbb..398e9b72f4 100644 --- a/src/buildblock/ProjDataInfo.cxx +++ b/src/buildblock/ProjDataInfo.cxx @@ -200,7 +200,7 @@ ProjDataInfo::set_tof_mash_factor(const int new_num) tof_increament_in_mm = tof_delta_time_to_mm(scanner_ptr->get_size_of_timing_pos()); min_unmashed_tof_pos_num = - (scanner_ptr->get_num_max_of_timing_poss())/2; - max_unmashed_tof_pos_num = min_tof_pos_num + (scanner_ptr->get_num_max_of_timing_poss()) -1; + max_unmashed_tof_pos_num = min_unmashed_tof_pos_num + (scanner_ptr->get_num_max_of_timing_poss()) -1; // Upper and lower boundaries of the timing poss; tof_bin_unmashed_boundaries_mm.grow(min_unmashed_tof_pos_num, max_unmashed_tof_pos_num); diff --git a/src/test/test_proj_data_info.cxx b/src/test/test_proj_data_info.cxx index 73326097c8..9c7e12e103 100644 --- a/src/test/test_proj_data_info.cxx +++ b/src/test/test_proj_data_info.cxx @@ -106,6 +106,30 @@ void ProjDataInfoTests:: test_generic_proj_data_info(ProjDataInfo& proj_data_info) { + cerr << "\tTests on get_min/max_num\n"; + check_if_equal(proj_data_info.get_max_tangential_pos_num() - proj_data_info.get_min_tangential_pos_num() + 1, + proj_data_info.get_num_tangential_poss(), + "basic check on min/max/num_tangential_pos_num"); + check(abs(proj_data_info.get_max_tangential_pos_num() + proj_data_info.get_min_tangential_pos_num()) <=1, + "check on min/max_tangential_pos_num being (almost) centred"); + check_if_equal(proj_data_info.get_max_tof_pos_num() - proj_data_info.get_min_tof_pos_num() + 1, + proj_data_info.get_num_tof_poss(), + "basic check on min/max/num_tof_pos_num"); + check_if_equal(proj_data_info.get_max_tof_pos_num() + proj_data_info.get_min_tof_pos_num(), 0, + "check on min/max_tof_pos_num being (almost) centred"); + check_if_equal(proj_data_info.get_max_view_num() - proj_data_info.get_min_view_num() + 1, + proj_data_info.get_num_views(), + "basic check on min/max/num_view_num"); + check_if_equal(proj_data_info.get_max_segment_num() - proj_data_info.get_min_segment_num() + 1, + proj_data_info.get_num_segments(), + "basic check on min/max/num_segment_num"); + // not strictly necessary in most of the code, but most likely required in some of it + check_if_equal(proj_data_info.get_max_segment_num() + proj_data_info.get_min_segment_num(), 0, + "check on min/max_segment_num being centred"); + check_if_equal(proj_data_info.get_max_axial_pos_num(0) - proj_data_info.get_min_axial_pos_num(0) + 1, + proj_data_info.get_num_axial_poss(0), + "basic check on min/max/num_axial_pos_num"); + cerr << "\tTests on get_LOR/get_bin\n"; int max_diff_segment_num=0; int max_diff_view_num=0; From d0753e303e3518a68c91840c5100ea363a429c40 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sat, 8 Feb 2020 22:59:48 +0000 Subject: [PATCH 143/509] renamed Scanner::get_num_max_of_timing_poss to get_max_num_timing_poss --- src/IO/InterfileHeader.cxx | 4 ++-- src/buildblock/ProjDataInfo.cxx | 16 ++++++++-------- src/buildblock/Scanner.cxx | 2 +- src/include/stir/ProjDataInfo.inl | 2 +- src/include/stir/Scanner.h | 2 +- src/include/stir/Scanner.inl | 2 +- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/IO/InterfileHeader.cxx b/src/IO/InterfileHeader.cxx index 1a019ff38e..2ba80aa28c 100644 --- a/src/IO/InterfileHeader.cxx +++ b/src/IO/InterfileHeader.cxx @@ -1263,10 +1263,10 @@ bool InterfilePDFSHeader::post_processing() if (guessed_scanner_ptr->is_tof_ready()) { - if (max_num_timing_poss != guessed_scanner_ptr->get_num_max_of_timing_poss()) + if (max_num_timing_poss != guessed_scanner_ptr->get_max_num_timing_poss()) { warning("Interfile warning: 'Number of TOF time bins' (%d) is expected to be %d.", - max_num_timing_poss, guessed_scanner_ptr->get_num_max_of_timing_poss()); + max_num_timing_poss, guessed_scanner_ptr->get_max_num_timing_poss()); mismatch_between_header_and_guess = true; } if (size_of_timing_pos != guessed_scanner_ptr->get_size_of_timing_pos()) diff --git a/src/buildblock/ProjDataInfo.cxx b/src/buildblock/ProjDataInfo.cxx index 398e9b72f4..3aeb954a96 100644 --- a/src/buildblock/ProjDataInfo.cxx +++ b/src/buildblock/ProjDataInfo.cxx @@ -193,14 +193,14 @@ ProjDataInfo::set_tof_mash_factor(const int new_num) { if (scanner_ptr->is_tof_ready() && new_num > 0 ) { - if(tof_mash_factor < 0 || tof_mash_factor > scanner_ptr->get_num_max_of_timing_poss()) + if(tof_mash_factor < 0 || tof_mash_factor > scanner_ptr->get_max_num_timing_poss()) error("ProjDataInfo: TOF mashing factor must be positive and smaller or equal than" "the scanner's number of max timing bins. Abort."); tof_mash_factor = new_num; tof_increament_in_mm = tof_delta_time_to_mm(scanner_ptr->get_size_of_timing_pos()); - min_unmashed_tof_pos_num = - (scanner_ptr->get_num_max_of_timing_poss())/2; - max_unmashed_tof_pos_num = min_unmashed_tof_pos_num + (scanner_ptr->get_num_max_of_timing_poss()) -1; + min_unmashed_tof_pos_num = - (scanner_ptr->get_max_num_timing_poss())/2; + max_unmashed_tof_pos_num = min_unmashed_tof_pos_num + (scanner_ptr->get_max_num_timing_poss()) -1; // Upper and lower boundaries of the timing poss; tof_bin_unmashed_boundaries_mm.grow(min_unmashed_tof_pos_num, max_unmashed_tof_pos_num); @@ -225,11 +225,11 @@ ProjDataInfo::set_tof_mash_factor(const int new_num) // Now, initialise the mashed TOF bins. tof_increament_in_mm = tof_delta_time_to_mm(tof_mash_factor * scanner_ptr->get_size_of_timing_pos()); - min_tof_pos_num = - (scanner_ptr->get_num_max_of_timing_poss() / tof_mash_factor)/2; - max_tof_pos_num = min_tof_pos_num + (scanner_ptr->get_num_max_of_timing_poss() / tof_mash_factor) -1; + min_tof_pos_num = - (scanner_ptr->get_max_num_timing_poss() / tof_mash_factor)/2; + max_tof_pos_num = min_tof_pos_num + (scanner_ptr->get_max_num_timing_poss() / tof_mash_factor) -1; - min_unmashed_tof_pos_num = - (scanner_ptr->get_num_max_of_timing_poss())/2; - max_unmashed_tof_pos_num = min_tof_pos_num + (scanner_ptr->get_num_max_of_timing_poss()) -1; + min_unmashed_tof_pos_num = - (scanner_ptr->get_max_num_timing_poss())/2; + max_unmashed_tof_pos_num = min_tof_pos_num + (scanner_ptr->get_max_num_timing_poss()) -1; num_tof_bins = max_tof_pos_num - min_tof_pos_num +1 ; @@ -677,7 +677,7 @@ ProjDataInfo* ProjDataInfo::ask_parameters() const int tof_mash_factor = scanner_ptr->is_tof_ready() ? ask_num("Time-of-flight mash factor:", 0, - scanner_ptr->get_num_max_of_timing_poss(), 25) : 0; + scanner_ptr->get_max_num_timing_poss(), 25) : 0; const bool arc_corrected = ask("Is the data arc-corrected?",true); diff --git a/src/buildblock/Scanner.cxx b/src/buildblock/Scanner.cxx index e564765433..567dd39f83 100644 --- a/src/buildblock/Scanner.cxx +++ b/src/buildblock/Scanner.cxx @@ -937,7 +937,7 @@ Scanner::parameter_info() const if (is_tof_ready()) { - s << "Number of TOF time bins :=" << get_num_max_of_timing_poss() << "\n"; + s << "Number of TOF time bins :=" << get_max_num_timing_poss() << "\n"; s << "Size of timing bin (ps) :=" << get_size_of_timing_pos() << "\n"; s << "Timing resolution (ps) :=" << get_timing_resolution() << "\n"; } diff --git a/src/include/stir/ProjDataInfo.inl b/src/include/stir/ProjDataInfo.inl index 887bf993d6..3acf6c15d2 100644 --- a/src/include/stir/ProjDataInfo.inl +++ b/src/include/stir/ProjDataInfo.inl @@ -174,7 +174,7 @@ ProjDataInfo::get_max_tof_pos_num() const float ProjDataInfo::get_coincidence_window_in_pico_sec() const { - return scanner_ptr->is_tof_ready()? (scanner_ptr->get_num_max_of_timing_poss() * + return scanner_ptr->is_tof_ready()? (scanner_ptr->get_max_num_timing_poss() * scanner_ptr->get_size_of_timing_pos()) :(scanner_ptr->get_size_of_timing_pos()); } diff --git a/src/include/stir/Scanner.h b/src/include/stir/Scanner.h index 63f64c8837..7f16997e71 100644 --- a/src/include/stir/Scanner.h +++ b/src/include/stir/Scanner.h @@ -276,7 +276,7 @@ class Scanner /* inline int get_num_layers_singles_units() const; */ inline int get_num_singles_units() const; //! Get the maximum number of TOF bins. - inline int get_num_max_of_timing_poss() const; + inline int get_max_num_timing_poss() const; //! Get the delta t which correspnds to the max number of TOF bins in picosecs. inline float get_size_of_timing_pos() const; //! Get the timing resolution of the scanner. diff --git a/src/include/stir/Scanner.inl b/src/include/stir/Scanner.inl index bc5480ea25..a948c6aac2 100644 --- a/src/include/stir/Scanner.inl +++ b/src/include/stir/Scanner.inl @@ -229,7 +229,7 @@ Scanner::get_reference_energy() const return reference_energy; } -int Scanner::get_num_max_of_timing_poss() const +int Scanner::get_max_num_timing_poss() const { return max_num_of_timing_poss; } From 5a45c96dd6cabb0c309d63277aefc1d95e752814 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sat, 8 Feb 2020 23:45:27 +0000 Subject: [PATCH 144/509] remove compiler warnings in ProjDataFromStream --- src/buildblock/ProjDataFromStream.cxx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/buildblock/ProjDataFromStream.cxx b/src/buildblock/ProjDataFromStream.cxx index 2b57456d60..f506086f4f 100644 --- a/src/buildblock/ProjDataFromStream.cxx +++ b/src/buildblock/ProjDataFromStream.cxx @@ -386,6 +386,11 @@ ProjDataFromStream::get_offsets(const int view_num, const int segment_num, temp[2] = 0; return temp; } + else + { + error("ProjDataFromStream::get_offsets: unsupported storage_order"); + return vector(); // return something to avoid compiler warning + } } Succeeded @@ -641,7 +646,8 @@ ProjDataFromStream::get_offsets_bin(const Bin this_bin) const } else { - error("ProjDataFromStream::get_offsets_bin: unsupported storage order\n"); + error("ProjDataFromStream::get_offsets_bin: unsupported storage order"); + return vector(); // return something to avoid compiler warning } } @@ -737,7 +743,8 @@ ProjDataFromStream::get_offsets_sino(const int ax_pos_num, const int segment_num } else { - error("ProjDataFromStream::get_offsets_sino: unsupported storage order\n"); + error("ProjDataFromStream::get_offsets_sino: unsupported storage order"); + return vector(); // return something to avoid compiler warning } } From b1402b36b9027382d4834e1b22523e1a3d1dc3d6 Mon Sep 17 00:00:00 2001 From: Nikos Efthimiou Date: Wed, 8 Apr 2020 17:00:54 -0400 Subject: [PATCH 145/509] fix ProjDataInfo comparison --- src/buildblock/ProjDataInfo.cxx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/buildblock/ProjDataInfo.cxx b/src/buildblock/ProjDataInfo.cxx index 3aeb954a96..6bd9ed613c 100644 --- a/src/buildblock/ProjDataInfo.cxx +++ b/src/buildblock/ProjDataInfo.cxx @@ -724,8 +724,8 @@ blindly_equals(const root_type * const that) const (get_max_view_num()==proj.get_max_view_num()) && (get_min_tangential_pos_num() ==proj.get_min_tangential_pos_num())&& (get_max_tangential_pos_num() ==proj.get_max_tangential_pos_num())&& - (get_min_tof_pos_num() ==proj.get_min_tof_pos_num())&& - (get_max_tof_pos_num() ==proj.get_max_tof_pos_num())&& + (that->is_tof_data() && this->is_tof_data() ? (get_min_tof_pos_num() == proj.get_min_tof_pos_num()) && + (get_max_tof_pos_num() == proj.get_max_tof_pos_num()) : true) && equal(min_axial_pos_per_seg.begin(), min_axial_pos_per_seg.end(), proj.min_axial_pos_per_seg.begin())&& equal(max_axial_pos_per_seg.begin(), max_axial_pos_per_seg.end(), proj.max_axial_pos_per_seg.begin())&& (*get_scanner_ptr()== *(proj.get_scanner_ptr()))&& @@ -774,9 +774,9 @@ operator>=(const ProjDataInfo& proj_data_info) const proj_data_info.get_min_segment_num() < larger_proj_data_info.get_min_segment_num() || proj_data_info.get_max_tangential_pos_num() > larger_proj_data_info.get_max_tangential_pos_num() || proj_data_info.get_min_tangential_pos_num() < larger_proj_data_info.get_min_tangential_pos_num() || - ((proj_data_info.get_min_tof_pos_num() < larger_proj_data_info.get_min_tof_pos_num() || - proj_data_info.get_max_tof_pos_num() > larger_proj_data_info.get_max_tof_pos_num()) && - (proj_data_info.is_tof_data() && larger_proj_data_info.is_tof_data()))) + ((proj_data_info.is_tof_data() && larger_proj_data_info.is_tof_data()) ? + (proj_data_info.get_min_tof_pos_num() < larger_proj_data_info.get_min_tof_pos_num() || + proj_data_info.get_max_tof_pos_num() > larger_proj_data_info.get_max_tof_pos_num()) : true)) return false; for (int segment_num=proj_data_info.get_min_segment_num(); From b70a0c19aca0d3118dc4d228d86fe9c40ebf4065 Mon Sep 17 00:00:00 2001 From: Nikos Efthimiou Date: Wed, 8 Apr 2020 18:17:15 -0400 Subject: [PATCH 146/509] fix on previous commit --- src/buildblock/ProjDataInfo.cxx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/buildblock/ProjDataInfo.cxx b/src/buildblock/ProjDataInfo.cxx index 6bd9ed613c..27ce1739f0 100644 --- a/src/buildblock/ProjDataInfo.cxx +++ b/src/buildblock/ProjDataInfo.cxx @@ -724,7 +724,7 @@ blindly_equals(const root_type * const that) const (get_max_view_num()==proj.get_max_view_num()) && (get_min_tangential_pos_num() ==proj.get_min_tangential_pos_num())&& (get_max_tangential_pos_num() ==proj.get_max_tangential_pos_num())&& - (that->is_tof_data() && this->is_tof_data() ? (get_min_tof_pos_num() == proj.get_min_tof_pos_num()) && + (proj.is_tof_data() && is_tof_data() ? (get_min_tof_pos_num() == proj.get_min_tof_pos_num()) && (get_max_tof_pos_num() == proj.get_max_tof_pos_num()) : true) && equal(min_axial_pos_per_seg.begin(), min_axial_pos_per_seg.end(), proj.min_axial_pos_per_seg.begin())&& equal(max_axial_pos_per_seg.begin(), max_axial_pos_per_seg.end(), proj.max_axial_pos_per_seg.begin())&& @@ -774,9 +774,9 @@ operator>=(const ProjDataInfo& proj_data_info) const proj_data_info.get_min_segment_num() < larger_proj_data_info.get_min_segment_num() || proj_data_info.get_max_tangential_pos_num() > larger_proj_data_info.get_max_tangential_pos_num() || proj_data_info.get_min_tangential_pos_num() < larger_proj_data_info.get_min_tangential_pos_num() || - ((proj_data_info.is_tof_data() && larger_proj_data_info.is_tof_data()) ? - (proj_data_info.get_min_tof_pos_num() < larger_proj_data_info.get_min_tof_pos_num() || - proj_data_info.get_max_tof_pos_num() > larger_proj_data_info.get_max_tof_pos_num()) : true)) + ((proj_data_info.get_min_tof_pos_num() < larger_proj_data_info.get_min_tof_pos_num() || + proj_data_info.get_max_tof_pos_num() > larger_proj_data_info.get_max_tof_pos_num()) && + (proj_data_info.is_tof_data() && larger_proj_data_info.is_tof_data()))) return false; for (int segment_num=proj_data_info.get_min_segment_num(); From 2bd6cf85188c241affb5ef8b82381c0e7e3c77b5 Mon Sep 17 00:00:00 2001 From: Nikos Efthimiou Date: Sun, 24 May 2020 23:19:52 -0400 Subject: [PATCH 147/509] Delete a small duplicate --- ...ForMeanAndListModeDataWithProjMatrixByBin.cxx | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx index 3ea159a3d5..a63c0b0317 100644 --- a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx +++ b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx @@ -528,18 +528,10 @@ compute_sub_gradient_without_penalty_plus_sensitivity(TargetT& gradient, } } - measured_bin.set_bin_value(1.0f); - // If more than 1 subsets, check if the current bin belongs to - // the current. - if (this->num_subsets > 1) - { - Bin basic_bin = measured_bin; - this->PM_sptr->get_symmetries_ptr()->find_basic_bin(basic_bin); - if (subset_num != static_cast(basic_bin.view_num() % this->num_subsets)) - continue; - } - this->PM_sptr->get_proj_matrix_elems_for_one_bin(proj_matrix_row, - measured_bin); + measured_bin.set_bin_value(1.0f); + + this->PM_sptr->get_proj_matrix_elems_for_one_bin(proj_matrix_row, + measured_bin); //in_the_range++; fwd_bin.set_bin_value(0.0f); From f334ad8f68c42ba6476c06cdeda0ad11936294af Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Mon, 13 Jul 2020 15:52:33 +0000 Subject: [PATCH 148/509] first attempt to use axial_effects for ECAT8 normalisation untested --- .../BinNormalisationFromECAT8.h | 15 ++- .../BinNormalisationFromECAT8.cxx | 98 ++++++++++++------- 2 files changed, 74 insertions(+), 39 deletions(-) diff --git a/src/include/stir/recon_buildblock/BinNormalisationFromECAT8.h b/src/include/stir/recon_buildblock/BinNormalisationFromECAT8.h index d06e73050e..2111786402 100644 --- a/src/include/stir/recon_buildblock/BinNormalisationFromECAT8.h +++ b/src/include/stir/recon_buildblock/BinNormalisationFromECAT8.h @@ -1,6 +1,6 @@ /* Copyright (C) 2000-2007, Hammersmith Imanet Ltd - Copyright (C) 2013-2014 University College London + Copyright (C) 2013-2014, 2020 University College London Largely a copy of the ECAT7 version. @@ -99,8 +99,9 @@ class BinNormalisationFromECAT8 : bool use_dead_time() const; bool use_geometric_factors() const; bool use_crystal_interference_factors() const; + bool use_axial_effects_factors() const; -private: + private: Array<1,float> axial_t1_array; Array<1,float> axial_t2_array; Array<1,float> trans_t1_array; @@ -108,6 +109,12 @@ class BinNormalisationFromECAT8 : Array<2,float> geometric_factors; Array<2,float> efficiency_factors; Array<2,float> crystal_interference_factors; + Array<1,float> axial_effects; + //! lookup table from STIR ring-pair to a Siemens sinogram-index + Array<2,int> sino_index; + //! number of sinograms in Siemens sinogram (span=11?) + int num_Siemens_sinograms; + shared_ptr scanner_ptr; int num_transaxial_crystals_per_block; // TODO move to Scanner @@ -124,11 +131,15 @@ class BinNormalisationFromECAT8 : bool _use_dead_time; bool _use_geometric_factors; bool _use_crystal_interference_factors; + bool _use_axial_effects_factors; void read_norm_data(const string& filename); float get_dead_time_efficiency ( const DetectionPosition<>& det_pos, const double start_time, const double end_time) const; + //! initialise sino_index and num_Siemens_sinograms + void construct_sino_lookup_table(); + float find_axial_effects(int ring1, int ring2) const; // parsing stuff virtual void set_defaults(); virtual void initialise_keymap(); diff --git a/src/recon_buildblock/BinNormalisationFromECAT8.cxx b/src/recon_buildblock/BinNormalisationFromECAT8.cxx index 0e6625b180..d19113e3fd 100644 --- a/src/recon_buildblock/BinNormalisationFromECAT8.cxx +++ b/src/recon_buildblock/BinNormalisationFromECAT8.cxx @@ -1,6 +1,6 @@ /* Copyright (C) 2002-2011, Hammersmith Imanet Ltd - Copyright (C) 2013-2014, 2019 University College London + Copyright (C) 2013-2014, 2019, 2020 University College London This file contains is based on information supplied by Siemens but is distributed with their consent. @@ -165,7 +165,8 @@ BinNormalisationFromECAT8::set_defaults() this->_use_detector_efficiencies = true; this->_use_dead_time = false; this->_use_geometric_factors = true; - this->_use_crystal_interference_factors = true; + this->_use_crystal_interference_factors = true; + this->use_axial_effects_factors = true; } void @@ -182,6 +183,7 @@ initialise_keymap() //this->parser.add_key("use_dead_time", &this->_use_dead_time); this->parser.add_key("use_geometric_factors", &this->_use_geometric_factors); this->parser.add_key("use_crystal_interference_factors", &this->_use_crystal_interference_factors); + this->parser.add_key("use_axial_effects_factors", &this->_use_axial_effects_factors); this->parser.add_stop_key("End Bin Normalisation From ECAT8"); } @@ -345,7 +347,8 @@ MatrixFile* mptr = matrix_open(filename.c_str(), MAT_READ_ONLY, Norm3d); /*num_tangential_poss=*/scanner_ptr->get_max_num_non_arccorrected_bins(), //XXXnrm_subheader_ptr->num_r_elements, /*arc_corrected =*/false) )); - + + this->construct_sino_lookup_table(); /* Extract geometrical & crystal interference, and crystal efficiencies from the normalisation data. @@ -374,7 +377,9 @@ MatrixFile* mptr = matrix_open(filename.c_str(), MAT_READ_ONLY, Norm3d); efficiency_factors = Array<2,float>(IndexRange2D(0,scanner_ptr->get_num_rings()-1, 0, scanner_ptr->get_num_detectors_per_ring()-1)); - + + axial_effects = + Array<1,float>(num_Siemens_sinograms); #if 0 int geom_test = nrm_subheader_ptr->num_geo_corr_planes * (max_tang_pos_num-min_tang_pos_num +1); @@ -389,6 +394,8 @@ MatrixFile* mptr = matrix_open(filename.c_str(), MAT_READ_ONLY, Norm3d); error("failed reading crystal_interference_factors from '%s'", full_data_file_name); if (read_data(binary_data, efficiency_factors, ByteOrder::little_endian) != Succeeded::yes) error("failed reading efficiency_factors from '%s'", full_data_file_name); + if (read_data(binary_data, axial_effects, ByteOrder::little_endian) != Succeeded::yes) + error("failed reading axial_effects_factors from '%s'", full_data_file_name); if (scanner_ptr->get_type() == Scanner::Siemens_mMR) { @@ -543,22 +550,6 @@ use_crystal_interference_factors() const float BinNormalisationFromECAT8:: get_bin_efficiency(const Bin& bin, const double start_time, const double end_time) const { - - - // TODO disable when not HR+ or HR++ - /* - Additional correction for HR+ and HR++ - ====================================== - Modification of the normalisation based on segment number - Due to the difference in efficiency for the trues and scatter as the axial - angle increases - Scatter has a higher efficiency than trues when the axial angle is 0 (direct - planes) - As the axial angle increase the difference in efficiencies between trues and - scatter become closer - */ - const float geo_Z_corr = 1; - float total_efficiency = 0 ; @@ -657,11 +648,7 @@ get_bin_efficiency(const Bin& bin, const double start_time, const double end_tim if (this->use_geometric_factors()) { lor_efficiency_this_pair *= -#ifdef SAME_AS_PETER - 1.F; -#else // this is 3dbkproj (at the moment) geometric_factors[geo_plane_num][uncompressed_bin.tangential_pos_num()]; -#endif } lor_efficiency += lor_efficiency_this_pair; } @@ -677,20 +664,10 @@ get_bin_efficiency(const Bin& bin, const double start_time, const double end_tim } } - if (this->use_geometric_factors()) + if (this->use_axial_effects()) { - /* z==bin.get_axial_pos_num() only when min_axial_pos_num()==0*/ - // for oblique plaanes use the single radial profile from segment 0 - -#ifdef SAME_AS_PETER - const int geo_plane_num = 0; - - total_efficiency += view_efficiency * - geometric_factors[geo_plane_num][uncompressed_bin.tangential_pos_num()] * - geo_Z_corr; -#else - total_efficiency += view_efficiency * geo_Z_corr; -#endif + const float axial_effect_factor = find_axial_effects(uncompressed_bin.segment(), uncompressed_bin.axial_pos()); + total_efficiency += view_efficiency * axial_effect_factor; } else { @@ -701,6 +678,53 @@ get_bin_efficiency(const Bin& bin, const double start_time, const double end_tim } #endif +void +BinNormalisationFromECAT8:: +construct_sino_lookup_table() +{ + const int num_rings = this->scanner_sptr->get_num_rings(); + this->sino_index(num_rings,num_rings); + // construct proj_data_info in "native" Siemens space for the norm (span=11 usually?) + // TODO will have to get "native" span from somewhere. is it in the norm header? + shared_ptr proj_data_info_sptr = + ProjDataInfo::construct_projdata...(span=11); + this->num_Siemens_sinograms = proj_data_info_sptr->get_num_sinograms(); // TODO will have to be get_num_non_tof_sinograms() + + auto segment_sequence = ecat::find_segment_sequence(); + Bin bin; + std::vector > det_pos_pairs; + for (int z=0; z < this->num_Siemens_sinograms; ++z) + { + for (std::size_t i=0; isegment_sequence.size();++i) + { + bin.segment_num() = segment_sequence[i]; + const int num_ax_poss = proj_data_info_sptr->get_num_axial_poss(bin.segment_num(); + if (z< num_ax_poss)) + { + bin.axial_pos_num() = z; + proj_data_info_sptr->get_all_det_pos_pairs_for_bin(det_pos_pairs, bin); + for (auto iter=det_pos_pairs.begin();iter!=det_pos_pairs.end(); ++iter) + { + sino_index[iter->pos1().axial_coord()][iter->pos2().axial_coord()] = z; + sino_index[iter->pos2().axial_coord()][iter->pos1().axial_coord()] = z; + } + break; + } + else + { + z -= num_ax_poss; + } + } + } +} + +float +BinNormalisationFromECAT8:: +find_axial_effects(int ring1, int ring2) const +{ + return axial_effects[sino_index[ring1][ring2]]; +} + float BinNormalisationFromECAT8::get_dead_time_efficiency (const DetectionPosition<>& det_pos, From b675157bef2cf174cf58d2a694dfc9c233372ba5 Mon Sep 17 00:00:00 2001 From: danieldeidda Date: Thu, 16 Jul 2020 15:36:31 +0100 Subject: [PATCH 149/509] ECAT8norm: completed function, construct_sino_lookup_table(), for axial effects --- .../BinNormalisationFromECAT8.cxx | 35 +++++++++++++------ 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/src/recon_buildblock/BinNormalisationFromECAT8.cxx b/src/recon_buildblock/BinNormalisationFromECAT8.cxx index d19113e3fd..db0c29fa3e 100644 --- a/src/recon_buildblock/BinNormalisationFromECAT8.cxx +++ b/src/recon_buildblock/BinNormalisationFromECAT8.cxx @@ -166,7 +166,7 @@ BinNormalisationFromECAT8::set_defaults() this->_use_dead_time = false; this->_use_geometric_factors = true; this->_use_crystal_interference_factors = true; - this->use_axial_effects_factors = true; + this->_use_axial_effects_factors = true; } void @@ -539,6 +539,13 @@ use_geometric_factors() const return this->_use_geometric_factors; } +bool +BinNormalisationFromECAT8:: +use_axial_effects_factors() const +{ + return this->_use_axial_effects_factors; +} + bool BinNormalisationFromECAT8:: use_crystal_interference_factors() const @@ -664,9 +671,9 @@ get_bin_efficiency(const Bin& bin, const double start_time, const double end_tim } } - if (this->use_axial_effects()) + if (this->use_axial_effects_factors()) { - const float axial_effect_factor = find_axial_effects(uncompressed_bin.segment(), uncompressed_bin.axial_pos()); + const float axial_effect_factor = find_axial_effects(uncompressed_bin.segment_num(), uncompressed_bin.axial_pos_num()); total_efficiency += view_efficiency * axial_effect_factor; } else @@ -682,24 +689,30 @@ void BinNormalisationFromECAT8:: construct_sino_lookup_table() { - const int num_rings = this->scanner_sptr->get_num_rings(); - this->sino_index(num_rings,num_rings); + const int num_rings = this->scanner_ptr->get_num_rings(); + this->sino_index=Array<2,int>(IndexRange2D(0, num_rings-1, + 0, num_rings-1)); // construct proj_data_info in "native" Siemens space for the norm (span=11 usually?) // TODO will have to get "native" span from somewhere. is it in the norm header? - shared_ptr proj_data_info_sptr = - ProjDataInfo::construct_projdata...(span=11); + shared_ptr proj_data_info_uptr=ProjDataInfo::construct_proj_data_info(this->scanner_ptr, 11, 49, + this->scanner_ptr->get_max_num_views()-1, + this->scanner_ptr->get_max_num_non_arccorrected_bins()); + + shared_ptr proj_data_info_sptr( + dynamic_cast(proj_data_info_uptr->clone())); + this->num_Siemens_sinograms = proj_data_info_sptr->get_num_sinograms(); // TODO will have to be get_num_non_tof_sinograms() - auto segment_sequence = ecat::find_segment_sequence(); + auto segment_sequence = ecat::find_segment_sequence(*proj_data_info_sptr); Bin bin; std::vector > det_pos_pairs; for (int z=0; z < this->num_Siemens_sinograms; ++z) { - for (std::size_t i=0; isegment_sequence.size();++i) + for (std::size_t i=0; iget_num_axial_poss(bin.segment_num(); - if (z< num_ax_poss)) + const int num_ax_poss = proj_data_info_sptr->get_num_axial_poss(bin.segment_num()); + if (z< num_ax_poss) { bin.axial_pos_num() = z; proj_data_info_sptr->get_all_det_pos_pairs_for_bin(det_pos_pairs, bin); From 7e0282f0593c0eef157b5e99d8f05eb9504c74dc Mon Sep 17 00:00:00 2001 From: danieldeidda Date: Wed, 29 Jul 2020 11:51:59 +0100 Subject: [PATCH 150/509] make the creation of the projdatainfo shorter --- src/recon_buildblock/BinNormalisationFromECAT8.cxx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/recon_buildblock/BinNormalisationFromECAT8.cxx b/src/recon_buildblock/BinNormalisationFromECAT8.cxx index db0c29fa3e..a611d0b572 100644 --- a/src/recon_buildblock/BinNormalisationFromECAT8.cxx +++ b/src/recon_buildblock/BinNormalisationFromECAT8.cxx @@ -694,12 +694,12 @@ construct_sino_lookup_table() 0, num_rings-1)); // construct proj_data_info in "native" Siemens space for the norm (span=11 usually?) // TODO will have to get "native" span from somewhere. is it in the norm header? - shared_ptr proj_data_info_uptr=ProjDataInfo::construct_proj_data_info(this->scanner_ptr, 11, 49, + ProjDataInfo* proj_data_info=ProjDataInfo::construct_proj_data_info(this->scanner_ptr, 11, 49, this->scanner_ptr->get_max_num_views()-1, - this->scanner_ptr->get_max_num_non_arccorrected_bins()); + this->scanner_ptr->get_max_num_non_arccorrected_bins())->clone(); - shared_ptr proj_data_info_sptr( - dynamic_cast(proj_data_info_uptr->clone())); + shared_ptr proj_data_info_sptr + (dynamic_cast(proj_data_info)); this->num_Siemens_sinograms = proj_data_info_sptr->get_num_sinograms(); // TODO will have to be get_num_non_tof_sinograms() From ffe06f6c6f01f19e1b50d81264d19bf2a7d8ec03 Mon Sep 17 00:00:00 2001 From: danieldeidda Date: Wed, 5 Aug 2020 11:02:19 +0100 Subject: [PATCH 151/509] fixed get_bin_efficiency and call find_axial_factors() with ring1 and ring2; save axial effects to file; added interfileRawDataHeaderSiemens; initialise view and tang_pos to 0 in construct_sino_lookup_table(). --- .../BinNormalisationFromECAT8.cxx | 44 ++++++++++++------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/src/recon_buildblock/BinNormalisationFromECAT8.cxx b/src/recon_buildblock/BinNormalisationFromECAT8.cxx index a611d0b572..727cd09d9e 100644 --- a/src/recon_buildblock/BinNormalisationFromECAT8.cxx +++ b/src/recon_buildblock/BinNormalisationFromECAT8.cxx @@ -45,6 +45,7 @@ #include "stir/display.h" #include "stir/IO/read_data.h" #include "stir/IO/InterfileHeader.h" +#include "stir/IO/InterfileHeaderSiemens.h" #include "stir/ByteOrder.h" #include "stir/is_null_ptr.h" #include @@ -259,8 +260,8 @@ MatrixFile* mptr = matrix_open(filename.c_str(), MAT_READ_ONLY, Norm3d); num_transaxial_crystals_per_block = nrm_subheader_ptr->num_transaxial_crystals ; #endif #if 0 - InterfileHeader interfile_parser; - ignore_key("data format"); + InterfileRawDataHeaderSiemens interfile_parser; +// interfile_parser.ignore_key("data format"); interfile_parser.parse(filename.c_str()); #else @@ -474,12 +475,13 @@ MatrixFile* mptr = matrix_open(filename.c_str(), MAT_READ_ONLY, Norm3d); #if 1 // to test pipe the obtained values into file - ofstream out_geom; + ofstream out_geom, out_axial; ofstream out_inter; ofstream out_eff; out_geom.open("geom_out.txt",ios::out); out_inter.open("inter_out.txt",ios::out); out_eff.open("eff_out.txt",ios::out); + out_axial.open("axial_out.txt",ios::out); for ( int i = geometric_factors.get_min_index(); i<=geometric_factors.get_max_index();i++) { @@ -508,6 +510,11 @@ MatrixFile* mptr = matrix_open(filename.c_str(), MAT_READ_ONLY, Norm3d); } out_eff << std::endl<< std::endl; } + + for ( int i = axial_effects.get_min_index(); i<=axial_effects.get_max_index();i++) + { + out_axial << axial_effects[i] << " " << std::endl; + } #endif @@ -595,7 +602,8 @@ get_bin_efficiency(const Bin& bin, const double start_time, const double end_tim uncompressed_bin, detection_position_pair); - + const DetectionPosition<>& pos1 = detection_position_pair.pos1(); + const DetectionPosition<>& pos2 = detection_position_pair.pos2(); float lor_efficiency= 0.; /* @@ -669,17 +677,18 @@ get_bin_efficiency(const Bin& bin, const double start_time, const double end_tim { view_efficiency += lor_efficiency; } - } + if (this->use_axial_effects_factors()) { - const float axial_effect_factor = find_axial_effects(uncompressed_bin.segment_num(), uncompressed_bin.axial_pos_num()); + const float axial_effect_factor = find_axial_effects(pos1.axial_coord(), pos2.axial_coord()); total_efficiency += view_efficiency * axial_effect_factor; } else { total_efficiency += view_efficiency; } + } } return total_efficiency; } @@ -693,23 +702,28 @@ construct_sino_lookup_table() this->sino_index=Array<2,int>(IndexRange2D(0, num_rings-1, 0, num_rings-1)); // construct proj_data_info in "native" Siemens space for the norm (span=11 usually?) - // TODO will have to get "native" span from somewhere. is it in the norm header? - ProjDataInfo* proj_data_info=ProjDataInfo::construct_proj_data_info(this->scanner_ptr, 11, 49, - this->scanner_ptr->get_max_num_views()-1, - this->scanner_ptr->get_max_num_non_arccorrected_bins())->clone(); - - shared_ptr proj_data_info_sptr - (dynamic_cast(proj_data_info)); + // TODO will have to get "native" span from somewhere. is it in the norm header? 49 + unique_ptr proj_data_info_uptr=ProjDataInfo::construct_proj_data_info(this->scanner_ptr, 11, num_rings -1, + this->scanner_ptr->get_max_num_views(), + this->scanner_ptr->get_max_num_non_arccorrected_bins(), + false); + + shared_ptr proj_data_info_sptr( + dynamic_cast(proj_data_info_uptr->clone())); this->num_Siemens_sinograms = proj_data_info_sptr->get_num_sinograms(); // TODO will have to be get_num_non_tof_sinograms() auto segment_sequence = ecat::find_segment_sequence(*proj_data_info_sptr); Bin bin; + bin.tangential_pos_num()=0; + bin.view_num()=0; std::vector > det_pos_pairs; - for (int z=0; z < this->num_Siemens_sinograms; ++z) + for (int Siemens_sino_index=0; Siemens_sino_index< this->num_Siemens_sinograms; ++Siemens_sino_index) { + int z=Siemens_sino_index; + for (std::size_t i=0; iget_num_axial_poss(bin.segment_num()); if (z< num_ax_poss) From 0f0cccb30c2abe56f6fb2cb2ff6dbf4ade4cb1d4 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Wed, 2 Dec 2020 11:31:52 +0000 Subject: [PATCH 152/509] correct ProjData::get_num_non_tof_sinograms() --- src/include/stir/ProjData.inl | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/include/stir/ProjData.inl b/src/include/stir/ProjData.inl index 6b2122589f..5ed5f80f7b 100644 --- a/src/include/stir/ProjData.inl +++ b/src/include/stir/ProjData.inl @@ -93,12 +93,10 @@ int ProjData::get_max_tof_pos_num() const { return proj_data_info_ptr->get_max_tof_pos_num(); } int ProjData::get_num_non_tof_sinograms() const -{ return proj_data_info_ptr->get_num_sinograms(); } +{ return proj_data_info_ptr->get_num_non_tof_sinograms(); } int ProjData::get_num_sinograms() const -{ - return this->get_num_non_tof_sinograms()*this->get_num_tof_poss(); -} +{ return proj_data_info_ptr->get_num_sinograms(); } std::size_t ProjData::size_all() const { return proj_data_info_ptr->size_all(); } From 521be8da8204f87f6e9416ca047d8584b650c3ad Mon Sep 17 00:00:00 2001 From: NikEfth Date: Thu, 17 Dec 2020 20:28:51 -0500 Subject: [PATCH 153/509] If failed to find a TOF bin return min rather than centre --- src/include/stir/ProjDataInfo.inl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/include/stir/ProjDataInfo.inl b/src/include/stir/ProjDataInfo.inl index 3acf6c15d2..0c1507bf75 100644 --- a/src/include/stir/ProjDataInfo.inl +++ b/src/include/stir/ProjDataInfo.inl @@ -99,7 +99,7 @@ ProjDataInfo::get_tof_bin(const double delta) const } // TODO handle differently warning(boost::format("TOF delta time %g out of range") % delta); - return 0; + return min_tof_pos_num; } int @@ -116,7 +116,7 @@ ProjDataInfo::get_unmashed_tof_bin(const double delta) const } // TODO handle differently warning(boost::format("TOF delta time %g out of range") % delta); - return 0; + return min_tof_pos_num; } From 66df04293f7dc29e271e1e3f03c017d62163ca97 Mon Sep 17 00:00:00 2001 From: NikEfth Date: Fri, 18 Dec 2020 01:15:57 -0500 Subject: [PATCH 154/509] Update LmToProjData.cxx and CListEventCylindricalScannerWithDiscreteDetectors.inl Uncommented the two commands I had left out. --- ...ventCylindricalScannerWithDiscreteDetectors.inl | 4 ++-- src/listmode_buildblock/LmToProjData.cxx | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/include/stir/listmode/CListEventCylindricalScannerWithDiscreteDetectors.inl b/src/include/stir/listmode/CListEventCylindricalScannerWithDiscreteDetectors.inl index 91582b0252..303035e2c6 100644 --- a/src/include/stir/listmode/CListEventCylindricalScannerWithDiscreteDetectors.inl +++ b/src/include/stir/listmode/CListEventCylindricalScannerWithDiscreteDetectors.inl @@ -32,8 +32,8 @@ START_NAMESPACE_STIR CListEventCylindricalScannerWithDiscreteDetectors:: CListEventCylindricalScannerWithDiscreteDetectors(const shared_ptr& proj_data_info_sptr) { -// this->uncompressed_proj_data_info_sptr.reset(dynamic_cast( -// *proj_data_info_sptr); + this->uncompressed_proj_data_info_sptr.reset(dynamic_cast( + proj_data_info_sptr.get())); if (is_null_ptr(this->uncompressed_proj_data_info_sptr)) error("CListEventCylindricalScannerWithDiscreteDetectors takes only ProjDataInfoCylindricalNoArcCorr. Abord."); diff --git a/src/listmode_buildblock/LmToProjData.cxx b/src/listmode_buildblock/LmToProjData.cxx index bb4add8641..78c954d539 100644 --- a/src/listmode_buildblock/LmToProjData.cxx +++ b/src/listmode_buildblock/LmToProjData.cxx @@ -321,13 +321,13 @@ post_processing() { shared_ptr scanner_sptr(new Scanner(*scanner_ptr)); // TODO this won't work for the HiDAC or so -// proj_data_info_cyl_uncompressed_ptr.reset(dynamic_cast( -// ProjDataInfo::ProjDataInfoCTI(scanner_sptr, -// 1, scanner_ptr->get_num_rings()-1, -// scanner_ptr->get_num_detectors_per_ring()/2, -// scanner_ptr->get_default_num_arccorrected_bins(), -// false, -// 1))); + // N.E: The following command used to do a dynamic cast which now I removed. + proj_data_info_cyl_uncompressed_ptr.reset(ProjDataInfo::ProjDataInfoCTI(scanner_sptr, + 1, scanner_ptr->get_num_rings()-1, + scanner_ptr->get_num_detectors_per_ring()/2, + scanner_ptr->get_default_num_arccorrected_bins(), + false, + 1)); if ( normalisation_ptr->set_up(proj_data_info_cyl_uncompressed_ptr) != Succeeded::yes) From 7f778d1588e28b9358e528acc9b9e006547da173 Mon Sep 17 00:00:00 2001 From: NikEfth Date: Sat, 19 Dec 2020 01:10:02 -0500 Subject: [PATCH 155/509] Curently Passes: * run_tests.sh * run_root_GATE.sh * run_scatter_tests.sh Update bcktest.cxx, ListModeData.cxx, and 4 more files... --- recon_test_pack/simulate_PET_data_for_tests.sh | 1 + .../CListEventCylindricalScannerWithDiscreteDetectors.inl | 3 +-- src/include/stir/listmode/ListModeData.h | 2 +- src/listmode_buildblock/CListModeDataROOT.cxx | 7 ++++--- src/listmode_buildblock/CListRecordROOT.cxx | 4 ++-- src/listmode_buildblock/ListModeData.cxx | 6 +++--- src/recon_test/bcktest.cxx | 6 +++--- src/test/test_multiple_proj_data.cxx | 2 +- 8 files changed, 16 insertions(+), 15 deletions(-) diff --git a/recon_test_pack/simulate_PET_data_for_tests.sh b/recon_test_pack/simulate_PET_data_for_tests.sh index e1b965bd1a..54df49d607 100755 --- a/recon_test_pack/simulate_PET_data_for_tests.sh +++ b/recon_test_pack/simulate_PET_data_for_tests.sh @@ -48,6 +48,7 @@ template_sino=my_DSTE_3D_rd2_template.hs cat > my_input.txt <& proj_data_info_sptr) { - this->uncompressed_proj_data_info_sptr.reset(dynamic_cast( - proj_data_info_sptr.get())); + this->uncompressed_proj_data_info_sptr = std::dynamic_pointer_cast< const ProjDataInfoCylindricalNoArcCorr >(proj_data_info_sptr->create_shared_clone()); if (is_null_ptr(this->uncompressed_proj_data_info_sptr)) error("CListEventCylindricalScannerWithDiscreteDetectors takes only ProjDataInfoCylindricalNoArcCorr. Abord."); diff --git a/src/include/stir/listmode/ListModeData.h b/src/include/stir/listmode/ListModeData.h index 75e0e20ba7..3bea3f59fd 100644 --- a/src/include/stir/listmode/ListModeData.h +++ b/src/include/stir/listmode/ListModeData.h @@ -221,7 +221,7 @@ class ListModeData : public ExamData list mode data that is being read. \warning This member is obsolete and might be removed soon. */ - virtual const Scanner* get_scanner_ptr() const ; + virtual shared_ptr get_scanner_ptr() const ; //! Return if the file stores delayed events as well (as opposed to prompts) virtual bool has_delayeds() const = 0; diff --git a/src/listmode_buildblock/CListModeDataROOT.cxx b/src/listmode_buildblock/CListModeDataROOT.cxx index 9e620b021f..4d7ab3762f 100644 --- a/src/listmode_buildblock/CListModeDataROOT.cxx +++ b/src/listmode_buildblock/CListModeDataROOT.cxx @@ -185,14 +185,15 @@ CListModeDataROOT(const std::string& hroot_filename) error(error_str.c_str()); } - shared_ptr tmp( ProjDataInfo::construct_proj_data_info(this_scanner_sptr, + proj_data_info_sptr = std::const_pointer_cast( ProjDataInfo::construct_proj_data_info(this_scanner_sptr, 1, this_scanner_sptr->get_num_rings()-1, this_scanner_sptr->get_num_detectors_per_ring()/2, this_scanner_sptr->get_max_num_non_arccorrected_bins(), /* arc_correction*/false, - tof_mash_factor)); - this->set_proj_data_info_sptr(tmp); + tof_mash_factor)->create_shared_clone()); + //this->set_proj_data_info_sptr(tmp); + if (this->open_lm_file() == Succeeded::no) error("CListModeDataROOT: error opening ROOT file for filename '%s'", diff --git a/src/listmode_buildblock/CListRecordROOT.cxx b/src/listmode_buildblock/CListRecordROOT.cxx index fedb7e7f97..c4f033c422 100644 --- a/src/listmode_buildblock/CListRecordROOT.cxx +++ b/src/listmode_buildblock/CListRecordROOT.cxx @@ -51,8 +51,8 @@ void CListEventROOT::get_detection_position(DetectionPositionPair<>& _det_pos) c _det_pos.pos1() = det1; _det_pos.pos2() = det2; -// _det_pos.timing_pos() = this->get_uncompressed_proj_data_info_sptr()->get_tof_bin(delta_time); - _det_pos.timing_pos() = this->get_uncompressed_proj_data_info_sptr()->get_unmashed_tof_bin(delta_time); + _det_pos.timing_pos() = this->get_uncompressed_proj_data_info_sptr()->get_tof_bin(delta_time); +// _det_pos.timing_pos() = this->get_uncompressed_proj_data_info_sptr()->get_unmashed_tof_bin(delta_time); } void CListEventROOT::set_detection_position(const DetectionPositionPair<>&) diff --git a/src/listmode_buildblock/ListModeData.cxx b/src/listmode_buildblock/ListModeData.cxx index e223c85eac..6fcafc0f9a 100644 --- a/src/listmode_buildblock/ListModeData.cxx +++ b/src/listmode_buildblock/ListModeData.cxx @@ -40,20 +40,20 @@ ListModeData:: ~ListModeData() {} -const Scanner* +shared_ptr ListModeData:: get_scanner_ptr() const { if(is_null_ptr(proj_data_info_sptr)) error("ListModeData: ProjDataInfo has not been set."); - return proj_data_info_sptr->get_scanner_ptr(); + return proj_data_info_sptr->get_scanner_sptr(); } void ListModeData:: set_proj_data_info_sptr(shared_ptr new_proj_data_info_sptr) { - proj_data_info_sptr = new_proj_data_info_sptr; + proj_data_info_sptr = new_proj_data_info_sptr->create_shared_clone(); } shared_ptr diff --git a/src/recon_test/bcktest.cxx b/src/recon_test/bcktest.cxx index 84841caab4..15286536bc 100644 --- a/src/recon_test/bcktest.cxx +++ b/src/recon_test/bcktest.cxx @@ -280,10 +280,10 @@ main(int argc, char **argv) do { int min_timing_num = ask_num("Minimum timing position index to backproject", - proj_data_info_ptr->get_min_tof_pos_num(), proj_data_info_ptr->get_max_tof_pos_num(), - proj_data_info_ptr->get_min_tof_pos_num()); + proj_data_info_sptr->get_min_tof_pos_num(), proj_data_info_sptr->get_max_tof_pos_num(), + proj_data_info_sptr->get_min_tof_pos_num()); int max_timing_num = ask_num("Maximum timing position index to backproject", - min_timing_num, proj_data_info_ptr->get_max_tof_pos_num(), + min_timing_num, proj_data_info_sptr->get_max_tof_pos_num(), min_timing_num); int min_segment_num = ask_num("Minimum segment number to backproject", proj_data_info_sptr->get_min_segment_num(), proj_data_info_sptr->get_max_segment_num(), 0); diff --git a/src/test/test_multiple_proj_data.cxx b/src/test/test_multiple_proj_data.cxx index 35445558e1..b03d2af0b8 100644 --- a/src/test/test_multiple_proj_data.cxx +++ b/src/test/test_multiple_proj_data.cxx @@ -41,7 +41,7 @@ #include "stir/MultipleProjData.h" #include "stir/ProjDataInMemory.h" #include - +#include #ifndef STIR_NO_NAMESPACES using std::cerr; using std::setw; From dc0b2933a72b1816e2c3e17efe0d78e92bf66f5c Mon Sep 17 00:00:00 2001 From: NikEfth Date: Sat, 19 Dec 2020 02:33:19 -0500 Subject: [PATCH 156/509] Update and Pass run_test_simulate_and_recon.sh --- .../run_test_simulate_and_recon.sh | 25 +------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/recon_test_pack/run_test_simulate_and_recon.sh b/recon_test_pack/run_test_simulate_and_recon.sh index 67567ea0ec..9405b3937e 100755 --- a/recon_test_pack/run_test_simulate_and_recon.sh +++ b/recon_test_pack/run_test_simulate_and_recon.sh @@ -77,31 +77,8 @@ echo "Using `command -v OSMAPOSL`" LC_ALL=C export LC_ALL -<<<<<<< HEAD -echo "=== make emission image" -generate_image generate_uniform_cylinder.par -echo "=== make attenuation image" -generate_image generate_atten_cylinder.par -echo "=== create template sinogram (DSTE in 3D with max ring diff 2 to save time)" -template_sino=my_DSTE_3D_rd2_template.hs -cat > my_input.txt < my_create_${template_sino}.log 2>&1 -if [ $? -ne 0 ]; then - echo "ERROR running create_projdata_template. Check my_create_${template_sino}.log"; exit 1; -fi - -# create sinograms -./simulate_data.sh my_uniform_cylinder.hv my_atten_image.hv ${template_sino} -======= ./simulate_PET_data_for_tests.sh ->>>>>>> master + if [ $? -ne 0 ]; then echo "Error running simulation" exit 1 From 11d9b04e42368b4b94bd14621f96370e40adc879 Mon Sep 17 00:00:00 2001 From: NikEfth Date: Sat, 19 Dec 2020 17:40:30 -0500 Subject: [PATCH 157/509] Compiles with GESinga TOF support and stir.i updates. Update stir.i, CListModeDataGEHDF5.cxx, and 3 more files... --- src/buildblock/ProjDataGEHDF5.cxx | 6 +- src/include/stir/ProjDataGEHDF5.h | 6 +- src/include/stir/listmode/CListRecordGEHDF5.h | 204 +++--------------- .../CListModeDataGEHDF5.cxx | 2 +- src/swig/stir.i | 61 +++--- 5 files changed, 75 insertions(+), 204 deletions(-) diff --git a/src/buildblock/ProjDataGEHDF5.cxx b/src/buildblock/ProjDataGEHDF5.cxx index c1cf35dd92..5c046773a5 100644 --- a/src/buildblock/ProjDataGEHDF5.cxx +++ b/src/buildblock/ProjDataGEHDF5.cxx @@ -125,7 +125,8 @@ void ProjDataGEHDF5::initialise_ax_pos_offset() Viewgram ProjDataGEHDF5:: get_viewgram(const int view_num, const int segment_num, - const bool make_num_tangential_poss_odd) const + const bool make_num_tangential_poss_odd, + const int timing_pos) const { if (make_num_tangential_poss_odd) error("make_num_tangential_poss_odd not supported by ProjDataGEHDF5"); @@ -194,7 +195,8 @@ Succeeded ProjDataGEHDF5::set_viewgram(const Viewgram& v) return Succeeded::no; } -Sinogram ProjDataGEHDF5::get_sinogram(const int ax_pos_num, const int segment_num,const bool make_num_tangential_poss_odd) const +Sinogram ProjDataGEHDF5::get_sinogram(const int ax_pos_num, const int segment_num,const bool make_num_tangential_poss_odd, + const int timing_pos) const { // TODO error("ProjDataGEHDF5::get_sinogram not implemented yet"); diff --git a/src/include/stir/ProjDataGEHDF5.h b/src/include/stir/ProjDataGEHDF5.h index 2adcb21bb2..9dca79e642 100644 --- a/src/include/stir/ProjDataGEHDF5.h +++ b/src/include/stir/ProjDataGEHDF5.h @@ -67,9 +67,11 @@ class ProjDataGEHDF5 : public ProjData //! Set Sinogram Succeeded set_sinogram(const Sinogram& s); //! Get Viewgram - Viewgram get_viewgram(const int view_num, const int segment_num,const bool make_num_tangential_poss_odd=false) const; + Viewgram get_viewgram(const int view_num, const int segment_num,const bool make_num_tangential_poss_odd=false, + const int timing_pos = 0) const; //! Get Sinogram - Sinogram get_sinogram(const int ax_pos_num, const int sergment_num,const bool make_num_tangential_poss_odd=false) const; + Sinogram get_sinogram(const int ax_pos_num, const int sergment_num,const bool make_num_tangential_poss_odd=false, + const int timing_pos = 0) const; //! Get the segment sequence std::vector get_segment_sequence_in_hdf5() const; std::vector< unsigned int > seg_ax_offset; diff --git a/src/include/stir/listmode/CListRecordGEHDF5.h b/src/include/stir/listmode/CListRecordGEHDF5.h index b88b5c1d96..06d2d5f234 100644 --- a/src/include/stir/listmode/CListRecordGEHDF5.h +++ b/src/include/stir/listmode/CListRecordGEHDF5.h @@ -32,77 +32,6 @@ START_NAMESPACE_STIR namespace GE { namespace RDF_HDF5 { -<<<<<<< HEAD:src/include/stir/listmode/CListRecordGESigna.h -/*********************************** - * Supported Event Types - ***********************************/ -enum EventType -{ - EXTENDED_EVT = 0x0, - COINC_EVT = 0x1 -}; - -/*********************************** - * Supported Extended Event Types - ***********************************/ -enum ExtendedEvtType -{ - TIME_MARKER_EVT = 0x0, - COINC_COUNT_EVT = 0x1, - EXTERN_TRIG_EVT = 0x2, - TABLE_POS_EVT = 0x3, - /* RESERVED = 0x4 to 0xE */ - /* 0xE is temporary taken here to mark end of it. */ - END_LIST_EVT = 0xE, - SINGLE_EVT = 0xF -}; - - -//! Class for storing and using a coincidence event from a GE Signa PET/MR listmode file -/*! \ingroup listmode - \ingroup GE - This class cannot have virtual functions, as it needs to just store the data 6 bytes for CListRecordGESigna to work. -*/ -class CListEventDataGESigna -{ - public: - inline bool is_prompt() const { return true; } // TODO - inline Succeeded set_prompt(const bool prompt = true) - { - //if (prompt) random=1; else random=0; return Succeeded::yes; - return Succeeded::no; - } - inline void get_detection_position(DetectionPositionPair<>& det_pos) const - { - // TODO 447->get_num_detectors_per_ring()-1 - if (deltaTime<0) - { - det_pos.pos1().tangential_coord() = 447 - hiXtalTransAxID; - det_pos.pos1().axial_coord() = hiXtalAxialID; - det_pos.pos2().tangential_coord() = 447 - loXtalTransAxID; - det_pos.pos2().axial_coord() = loXtalAxialID; - det_pos.timing_pos() = -get_tof_bin(); - } - else - { - det_pos.pos1().tangential_coord() = 447 - loXtalTransAxID; - det_pos.pos1().axial_coord() = loXtalAxialID; - det_pos.pos2().tangential_coord() = 447 - hiXtalTransAxID; - det_pos.pos2().axial_coord() = hiXtalAxialID; - det_pos.timing_pos() = get_tof_bin(); - } - } - inline bool is_event() const - { - return (eventType==COINC_EVT)/* && eventTypeExt==COINC_COUNT_EVT)*/; - } // TODO need to find out how to see if it's a coincidence event - inline int get_tof_bin() const - { - return static_cast(deltaTime); - } - private: - -======= namespace detail { /*********************************** * Supported Event Length Modes @@ -146,7 +75,6 @@ class CListEventDataGESigna class CListAnyRecordDataGEHDF5 { public: ->>>>>>> master:src/include/stir/listmode/CListRecordGEHDF5.h #if STIRIsNativeByteOrderBigEndian // Do byteswapping first before using this bit field. TODO; @@ -172,39 +100,24 @@ class CListEventDataGESigna //if (prompt) random=1; else random=0; return Succeeded::yes; return Succeeded::no; } + inline void get_detection_position(DetectionPositionPair<>& det_pos) const + { + // TODO 447->get_num_detectors_per_ring()-1 + det_pos.pos1().tangential_coord() = 447 - loXtalTransAxID; + det_pos.pos1().axial_coord() = loXtalAxialID; + det_pos.pos2().tangential_coord() = 447 - hiXtalTransAxID; +// std::cout << hiXtalTransAxID << " " << loXtalTransAxID << std::endl; + det_pos.pos2().axial_coord() = hiXtalAxialID; + } inline bool is_event() const { return (eventType==COINC_EVT)/* && eventTypeExt==COINC_COUNT_EVT)*/; } // TODO need to find out how to see if it's a coincidence event -<<<<<<< HEAD:src/include/stir/listmode/CListRecordGESigna.h -//! A class for storing and using a timing 'event' from a GE Signa PET/MR listmode file -/*! \ingroup listmode - \ingroup GE - This class cannot have virtual functions, as it needs to just store the data 6 bytes for CListRecordGESigna to work. - */ -class ListTimeDataGESigna -{ - public: - inline unsigned long get_time_in_millisecs() const - { return (time_hi()<<16) | time_lo(); } - inline Succeeded set_time_in_millisecs(const unsigned long time_in_millisecs) - { - data.timeMarkerLS = ((1UL<<16)-1) & (time_in_millisecs); - data.timeMarkerMS = (time_in_millisecs) >> 16; - // TODO return more useful value - return Succeeded::yes; - } - inline bool is_time() const - { // TODO need to find out how to see if it's a timing event - return (data.eventType==EXTENDED_EVT) && (data.eventTypeExt==TIME_MARKER_EVT); - }// TODO - -private: - typedef union{ - struct { -======= ->>>>>>> master:src/include/stir/listmode/CListRecordGEHDF5.h + inline int get_tof_bin() const + { + return static_cast(deltaTime); + } #if STIRIsNativeByteOrderBigEndian // Do byteswapping first before using this bit field. TODO @@ -221,16 +134,8 @@ class ListTimeDataGESigna boost::uint16_t loXtalAxialID:6; /* Low Crystal Axial Id */ boost::uint16_t loXtalTransAxID:10; /* Low Crystal Trans-Axial Id */ #endif -<<<<<<< HEAD:src/include/stir/listmode/CListRecordGESigna.h - }; - } data_t; - data_t data; + }; /*-coincidence event*/ - unsigned long time_lo() const - { return data.timeMarkerLS; } - unsigned long time_hi() const - { return data.timeMarkerMS; } -}; #if 0 //! A class for storing and using a trigger 'event' from a GE Signa PET/MR listmode file @@ -262,9 +167,22 @@ class CListGatingDataGESigna private: typedef union{ struct { -======= - }; /*-coincidence event*/ +#if STIRIsNativeByteOrderBigEndian + boost::uint32_t signature : 5; + boost::uint32_t reserved : 3; + boost::uint32_t value : 24; // timing info here in the first word, but we're ignoring it +#else + boost::uint32_t value : 24; + boost::uint32_t reserved : 3; + boost::uint32_t signature : 5; +#endif + }; + boost::uint32_t raw; + } oneword_t; + oneword_t words[2]; +}; +#endif //! A class for storing and using a timing 'event' from a GE RDF9 listmode file /*! \ingroup listmode @@ -291,7 +209,6 @@ class CListGatingDataGESigna private: typedef union{ struct { ->>>>>>> master:src/include/stir/listmode/CListRecordGEHDF5.h #if STIRIsNativeByteOrderBigEndian TODO #else @@ -306,17 +223,9 @@ class CListGatingDataGESigna boost::uint16_t timeMarkerLS:16; /* Least Significant 16 bits of 32-bit Time Marker */ boost::uint16_t timeMarkerMS:16; /* Most Significant 16 bits of 32-bitTime Marker */ #endif -<<<<<<< HEAD:src/include/stir/listmode/CListRecordGESigna.h - }; - boost::uint32_t raw; - } oneword_t; - oneword_t words[2]; -}; -======= }; } data_t; data_t data; ->>>>>>> master:src/include/stir/listmode/CListRecordGEHDF5.h unsigned long time_lo() const { return data.timeMarkerLS; } @@ -340,30 +249,20 @@ class CListRecordGEHDF5 : public CListRecord, public ListTime, // public CListGa //typedef CListGatingDataGEHDF5 GatingType; public: -<<<<<<< HEAD:src/include/stir/listmode/CListRecordGESigna.h - CListRecordGESigna(const shared_ptr& proj_data_info_sptr) : - CListEventCylindricalScannerWithDiscreteDetectors(proj_data_info_sptr) - {} - - bool is_time() const - { - return this->time_data.is_time(); -======= //! constructor /*! Takes the scanner and first_time stamp. The former will be used for checking and swapping, the latter for adjusting the time of each event, as GE listmode files do not start with time-stamp 0. get_time_in_millisecs() should therefore be zero at the first time stamp. */ - CListRecordGEHDF5(const shared_ptr& scanner_sptr, const unsigned long first_time_stamp) : - CListEventCylindricalScannerWithDiscreteDetectors(scanner_sptr), + CListRecordGEHDF5(const shared_ptr& proj_data_info_sptr, const unsigned long first_time_stamp) : + CListEventCylindricalScannerWithDiscreteDetectors(proj_data_info_sptr), first_time_stamp(first_time_stamp) {} bool is_time() const { return this->time_data.is_time(); ->>>>>>> master:src/include/stir/listmode/CListRecordGEHDF5.h } #if 0 bool is_gating_input() const @@ -399,15 +298,10 @@ dynamic_cast(&e2) != 0 && #endif } -<<<<<<< HEAD:src/include/stir/listmode/CListRecordGESigna.h - // time - inline unsigned long get_time_in_millisecs() const - { return time_data.get_time_in_millisecs(); } -======= // time inline unsigned long get_time_in_millisecs() const { return time_data.get_time_in_millisecs() - first_time_stamp; } ->>>>>>> master:src/include/stir/listmode/CListRecordGEHDF5.h + inline Succeeded set_time_in_millisecs(const unsigned long time_in_millisecs) { return time_data.set_time_in_millisecs(time_in_millisecs); } #if 0 @@ -423,9 +317,9 @@ dynamic_cast(&e2) != 0 && virtual void get_detection_position(DetectionPositionPair<>& det_pos) const { - det_pos.pos1().tangential_coord() = scanner_sptr->get_num_detectors_per_ring() - 1 - event_data.loXtalTransAxID; + det_pos.pos1().tangential_coord() = this->uncompressed_proj_data_info_sptr->get_scanner_sptr()->get_num_detectors_per_ring() - 1 - event_data.loXtalTransAxID; det_pos.pos1().axial_coord() = event_data.loXtalAxialID; - det_pos.pos2().tangential_coord() = scanner_sptr->get_num_detectors_per_ring() - 1 - event_data.hiXtalTransAxID; + det_pos.pos2().tangential_coord() = this->uncompressed_proj_data_info_sptr->get_scanner_sptr()->get_num_detectors_per_ring() - 1 - event_data.hiXtalTransAxID; det_pos.pos2().axial_coord() = event_data.hiXtalAxialID; } @@ -435,22 +329,6 @@ dynamic_cast(&e2) != 0 && error("TODO"); } -<<<<<<< HEAD:src/include/stir/listmode/CListRecordGESigna.h - virtual std::size_t size_of_record_at_ptr(const char * const data_ptr, const std::size_t /*size*/, - const bool do_byte_swap) const - { - // TODO: get size of record from the file, whereas here I have hard-coded as being 6bytes (I know it's the case for the Orsay data) OtB 15/09 - - return std::size_t(6); // std::size_t(data_ptr[0]&0x80); - } - - virtual Succeeded init_from_data_ptr(const char * const data_ptr, - const std::size_t -#ifndef NDEBUG - size // only used within assert, so don't define otherwise to avoid compiler warning -#endif - , const bool do_byte_swap) -======= virtual std::size_t size_of_record_at_ptr(const char * const data_ptr, const std::size_t, const bool do_byte_swap) const { @@ -478,7 +356,6 @@ dynamic_cast(&e2) != 0 && virtual Succeeded init_from_data_ptr(const char * const data_ptr, const std::size_t size, const bool do_byte_swap) ->>>>>>> master:src/include/stir/listmode/CListRecordGEHDF5.h { assert(size >= 6); assert(size <= 16); @@ -486,7 +363,6 @@ dynamic_cast(&e2) != 0 && if (do_byte_swap) { -<<<<<<< HEAD:src/include/stir/listmode/CListRecordGESigna.h ByteOrder::swap_order(this->raw[0]); } if (this->is_event() || this->is_time()) @@ -501,10 +377,6 @@ dynamic_cast(&e2) != 0 && { error("don't know how to byteswap"); ByteOrder::swap_order(this->raw[1]); -======= - error("ClistRecordGEHDF5: byte-swapping not supported yet. sorry"); - //ByteOrder::swap_order(this->raw[0]); ->>>>>>> master:src/include/stir/listmode/CListRecordGEHDF5.h } if (this->is_event()) @@ -512,9 +384,6 @@ dynamic_cast(&e2) != 0 && // set TOF info in ps this->delta_time = this->event_data.get_tof_bin() *this-> get_scanner_ptr()->get_size_of_timing_pos(); } - - - return Succeeded::yes; } @@ -528,13 +397,8 @@ dynamic_cast(&e2) != 0 && }; BOOST_STATIC_ASSERT(sizeof(boost::int32_t)==4); BOOST_STATIC_ASSERT(sizeof(DataType)==6); -<<<<<<< HEAD:src/include/stir/listmode/CListRecordGESigna.h - BOOST_STATIC_ASSERT(sizeof(TimeType)==6); - //BOOST_STATIC_ASSERT(sizeof(GatingType)==8); -======= BOOST_STATIC_ASSERT(sizeof(TimeType)==6); //BOOST_STATIC_ASSERT(sizeof(GatingType)==8); ->>>>>>> master:src/include/stir/listmode/CListRecordGEHDF5.h }; diff --git a/src/listmode_buildblock/CListModeDataGEHDF5.cxx b/src/listmode_buildblock/CListModeDataGEHDF5.cxx index 64bfcb4c59..d50cc8ce87 100644 --- a/src/listmode_buildblock/CListModeDataGEHDF5.cxx +++ b/src/listmode_buildblock/CListModeDataGEHDF5.cxx @@ -65,7 +65,7 @@ get_empty_record_sptr() const if (is_null_ptr(this->get_proj_data_info_sptr())) error("listmode file needs to be opened before calling get_empty_record_sptr()"); - shared_ptr sptr(new CListRecordT(this->get_proj_data_info_sptr()->get_scanner_sptr(), + shared_ptr sptr(new CListRecordT(this->get_proj_data_info_sptr(), this->first_time_stamp)); return sptr; } diff --git a/src/swig/stir.i b/src/swig/stir.i index 4909212482..e8ad993ab7 100644 --- a/src/swig/stir.i +++ b/src/swig/stir.i @@ -707,26 +707,26 @@ namespace std { proj_data.fill_from(array_iter); } - static Array<3,float> create_array_for_proj_data(const ProjData& proj_data) - { - // int num_sinos=proj_data.get_num_axial_poss(0); - // for (int s=1; s<= proj_data.get_max_segment_num(); ++s) - // { - // num_sinos += 2*proj_data.get_num_axial_poss(s); - // } - int num_sinos = proj_data.get_num_sinograms(); - - Array<3,float> array(IndexRange3D(num_sinos, proj_data.get_num_views(), proj_data.get_num_tangential_poss())); - return array; - } - - static Array<3,float> projdata_to_3D(const ProjData& proj_data) - { - Array<3,float> array = create_array_for_proj_data(proj_data); - Array<3,float>::full_iterator array_iter = array.begin_all(); - copy_to(proj_data, array_iter); - return array; - } +// static Array<3,float> create_array_for_proj_data(const ProjData& proj_data) +// { +// // int num_sinos=proj_data.get_num_axial_poss(0); +// // for (int s=1; s<= proj_data.get_max_segment_num(); ++s) +// // { +// // num_sinos += 2*proj_data.get_num_axial_poss(s); +// // } +// int num_sinos = proj_data.get_num_sinograms(); + +// Array<3,float> array(IndexRange3D(num_sinos, proj_data.get_num_views(), proj_data.get_num_tangential_poss())); +// return array; +// } + +// static Array<3,float> projdata_to_3D(const ProjData& proj_data) +// { +// Array<3,float> array = create_array_for_proj_data(proj_data); +// Array<3,float>::full_iterator array_iter = array.begin_all(); +// copy_to(proj_data, array_iter); +// return array; +// } } // end of namespace @@ -1448,14 +1448,14 @@ namespace stir { if (PyIter_Check(arg)) { // From TOF branch - //Array<4,float> array = swigstir::create_array_for_proj_data(*$self); - //swigstir::fill_Array_from_Python_iterator(&array, arg); - //swigstir::fill_proj_data_from_4D(*$self, array); + Array<4,float> array = swigstir::create_array_for_proj_data(*$self); + swigstir::fill_Array_from_Python_iterator(&array, arg); + swigstir::fill_proj_data_from_4D(*$self, array); // TODO avoid need for copy to Array - Array<3,float> array = swigstir::create_array_for_proj_data(*$self); - swigstir::fill_Array_from_Python_iterator(&array, arg); - fill_from(*$self, array.begin_all(), array.end_all()); +// Array<3,float> array = swigstir::create_array_for_proj_data(*$self); +// swigstir::fill_Array_from_Python_iterator(&array, arg); +// fill_from(*$self, array.begin_all(), array.end_all()); } else { @@ -1495,9 +1495,12 @@ namespace stir { { if (PyIter_Check(arg)) { - Array<3,float> array = swigstir::create_array_for_proj_data(*$self); - swigstir::fill_Array_from_Python_iterator(&array, arg); - fill_from(*$self, array.begin_all(), array.end_all()); + Array<4,float> array = swigstir::create_array_for_proj_data(*$self); + swigstir::fill_Array_from_Python_iterator(&array, arg); + swigstir::fill_proj_data_from_4D(*$self, array); +// Array<3,float> array = swigstir::create_array_for_proj_data(*$self); +// swigstir::fill_Array_from_Python_iterator(&array, arg); +// fill_from(*$self, array.begin_all(), array.end_all()); } else { From b3f91c0fb1124ca8bbffa7d5db1aef3714bfa2b4 Mon Sep 17 00:00:00 2001 From: NikEfth Date: Sat, 19 Dec 2020 19:38:35 -0500 Subject: [PATCH 158/509] T.B.C. this value allows the test_ArcCorrection to pass --- src/buildblock/Scanner.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/buildblock/Scanner.cxx b/src/buildblock/Scanner.cxx index 14989590e2..e8c90fed27 100644 --- a/src/buildblock/Scanner.cxx +++ b/src/buildblock/Scanner.cxx @@ -436,7 +436,7 @@ case PETMR_Signa: 311.8F, 8.5F, 5.56F, - 2.01565F, // TO CHECK + 2.1306F, // TO CHECK static_cast(-5.23*_PI/180),//sign? TODO value 5, 4, From 76994bef4482a9e83dfd3d7cbad9877ac35fd43d Mon Sep 17 00:00:00 2001 From: NikEfth Date: Sat, 19 Dec 2020 20:17:07 -0500 Subject: [PATCH 159/509] Yet another scanner generation fix. --- examples/PET_simulation/generate_input_data.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/PET_simulation/generate_input_data.sh b/examples/PET_simulation/generate_input_data.sh index aaf4790307..ab83aa01c8 100755 --- a/examples/PET_simulation/generate_input_data.sh +++ b/examples/PET_simulation/generate_input_data.sh @@ -49,6 +49,7 @@ template_sino=my_DSTE_3D_rd1_template.hs cat > my_input.txt < Date: Mon, 21 Dec 2020 11:59:30 -0500 Subject: [PATCH 160/509] Revert "T.B.C. this value allows the test_ArcCorrection to pass" This reverts commit b3f91c0fb1124ca8bbffa7d5db1aef3714bfa2b4. --- src/buildblock/Scanner.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/buildblock/Scanner.cxx b/src/buildblock/Scanner.cxx index e8c90fed27..14989590e2 100644 --- a/src/buildblock/Scanner.cxx +++ b/src/buildblock/Scanner.cxx @@ -436,7 +436,7 @@ case PETMR_Signa: 311.8F, 8.5F, 5.56F, - 2.1306F, // TO CHECK + 2.01565F, // TO CHECK static_cast(-5.23*_PI/180),//sign? TODO value 5, 4, From 4a2be3211ea8f021570c5fd52aaf8aefc9abfe1b Mon Sep 17 00:00:00 2001 From: NikEfth Date: Mon, 21 Dec 2020 12:00:47 -0500 Subject: [PATCH 161/509] Reduce the threshold on overlap_interpolate.inl to fix a numerical instability with Signa. --- src/include/stir/numerics/overlap_interpolate.inl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/include/stir/numerics/overlap_interpolate.inl b/src/include/stir/numerics/overlap_interpolate.inl index 36d7fc9ccf..1f05dc8732 100644 --- a/src/include/stir/numerics/overlap_interpolate.inl +++ b/src/include/stir/numerics/overlap_interpolate.inl @@ -86,9 +86,9 @@ void // we'll take it 1000 times smaller than the minimum of the average out_box size or in_box size const coord_t epsilon = std::min(((*(out_coord_end-1)) - (*out_coord_begin)) / - ((out_coord_end-1 - out_coord_begin)*1000), + ((out_coord_end-1 - out_coord_begin)*10000), ((*(in_coord_end-1)) - (*in_coord_begin)) / - ((in_coord_end-1 - in_coord_begin)*1000)); + ((in_coord_end-1 - in_coord_begin)*10000)); // do actual interpolation // we walk through the boxes, checking the overlap. From 970264ba915ef4c9d9bff5b442e59d4ae7320264 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sat, 30 Jan 2021 11:07:45 +0000 Subject: [PATCH 162/509] hopefully more clear printing of TOF info --- src/buildblock/ProjDataInfo.cxx | 1 + src/utilities/list_projdata_info.cxx | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/buildblock/ProjDataInfo.cxx b/src/buildblock/ProjDataInfo.cxx index 9bd4008bd6..658c1678b7 100644 --- a/src/buildblock/ProjDataInfo.cxx +++ b/src/buildblock/ProjDataInfo.cxx @@ -327,6 +327,7 @@ ProjDataInfo::parameter_info() const << get_bed_position_vertical() << endl; s << "start horizontal bed position (mm) := " << get_bed_position_horizontal() << endl; + s << "\nNumber of TOF positions in data: " << get_num_tof_poss() << '\n'; s << "\nSegment_num range: (" << get_min_segment_num() << ", " << get_max_segment_num() << ")\n"; diff --git a/src/utilities/list_projdata_info.cxx b/src/utilities/list_projdata_info.cxx index a5dfd661f9..c64f50bc31 100644 --- a/src/utilities/list_projdata_info.cxx +++ b/src/utilities/list_projdata_info.cxx @@ -137,11 +137,10 @@ int main(int argc, char *argv[]) const int max_segment_num = proj_data_sptr->get_max_segment_num(); const int min_timing_num = proj_data_sptr->get_min_tof_pos_num(); const int max_timing_num = proj_data_sptr->get_max_tof_pos_num(); - std::cout << "\nTotal number of TOF positions: " << proj_data_sptr->get_num_tof_poss(); for (int timing_num = min_timing_num; timing_num <= max_timing_num; ++timing_num) { - std::cout << "\nTiming location: " << timing_num; + std::cout << "\nTOF bin: " << timing_num; bool accumulators_initialized = false; float accum_min=std::numeric_limits::max(); // initialize to very large in case projdata is empty (although that's unlikely) float accum_max=std::numeric_limits::min(); From 31c399089876c44538c58b0b691f05edd210d654 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sat, 30 Jan 2021 11:08:20 +0000 Subject: [PATCH 163/509] allow SSRB tof_mashing for proj_data_info actual proj-data not done yet (will call error()) --- src/buildblock/SSRB.cxx | 8 ++++++++ src/include/stir/SSRB.h | 4 +++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/buildblock/SSRB.cxx b/src/buildblock/SSRB.cxx index 906bfd8ce8..7110443478 100644 --- a/src/buildblock/SSRB.cxx +++ b/src/buildblock/SSRB.cxx @@ -164,6 +164,14 @@ SSRB(const ProjDataInfo& in_proj_data_info, out_segment_num); } } + + if (num_tof_bins_to_combine!=1) + { + if (num_tof_bins_to_combine<1) + error("SSRB: num_tof_bins_to_combine needs to be at least 1"); + const int new_tof_mash_factor = in_proj_data_info_sptr->get_tof_mash_factor() * num_tof_bins_to_combine; + out_proj_data_info_sptr->set_tof_mash_factor(new_tof_mash_factor); + } return out_proj_data_info_sptr; } diff --git a/src/include/stir/SSRB.h b/src/include/stir/SSRB.h index d2f137d110..72a10da0e9 100644 --- a/src/include/stir/SSRB.h +++ b/src/include/stir/SSRB.h @@ -49,7 +49,7 @@ class ProjDataInfo; away some bins. Half of the bins will be thrown away at each 'side' of a sinogram (see below). \param max_in_segment_num_to_process rebinned in_proj_data only upto this segment. Default value -1 means 'do all segments'. - \param num_tof_bins_to_combine currently has to be 1. + \param num_tof_bins_to_combine can be used to increase TOF mashing. The original SSRB algorithm was developed in M.E. Daube-Witherspoon and G. Muehllehner, (1987) Treatment of axial data in three-dimensional PET, @@ -101,6 +101,7 @@ SSRB(const ProjDataInfo& in_proj_data_info, Default value -1 means 'do all segments'. \param do_normalisation (default true) wether to normalise the output sinograms corresponding to how many input sinograms contribute to them. + \param num_tof_bins_to_combine currently has to be 1. If it doesn't, error() will be called. \see SSRB(const ProjDataInfo& in_proj_data_info, const int num_segments_to_combine, @@ -132,6 +133,7 @@ SSRB(const std::string& output_filename, corresponding to how many input sinograms contribute to them. \warning in_proj_data_info has to be (at least) of type ProjDataInfoCylindrical + \warning TOF info has to match currently. If it doesn't, error() will be called. */ void SSRB(ProjData& out_projdata, From 930e41622b35d4c511e077a283423062e73866f3 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sat, 21 Aug 2021 02:00:46 +0100 Subject: [PATCH 164/509] major revision of BinNormalisationFromECAT8 header parsing - created new class InterfileNormHeaderSiemens where all the parsing is done (moving some bits from BinNormalisationFromECAT8, but adding much more) - add more consistency checks after parsing (still not enough) - fix problem that we always assumed span=11 in the norm header as we now parse it - fix creating of internal proj_data_info which assumed max_ring_difference=num_rings-1 as we now parse it --- src/IO/InterfileHeader.cxx | 6 - src/IO/InterfileHeaderSiemens.cxx | 123 +++++++++++++++++- src/include/stir/IO/InterfileHeaderSiemens.h | 52 ++++++-- .../BinNormalisationFromECAT8.h | 1 + .../BinNormalisationFromECAT8.cxx | 72 +++------- 5 files changed, 177 insertions(+), 77 deletions(-) diff --git a/src/IO/InterfileHeader.cxx b/src/IO/InterfileHeader.cxx index 38d23827c6..f38bcc98f7 100644 --- a/src/IO/InterfileHeader.cxx +++ b/src/IO/InterfileHeader.cxx @@ -300,12 +300,6 @@ bool InterfileHeader::post_processing() warning("Interfile error: no matrix size keywords present\n"); return true; } - if (matrix_size[matrix_size.size()-1].size()!=1) - { - warning("Interfile error: last dimension (%d) of 'matrix size' cannot be a list of numbers\n", - matrix_size[matrix_size.size()-1].size()); - return true; - } for (unsigned int dim=0; dim != matrix_size.size(); ++dim) { if (matrix_size[dim].size()==0) diff --git a/src/IO/InterfileHeaderSiemens.cxx b/src/IO/InterfileHeaderSiemens.cxx index 2a3429bb3a..8e9db1bbbc 100644 --- a/src/IO/InterfileHeaderSiemens.cxx +++ b/src/IO/InterfileHeaderSiemens.cxx @@ -2,7 +2,7 @@ Copyright (C) 2000 PARAPET partners Copyright (C) 2000 - 2009-04-30, Hammersmith Imanet Ltd Copyright (C) 2011-07-01 - 2012, Kris Thielemans - Copyright (C) 2013, 2016, 2018, 2020 University College London + Copyright (C) 2013, 2016, 2018, 2020, 2021 University College London Copyright (C) 2018 STFC This file is part of STIR. @@ -29,6 +29,7 @@ #include "stir/IO/stir_ecat_common.h" #include #include +#include "stir/stream.h" #ifndef STIR_NO_NAMESPACES using std::binary_function; @@ -47,15 +48,17 @@ InterfileHeaderSiemens::InterfileHeaderSiemens() { // always PET exam_info_sptr->imaging_modality = ImagingModality::PT; - + + byte_order_values.clear(); byte_order_values.push_back("LITTLEENDIAN"); byte_order_values.push_back("BIGENDIAN"); - + + PET_data_type_values.clear(); PET_data_type_values.push_back("Emission"); PET_data_type_values.push_back("Transmission"); PET_data_type_values.push_back("Blank"); PET_data_type_values.push_back("AttenuationCorrection"); - PET_data_type_values.push_back("Normalisation"); + PET_data_type_values.push_back("Normalization"); PET_data_type_values.push_back("Image"); for (int patient_position_idx = 0; patient_position_idx <= PatientPosition::unknown_position; ++patient_position_idx) @@ -168,7 +171,7 @@ InterfileRawDataHeaderSiemens::InterfileRawDataHeaderSiemens() : InterfileHeaderSiemens() { // first set to some crazy values - num_segments = -1; + num_segments = 0; num_rings = -1; maximum_ring_difference = -1; axial_compression = -1; @@ -247,8 +250,8 @@ bool InterfileRawDataHeaderSiemens::post_processing() const std::string PET_data_type = standardise_interfile_keyword(PET_data_type_values[PET_data_type_index]); - if (PET_data_type != "emission" && PET_data_type != "transmission") - { error("Interfile error: expecting emission or transmission for 'PET data type'"); } + if (PET_data_type != "emission" && PET_data_type != "transmission" && PET_data_type != "normalization") + { error("Interfile error: expecting 'emission' or 'transmission' or 'normalization' for 'PET data type'"); } // handle scanner @@ -531,5 +534,111 @@ bool InterfileListmodeHeaderSiemens::post_processing() return false; } +InterfileNormHeaderSiemens::InterfileNormHeaderSiemens() + : InterfileRawDataHeaderSiemens() +{ + // some defaults + calib_factor = 1.F; + cross_calib_factor = 1.F; + num_buckets = 0; // should be set normally + num_components = 0; // should be set to 8 normally + axial_compression = 11; // should be set normally but seems to be this always + + ignore_key("data description"); + ignore_key("%expiration date (yyyy:mm:dd)"); + ignore_key("%expiration time (hh:mm:ss GMT-05:00)"); + ignore_key("%raw normalization scans description"); + + // remove some standard keys, which Siemens has replaced with similar names + remove_key("matrix size"); + remove_key("matrix axis label"); + remove_key("scaling factor (mm/pixel)"); + + // keywords for the components + add_key("%number of normalization components", + KeyArgument::INT, (KeywordProcessor)&InterfileNormHeaderSiemens::read_num_components, &num_components); + add_vectorised_key("%matrix size", &matrix_size); + add_vectorised_key("%matrix axis label", &matrix_labels); + ignore_key("%matrix axis unit"); + ignore_key("%normalization component"); + ignore_key("%normalization components description"); + add_vectorised_key("data offset in bytes", &data_offset_each_dataset); + remove_key("number of dimensions"); + add_vectorised_key("number of dimensions", &number_of_dimensions); + ignore_key("%scale factor"); + + // other things + add_key("%number of buckets", &num_buckets); + + ignore_key("%global scanner calibration factor"); + add_key("%scanner quantification factor (Bq*s/ECAT counts)",& calib_factor); + add_key("%cross calibration factor",& cross_calib_factor); + ignore_key("%calibration date (yyyy:mm:dd)"); + ignore_key("%calibration time (hh:mm:ss GMT+00:00)"); + + ignore_key("%number of normalization scans"); + ignore_key("%normalization scan"); + remove_key("image duration (sec)"); + ignore_key("image duration (sec)"); +#if 0 + // change to vectorised key + // would need to set image_durations length from "number of normalization scans" + add_vectorised_key("image duration (sec)", &image_durations); +#endif + ignore_key("%data format"); + ignore_key("%data set description"); + ignore_key("total number of data sets"); + ignore_key("%data set"); +} + +void InterfileNormHeaderSiemens::read_num_components() +{ + set_variable(); + + matrix_labels.resize(num_components); + matrix_size.resize(num_components); + // matrix_axis_units.resize(num_components); + // matrix_axis_units.resize(num_components); + // pixel_sizes.resize(num_components); + // normalization_components.resize(num_components); + data_offset_each_dataset.resize(num_components); + number_of_dimensions.resize(num_components); +} + +bool InterfileNormHeaderSiemens::post_processing() +{ + if (matrix_size.size() < 4) + error("Error parsing ECAT8 norm file header: '%number of normalization components='" + + std::to_string(matrix_size.size())+ + "' but should be at least 4"); + // %normalization component [1]:=geometric effects + if (matrix_size[0].size() != 2) + error("Error parsing ECAT8 norm file header: '%matrix size[1]' should have length 2"); + // %normalization component [3]:=crystal efficiencies + if (matrix_size[2].size() != 2) + error("Error parsing ECAT8 norm file header: '%matrix size[3]' should have length 2"); + + // TODO should do far more checks... + + // remove trailing \r (or other white space) occuring in mMR norm files (they sometimes have \r\r at end of line) + std::string s=exam_info_sptr->originating_system; + s.erase( std::remove_if( s.begin(), s.end(), isspace ), s.end() ); + exam_info_sptr->originating_system=s; + s=data_file_name; + s.erase( std::remove_if( s.begin(), s.end(), isspace ), s.end() ); + data_file_name=s; + + // norm headers don't seem to have "number of views". We need to get it from elsewhere... + // The crystal efficiencies have as first dimension the number of crystals, so let's use that. + const int num_detectors_per_ring = matrix_size[2][0]; + this->num_views = num_detectors_per_ring/2; + // find num_bins from geometric effects + this->num_bins = matrix_size[0][0]; + + if (InterfileRawDataHeaderSiemens::post_processing() == true) + return true; + + return false; +} END_NAMESPACE_STIR diff --git a/src/include/stir/IO/InterfileHeaderSiemens.h b/src/include/stir/IO/InterfileHeaderSiemens.h index c492da24a3..bdbb9e2ee4 100644 --- a/src/include/stir/IO/InterfileHeaderSiemens.h +++ b/src/include/stir/IO/InterfileHeaderSiemens.h @@ -1,24 +1,21 @@ /* - Copyright (C) 2002-2007, Hammersmith Imanet Ltd - Copyright (C) 2013, 2016 University College London + Copyright (C) 2013, 2016, 2020, 2021 University College London This file is part of STIR. - SPDX-License-Identifier: Apache-2.0 AND License-ref-PARAPET-license + SPDX-License-Identifier: Apache-2.0 See STIR/LICENSE.txt for details */ /*! \file \ingroup InterfileIO + \ingroup ECAT \brief This file declares the classes stir::InterfileHeaderSiemens, - stir::InterfileImageHeader, stir::InterfilePDFSHeader + stir::InterfileRawDataHeaderSiemens, stir::InterfilePDFSHeaderSiemens, + stir::InterfileListmodeHeaderSiemens, stir:InterfileNormHeaderSiemens \author Kris Thielemans - \author Sanida Mustafovic - \author PARAPET project - See http://stir.sourceforge.net for a description of the full - proposal for Interfile headers for 3D PET. */ @@ -39,6 +36,7 @@ class ProjDataInfo; \brief a class for Interfile keywords (and parsing) common to all types of data \ingroup InterfileIO + \ingroup ECAT */ class InterfileHeaderSiemens : public InterfileHeader { @@ -80,6 +78,7 @@ class InterfileHeaderSiemens : public InterfileHeader /*! \brief a class for Interfile keywords (and parsing) specific to images \ingroup InterfileIO + \ingroup ECAT */ class InterfileImageHeader : public InterfileHeaderSiemens { @@ -101,8 +100,9 @@ class InterfileImageHeader : public InterfileHeaderSiemens /*! \brief a class for Interfile keywords (and parsing) specific to -Siemens PET projection or list mode data +Siemens PET projection, list mode data or norm data \ingroup InterfileIO +\ingroup ECAT */ class InterfileRawDataHeaderSiemens : public InterfileHeaderSiemens { @@ -143,8 +143,9 @@ class InterfileRawDataHeaderSiemens : public InterfileHeaderSiemens /*! \brief a class for Interfile keywords (and parsing) specific to -projection data (i.e. ProjDataFromStream) +projection data (i.e. ProjDataFromStream) for Siemens PET scanners \ingroup InterfileIO +\ingroup ECAT */ class InterfilePDFSHeaderSiemens : public InterfileRawDataHeaderSiemens { @@ -179,8 +180,9 @@ class InterfilePDFSHeaderSiemens : public InterfileRawDataHeaderSiemens /*! \brief a class for Interfile keywords (and parsing) specific to -Siemesn listmode data (in PETLINK format) +Siemens listmode data (in PETLINK format) \ingroup InterfileIO +\ingroup ECAT */ class InterfileListmodeHeaderSiemens : public InterfileRawDataHeaderSiemens { @@ -209,6 +211,34 @@ class InterfileListmodeHeaderSiemens : public InterfileRawDataHeaderSiemens }; +/*! +\brief a class for Interfile keywords (and parsing) specific to +Siemens (component-based) normalisation data +\ingroup InterfileIO +\ingroup ECAT +*/ +class InterfileNormHeaderSiemens : public InterfileRawDataHeaderSiemens + { + public: + InterfileNormHeaderSiemens(); + + protected: + + //! Returns false if OK, true if not. + virtual bool post_processing() override; + + public: + float calib_factor; + float cross_calib_factor; + int num_buckets; + + private: + int num_components; + std::vector> number_of_dimensions; + void read_num_components(); + + }; + END_NAMESPACE_STIR #endif // __stir_InterfileHeaderSiemens_H__ diff --git a/src/include/stir/recon_buildblock/BinNormalisationFromECAT8.h b/src/include/stir/recon_buildblock/BinNormalisationFromECAT8.h index 8659bbbb35..f615aef11f 100644 --- a/src/include/stir/recon_buildblock/BinNormalisationFromECAT8.h +++ b/src/include/stir/recon_buildblock/BinNormalisationFromECAT8.h @@ -121,6 +121,7 @@ class BinNormalisationFromECAT8 : // TODO move to Scanner int num_axial_blocks_per_singles_unit; shared_ptr proj_data_info_ptr; + shared_ptr norm_proj_data_info_sptr; ProjDataInfoCylindricalNoArcCorr const * proj_data_info_cyl_ptr; shared_ptr proj_data_info_cyl_uncompressed_ptr; int span; diff --git a/src/recon_buildblock/BinNormalisationFromECAT8.cxx b/src/recon_buildblock/BinNormalisationFromECAT8.cxx index 7036004339..bb07716999 100644 --- a/src/recon_buildblock/BinNormalisationFromECAT8.cxx +++ b/src/recon_buildblock/BinNormalisationFromECAT8.cxx @@ -1,6 +1,6 @@ /* Copyright (C) 2002-2011, Hammersmith Imanet Ltd - Copyright (C) 2013-2014, 2019, 2020 University College London + Copyright (C) 2013-2014, 2019, 2020, 2021 University College London This file contains is based on information supplied by Siemens but is distributed with their consent. @@ -44,10 +44,10 @@ #include "stir/Bin.h" #include "stir/display.h" #include "stir/IO/read_data.h" -#include "stir/IO/InterfileHeader.h" #include "stir/IO/InterfileHeaderSiemens.h" #include "stir/ByteOrder.h" #include "stir/is_null_ptr.h" +#include "stir/utilities.h" #include #include #include @@ -262,52 +262,17 @@ MatrixFile* mptr = matrix_open(filename.c_str(), MAT_READ_ONLY, Norm3d); num_transaxial_crystals_per_block = nrm_subheader_ptr->num_transaxial_crystals ; #endif -#if 0 - InterfileRawDataHeaderSiemens interfile_parser; -// interfile_parser.ignore_key("data format"); - interfile_parser.parse(filename.c_str()); - -#else - KeyParser parser; - std::string originating_system; - std::string data_file_name; - int num_buckets; - { - parser.add_start_key("INTERFILE"); - parser.add_stop_key("END OF INTERFILE"); // add this for safety (even though it isn't always there) - parser.add_key("originating_system", &originating_system); - parser.add_key("name_of_data_file", &data_file_name); - parser.add_key("%number of buckets", &num_buckets); - parser.add_key("%scanner quantification factor (Bq*s/ECAT counts)",& calib_factor); - parser.add_key("%cross calibration factor",& cross_calib_factor); - parser.parse(filename.c_str()); - } -#endif - // remove trailing \r - std::string s=/*interfile_parser.*/originating_system; - s.erase( std::remove_if( s.begin(), s.end(), isspace ), s.end() ); - /*interfile_parser.*/originating_system=s; - s=/*interfile_parser.*/data_file_name; - s.erase( std::remove_if( s.begin(), s.end(), isspace ), s.end() ); - /*interfile_parser.*/data_file_name=s; - - this->scanner_ptr.reset(Scanner::get_scanner_from_name(/*interfile_parser.*/originating_system)); - switch(this->scanner_ptr->get_type()) - { - //case Scanner::E1080: - case Scanner::Siemens_mCT: - case Scanner::Siemens_mMR: - break; - default: - error(boost::format("Unknown originating_system '%s', when parsing file '%s'") % /*interfile_parser.*/originating_system % filename ); - } + InterfileNormHeaderSiemens norm_parser; + norm_parser.parse(filename.c_str()); + + this->norm_proj_data_info_sptr = norm_parser.data_info_ptr; + this->scanner_ptr = norm_parser.data_info_ptr->get_scanner_sptr(); - char directory_name[max_filename_length]; + char directory_name[max_filename_length]; get_directory_name(directory_name, filename.c_str()); char full_data_file_name[max_filename_length]; - strcpy(full_data_file_name, data_file_name.c_str()); + strcpy(full_data_file_name, norm_parser.data_file_name.c_str()); prepend_directory_name(full_data_file_name, directory_name); - num_transaxial_crystals_per_block = scanner_ptr->get_num_transaxial_crystals_per_block(); // Calculate the number of axial blocks per singles unit and // total number of blocks per singles unit. @@ -393,13 +358,20 @@ MatrixFile* mptr = matrix_open(filename.c_str(), MAT_READ_ONLY, Norm3d); int eff_test = scanner_ptr->get_num_detectors_per_ring() * scanner_ptr->get_num_rings(); #endif - std::ifstream binary_data(full_data_file_name, std::ios::binary | std::ios::in); + std::ifstream binary_data; + open_read_binary(binary_data, full_data_file_name); if (read_data(binary_data, geometric_factors, ByteOrder::little_endian) != Succeeded::yes) error("failed reading geo factors from '%s'", full_data_file_name); + if (binary_data.tellg() != norm_parser.data_offset_each_dataset[1]) + error("Error reading ECAT8 norm file: wrong offset after component 1"); if (read_data(binary_data, crystal_interference_factors, ByteOrder::little_endian) != Succeeded::yes) error("failed reading crystal_interference_factors from '%s'", full_data_file_name); + if (binary_data.tellg() != norm_parser.data_offset_each_dataset[2]) + error("Error reading ECAT8 norm file: wrong offset after component 2"); if (read_data(binary_data, efficiency_factors, ByteOrder::little_endian) != Succeeded::yes) error("failed reading efficiency_factors from '%s'", full_data_file_name); + if (binary_data.tellg() != norm_parser.data_offset_each_dataset[3]) + error("Error reading ECAT8 norm file: wrong offset after component 3"); if (read_data(binary_data, axial_effects, ByteOrder::little_endian) != Succeeded::yes) error("failed reading axial_effects_factors from '%s'", full_data_file_name); @@ -711,15 +683,9 @@ construct_sino_lookup_table() const int num_rings = this->scanner_ptr->get_num_rings(); this->sino_index=Array<2,int>(IndexRange2D(0, num_rings-1, 0, num_rings-1)); - // construct proj_data_info in "native" Siemens space for the norm (span=11 usually?) - // TODO will have to get "native" span from somewhere. is it in the norm header? 49 - unique_ptr proj_data_info_uptr=ProjDataInfo::construct_proj_data_info(this->scanner_ptr, 11, num_rings -1, - this->scanner_ptr->get_max_num_views(), - this->scanner_ptr->get_max_num_non_arccorrected_bins(), - false); - + shared_ptr proj_data_info_sptr( - dynamic_cast(proj_data_info_uptr->clone())); + dynamic_cast(norm_proj_data_info_sptr->clone())); this->num_Siemens_sinograms = proj_data_info_sptr->get_num_sinograms(); // TODO will have to be get_num_non_tof_sinograms() From f05c07a3a03fbbb6b25f633f356a90cc9d4f35c7 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sat, 21 Aug 2021 02:08:17 +0100 Subject: [PATCH 165/509] fix bug in axial_effects for ECAT8 norm we were using the wrong variable in a loop in construct_sino_lookup_table. Also use number of non-tof sinograms. --- src/recon_buildblock/BinNormalisationFromECAT8.cxx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/recon_buildblock/BinNormalisationFromECAT8.cxx b/src/recon_buildblock/BinNormalisationFromECAT8.cxx index bb07716999..d53a444f6c 100644 --- a/src/recon_buildblock/BinNormalisationFromECAT8.cxx +++ b/src/recon_buildblock/BinNormalisationFromECAT8.cxx @@ -687,7 +687,7 @@ construct_sino_lookup_table() shared_ptr proj_data_info_sptr( dynamic_cast(norm_proj_data_info_sptr->clone())); - this->num_Siemens_sinograms = proj_data_info_sptr->get_num_sinograms(); // TODO will have to be get_num_non_tof_sinograms() + this->num_Siemens_sinograms = proj_data_info_sptr->get_num_non_tof_sinograms(); auto segment_sequence = ecat::find_segment_sequence(*proj_data_info_sptr); Bin bin; @@ -708,8 +708,8 @@ construct_sino_lookup_table() proj_data_info_sptr->get_all_det_pos_pairs_for_bin(det_pos_pairs, bin); for (auto iter=det_pos_pairs.begin();iter!=det_pos_pairs.end(); ++iter) { - sino_index[iter->pos1().axial_coord()][iter->pos2().axial_coord()] = z; - sino_index[iter->pos2().axial_coord()][iter->pos1().axial_coord()] = z; + sino_index[iter->pos1().axial_coord()][iter->pos2().axial_coord()] = Siemens_sino_index; + sino_index[iter->pos2().axial_coord()][iter->pos1().axial_coord()] = Siemens_sino_index; } break; } From e2d0ba18cda064232a0d00998932908e3702650e Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sat, 21 Aug 2021 02:11:34 +0100 Subject: [PATCH 166/509] take new use of axial effects into account in the mMR sample.par files --- .../correct_projdata_no_axial_effects.par | 62 +++++++++++++++++++ .../correct_projdata_only_counts.par | 5 +- 2 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 examples/Siemens-mMR/correct_projdata_no_axial_effects.par diff --git a/examples/Siemens-mMR/correct_projdata_no_axial_effects.par b/examples/Siemens-mMR/correct_projdata_no_axial_effects.par new file mode 100644 index 0000000000..8613415161 --- /dev/null +++ b/examples/Siemens-mMR/correct_projdata_no_axial_effects.par @@ -0,0 +1,62 @@ +correct_projdata Parameters := + ; a sample file that switches the use of the "axial effects" + ; (the 4th component in the ECAT8 norm file) off + input file := ${INPUT} + + ; Current way of specifying time frames, pending modifications to + ; STIR to read time info from the headers + ; see class documentation for TimeFrameDefinitions for the format of this file + ; time frame definition filename := frames.fdef + + ; if a frame definition file is specified, you can say that the input data + ; corresponds to a specific time frame + ; the number should be between 1 and num_frames and defaults to 1 + ; this is currently only used to pass the relevant time to the normalisation + ; time frame number := 1 + + ; output file + ; for future compatibility, do not use an extension in the name of the + ; output file. It will be added automatically + output filename := ${OUTPUT} + + ; default value for next is -1, meaning 'all segments' + ; maximum absolute segment number to process := + + + ; use data in the input file, or substitute data with all 1's + ; (useful to get correction factors only) + ; default is '1' + use data (1) or set to one (0) := 0 + + ; precorrect data, or undo precorrection + ; default is '1' + ; apply (1) or undo (0) correction := + + ; parameters specifying correction factors + ; if no value is given, the corresponding correction will not be performed + + ; random coincidences estimate, subtracted before anything else is done + ;randoms projdata filename := random.hs + ; normalisation (or binwise multiplication, so can contain attenuation factors as well) + Bin Normalisation type := from ecat8 + Bin Normalisation From ecat8 := + normalisation filename:= ${ECATNORM} + use_axial_effects_factors:=0 + End Bin Normalisation From ecat8:= + + ; attenuation image, will be forward projected to get attenuation factors + ; OBSOLETE + ;attenuation image filename := attenuation_image.hv + + ; forward projector used to estimate attenuation factors, defaults to Ray Tracing + ; OBSOLETE + ;forward_projector type := Ray Tracing + + ; scatter term to be subtracted AFTER norm+atten correction + ; defaults to 0 + ;scatter projdata filename := scatter.hs + + ; to interpolate to uniform sampling in 's', set value to 1 + ; arc correction := 0 + +END:= diff --git a/examples/Siemens-mMR/correct_projdata_only_counts.par b/examples/Siemens-mMR/correct_projdata_only_counts.par index 734ba63b9c..68cb0ff0be 100644 --- a/examples/Siemens-mMR/correct_projdata_only_counts.par +++ b/examples/Siemens-mMR/correct_projdata_only_counts.par @@ -1,4 +1,6 @@ correct_projdata Parameters := +; a par file to investigate what BinNormalisationFromECAT8 does when only +; taking span/mashing into account (i.e. actually ignoring the norm factors themselves) input file := ${INPUT} @@ -44,7 +46,8 @@ correct_projdata Parameters := use_detector_efficiencies:=0 use_geometric_factors:=0 use_crystal_interference_factors:=0 - End Bin Normalisation From ecat8:= + use_axial_effects_factors:=0 + End Bin Normalisation From ecat8:= ; attenuation image, will be forward projected to get attenuation factors ; OBSOLETE From 7d5f139932f2f497353c84240ead183ed17710b8 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sat, 21 Aug 2021 13:26:54 +0100 Subject: [PATCH 167/509] conversion fix in BinNormalisationFromECAT8 fix to accomodate VS2015 --- src/recon_buildblock/BinNormalisationFromECAT8.cxx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/recon_buildblock/BinNormalisationFromECAT8.cxx b/src/recon_buildblock/BinNormalisationFromECAT8.cxx index d53a444f6c..cb16732e77 100644 --- a/src/recon_buildblock/BinNormalisationFromECAT8.cxx +++ b/src/recon_buildblock/BinNormalisationFromECAT8.cxx @@ -362,15 +362,15 @@ MatrixFile* mptr = matrix_open(filename.c_str(), MAT_READ_ONLY, Norm3d); open_read_binary(binary_data, full_data_file_name); if (read_data(binary_data, geometric_factors, ByteOrder::little_endian) != Succeeded::yes) error("failed reading geo factors from '%s'", full_data_file_name); - if (binary_data.tellg() != norm_parser.data_offset_each_dataset[1]) + if (binary_data.tellg() != std::streampos(norm_parser.data_offset_each_dataset[1])) error("Error reading ECAT8 norm file: wrong offset after component 1"); if (read_data(binary_data, crystal_interference_factors, ByteOrder::little_endian) != Succeeded::yes) error("failed reading crystal_interference_factors from '%s'", full_data_file_name); - if (binary_data.tellg() != norm_parser.data_offset_each_dataset[2]) + if (binary_data.tellg() != std::streampos(norm_parser.data_offset_each_dataset[2])) error("Error reading ECAT8 norm file: wrong offset after component 2"); if (read_data(binary_data, efficiency_factors, ByteOrder::little_endian) != Succeeded::yes) error("failed reading efficiency_factors from '%s'", full_data_file_name); - if (binary_data.tellg() != norm_parser.data_offset_each_dataset[3]) + if (binary_data.tellg() != std::streampos(norm_parser.data_offset_each_dataset[3])) error("Error reading ECAT8 norm file: wrong offset after component 3"); if (read_data(binary_data, axial_effects, ByteOrder::little_endian) != Succeeded::yes) error("failed reading axial_effects_factors from '%s'", full_data_file_name); From 5c2938bfe3232e152bc56765882dcd341c59876e Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sat, 21 Aug 2021 13:34:14 +0100 Subject: [PATCH 168/509] fix wrong use of exam_info in BinNormalisation::set_up() unclear use of member variable and argument, probably was a bug that the argument was ignored. --- src/recon_buildblock/BinNormalisation.cxx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/recon_buildblock/BinNormalisation.cxx b/src/recon_buildblock/BinNormalisation.cxx index 6f5de6f566..0697621165 100644 --- a/src/recon_buildblock/BinNormalisation.cxx +++ b/src/recon_buildblock/BinNormalisation.cxx @@ -56,11 +56,11 @@ get_exam_info_sptr() const Succeeded BinNormalisation:: -set_up(const shared_ptr& exam_info_sptr, const shared_ptr& proj_data_info_sptr ) +set_up(const shared_ptr& exam_info_sptr_v, const shared_ptr& proj_data_info_sptr_v ) { _already_set_up = true; - _proj_data_info_sptr = proj_data_info_sptr->create_shared_clone(); - this->exam_info_sptr=exam_info_sptr; + _proj_data_info_sptr = proj_data_info_sptr_v; + this->exam_info_sptr=exam_info_sptr_v; return Succeeded::yes; } From f0fda6d9067c0fbbd074a1cfd190bdc498955053 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Tue, 24 Aug 2021 09:22:30 +0100 Subject: [PATCH 169/509] BinNormalisationForECAT8 axial_effects extrapolation Siemens data is often acquired with max_ring_difference < num_rings-1, and the axial_effects stored are therefore limited. We now extrapolate them to just use 1. --- .../BinNormalisationFromECAT8.h | 22 +++++++++++-- .../BinNormalisationFromECAT8.cxx | 33 ++++++++++++++----- 2 files changed, 44 insertions(+), 11 deletions(-) diff --git a/src/include/stir/recon_buildblock/BinNormalisationFromECAT8.h b/src/include/stir/recon_buildblock/BinNormalisationFromECAT8.h index f615aef11f..b8bcfe25c3 100644 --- a/src/include/stir/recon_buildblock/BinNormalisationFromECAT8.h +++ b/src/include/stir/recon_buildblock/BinNormalisationFromECAT8.h @@ -57,20 +57,36 @@ START_NAMESPACE_ECAT \par Parsing example \verbatim - Bin Normalisation type := from ecat7 + Bin Normalisation type := from ecat8 Bin Normalisation From ECAT8:= - normalisation filename:= myfile.hn + normalisation filename:= myfile.n.hdr ; next keywords can be used to switch off some of the normalisation components - ; do not use unless you know why + ; do not use unless you know why. + ; Default values are indicated below (i.e. use all of them) ; use_gaps:=1 ; use_detector_efficiencies:=1 ; use_dead_time:=1 ; use_geometric_factors:=1 ; use_crystal_interference_factors:=1 + ; use_axial_effects_factors:=1 End Bin Normalisation From ECAT8:= \endverbatim + \par More information + + Siemens stores `axial effects`, i.e. one number per sinogram. This normally limits the use of the + file to data that have been acquired with the same `span`, which is usually 11 for present Siemens scanners. + + We work around this in 2 ways: + - we assume that the `axial_effects_factor` is the same for every ring pair contributing to a particular + Siemens sinogram + - if there is no corresponding Siemens sinogram (i.e. the norm file has been acquired with a particular + maximum ring difference, smaller than what is actually possible with the scanner), we use an + `axial_effects_factor` of 1. This should be reasonable as the numbers are around 1 (on the mMR). + + This stratey allows us to give normalisation factor for span=1 data, even if the norm file is for span=11. + \todo dead-time is not yet implemented diff --git a/src/recon_buildblock/BinNormalisationFromECAT8.cxx b/src/recon_buildblock/BinNormalisationFromECAT8.cxx index cb16732e77..0ded885cdd 100644 --- a/src/recon_buildblock/BinNormalisationFromECAT8.cxx +++ b/src/recon_buildblock/BinNormalisationFromECAT8.cxx @@ -682,14 +682,17 @@ construct_sino_lookup_table() { const int num_rings = this->scanner_ptr->get_num_rings(); this->sino_index=Array<2,int>(IndexRange2D(0, num_rings-1, - 0, num_rings-1)); + 0, num_rings-1)); + // fill with invalid index such that we can detect out-of-range + // see find_axial_effects + this->sino_index.fill(-1); - shared_ptr proj_data_info_sptr( - dynamic_cast(norm_proj_data_info_sptr->clone())); + auto proj_data_info_no_arccorr_ptr = + dynamic_cast(norm_proj_data_info_sptr.get()); - this->num_Siemens_sinograms = proj_data_info_sptr->get_num_non_tof_sinograms(); + this->num_Siemens_sinograms = proj_data_info_no_arccorr_ptr->get_num_non_tof_sinograms(); - auto segment_sequence = ecat::find_segment_sequence(*proj_data_info_sptr); + const auto segment_sequence = ecat::find_segment_sequence(*proj_data_info_no_arccorr_ptr); Bin bin; bin.tangential_pos_num()=0; bin.view_num()=0; @@ -701,11 +704,11 @@ construct_sino_lookup_table() for (std::size_t i=0; iget_num_axial_poss(bin.segment_num()); + const int num_ax_poss = proj_data_info_no_arccorr_ptr->get_num_axial_poss(bin.segment_num()); if (z< num_ax_poss) { bin.axial_pos_num() = z; - proj_data_info_sptr->get_all_det_pos_pairs_for_bin(det_pos_pairs, bin); + proj_data_info_no_arccorr_ptr->get_all_det_pos_pairs_for_bin(det_pos_pairs, bin); for (auto iter=det_pos_pairs.begin();iter!=det_pos_pairs.end(); ++iter) { sino_index[iter->pos1().axial_coord()][iter->pos2().axial_coord()] = Siemens_sino_index; @@ -725,7 +728,21 @@ float BinNormalisationFromECAT8:: find_axial_effects(int ring1, int ring2) const { - return axial_effects[sino_index[ring1][ring2]]; + // A variable to see if we're going to write a warning + // TODO should use a member variable, reset in set_up() I guess, in case somebody sets-up an + // existing norm object for different geometry, but maybe that's unlikely. + static bool first_time = true; + const int Siemens_sino_index = sino_index[ring1][ring2]; + if (Siemens_sino_index<0) + { + if (first_time) + { + warning("ECAT8 norm axial effects not given for the largest ring differences. I will just use 1 for those."); + first_time = false; + } + return 1.F; + } + return axial_effects[Siemens_sino_index]; } From 9fcf096ba11fe39196582870ef4c8388a493a27f Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sun, 7 Nov 2021 12:01:53 +0000 Subject: [PATCH 170/509] list_proj_data_info and test fixes - list_proj_data_info now accumulates over all TOF bins (as opposed to printing min/max/sum per TOF bin) - fix run_test_zoom_image.sh --- recon_test_pack/run_test_zoom_image.sh | 2 +- src/utilities/list_projdata_info.cxx | 25 ++++++++++++------------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/recon_test_pack/run_test_zoom_image.sh b/recon_test_pack/run_test_zoom_image.sh index 6bb939a66d..2c905a26a1 100755 --- a/recon_test_pack/run_test_zoom_image.sh +++ b/recon_test_pack/run_test_zoom_image.sh @@ -176,7 +176,7 @@ if [ $? -ne 0 ]; then exit 1 fi -new_sum=`list_projdata_info --sum my_prompts.hs | awk -F: 'NR==4 { print $2}'` +new_sum=`list_projdata_info --sum my_prompts.hs | awk -F: 'NR==2 { print $2}'` if compare_values $org_sum $new_sum .01 then diff --git a/src/utilities/list_projdata_info.cxx b/src/utilities/list_projdata_info.cxx index 94feb7f747..bbe6c4ee1f 100644 --- a/src/utilities/list_projdata_info.cxx +++ b/src/utilities/list_projdata_info.cxx @@ -130,14 +130,13 @@ int main(int argc, char *argv[]) const int min_timing_num = proj_data_sptr->get_min_tof_pos_num(); const int max_timing_num = proj_data_sptr->get_max_tof_pos_num(); + bool accumulators_initialized = false; + float accum_min=std::numeric_limits::max(); // initialize to very large in case projdata is empty (although that's unlikely) + float accum_max=std::numeric_limits::min(); + double sum=0.; for (int timing_num = min_timing_num; timing_num <= max_timing_num; ++timing_num) { - std::cout << "\nTOF bin: " << timing_num; - bool accumulators_initialized = false; - float accum_min=std::numeric_limits::max(); // initialize to very large in case projdata is empty (although that's unlikely) - float accum_max=std::numeric_limits::min(); - double sum=0.; - for (int segment_num = min_segment_num; segment_num<= max_segment_num; ++segment_num) + for (int segment_num = min_segment_num; segment_num<= max_segment_num; ++segment_num) { const SegmentByView seg(proj_data_sptr->get_segment_by_view(segment_num, timing_num)); const float this_max=seg.find_max(); @@ -155,14 +154,14 @@ int main(int argc, char *argv[]) if (accum_min>this_min) accum_min=this_min; } } - if (print_min) - std::cout << "\nData min: " << accum_min; - if (print_max) - std::cout << "\nData max: " << accum_max; - if (print_sum) - std::cout << "\nData sum: " << sum; - std::cout << "\n"; } + if (print_min) + std::cout << "\nData min: " << accum_min; + if (print_max) + std::cout << "\nData max: " << accum_max; + if (print_sum) + std::cout << "\nData sum: " << sum; + std::cout << "\n"; } return EXIT_SUCCESS; } From f0c8efa7a8ecc7d3fbd43a726c2a7501c1b61914 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sun, 7 Nov 2021 22:56:00 +0000 Subject: [PATCH 171/509] another fix for run_test_zoom_image.sh [appveyor skip] --- recon_test_pack/run_test_zoom_image.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/recon_test_pack/run_test_zoom_image.sh b/recon_test_pack/run_test_zoom_image.sh index 2c905a26a1..3eebf8ba17 100755 --- a/recon_test_pack/run_test_zoom_image.sh +++ b/recon_test_pack/run_test_zoom_image.sh @@ -76,7 +76,7 @@ get_ROI_value() { # warning: return 0 if they are different (which is non-standard I guess) compare_values() { if [ $# -ne 3 ]; then - echo "Something wrong with call to compare_values" + echo "Someting wrong with call to compare_values" exit 1 fi error_bigger_than_x=`echo $1 $2 $3 | awk '{ print(($2/$1 - 1)*($2/$1 - 1)> ($3 * $3)) }'` @@ -168,7 +168,7 @@ if [ $? -ne 0 ]; then exit 1 fi -org_sum=`list_projdata_info --sum my_prompts.hs | awk -F: 'NR==4 { print $2}'` +org_sum=`list_projdata_info --sum my_prompts.hs | awk -F: '/sum/ { print $2}'` ./simulate_data.sh my_zoom_test4.hv my_atten_image.hv ${template_sino} 0 if [ $? -ne 0 ]; then @@ -176,11 +176,11 @@ if [ $? -ne 0 ]; then exit 1 fi -new_sum=`list_projdata_info --sum my_prompts.hs | awk -F: 'NR==2 { print $2}'` +new_sum=`list_projdata_info --sum my_prompts.hs | awk -F: '/sum/ { print $2}'` if compare_values $org_sum $new_sum .01 then - echo "DIFFERENCE IN su of prompts IS TOO LARGE." + echo "DIFFERENCE IN sum of prompts IS TOO LARGE." exit 1 fi From 17daf2837a9170d36aa4885d869a2cf211fa4a24 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sun, 5 Dec 2021 18:19:51 +0000 Subject: [PATCH 172/509] made nonTOF GE Signa consistent with TOF version The nonTOF parameters were not updated, resulting in test failure. --- src/buildblock/Scanner.cxx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/buildblock/Scanner.cxx b/src/buildblock/Scanner.cxx index f911be8d77..0fa364bc9b 100644 --- a/src/buildblock/Scanner.cxx +++ b/src/buildblock/Scanner.cxx @@ -447,10 +447,10 @@ case PETMR_Signa: 357, 331, // TODO 2 * 224, - 317.0F, - 9.4F, - 5.55F, - 2.1306F, // TO CHECK + 311.8F, + 8.5F, + 5.56F, + 2.01565F, // TO CHECK static_cast(-5.23*_PI/180),//sign? TODO value 5, 4, From 09815104b14d035768782620f11e005bd68ffe9a Mon Sep 17 00:00:00 2001 From: Robert Twyman Date: Tue, 1 Feb 2022 12:17:31 -0800 Subject: [PATCH 173/509] Add TOF info to DMI 5-ring Temporary value (same as 3/4 ring versions) --- src/buildblock/Scanner.cxx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/buildblock/Scanner.cxx b/src/buildblock/Scanner.cxx index e12d90a31f..ea60f9ec60 100644 --- a/src/buildblock/Scanner.cxx +++ b/src/buildblock/Scanner.cxx @@ -554,7 +554,10 @@ case PETMR_Signa: 1, 1, 1, 0.0944F, // energy resolution from Hsu et al. 2017 - 511.F); + 511.F, + (short int)(0), + (float)(0), //TODO + (float)(0)); break; case HZLR: From 72ffac3f85abe26fded36878cb81b3f33c9de79f Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Tue, 8 Feb 2022 16:47:53 +0000 Subject: [PATCH 174/509] Update build-test.yml enabla GHA on tof_sino_UCL branch until it' smerged --- .github/workflows/build-test.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index bd397b5dc2..c8f603bbe7 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -2,9 +2,13 @@ name: Build and ctest and recon_test_pack CI on: push: - branches: [ master ] + branches: + - master + - tof_sino_UCL pull_request: - branches: [ master ] + branches: + - master + - tof_sino_UCL jobs: build: From c2e95abb462aba7d676f70318f318363e133db5d Mon Sep 17 00:00:00 2001 From: robbietuk Date: Thu, 10 Feb 2022 11:36:57 -0800 Subject: [PATCH 175/509] Allow view_segment + TOF bin OpenMP (#22) Combine both vs_nums_to_process and tof_pos into OpenMP iterations This should accelerate general reconstructions but this is a "quick fix". Co-authored-by: Kris Thielemans --- src/recon_buildblock/BackProjectorByBin.cxx | 16 ++++++++-------- src/recon_buildblock/ForwardProjectorByBin.cxx | 9 +++++++-- src/recon_buildblock/distributable.cxx | 7 ++++++- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/recon_buildblock/BackProjectorByBin.cxx b/src/recon_buildblock/BackProjectorByBin.cxx index 4479231e74..b323e57876 100644 --- a/src/recon_buildblock/BackProjectorByBin.cxx +++ b/src/recon_buildblock/BackProjectorByBin.cxx @@ -200,21 +200,21 @@ BackProjectorByBin::back_project(const ProjData& proj_data, int subset_num, int subset_num, num_subsets); #ifdef STIR_OPENMP -#pragma omp parallel shared(proj_data, symmetries_sptr) -#endif - { -#ifdef STIR_OPENMP -#pragma omp for schedule(dynamic) + #if _OPENMP <201107 + #pragma omp parallel for shared(proj_data, symmetries_sptr) schedule(dynamic) + #else + // OpenMP loop over both vs_nums_to_process and tof_pos_num + #pragma omp parallel for shared(proj_data, symmetries_sptr) schedule(dynamic) collapse(2) + #endif #endif // note: older versions of openmp need an int as loop for (int i=0; i(vs_nums_to_process.size()); ++i) { - const ViewSegmentNumbers vs=vs_nums_to_process[i]; - for (int k=proj_data.get_proj_data_info_sptr()->get_min_tof_pos_num(); k<=proj_data.get_proj_data_info_sptr()->get_max_tof_pos_num(); ++k) { + const ViewSegmentNumbers vs=vs_nums_to_process[i]; #ifdef STIR_OPENMP RelatedViewgrams viewgrams; #pragma omp critical (BACKPROJECTORBYBIN_GETVIEWGRAMS) @@ -228,7 +228,7 @@ BackProjectorByBin::back_project(const ProjData& proj_data, int subset_num, int back_project(viewgrams); } } - } + #ifdef STIR_OPENMP // "reduce" data constructed by threads { diff --git a/src/recon_buildblock/ForwardProjectorByBin.cxx b/src/recon_buildblock/ForwardProjectorByBin.cxx index ae9e3d24fe..6600cbdaf2 100644 --- a/src/recon_buildblock/ForwardProjectorByBin.cxx +++ b/src/recon_buildblock/ForwardProjectorByBin.cxx @@ -195,16 +195,21 @@ ForwardProjectorByBin::forward_project(ProjData& proj_data, proj_data.get_min_segment_num(), proj_data.get_max_segment_num(), subset_num, num_subsets); #ifdef STIR_OPENMP -#pragma omp parallel for shared(proj_data, symmetries_sptr) schedule(dynamic) + #if _OPENMP <201107 + #pragma omp parallel for shared(proj_data, symmetries_sptr) schedule(dynamic) + #else + // OpenMP loop over both vs_nums_to_process and tof_pos_num + #pragma omp parallel for shared(proj_data, symmetries_sptr) schedule(dynamic) collapse(2) + #endif #endif // note: older versions of openmp need an int as loop for (int i=0; i(vs_nums_to_process.size()); ++i) { - const ViewSegmentNumbers vs=vs_nums_to_process[i]; for (int k=proj_data.get_proj_data_info_sptr()->get_min_tof_pos_num(); k<=proj_data.get_proj_data_info_sptr()->get_max_tof_pos_num(); ++k) { + const ViewSegmentNumbers vs=vs_nums_to_process[i]; if (proj_data.get_proj_data_info_sptr()->is_tof_data()) info(boost::format("Processing view %1% of segment %2% of TOF bin %3%") % vs.view_num() % vs.segment_num() % k); else diff --git a/src/recon_buildblock/distributable.cxx b/src/recon_buildblock/distributable.cxx index da79699be5..b91973b284 100644 --- a/src/recon_buildblock/distributable.cxx +++ b/src/recon_buildblock/distributable.cxx @@ -431,7 +431,12 @@ void distributable_computation( local_counts.resize(omp_get_max_threads(), 0); local_count2s.resize(omp_get_max_threads(), 0); } -#pragma omp for schedule(dynamic) + #if _OPENMP <201107 + #pragma omp for schedule(dynamic) + #else + // OpenMP loop over both vs_nums_to_process and tof_pos_num + #pragma omp for schedule(dynamic) collapse(2) + #endif #endif for (int timing_pos_num = min_timing_pos_num; timing_pos_num <= max_timing_pos_num; ++timing_pos_num) From f2fe0752fa2c6202c922b8053074992861ae9c8b Mon Sep 17 00:00:00 2001 From: robbietuk Date: Thu, 10 Feb 2022 11:39:51 -0800 Subject: [PATCH 176/509] Simplify apply_tof_kernel_and_symm_transformation geometric computations (#20) simplify TOF convolution calculations based on geometry, and move as much as possible out of the for loop. This provides a moderate speed-up --- .../stir/recon_buildblock/ProjMatrixByBin.inl | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/src/include/stir/recon_buildblock/ProjMatrixByBin.inl b/src/include/stir/recon_buildblock/ProjMatrixByBin.inl index 21d82a21a0..a581824aab 100644 --- a/src/include/stir/recon_buildblock/ProjMatrixByBin.inl +++ b/src/include/stir/recon_buildblock/ProjMatrixByBin.inl @@ -140,7 +140,6 @@ ProjMatrixByBin::apply_tof_kernel_and_symm_transformation(ProjMatrixElemsForOneB const unique_ptr& symm_ptr) STIR_MUTABLE_CONST { - CartesianCoordinate3D voxel_center; float new_value = 0.f; float low_dist = 0.f; float high_dist = 0.f; @@ -149,9 +148,7 @@ ProjMatrixByBin::apply_tof_kernel_and_symm_transformation(ProjMatrixElemsForOneB const CartesianCoordinate3D middle = (point1 + point2)*0.5f; const CartesianCoordinate3D diff = point2 - middle; - const float lor_length = 1.f / (std::sqrt(diff.x() * diff.x() + - diff.y() * diff.y() + - diff.z() * diff.z())); + const CartesianCoordinate3D diff_unit_vector(diff/static_cast(norm(diff))); for (ProjMatrixElemsForOneBin::iterator element_ptr = probabilities.begin(); element_ptr != probabilities.end(); ++element_ptr) @@ -159,14 +156,7 @@ ProjMatrixByBin::apply_tof_kernel_and_symm_transformation(ProjMatrixElemsForOneB Coordinate3D c(element_ptr->get_coords()); symm_ptr->transform_image_coordinates(c); - voxel_center = - image_info_sptr->get_physical_coordinates_for_indices (c); - - project_point_on_a_line(point1, point2, voxel_center); - - const CartesianCoordinate3D x = voxel_center - middle; - - const float d2 = -inner_product(x, diff) * lor_length; + const float d2 = -inner_product(image_info_sptr->get_physical_coordinates_for_indices (c) - middle, diff_unit_vector); low_dist = ((proj_data_info_sptr->tof_bin_boundaries_mm[probabilities.get_bin_ptr()->timing_pos_num()].low_lim - d2) * r_sqrt2_gauss_sigma); high_dist = ((proj_data_info_sptr->tof_bin_boundaries_mm[probabilities.get_bin_ptr()->timing_pos_num()].high_lim - d2) * r_sqrt2_gauss_sigma); From 0ab8655835858644b9461e703f511a9650a663f4 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Thu, 10 Feb 2022 23:27:04 +0000 Subject: [PATCH 177/509] Apply suggestions from code review on TOF PR --- scripts/plot_TOF_bins.m | 2 +- src/buildblock/ProjDataInMemory.cxx | 9 ++- src/include/stir/listmode/CListRecordGEHDF5.h | 71 +------------------ 3 files changed, 9 insertions(+), 73 deletions(-) diff --git a/scripts/plot_TOF_bins.m b/scripts/plot_TOF_bins.m index d566be56db..324e9ea7a4 100644 --- a/scripts/plot_TOF_bins.m +++ b/scripts/plot_TOF_bins.m @@ -7,7 +7,7 @@ %% clc; clear all; %Path to TOF files. -path_name ='/home/nikos/Desktop/conv_LOR/' +path_name = './' pre_sort_files_in_path = dir(path_name) nums = [] diff --git a/src/buildblock/ProjDataInMemory.cxx b/src/buildblock/ProjDataInMemory.cxx index 566ea0b4a4..6482193ef7 100644 --- a/src/buildblock/ProjDataInMemory.cxx +++ b/src/buildblock/ProjDataInMemory.cxx @@ -166,9 +166,12 @@ get_size_of_buffer_in_bytes() const float ProjDataInMemory::get_bin_value(Bin& bin) { -// Viewgram viewgram = get_viewgram(bin.view_num(),bin.segment_num()); -// return viewgram[bin.axial_pos_num()][bin.tangential_pos_num()]; - return ProjDataFromStream::get_bin_value(bin); + // first find offset in the stream + std::vector offsets = get_offsets_bin(bin); + const std::streamoff total_offset = offsets[0]; + // now convert to index in the buffer + const int index = static_cast(total_offset/sizeof(float)); + return buffer[index]; } void diff --git a/src/include/stir/listmode/CListRecordGEHDF5.h b/src/include/stir/listmode/CListRecordGEHDF5.h index 06d2d5f234..ac4a13c171 100644 --- a/src/include/stir/listmode/CListRecordGEHDF5.h +++ b/src/include/stir/listmode/CListRecordGEHDF5.h @@ -101,14 +101,6 @@ namespace RDF_HDF5 { return Succeeded::no; } inline void get_detection_position(DetectionPositionPair<>& det_pos) const - { - // TODO 447->get_num_detectors_per_ring()-1 - det_pos.pos1().tangential_coord() = 447 - loXtalTransAxID; - det_pos.pos1().axial_coord() = loXtalAxialID; - det_pos.pos2().tangential_coord() = 447 - hiXtalTransAxID; -// std::cout << hiXtalTransAxID << " " << loXtalTransAxID << std::endl; - det_pos.pos2().axial_coord() = hiXtalAxialID; - } inline bool is_event() const { return (eventType==COINC_EVT)/* && eventTypeExt==COINC_COUNT_EVT)*/; @@ -137,53 +129,6 @@ namespace RDF_HDF5 { }; /*-coincidence event*/ -#if 0 -//! A class for storing and using a trigger 'event' from a GE Signa PET/MR listmode file -/*! \ingroup listmode - \ingroup GE - This class cannot have virtual functions, as it needs to just store the data 6 bytes for CListRecordGESigna to work. - */ -class CListGatingDataGESigna -{ - public: - #if 0 - inline unsigned long get_time_in_millisecs() const - { return (time_hi()<<24) | time_lo(); } - inline Succeeded set_time_in_millisecs(const unsigned long time_in_millisecs) - { - words[0].value = ((1UL<<24)-1) & (time_in_millisecs); - words[1].value = (time_in_millisecs) >> 24; - // TODO return more useful value - return Succeeded::yes; - } - #endif - inline bool is_gating_input() const - { return (words[0].signature==21) && (words[1].signature==29); } - inline unsigned int get_gating() const - { return words[0].reserved; } // return "reserved" bits. might be something in there - inline Succeeded set_gating(unsigned int g) - { words[0].reserved = g&7; return Succeeded::yes; } - -private: - typedef union{ - struct { -#if STIRIsNativeByteOrderBigEndian - boost::uint32_t signature : 5; - boost::uint32_t reserved : 3; - boost::uint32_t value : 24; // timing info here in the first word, but we're ignoring it -#else - boost::uint32_t value : 24; - boost::uint32_t reserved : 3; - boost::uint32_t signature : 5; -#endif - }; - boost::uint32_t raw; - } oneword_t; - oneword_t words[2]; -}; - -#endif - //! A class for storing and using a timing 'event' from a GE RDF9 listmode file /*! \ingroup listmode \ingroup GE @@ -363,20 +308,8 @@ dynamic_cast(&e2) != 0 && if (do_byte_swap) { - ByteOrder::swap_order(this->raw[0]); - } - if (this->is_event() || this->is_time()) - { -// std::cout << "This is an event \n" ; - assert(size >= 6); - - std::copy(data_ptr+6, data_ptr+6, reinterpret_cast(&this->raw[1])); -// std::cout << "after assert an event \n" ; - } - if (do_byte_swap) - { - error("don't know how to byteswap"); - ByteOrder::swap_order(this->raw[1]); + error("ClistRecordGEHDF5: byte-swapping not supported yet. sorry"); + //ByteOrder::swap_order(this->raw[0]); } if (this->is_event()) From 74baab9b813269bc4d73d1abf7adef4f29e3355d Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Thu, 10 Feb 2022 22:00:50 +0000 Subject: [PATCH 178/509] remove file checked in by accident [ci skip] --- CMakeLists.txt.user.4.10-pre1 | 9206 --------------------------------- 1 file changed, 9206 deletions(-) delete mode 100644 CMakeLists.txt.user.4.10-pre1 diff --git a/CMakeLists.txt.user.4.10-pre1 b/CMakeLists.txt.user.4.10-pre1 deleted file mode 100644 index b7880ef2eb..0000000000 --- a/CMakeLists.txt.user.4.10-pre1 +++ /dev/null @@ -1,9206 +0,0 @@ - - - - - - EnvironmentId - {c62e0ab5-e250-4d63-8bd6-413f8f0df425} - - - ProjectExplorer.Project.ActiveTarget - 0 - - - ProjectExplorer.Project.EditorSettings - - true - false - true - - Cpp - - CppGlobal - - - - QmlJS - - QmlJSGlobal - - - 2 - UTF-8 - false - 4 - false - 80 - true - true - 1 - true - false - 0 - true - true - 0 - 8 - true - 1 - true - true - true - false - - - - ProjectExplorer.Project.PluginSettings - - - true - - - - ProjectExplorer.Project.Target.0 - - Desktop - Desktop - {7a6826a5-1cc3-4f1a-a2f0-7c499b37d398} - 0 - 0 - 57 - - - BUILD_DOCUMENTATION:BOOL=OFF - CMAKE_BUILD_TYPE:STRING=Debug - CMAKE_CXX_COMPILER:STRING=/usr/bin/g++ - CMAKE_C_COMPILER:STRING=%{Compiler:Executable:C} - CMAKE_PREFIX_PATH:STRING=%{Qt:QT_INSTALL_PREFIX} - DISABLE_AVW:BOOL=ON - DISABLE_CERN_ROOT:BOOL=ON - DISABLE_CERN_ROOT_SUPPORT:BOOL=ON - DISABLE_HDF5_SUPPORT:BOOL=ON - DISABLE_ITK:BOOL=ON - DISABLE_LLN_MATRIX:BOOL=ON - DISABLE_RDF:BOOL=ON - DISABLE_SIMSET:BOOL=OFF - DISABLE_STIR_LOCAL:BOOL=ON - QT_QMAKE_EXECUTABLE:STRING=%{Qt:qmakeExecutable} - SIMSET_INCLUDE_DIRS:PATH=/home/nikos/Workspace/src/2.9.2/src - SIMSET_LIBRARY:FILEPATH=/home/nikos/Workspace/src/2.9.2/lib/libsimset.so - STIR_OPENMP:BOOL=OFF - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug - - - -j7 - - Current executable - - true - CMake Build - - CMakeProjectManager.MakeStep - - 1 - Build - - ProjectExplorer.BuildSteps.Build - - - - - - clean - - true - CMake Build - - CMakeProjectManager.MakeStep - - 1 - Clean - - ProjectExplorer.BuildSteps.Clean - - 2 - false - - Debug - Debug - CMakeProjectManager.CMakeBuildConfiguration - - - - BUILD_DOCUMENTATION:BOOL=OFF - CMAKE_BUILD_TYPE:STRING=Release - CMAKE_CXX_COMPILER:STRING=%{Compiler:Executable:Cxx} - CMAKE_C_COMPILER:STRING=/usr/bin/gcc - CMAKE_PREFIX_PATH:STRING=%{Qt:QT_INSTALL_PREFIX} - DISABLE_AVW:BOOL=ON - DISABLE_CERN_ROOT:BOOL=ON - DISABLE_HDF5_SUPPORT:BOOL=ON - DISABLE_ITK:BOOL=ON - DISABLE_LLN_MATRIX:BOOL=ON - DISABLE_RDF:BOOL=ON - DISABLE_SIMSET:BOOL=OFF - DISABLE_STIR_LOCAL:BOOL=ON - QT_QMAKE_EXECUTABLE:STRING=%{Qt:qmakeExecutable} - SIMSET_INCLUDE_DIRS:PATH=/home/nikos/Workspace/src/2.9.2/src - SIMSET_LIBRARY:FILEPATH=/home/nikos/Workspace/src/2.9.2/lib/libsimset.so - STIR_OPENMP:BOOL=ON - - /home/nikos/Workspace/src/build-STIR-Desktop-Release - - - -j7 - - Current executable - - true - CMake Build - - CMakeProjectManager.MakeStep - - 1 - Build - - ProjectExplorer.BuildSteps.Build - - - - - - clean - - true - CMake Build - - CMakeProjectManager.MakeStep - - 1 - Clean - - ProjectExplorer.BuildSteps.Clean - - 2 - false - - Release - Release - CMakeProjectManager.CMakeBuildConfiguration - - 2 - - - 0 - Deploy - - ProjectExplorer.BuildSteps.Deploy - - 1 - Deploy Configuration - - ProjectExplorer.DefaultDeployConfiguration - - 1 - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - find_ML_singles_from_delayed - CMakeProjectManager.CMakeRunConfiguration.find_ML_singles_from_delayed -/home/nikos/Workspace/src/STIR/src/utilities/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/utilities - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - zeropad_planes - CMakeProjectManager.CMakeRunConfiguration.zeropad_planes -/home/nikos/Workspace/src/STIR/src/utilities/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/utilities - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - list_detector_and_bin_info - CMakeProjectManager.CMakeRunConfiguration.list_detector_and_bin_info -/home/nikos/Workspace/src/STIR/src/utilities/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/utilities - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - test_proj_data_in_memory - CMakeProjectManager.CMakeRunConfiguration.test_proj_data_in_memory -/home/nikos/Workspace/src/STIR/src/test/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/test - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - test_convert_array - CMakeProjectManager.CMakeRunConfiguration.test_convert_array -/home/nikos/Workspace/src/STIR/src/test/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/test - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - test_export_array - CMakeProjectManager.CMakeRunConfiguration.test_export_array -/home/nikos/Workspace/src/STIR/src/test/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/test - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - test_NestedIterator - CMakeProjectManager.CMakeRunConfiguration.test_NestedIterator -/home/nikos/Workspace/src/STIR/src/test/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/test - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - test_multiple_proj_data - CMakeProjectManager.CMakeRunConfiguration.test_multiple_proj_data -/home/nikos/Workspace/src/STIR/src/test/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/test - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - test_GeneralisedPoissonNoiseGenerator - CMakeProjectManager.CMakeRunConfiguration.test_GeneralisedPoissonNoiseGenerator -/home/nikos/Workspace/src/STIR/src/test/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/test - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - test_SeparableMetzArrayFilter - CMakeProjectManager.CMakeRunConfiguration.test_SeparableMetzArrayFilter -/home/nikos/Workspace/src/STIR/src/test/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/test - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - test_Fourier - CMakeProjectManager.CMakeRunConfiguration.test_Fourier -/home/nikos/Workspace/src/STIR/src/test/numerics/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/test/numerics - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - test_integrate_discrete_function - CMakeProjectManager.CMakeRunConfiguration.test_integrate_discrete_function -/home/nikos/Workspace/src/STIR/src/test/numerics/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/test/numerics - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - test_overlap_interpolate - CMakeProjectManager.CMakeRunConfiguration.test_overlap_interpolate -/home/nikos/Workspace/src/STIR/src/test/numerics/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/test/numerics - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - correct_projdata - CMakeProjectManager.CMakeRunConfiguration.correct_projdata -/home/nikos/Workspace/src/STIR/src/utilities/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/utilities - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - test_BSplines - CMakeProjectManager.CMakeRunConfiguration.test_BSplines -/home/nikos/Workspace/src/STIR/src/test/numerics/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/test/numerics - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - test_BSplinesRegularGrid - CMakeProjectManager.CMakeRunConfiguration.test_BSplinesRegularGrid -/home/nikos/Workspace/src/STIR/src/test/numerics/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/test/numerics - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - test_IR_filters - CMakeProjectManager.CMakeRunConfiguration.test_IR_filters -/home/nikos/Workspace/src/STIR/src/test/numerics/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/test/numerics - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - test_matrices - CMakeProjectManager.CMakeRunConfiguration.test_matrices -/home/nikos/Workspace/src/STIR/src/test/numerics/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/test/numerics - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - test_BSplinesRegularGrid1D - CMakeProjectManager.CMakeRunConfiguration.test_BSplinesRegularGrid1D -/home/nikos/Workspace/src/STIR/src/test/numerics/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/test/numerics - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - test_erf - CMakeProjectManager.CMakeRunConfiguration.test_erf -/home/nikos/Workspace/src/STIR/src/test/numerics/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/test/numerics - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - test_modelling - CMakeProjectManager.CMakeRunConfiguration.test_modelling -/home/nikos/Workspace/src/STIR/src/test/modelling/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/test/modelling - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - test_ParametricDiscretisedDensity - CMakeProjectManager.CMakeRunConfiguration.test_ParametricDiscretisedDensity -/home/nikos/Workspace/src/STIR/src/test/modelling/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/test/modelling - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - rebin_sgl_file - CMakeProjectManager.CMakeRunConfiguration.rebin_sgl_file -/home/nikos/Workspace/src/STIR/src/listmode_utilities/ - - 3768 - false - true - false - false - true - - - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - add_ecat7_header_to_sgl - CMakeProjectManager.CMakeRunConfiguration.add_ecat7_header_to_sgl -/home/nikos/Workspace/src/STIR/src/listmode_utilities/ - - 3768 - false - true - false - false - true - - - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - manip_image - CMakeProjectManager.CMakeRunConfiguration.manip_image -/home/nikos/Workspace/src/STIR/src/utilities/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/utilities - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - print_sgl_values - CMakeProjectManager.CMakeRunConfiguration.print_sgl_values -/home/nikos/Workspace/src/STIR/src/listmode_utilities/ - - 3768 - false - true - false - false - true - - - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - scan_sgl_file - CMakeProjectManager.CMakeRunConfiguration.scan_sgl_file -/home/nikos/Workspace/src/STIR/src/listmode_utilities/ - - 3768 - false - true - false - false - true - - - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - conv_to_ecat6 - CMakeProjectManager.CMakeRunConfiguration.conv_to_ecat6 -/home/nikos/Workspace/src/STIR/src/utilities/ecat/ - - 3768 - false - true - false - false - true - - - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - copy_ecat7_header - CMakeProjectManager.CMakeRunConfiguration.copy_ecat7_header -/home/nikos/Workspace/src/STIR/src/utilities/ecat/ - - 3768 - false - true - false - false - true - - - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - ifheaders_for_ecat7 - CMakeProjectManager.CMakeRunConfiguration.ifheaders_for_ecat7 -/home/nikos/Workspace/src/STIR/src/utilities/ecat/ - - 3768 - false - true - false - false - true - - - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - ecat_swap_corners - CMakeProjectManager.CMakeRunConfiguration.ecat_swap_corners -/home/nikos/Workspace/src/STIR/src/utilities/ecat/ - - 3768 - false - true - false - false - true - - - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - is_ecat7_file - CMakeProjectManager.CMakeRunConfiguration.is_ecat7_file -/home/nikos/Workspace/src/STIR/src/utilities/ecat/ - - 3768 - false - true - false - false - true - - - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - conv_to_ecat7 - CMakeProjectManager.CMakeRunConfiguration.conv_to_ecat7 -/home/nikos/Workspace/src/STIR/src/utilities/ecat/ - - 3768 - false - true - false - false - true - - - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - print_ecat_singles_values - CMakeProjectManager.CMakeRunConfiguration.print_ecat_singles_values -/home/nikos/Workspace/src/STIR/src/utilities/ecat/ - - 3768 - false - true - false - false - true - - - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - convecat6_if - CMakeProjectManager.CMakeRunConfiguration.convecat6_if -/home/nikos/Workspace/src/STIR/src/utilities/ecat/ - - 3768 - false - true - false - false - true - - - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - find_fwhm_in_image - CMakeProjectManager.CMakeRunConfiguration.find_fwhm_in_image -/home/nikos/Workspace/src/STIR/src/utilities/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/utilities - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - test_time_of_flight - CMakeProjectManager.CMakeRunConfiguration.test_time_of_flight -/home/nikos/Workspace/src/STIR/src/test/ - - 3768 - false - true - false - false - true - - - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - read_geo_factors - CMakeProjectManager.CMakeRunConfiguration.read_geo_factors -/home/nikos/Workspace/src/STIR/src/utilities/ - - 3768 - false - true - false - false - true - - - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - extract_dynamic_images - CMakeProjectManager.CMakeRunConfiguration.extract_dynamic_images -/home/nikos/Workspace/src/STIR/src/modelling_utilities/ - - 3768 - false - true - false - false - true - - - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - conv_GEHDF5_to_interfile - CMakeProjectManager.CMakeRunConfiguration.conv_GEHDF5_to_interfile -/home/nikos/Workspace/src/STIR/src/utilities/ - - 3768 - false - true - false - false - true - - - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - construct_randoms_from_GEsingles - CMakeProjectManager.CMakeRunConfiguration.construct_randoms_from_GEsingles -/home/nikos/Workspace/src/STIR/src/utilities/ - - 3768 - false - true - false - false - true - - - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - read_nontof_conv_tof - CMakeProjectManager.CMakeRunConfiguration.read_nontof_conv_tof -/home/nikos/Workspace/src/STIR/src/utilities/ - - 3768 - false - true - false - false - true - - - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - extract_segments - CMakeProjectManager.CMakeRunConfiguration.extract_segments -/home/nikos/Workspace/src/STIR/src/utilities/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/utilities - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - calculate_attenuation_coefficients - CMakeProjectManager.CMakeRunConfiguration.calculate_attenuation_coefficients -/home/nikos/Workspace/src/STIR/src/utilities/ - - 3768 - false - true - false - false - true - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/utilities - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - compare_image - CMakeProjectManager.CMakeRunConfiguration.compare_image -/home/nikos/Workspace/src/STIR/src/utilities/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/utilities - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - compare_projdata - CMakeProjectManager.CMakeRunConfiguration.compare_projdata -/home/nikos/Workspace/src/STIR/src/utilities/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/utilities - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - list_image_values - CMakeProjectManager.CMakeRunConfiguration.list_image_values -/home/nikos/Workspace/src/STIR/src/utilities/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/utilities - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - stir_math - CMakeProjectManager.CMakeRunConfiguration.stir_math -/home/nikos/Workspace/src/STIR/src/utilities/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/utilities - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - find_ML_normfactors3D - CMakeProjectManager.CMakeRunConfiguration.find_ML_normfactors3D -/home/nikos/Workspace/src/STIR/src/utilities/ - norm norm_sino_f1g1d0b0.hs forward_model.hs 3 3 - 3768 - false - true - false - false - true - true - /home/nikos/Desktop/cur_Active/York/norm/ - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/utilities - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - shift_image_origin - CMakeProjectManager.CMakeRunConfiguration.shift_image_origin -/home/nikos/Workspace/src/STIR/src/utilities/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/utilities - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - list_projdata_info - CMakeProjectManager.CMakeRunConfiguration.list_projdata_info -/home/nikos/Workspace/src/STIR/src/utilities/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/utilities - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - postfilter - CMakeProjectManager.CMakeRunConfiguration.postfilter -/home/nikos/Workspace/src/STIR/src/utilities/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/utilities - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - list_image_info - CMakeProjectManager.CMakeRunConfiguration.list_image_info -/home/nikos/Workspace/src/STIR/src/utilities/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/utilities - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - display_projdata - CMakeProjectManager.CMakeRunConfiguration.display_projdata -/home/nikos/Workspace/src/STIR/src/utilities/ - sim_SSino_f1g1d0b0.hs - 3768 - false - true - false - false - true - true - /home/nikos/Desktop/tmp/ - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/utilities - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - do_linear_regression - CMakeProjectManager.CMakeRunConfiguration.do_linear_regression -/home/nikos/Workspace/src/STIR/src/utilities/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/utilities - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - create_projdata_template - CMakeProjectManager.CMakeRunConfiguration.create_projdata_template -/home/nikos/Workspace/src/STIR/src/utilities/ - tmpl_scanner - 3768 - false - true - false - false - true - true - /home/nikos/Desktop/cur_Active/XCAT_Test_Short/ - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/utilities - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - SSRB - CMakeProjectManager.CMakeRunConfiguration.SSRB -/home/nikos/Workspace/src/STIR/src/utilities/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/utilities - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - list_ROI_values - CMakeProjectManager.CMakeRunConfiguration.list_ROI_values -/home/nikos/Workspace/src/STIR/src/utilities/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/utilities - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - poisson_noise - CMakeProjectManager.CMakeRunConfiguration.poisson_noise -/home/nikos/Workspace/src/STIR/src/utilities/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/utilities - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - apply_normfactors3D - CMakeProjectManager.CMakeRunConfiguration.apply_normfactors3D -/home/nikos/Workspace/src/STIR/src/utilities/ - norm_sino norm ones.hs 0 3 3 1 1 0 0 - 3768 - false - true - false - false - true - true - /home/nikos/Desktop/cur_Active/York/norm/ - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/utilities - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - attenuation_coefficients_to_projections - CMakeProjectManager.CMakeRunConfiguration.attenuation_coefficients_to_projections -/home/nikos/Workspace/src/STIR/src/utilities/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/utilities - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - manip_projdata - CMakeProjectManager.CMakeRunConfiguration.manip_projdata -/home/nikos/Workspace/src/STIR/src/utilities/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/utilities - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - get_time_frame_info - CMakeProjectManager.CMakeRunConfiguration.get_time_frame_info -/home/nikos/Workspace/src/STIR/src/utilities/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/utilities - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - generate_image - CMakeProjectManager.CMakeRunConfiguration.generate_image -/home/nikos/Workspace/src/STIR/src/utilities/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/utilities - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - zoom_image - CMakeProjectManager.CMakeRunConfiguration.zoom_image -/home/nikos/Workspace/src/STIR/src/utilities/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/utilities - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - abs_image - CMakeProjectManager.CMakeRunConfiguration.abs_image -/home/nikos/Workspace/src/STIR/src/utilities/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/utilities - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - conv_interfile_to_gipl - CMakeProjectManager.CMakeRunConfiguration.conv_interfile_to_gipl -/home/nikos/Workspace/src/STIR/src/utilities/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/utilities - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - convert_to_binary_image - CMakeProjectManager.CMakeRunConfiguration.convert_to_binary_image -/home/nikos/Workspace/src/STIR/src/utilities/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/utilities - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - construct_randoms_from_singles - CMakeProjectManager.CMakeRunConfiguration.construct_randoms_from_singles -/home/nikos/Workspace/src/STIR/src/utilities/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/utilities - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - apply_normfactors - CMakeProjectManager.CMakeRunConfiguration.apply_normfactors -/home/nikos/Workspace/src/STIR/src/utilities/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/utilities - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - warp_image - CMakeProjectManager.CMakeRunConfiguration.warp_image -/home/nikos/Workspace/src/STIR/src/utilities/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/utilities - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - rebin_projdata - CMakeProjectManager.CMakeRunConfiguration.rebin_projdata -/home/nikos/Workspace/src/STIR/src/utilities/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/utilities - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - write_proj_matrix_by_bin - CMakeProjectManager.CMakeRunConfiguration.write_proj_matrix_by_bin -/home/nikos/Workspace/src/STIR/src/utilities/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/utilities - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - conv_gipl_to_interfile - CMakeProjectManager.CMakeRunConfiguration.conv_gipl_to_interfile -/home/nikos/Workspace/src/STIR/src/utilities/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/utilities - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - shift_image - CMakeProjectManager.CMakeRunConfiguration.shift_image -/home/nikos/Workspace/src/STIR/src/utilities/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/utilities - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - back_project - CMakeProjectManager.CMakeRunConfiguration.back_project -/home/nikos/Workspace/src/STIR/src/utilities/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/utilities - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - conv_GATE_raw_ECAT_projdata_to_interfile - CMakeProjectManager.CMakeRunConfiguration.conv_GATE_raw_ECAT_projdata_to_interfile -/home/nikos/Workspace/src/STIR/src/utilities/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/utilities - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - estimate_scatter - CMakeProjectManager.CMakeRunConfiguration.estimate_scatter -/home/nikos/Workspace/src/STIR/src/scatter_utilities/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/scatter_utilities - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - create_tail_mask_from_ACFs - CMakeProjectManager.CMakeRunConfiguration.create_tail_mask_from_ACFs -/home/nikos/Workspace/src/STIR/src/scatter_utilities/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/scatter_utilities - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - upsample_and_fit_single_scatter - CMakeProjectManager.CMakeRunConfiguration.upsample_and_fit_single_scatter -/home/nikos/Workspace/src/STIR/src/scatter_utilities/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/scatter_utilities - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - mult_image_parameters - CMakeProjectManager.CMakeRunConfiguration.mult_image_parameters -/home/nikos/Workspace/src/STIR/src/modelling_utilities/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/modelling_utilities - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - warp_and_accumulate_gated_images - CMakeProjectManager.CMakeRunConfiguration.warp_and_accumulate_gated_images -/home/nikos/Workspace/src/STIR/src/utilities/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/utilities - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - get_dynamic_images_from_parametric_images - CMakeProjectManager.CMakeRunConfiguration.get_dynamic_images_from_parametric_images -/home/nikos/Workspace/src/STIR/src/modelling_utilities/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/modelling_utilities - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - extract_single_images_from_dynamic_image - CMakeProjectManager.CMakeRunConfiguration.extract_single_images_from_dynamic_image -/home/nikos/Workspace/src/STIR/src/modelling_utilities/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/modelling_utilities - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - apply_patlak_to_images - CMakeProjectManager.CMakeRunConfiguration.apply_patlak_to_images -/home/nikos/Workspace/src/STIR/src/modelling_utilities/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/modelling_utilities - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - make_parametric_image_from_components - CMakeProjectManager.CMakeRunConfiguration.make_parametric_image_from_components -/home/nikos/Workspace/src/STIR/src/modelling_utilities/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/modelling_utilities - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - mult_model_with_dyn_images - CMakeProjectManager.CMakeRunConfiguration.mult_model_with_dyn_images -/home/nikos/Workspace/src/STIR/src/modelling_utilities/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/modelling_utilities - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - write_patlak_matrix - CMakeProjectManager.CMakeRunConfiguration.write_patlak_matrix -/home/nikos/Workspace/src/STIR/src/modelling_utilities/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/modelling_utilities - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - lm_to_projdata_bootstrap - CMakeProjectManager.CMakeRunConfiguration.lm_to_projdata_bootstrap -/home/nikos/Workspace/src/STIR/src/listmode_utilities/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/listmode_utilities - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - lm_to_projdata - CMakeProjectManager.CMakeRunConfiguration.lm_to_projdata -/home/nikos/Workspace/src/STIR/src/listmode_utilities/ - lm_to_projdata.par - 3768 - false - true - false - false - true - true - /home/nikos/Desktop/cur_Active/XCAT_Test_Short/ - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/listmode_utilities - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - lm_fansums - CMakeProjectManager.CMakeRunConfiguration.lm_fansums -/home/nikos/Workspace/src/STIR/src/listmode_utilities/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/listmode_utilities - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - list_lm_events - CMakeProjectManager.CMakeRunConfiguration.list_lm_events -/home/nikos/Workspace/src/STIR/src/listmode_utilities/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/listmode_utilities - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - find_maxima_in_image - CMakeProjectManager.CMakeRunConfiguration.find_maxima_in_image -/home/nikos/Workspace/src/STIR/src/utilities/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/utilities - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - list_lm_countrates - CMakeProjectManager.CMakeRunConfiguration.list_lm_countrates -/home/nikos/Workspace/src/STIR/src/listmode_utilities/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/listmode_utilities - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - FBP2D - CMakeProjectManager.CMakeRunConfiguration.FBP2D -/home/nikos/Workspace/src/STIR/src/analytic/FBP2D/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/analytic/FBP2D - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - FBP3DRP - CMakeProjectManager.CMakeRunConfiguration.FBP3DRP -/home/nikos/Workspace/src/STIR/src/analytic/FBP3DRP/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/analytic/FBP3DRP - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - OSMAPOSL - CMakeProjectManager.CMakeRunConfiguration.OSMAPOSL -/home/nikos/Workspace/src/STIR/src/iterative/OSMAPOSL/ - OSMAPOSL_test_lm.par - 3768 - false - true - false - false - true - true - /home/nikos/Desktop/tmp/ - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/iterative/OSMAPOSL - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - OSSPS - CMakeProjectManager.CMakeRunConfiguration.OSSPS -/home/nikos/Workspace/src/STIR/src/iterative/OSSPS/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/iterative/OSSPS - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - POSMAPOSL - CMakeProjectManager.CMakeRunConfiguration.POSMAPOSL -/home/nikos/Workspace/src/STIR/src/iterative/POSMAPOSL/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/iterative/POSMAPOSL - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - POSSPS - CMakeProjectManager.CMakeRunConfiguration.POSSPS -/home/nikos/Workspace/src/STIR/src/iterative/POSSPS/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/iterative/POSSPS - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - conv_to_SimSET_att_image - CMakeProjectManager.CMakeRunConfiguration.conv_to_SimSET_att_image -/home/nikos/Workspace/src/STIR/src/SimSET/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/SimSET - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - write_phg_image_info - CMakeProjectManager.CMakeRunConfiguration.write_phg_image_info -/home/nikos/Workspace/src/STIR/src/SimSET/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/SimSET - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - conv_SimSET_projdata_to_STIR - CMakeProjectManager.CMakeRunConfiguration.conv_SimSET_projdata_to_STIR -/home/nikos/Workspace/src/STIR/src/SimSET/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/SimSET - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - stir_write_pgm - CMakeProjectManager.CMakeRunConfiguration.stir_write_pgm -/home/nikos/Workspace/src/STIR/src/utilities/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/utilities - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - test_DataSymmetriesForBins_PET_CartesianGrid - CMakeProjectManager.CMakeRunConfiguration.test_DataSymmetriesForBins_PET_CartesianGrid -/home/nikos/Workspace/src/STIR/src/recon_test/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/recon_test - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - test_PoissonLogLikelihoodWithLinearModelForMeanAndProjData - CMakeProjectManager.CMakeRunConfiguration.test_PoissonLogLikelihoodWithLinearModelForMeanAndProjData -/home/nikos/Workspace/src/STIR/src/recon_test/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/recon_test - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - fwdtest - CMakeProjectManager.CMakeRunConfiguration.fwdtest -/home/nikos/Workspace/src/STIR/src/recon_test/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/recon_test - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - bcktest - CMakeProjectManager.CMakeRunConfiguration.bcktest -/home/nikos/Workspace/src/STIR/src/recon_test/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/recon_test - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - recontest - CMakeProjectManager.CMakeRunConfiguration.recontest -/home/nikos/Workspace/src/STIR/src/recon_test/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/recon_test - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - test_display - CMakeProjectManager.CMakeRunConfiguration.test_display -/home/nikos/Workspace/src/STIR/src/test/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/test - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - test_linear_regression - CMakeProjectManager.CMakeRunConfiguration.test_linear_regression -/home/nikos/Workspace/src/STIR/src/test/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/test - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - test_filename_functions - CMakeProjectManager.CMakeRunConfiguration.test_filename_functions -/home/nikos/Workspace/src/STIR/src/test/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/test - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - test_interpolate - CMakeProjectManager.CMakeRunConfiguration.test_interpolate -/home/nikos/Workspace/src/STIR/src/test/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/test - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - test_Array - CMakeProjectManager.CMakeRunConfiguration.test_Array -/home/nikos/Workspace/src/STIR/src/test/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/test - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - forward_project - CMakeProjectManager.CMakeRunConfiguration.forward_project -/home/nikos/Workspace/src/STIR/src/utilities/ - forward_model norm_im.hv tmpl_scanner.hs - 3768 - false - true - false - false - true - true - /home/nikos/Desktop/cur_Active/York/norm/ - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/utilities - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - test_stir_math - CMakeProjectManager.CMakeRunConfiguration.test_stir_math -/home/nikos/Workspace/src/STIR/src/test/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/test - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - test_IO_ITKMulticomponent - CMakeProjectManager.CMakeRunConfiguration.test_IO_ITKMulticomponent -/home/nikos/Workspace/src/STIR/src/test/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/test - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - test_IO_ParametricDiscretisedDensity - CMakeProjectManager.CMakeRunConfiguration.test_IO_ParametricDiscretisedDensity -/home/nikos/Workspace/src/STIR/src/test/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/test - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - test_warp_image - CMakeProjectManager.CMakeRunConfiguration.test_warp_image -/home/nikos/Workspace/src/STIR/src/test/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/test - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - test_IO_DynamicDiscretisedDensity - CMakeProjectManager.CMakeRunConfiguration.test_IO_DynamicDiscretisedDensity -/home/nikos/Workspace/src/STIR/src/test/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/test - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - test_OutputFileFormat - CMakeProjectManager.CMakeRunConfiguration.test_OutputFileFormat -/home/nikos/Workspace/src/STIR/src/test/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/test - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - test_DynamicDiscretisedDensity - CMakeProjectManager.CMakeRunConfiguration.test_DynamicDiscretisedDensity -/home/nikos/Workspace/src/STIR/src/test/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/test - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - test_IO_DiscretisedDensity - CMakeProjectManager.CMakeRunConfiguration.test_IO_DiscretisedDensity -/home/nikos/Workspace/src/STIR/src/test/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/test - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - test_VectorWithOffset - CMakeProjectManager.CMakeRunConfiguration.test_VectorWithOffset -/home/nikos/Workspace/src/STIR/src/test/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/test - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - test_IndexRange - CMakeProjectManager.CMakeRunConfiguration.test_IndexRange -/home/nikos/Workspace/src/STIR/src/test/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/test - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - find_ML_normfactors - CMakeProjectManager.CMakeRunConfiguration.find_ML_normfactors -/home/nikos/Workspace/src/STIR/src/utilities/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/utilities - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - test_ArcCorrection - CMakeProjectManager.CMakeRunConfiguration.test_ArcCorrection -/home/nikos/Workspace/src/STIR/src/test/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/test - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - test_coordinates - CMakeProjectManager.CMakeRunConfiguration.test_coordinates -/home/nikos/Workspace/src/STIR/src/test/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/test - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - test_Scanner - CMakeProjectManager.CMakeRunConfiguration.test_Scanner -/home/nikos/Workspace/src/STIR/src/test/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/test - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - test_VoxelsOnCartesianGrid - CMakeProjectManager.CMakeRunConfiguration.test_VoxelsOnCartesianGrid -/home/nikos/Workspace/src/STIR/src/test/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/test - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - test_ROIs - CMakeProjectManager.CMakeRunConfiguration.test_ROIs -/home/nikos/Workspace/src/STIR/src/test/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/test - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - test_ArrayFilter - CMakeProjectManager.CMakeRunConfiguration.test_ArrayFilter -/home/nikos/Workspace/src/STIR/src/test/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/test - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - test_ByteOrder - CMakeProjectManager.CMakeRunConfiguration.test_ByteOrder -/home/nikos/Workspace/src/STIR/src/test/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/test - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - test_zoom_image - CMakeProjectManager.CMakeRunConfiguration.test_zoom_image -/home/nikos/Workspace/src/STIR/src/test/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/test - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - test_find_fwhm_in_image - CMakeProjectManager.CMakeRunConfiguration.test_find_fwhm_in_image -/home/nikos/Workspace/src/STIR/src/test/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/test - - - dwarf - - cpu-cycles - - - 250 - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - - test_proj_data_info - CMakeProjectManager.CMakeRunConfiguration.test_proj_data_info -/home/nikos/Workspace/src/STIR/src/test/ - - 3768 - false - true - false - false - true - - /home/nikos/Workspace/src/build-STIR-Desktop-Debug/src/test - - 136 - - - - ProjectExplorer.Project.TargetCount - 1 - - - ProjectExplorer.Project.Updater.FileVersion - 21 - - - Version - 21 - - From ec0bdcd1417a1703fb2dcbf6eb5e7735ddd53a12 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Thu, 10 Feb 2022 23:28:43 +0000 Subject: [PATCH 179/509] [CMAKE] remove outdated DISABLE_HDF5_SUPPORT line [ci skip] the option is now called DISABLE_HDF5, but this seems to have been ignored in a merge --- CMakeLists.txt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9ef2a4580d..c9f5a06225 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -116,10 +116,6 @@ if(NOT DISABLE_CERN_ROOT) endif() endif() -if(NOT DISABLE_HDF5_SUPPORT) - find_package(HDF5 COMPONENTS CXX) -endif() - if(NOT DISABLE_AVW) find_package(AVW) endif() From f78bd1782ee74496b1e422d38cacaeec62eed31b Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Thu, 10 Feb 2022 23:38:52 +0000 Subject: [PATCH 180/509] fix error introduced in code review suggestion --- src/include/stir/listmode/CListRecordGEHDF5.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/include/stir/listmode/CListRecordGEHDF5.h b/src/include/stir/listmode/CListRecordGEHDF5.h index ac4a13c171..c40b7eabbf 100644 --- a/src/include/stir/listmode/CListRecordGEHDF5.h +++ b/src/include/stir/listmode/CListRecordGEHDF5.h @@ -100,7 +100,6 @@ namespace RDF_HDF5 { //if (prompt) random=1; else random=0; return Succeeded::yes; return Succeeded::no; } - inline void get_detection_position(DetectionPositionPair<>& det_pos) const inline bool is_event() const { return (eventType==COINC_EVT)/* && eventTypeExt==COINC_COUNT_EVT)*/; From e066ead8c19b75a89c4090433723cbf1daf69d88 Mon Sep 17 00:00:00 2001 From: robbietuk Date: Tue, 15 Feb 2022 00:05:55 -0800 Subject: [PATCH 181/509] TOF erf interpolation class (FastErf) (#24) This merge adds the FastErf class, and uses it in ProjMatrixByBin for TOF. FastErf is implemented to accelerate the computation of the y=erf(xp) function, which was found to be a significant portion of the TOF computation, by precomputing a vector of values and interpolating an approximate y for the given xp. Three interpolations are added but only the linear interpolator is used in ProjMatrixByBin as a balance between computation speed and accuracy. See the PR (https://github.com/NikEfth/STIR/pull/24) for more details. --- src/include/stir/numerics/BSplinesDetail.inl | 3 + src/include/stir/numerics/FastErf.h | 104 ++++++++++++++++ src/include/stir/numerics/FastErf.inl | 114 ++++++++++++++++++ src/include/stir/numerics/erf.h | 4 + .../stir/recon_buildblock/ProjMatrixByBin.h | 8 +- .../stir/recon_buildblock/ProjMatrixByBin.inl | 10 +- src/recon_buildblock/ProjMatrixByBin.cxx | 4 + src/test/numerics/CMakeLists.txt | 2 +- src/test/numerics/test_erf.cxx | 100 ++++++++++++++- 9 files changed, 338 insertions(+), 11 deletions(-) create mode 100644 src/include/stir/numerics/FastErf.h create mode 100644 src/include/stir/numerics/FastErf.inl diff --git a/src/include/stir/numerics/BSplinesDetail.inl b/src/include/stir/numerics/BSplinesDetail.inl index cd86432c1e..7dd6ba663f 100644 --- a/src/include/stir/numerics/BSplinesDetail.inl +++ b/src/include/stir/numerics/BSplinesDetail.inl @@ -17,6 +17,8 @@ */ #include "stir/assign.h" +#ifndef __stir_numerics_BSplinesDetail_inl_ +#define __stir_numerics_BSplinesDetail_inl_ START_NAMESPACE_STIR namespace BSpline { @@ -343,3 +345,4 @@ namespace BSpline { } // end of namespace BSpline END_NAMESPACE_STIR +#endif diff --git a/src/include/stir/numerics/FastErf.h b/src/include/stir/numerics/FastErf.h new file mode 100644 index 0000000000..1422fb28ec --- /dev/null +++ b/src/include/stir/numerics/FastErf.h @@ -0,0 +1,104 @@ + +/*! + \file + \ingroup numerics + \brief Implementation of an erf interpolation + + \author Robert Twyman + \author Alexander Whitehead + \author Kris Thielemans +*/ +/* + Copyright (C) 2022, University College London + This file is part of STIR. + SPDX-License-Identifier: Apache-2.0 + See STIR/LICENSE.txt for details +*/ + +#include "stir/numerics/BSplines1DRegularGrid.h" + +#ifndef __stir_numerics_FastErf__H__ +#define __stir_numerics_FastErf__H__ + +START_NAMESPACE_STIR + + +/*! \ingroup numerics + \name FastErf + \brief The class acts as a potentially faster way to compute many erf values by precomputing the function at + regularly spaced intervals. [BSplines, linear, nearest neighbour] interpolation methods are available. + Note, nearest neighbour is fastest and BSplines slowest method. + + Warning: \c set_up() has to be called before use, and also after any \c set* function. This is currently not checked. +*/ +class FastErf +{ +private: + + //! The number of erf samples to take from -\c_maximum_sample_value to \c_maximum_sample_value + int _num_samples; + + //! The sampling period, computed as \c_maximum_sample_value / \c_num_samples) + double _sampling_period; + +// //! Used to check if setup has been run before parameter changes +// bool _is_setup = false; + + //! BSplines object using linear interpolation + BSpline::BSplines1DRegularGrid _spline; + + /*! The upper bound value x value of erf(x) used in sampling. For larger values erf(x) ~= 1. + * The negative \c_maximum_sample_value is used as the lower bound. + */ + double _maximum_sample_value; + + //! a vector/list of stored erf values + std::vector erf_values_vec; + +public: + explicit FastErf(const int num_samples = 1000, const float maximum_sample_value = 5) + : _num_samples(num_samples), _maximum_sample_value(maximum_sample_value ) + {} + + //! Returns the number of erf samples + inline int get_num_samples() const; + //! Sets the number of erf samples + inline void set_num_samples(int num_samples); + + //! Returns the maximum sample value + inline int get_maximum_sample_value() const; + //! Sets the maximum sample value + inline void set_maximum_sample_value(double maximum_sample_value); + + /*! \brief Computes the erf() values, sets up BSplines and sets up interpolation vectors. */ + inline void set_up(); + +/*! \brief Uses BSplines to interpolate the value of erf(xp) + * If xp out of range (-\c_maximum_sample_value \c_maximum_sample_value) then outputs -1 or 1 + * @param xp input argument for erf(xp) + * @return interpolated approximation of erf(xp) + */ +inline double get_erf_BSplines_interpolation(double xp) const; + +/*! \brief Uses linear interpolation of precomputed erf(x) values for erf(xp) + * @param xp input argument for erf(xp) + * @return linear interpolated approximation of erf(xp) + */ +inline double get_erf_linear_interpolation(double xp) const; + +/*! \brief Uses nearest neighbour interpolation of precomputed erf(x) values for erf(xp) + * @param xp input argument for erf(xp) + * @return nearest neighbour interpolated approximation of erf(xp) + */ +inline double get_erf_nearest_neighbour_interpolation(double xp) const; + + //! Wraps get_erf_linear_interpolation as a () operator + inline const double operator() (const double xp) const; + +}; + +END_NAMESPACE_STIR + +#include "stir/numerics/FastErf.inl" + +#endif // __stir_numerics_FastErf__H__ diff --git a/src/include/stir/numerics/FastErf.inl b/src/include/stir/numerics/FastErf.inl new file mode 100644 index 0000000000..e40fea9d54 --- /dev/null +++ b/src/include/stir/numerics/FastErf.inl @@ -0,0 +1,114 @@ +/*! + \file + \ingroup numerics + \brief Implementation of an erf interpolation + + \author Robert Twyman + \author Alexander Whitehead +*/ +/* + Copyright (C) 2022, University College London + This file is part of STIR. + SPDX-License-Identifier: Apache-2.0 + See STIR/LICENSE.txt for details +*/ + +#include "stir/numerics/BSplines1DRegularGrid.h" +#include "stir/numerics/erf.h" + +START_NAMESPACE_STIR + +inline void +FastErf:: +set_up() +{ + this->_sampling_period = (2 * this->_maximum_sample_value) / this->get_num_samples(); + + // Add two samples for end cases and compute a vector of erf values + std::vector erf_values(this->get_num_samples() + 2); + for (int i=0; iget_num_samples() + 2; ++i) + erf_values[i] = erf(i * this->_sampling_period - this->_maximum_sample_value); + + // Setup BSplines + BSpline::BSplines1DRegularGrid spline(erf_values, BSpline::linear); + this->_spline = spline; + + erf_values_vec = erf_values; +// this->_is_setup = true; +} + +inline double +FastErf::get_erf_BSplines_interpolation(double xp) const +{ +#if 0 + xp = std::clamp(xp,-this->_maximum_sample_value, this->_maximum_sample_value); +#else + xp = std::max(std::min(this->_maximum_sample_value, xp), -this->_maximum_sample_value); +#endif + return this->_spline.BSplines((xp + this->_maximum_sample_value) / this->_sampling_period); +} + + +inline double +FastErf:: +get_erf_linear_interpolation(double xp) const +{ +#if 0 + xp = std::clamp(xp,-this->_maximum_sample_value, this->_maximum_sample_value); +#else + xp = std::max(std::min(this->_maximum_sample_value, xp), -this->_maximum_sample_value); +#endif + // Find xp in index sequence + double xp_in_index = ((xp + this->_maximum_sample_value) / this->_sampling_period); + + // Find lower integer in index space + int lower = static_cast(floor(xp_in_index)); + + // Linear interpolation of xp between vec[lower] and vec[lower + 1] + return erf_values_vec[lower] + (xp_in_index - lower) * (erf_values_vec[lower + 1] - erf_values_vec[lower]); +} + +inline double +FastErf::get_erf_nearest_neighbour_interpolation(double xp) const +{ +#if 0 + xp = std::clamp(xp,-this->_maximum_sample_value, this->_maximum_sample_value); +#else + xp = std::max(std::min(this->_maximum_sample_value, xp), -this->_maximum_sample_value); +#endif + // Selects index of the nearest neighbour via rounding + return erf_values_vec[static_cast(std::round((xp + this->_maximum_sample_value) / this->_sampling_period))]; +} + +inline void +FastErf::set_num_samples(const int num_samples) +{ + this->_num_samples = num_samples; +} + +inline +int +FastErf::get_num_samples() const +{ + return this->_num_samples; +} + +int +FastErf::get_maximum_sample_value() const +{ + return this->_maximum_sample_value; +} + +void +FastErf::set_maximum_sample_value(double maximum_sample_value) +{ + this->_maximum_sample_value = maximum_sample_value; +} + +const double +FastErf::operator() (const double xp) const +{ + return get_erf_linear_interpolation(xp); +} + +END_NAMESPACE_STIR diff --git a/src/include/stir/numerics/erf.h b/src/include/stir/numerics/erf.h index 59ff3508c0..646a6672df 100644 --- a/src/include/stir/numerics/erf.h +++ b/src/include/stir/numerics/erf.h @@ -19,6 +19,9 @@ #include "stir/common.h" +#ifndef __stir_numerics_erf__H__ +#define __stir_numerics_erf__H__ + START_NAMESPACE_STIR /*! \ingroup numerics @@ -40,3 +43,4 @@ END_NAMESPACE_STIR #include "stir/numerics/erf.inl" +#endif // __stir_numerics_erf__H__ diff --git a/src/include/stir/recon_buildblock/ProjMatrixByBin.h b/src/include/stir/recon_buildblock/ProjMatrixByBin.h index 7d855fc5b7..9163031832 100644 --- a/src/include/stir/recon_buildblock/ProjMatrixByBin.h +++ b/src/include/stir/recon_buildblock/ProjMatrixByBin.h @@ -33,6 +33,7 @@ #include "stir/VectorWithOffset.h" #include "stir/TimedObject.h" #include "stir/VoxelsOnCartesianGrid.h" +#include "stir/numerics/FastErf.h" #include //#include #include @@ -249,8 +250,6 @@ class ProjMatrixByBin : //! 1/(2*sigma_in_mm) float r_sqrt2_gauss_sigma; - Array<1, float> cache_erf; - //! The function which actually applies the TOF kernel on the LOR. inline void apply_tof_kernel_and_symm_transformation(ProjMatrixElemsForOneBin& probabilities, const CartesianCoordinate3D& point1, @@ -260,7 +259,10 @@ class ProjMatrixByBin : //! Get the interal value erf(m - v_j) - erf(m -v_j) - inline void get_tof_value(const float d1, const float d2, float& val) const; + inline float get_tof_value(const float d1, const float d2) const; + + //! erf map + FastErf erf_interpolation; }; diff --git a/src/include/stir/recon_buildblock/ProjMatrixByBin.inl b/src/include/stir/recon_buildblock/ProjMatrixByBin.inl index a581824aab..3a2c9d6ddc 100644 --- a/src/include/stir/recon_buildblock/ProjMatrixByBin.inl +++ b/src/include/stir/recon_buildblock/ProjMatrixByBin.inl @@ -26,6 +26,7 @@ #include "stir/Succeeded.h" #include "stir/recon_buildblock/SymmetryOperation.h" #include "stir/geometry/line_distances.h" +#include "stir/numerics/erf.h" START_NAMESPACE_STIR @@ -169,17 +170,18 @@ ProjMatrixByBin::apply_tof_kernel_and_symm_transformation(ProjMatrixElemsForOneB continue; } - get_tof_value(low_dist, high_dist, new_value); + new_value = get_tof_value(low_dist, high_dist); new_value *= element_ptr->get_value(); *element_ptr = ProjMatrixElemsForOneBin::value_type(c, new_value); } } -void +float ProjMatrixByBin:: -get_tof_value(const float d1, const float d2, float& val) const +get_tof_value(const float d1, const float d2) const { - val = 0.5f * (erf(d2) - erf(d1)); +// val = 0.5f * (erf(d2) - erf(d1)); + return 0.5f * (erf_interpolation(d2) - erf_interpolation(d1)); } END_NAMESPACE_STIR diff --git a/src/recon_buildblock/ProjMatrixByBin.cxx b/src/recon_buildblock/ProjMatrixByBin.cxx index 2f0bb054ed..221c85e581 100644 --- a/src/recon_buildblock/ProjMatrixByBin.cxx +++ b/src/recon_buildblock/ProjMatrixByBin.cxx @@ -162,6 +162,10 @@ set_up( omp_init_lock(&this->cache_locks[view_num][seg_num]); #endif } + + // Setup the custom erf code + erf_interpolation.set_num_samples(200000); //200,000 =~12.8MB + erf_interpolation.set_up(); } diff --git a/src/test/numerics/CMakeLists.txt b/src/test/numerics/CMakeLists.txt index 2d7a0c6b9a..81130fc7c7 100644 --- a/src/test/numerics/CMakeLists.txt +++ b/src/test/numerics/CMakeLists.txt @@ -18,7 +18,7 @@ create_stir_test (test_IR_filters.cxx "" "") create_stir_test (test_BSplines.cxx "buildblock;IO;buildblock;numerics_buildblock;display" "") create_stir_test (test_BSplinesRegularGrid.cxx "buildblock;IO;buildblock;numerics_buildblock;display" "") create_stir_test (test_BSplinesRegularGrid1D.cxx "buildblock;IO;buildblock;numerics_buildblock;display" "") -create_stir_test (test_erf.cxx "" "") +create_stir_test (test_erf.cxx "buildblock;IO;buildblock;numerics_buildblock;display" "") create_stir_test (test_matrices.cxx "buildblock;IO;numerics_buildblock;buildblock;numerics_buildblock;display" "") create_stir_test (test_overlap_interpolate.cxx "buildblock;IO;buildblock;numerics_buildblock;display" "") create_stir_test (test_integrate_discrete_function.cxx "buildblock;IO;numerics_buildblock;display" "") diff --git a/src/test/numerics/test_erf.cxx b/src/test/numerics/test_erf.cxx index d3037ebf8b..8708bde9a1 100644 --- a/src/test/numerics/test_erf.cxx +++ b/src/test/numerics/test_erf.cxx @@ -19,8 +19,8 @@ #include "stir/RunTests.h" #include "stir/numerics/erf.h" +#include "stir/numerics/FastErf.h" #include - START_NAMESPACE_STIR /*! @@ -33,14 +33,33 @@ class erfTests : public RunTests erfTests() {} void run_tests(); + + /*!\brief Tests STIR's erf(x) function against known values */ + void test_stir_erf(); + + /*!\brief Tests the FastErf object and its interpolation methods. + * This test will construct a FastErf object and compute a range of erf values and compare to erf(x) + */ + void test_FastErf(); + private: - //istream& in; + //! Executes all the FastErf interpolation methods for a given xp + void actual_test_FastErf(const float xp); + + FastErf e; }; void erfTests::run_tests() -{ +{ std::cerr << "Testing Error Functions..." << std::endl; + test_stir_erf(); + test_FastErf(); +} + +void erfTests::test_stir_erf() +{ + std::cerr << " Testing stir error functions..." << std::endl; set_tolerance(0.000000000000001); @@ -106,6 +125,81 @@ void erfTests::run_tests() } } +void +erfTests::test_FastErf() +{ + std::cerr << " Testing stir FastErf ..." << std::endl; + set_tolerance(0.0001); + + e.set_num_samples(200000); + e.set_up(); + + const float upper_samle_limit = 2 * e.get_maximum_sample_value() + 1; + const float lower_samle_limit = -(upper_samle_limit); + double sample_period = _PI/ 10000; // Needed a number that wasn't regular and this worked... + // Test the FastErf interpolations 2* beyond the _maximum_sample_value. + // The while (-_maximum_sample_value > xp) or (_maximum_sample_value < xp), + // xp is clamped to -_maximum_sample_value or _maximum_sample_value + for (double xp = lower_samle_limit; xp < upper_samle_limit; xp += sample_period) + { + this->actual_test_FastErf(xp); + if (!this->everything_ok) + break; + } + + // Test cases where x is just smaller or larger than the lower or upper limits of FastErf + // This is an additional sanity check ensure there are no rounding or out of limit errors. + const float epsilon = sample_period/100; + std::vector extremity_test_xps(4); + extremity_test_xps[0] = e.get_maximum_sample_value() + epsilon; // above max + extremity_test_xps[1] = e.get_maximum_sample_value() - epsilon; // under max + extremity_test_xps[2] = -e.get_maximum_sample_value() + epsilon; // above min + extremity_test_xps[3] = -e.get_maximum_sample_value() - epsilon; // under min + + for (float xp : extremity_test_xps) + { + this->actual_test_FastErf(xp); + if (!this->everything_ok) + break; + } +} + + + +void erfTests::actual_test_FastErf(const float xp) +{ + //BSPlines + check_if_equal(e.get_erf_BSplines_interpolation(xp), erf(xp)); + if (!this->is_everything_ok()){ + std::cerr << "xp = " << xp + << "\tFastErf.get_erf_BSplines_interpolation(xp) = " << e.get_erf_BSplines_interpolation(xp) + << "\terf(xp) = " << erf(xp) << "\n"; + } + // Linear + check_if_equal(e.get_erf_linear_interpolation(xp), erf(xp)); + if (!this->is_everything_ok()){ + std::cerr << "linear xp = " << xp + << "\tFastErf.get_erf_linear_interpolation(xp) = " << e.get_erf_linear_interpolation(xp) + << "\terf(xp) = " << erf(xp) << "\n"; + } + + //NN + check_if_equal(e.get_erf_nearest_neighbour_interpolation(xp), erf(xp)); + if (!this->is_everything_ok()){ + std::cerr << "NN xp = " << xp + << "\tFastErf.get_erf_nearest_neighbour_interpolation(xp) = " << e.get_erf_nearest_neighbour_interpolation(xp) + << "\terf(xp) = " << erf(xp) << "\n"; + } + + // Operator () - This acts as a wrapper for e.get_erf_linear_interpolation(xp) + check_if_equal(e(xp), erf(xp)); + if (!this->is_everything_ok()){ + std::cerr << "NN xp = " << xp + << "\tFastErf(xp) = " << e(xp) + << "\terf(xp) = " << erf(xp) << "\n"; + } +} + END_NAMESPACE_STIR USING_NAMESPACE_STIR From 6976945ea4cfd9d71d03802ce2019e6b3672c8ae Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sat, 26 Feb 2022 22:13:33 +0000 Subject: [PATCH 182/509] re-enable forward projection without caching if nonTOF --- .../ForwardProjectorByBinUsingProjMatrixByBin.cxx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/recon_buildblock/ForwardProjectorByBinUsingProjMatrixByBin.cxx b/src/recon_buildblock/ForwardProjectorByBinUsingProjMatrixByBin.cxx index 30dbdf797e..1bdf3bcb90 100644 --- a/src/recon_buildblock/ForwardProjectorByBinUsingProjMatrixByBin.cxx +++ b/src/recon_buildblock/ForwardProjectorByBinUsingProjMatrixByBin.cxx @@ -147,7 +147,9 @@ ForwardProjectorByBinUsingProjMatrixByBin:: } else { - error("Need to do TOF stuff here"); + if (viewgrams.get_proj_data_info_sptr()->get_num_tof_poss() > 1) + error("Need to implement TOF stuff in ForwardProjectorByBinUsingProjMatrixByBin::forward_project without cache"); + // Complicated version which handles the symmetries explicitly. // Faster when no caching is performed, about just as fast when there is caching, // but of only basic bins. From 439f62c8807fd850b14aa38acb61d62fe844f682 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sat, 26 Feb 2022 22:13:48 +0000 Subject: [PATCH 183/509] remove the Discovery STE nonTOF scanner STE is always nonTOF, but it was changed to TOF for some reason (testing?) --- .../run_test_simulate_and_recon_with_motion.sh | 2 +- src/buildblock/Scanner.cxx | 15 +-------------- src/include/stir/Scanner.h | 2 +- 3 files changed, 3 insertions(+), 16 deletions(-) diff --git a/recon_test_pack/run_test_simulate_and_recon_with_motion.sh b/recon_test_pack/run_test_simulate_and_recon_with_motion.sh index 7311616007..61fda54345 100755 --- a/recon_test_pack/run_test_simulate_and_recon_with_motion.sh +++ b/recon_test_pack/run_test_simulate_and_recon_with_motion.sh @@ -116,7 +116,7 @@ fi echo "=== create template sinogram (DSTE in 3D with max ring diff 2 to save time)" template_sino=my_DSTE_3D_rd2_template.hs cat > my_input.txt <(-4.5490*_PI/180),//sign? 4, 2, 6, 8, 1, 1, 1, 0.0F, 511.F, - (short int)(410), - (float)(10.0F), - (float)(400.0F) );// TODO not sure about sign of view_offset - break; - - case DiscoverySTE_nonTOF: - - set_params(DiscoverySTE_nonTOF, string_list("GE Discovery STE nonTOF", "Discovery STE nonTOF"), - 24, 329, 293, 2 * 280, - 886.2F/2.F, 8.4F, 6.54F, 2.397F, - static_cast(-4.5490*_PI/180),//sign? - 4, 2, 6, 8, 1, 1, 1, - 0.0F, 511.F, (short int)(0.F), (float)(0.F), (float)(0.F) );// TODO not sure about sign of view_offset - break; + break; case ntest_TOF_50: // dummy // 8x8 blocks, 1 virtual "crystal", 56 blocks along the ring, 8 blocks in axial direction diff --git a/src/include/stir/Scanner.h b/src/include/stir/Scanner.h index 341735e4f9..97720b745d 100644 --- a/src/include/stir/Scanner.h +++ b/src/include/stir/Scanner.h @@ -139,7 +139,7 @@ class Scanner any given parameters. */ enum Type {E931, E951, E953, E921, E925, E961, E962, E966, E1080, test_scanner, Siemens_mMR,Siemens_mCT, RPT,HiDAC, - Advance, DiscoveryLS, DiscoveryST, DiscoverySTE, DiscoverySTE_nonTOF, DiscoveryRX, Discovery600, PETMR_Signa, PETMR_Signa_nonTOF, + Advance, DiscoveryLS, DiscoveryST, DiscoverySTE, DiscoveryRX, Discovery600, PETMR_Signa, PETMR_Signa_nonTOF, Discovery690, DiscoveryMI3ring, DiscoveryMI4ring, DiscoveryMI5ring, HZLR, RATPET, PANDA, HYPERimage, nanoPET, HRRT, Allegro, GeminiTF, SAFIRDualRingPrototype, User_defined_scanner,ntest_TOF_50, Unknown_scanner}; From 7437adfbdb6578506eba59797425489a87202f60 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sat, 26 Feb 2022 22:50:09 +0000 Subject: [PATCH 184/509] update run_test_time_of_flight.sh but still broken This test no longer works, but could be fixed by adjusting the script. --- recon_test_pack/run_test_time_of_flight.sh | 9 ++++- src/listmode_buildblock/LmToProjData.cxx | 41 +++++++++++++--------- 2 files changed, 33 insertions(+), 17 deletions(-) diff --git a/recon_test_pack/run_test_time_of_flight.sh b/recon_test_pack/run_test_time_of_flight.sh index 7b7ac9c008..73fff7a628 100755 --- a/recon_test_pack/run_test_time_of_flight.sh +++ b/recon_test_pack/run_test_time_of_flight.sh @@ -26,7 +26,14 @@ if [ -n "$TRAVIS" ]; then set -e fi -echo This script should work with STIR version '>'3.0. If you have +echo "This test is currently broken. Do not use." 1>&2 +# Things that are wrong +# - relies on LmToProjData.cxx to be compiled with a test function (which shouldn't be there) +# - relies on output of list_projdata_info by TOF bin etc, which isn't the case anymore + +exit 1 + +echo This script should work with STIR version '>'6.0. If you have echo a later version, you might have to update your test pack. echo Please check the web site. echo diff --git a/src/listmode_buildblock/LmToProjData.cxx b/src/listmode_buildblock/LmToProjData.cxx index dd83b7b372..7c1c1cd4fc 100644 --- a/src/listmode_buildblock/LmToProjData.cxx +++ b/src/listmode_buildblock/LmToProjData.cxx @@ -887,18 +887,23 @@ LmToProjData::run_tof_test_function() #if 1 error("TOF test function disabled"); #else - VectorWithOffset > - segments (template_proj_data_info_ptr->get_min_segment_num(), - template_proj_data_info_ptr->get_max_segment_num()); - + VectorWithOffset > + segments (template_proj_data_info_ptr->get_min_tof_pos_num(), + template_proj_data_info_ptr->get_max_tof_pos_num()); + for (int timing_pos_num=segments.get_min_index(); timing_pos_num<=segments.get_max_index(); ++timing_pos_num) + { + segments[timing_pos_num].resize(template_proj_data_info_ptr->get_min_segment_num(), + template_proj_data_info_ptr->get_max_segment_num()); + } + // *********** open output file shared_ptr output; shared_ptr proj_data_sptr; - ExamInfo this_frame_exam_info(*lm_data_ptr->get_exam_info_ptr()); + ExamInfo this_frame_exam_info(*lm_data_ptr->get_exam_info_sptr()); { - TimeFrameDefinitions this_time_frame_defs(frame_defs, current_frame_num); + TimeFrameDefinitions this_time_frame_defs(frame_defs, 1); this_frame_exam_info.set_time_frame_definitions(this_time_frame_defs); } @@ -924,28 +929,32 @@ LmToProjData::run_tof_test_function() min( proj_data_sptr->get_max_segment_num()+1, start_segment_index + num_segments_in_memory) - 1; if (!interactive) - allocate_segments(segments, start_segment_index, end_segment_index, - start_timing_pos_index, end_timing_pos_index, - proj_data_sptr->get_proj_data_info_ptr(), current_timing_pos_index); + allocate_segments(segments, + proj_data_sptr->get_min_tof_pos_num(), + proj_data_sptr->get_max_tof_pos_num(), + start_segment_index, end_segment_index, + proj_data_sptr->get_proj_data_info_sptr()); - for (int ax_num = proj_data_sptr->get_proj_data_info_ptr()->get_min_axial_pos_num(start_segment_index); - ax_num <= proj_data_sptr->get_proj_data_info_ptr()->get_max_axial_pos_num(start_segment_index); + for (int ax_num = proj_data_sptr->get_proj_data_info_sptr()->get_min_axial_pos_num(start_segment_index); + ax_num <= proj_data_sptr->get_proj_data_info_sptr()->get_max_axial_pos_num(start_segment_index); ++ax_num) { - for (int view_num = proj_data_sptr->get_proj_data_info_ptr()->get_min_view_num(); - view_num <= proj_data_sptr->get_proj_data_info_ptr()->get_max_view_num(); ++view_num) + for (int view_num = proj_data_sptr->get_proj_data_info_sptr()->get_min_view_num(); + view_num <= proj_data_sptr->get_proj_data_info_sptr()->get_max_view_num(); ++view_num) { - for (int tang_num = proj_data_sptr->get_proj_data_info_ptr()->get_min_tangential_pos_num(); - tang_num <= proj_data_sptr->get_proj_data_info_ptr()->get_max_tangential_pos_num(); + for (int tang_num = proj_data_sptr->get_proj_data_info_sptr()->get_min_tangential_pos_num(); + tang_num <= proj_data_sptr->get_proj_data_info_sptr()->get_max_tangential_pos_num(); ++tang_num) { - (*segments[start_segment_index])[view_num][ax_num][tang_num] = current_timing_pos_index; + (*(segments[current_timing_pos_index][start_segment_index]))[view_num][ax_num][tang_num] = current_timing_pos_index; } } } if (!interactive) save_and_delete_segments(output, segments, + proj_data_sptr->get_min_tof_pos_num(), + proj_data_sptr->get_max_tof_pos_num(), start_segment_index, end_segment_index, *proj_data_sptr); } // end of for loop for segment range From 8a34b4a7812ba5aff08d89c7ed77d93c42184332 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Mon, 28 Feb 2022 22:31:43 +0000 Subject: [PATCH 185/509] added extra test on TOF projdata --- src/test/test_time_of_flight.cxx | 40 +++++++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/src/test/test_time_of_flight.cxx b/src/test/test_time_of_flight.cxx index 3ddd24922a..0d2a97b863 100644 --- a/src/test/test_time_of_flight.cxx +++ b/src/test/test_time_of_flight.cxx @@ -1,5 +1,5 @@ /* - Copyright (C) 2016, UCL + Copyright (C) 2016, 2022, UCL Copyright (C) 2016, University of Hull This file is part of STIR. @@ -16,6 +16,7 @@ */ #include "stir/ProjDataInfoCylindricalNoArcCorr.h" +#include "stir/DetectionPositionPair.h" #include "stir/recon_buildblock/ProjMatrixByBin.h" #include "stir/recon_buildblock/ProjMatrixByBinUsingRayTracing.h" #include "stir/recon_buildblock/ForwardProjectorByBinUsingProjMatrixByBin.h" @@ -91,10 +92,12 @@ class FloatFloat{ \author Nikos Efthimiou - The following 2 tests are performed: + The following tests are performed: *. Compare the ProjDataInfo of the GE Signa scanner to known values. + *. Check if get_det_pos_pair_for_bin swaps detectors (or timing_pos) for bins with opposite timing_pos + *. Check that the sum of the TOF LOR is the same as the non TOF. \warning If you change the mashing factor the test_tof_proj_data_info() will fail. @@ -107,6 +110,8 @@ class TOF_Tests : public RunTests private: + void test_tof_proj_data_info_kernel(); + void test_tof_proj_data_info_det_pos(); void test_tof_proj_data_info(); //! This checks peaks a specific bin, finds the LOR and applies all the @@ -187,7 +192,7 @@ TOF_Tests::run_tests() } void -TOF_Tests::test_tof_proj_data_info() +TOF_Tests::test_tof_proj_data_info_kernel() { const int correct_tof_mashing_factor = 39; const int num_timing_positions = 9; @@ -227,6 +232,35 @@ TOF_Tests::test_tof_proj_data_info() } + +void +TOF_Tests::test_tof_proj_data_info_det_pos() +{ + + auto pdi_ptr = + dynamic_cast (test_proj_data_info_sptr.get()); + + Bin b1(1,2,3,4,5); + Bin b2 = b1; + b2.timing_pos_num() = -b1.timing_pos_num(); + + DetectionPositionPair<> dp1, dp2; + pdi_ptr->get_det_pos_pair_for_bin(dp1, b1); + pdi_ptr->get_det_pos_pair_for_bin(dp2, b2); + + check((dp1.timing_pos() == dp2.timing_pos() && dp1.pos1() == dp2.pos2() && dp1.pos2() == dp2.pos1()) + || (static_cast(dp1.timing_pos()) == -static_cast(dp2.timing_pos()) && dp1.pos1() == dp2.pos1() && dp1.pos2() == dp2.pos2()), + "get_det_pos_for_bin with bins of opposite timing_pos"); +} + + +void +TOF_Tests::test_tof_proj_data_info() +{ + test_tof_proj_data_info_kernel(); + test_tof_proj_data_info_det_pos(); +} + void TOF_Tests::test_tof_kernel_application(bool print_to_file) { From 37919a65950bd3088c38974342af70597008c140 Mon Sep 17 00:00:00 2001 From: Robert Twyman Date: Tue, 5 Apr 2022 20:13:00 -0700 Subject: [PATCH 186/509] [ci skip] Set defaults scanner parameters for TOF fields --- src/include/stir/Scanner.h | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/include/stir/Scanner.h b/src/include/stir/Scanner.h index d203313e4b..cf6a3d60dc 100644 --- a/src/include/stir/Scanner.h +++ b/src/include/stir/Scanner.h @@ -168,9 +168,9 @@ class Scanner int num_detector_layers_v, float energy_resolution_v = -1.0f, float reference_energy_v = -1.0f, - short int max_num_of_timing_poss, - float size_timing_pos, - float timing_resolution, + short int max_num_of_timing_poss = -1.0f, + float size_timing_pos = -1.0f, + float timing_resolution = -1.0f, const std::string& scanner_orientation_v = "X", const std::string& scanner_geometry_v = "Cylindrical", float axial_crystal_spacing_v = -1.0f, @@ -197,9 +197,9 @@ class Scanner int num_detector_layers_v, float energy_resolution_v = -1.0f, float reference_energy_v = -1.0f, - short int max_num_of_timing_poss, - float size_timing_pos, - float timing_resolution, + short int max_num_of_timing_poss = -1.0f, + float size_timing_pos = -1.0f, + float timing_resolution = -1.0f, const std::string& scanner_orientation_v = "X", const std::string& scanner_geometry_v = "Cylindrical", float axial_crystal_spacing_v = -1.0f, @@ -580,9 +580,9 @@ class Scanner int num_detector_layers_v, float energy_resolution_v = -1.0f, float reference_energy = -1.0f, - short int max_num_of_timing_poss_v, - float size_timing_pos_v, - float timing_resolution_v, + short int max_num_of_timing_poss_v = -1.0f, + float size_timing_pos_v = -1.0f, + float timing_resolution_v = -1.0f, const std::string& scanner_orientation_v = "", const std::string& scanner_geometry_v = "", float axial_crystal_spacing_v = -1.0f, From 78a7c1cc563d297476ce931597408e32d4ff3a45 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Wed, 6 Apr 2022 10:52:18 +0100 Subject: [PATCH 187/509] default TOF mashing factor to 1 when reading ROOT files --- src/listmode_buildblock/CListModeDataROOT.cxx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/listmode_buildblock/CListModeDataROOT.cxx b/src/listmode_buildblock/CListModeDataROOT.cxx index 8b5c8f18f2..bb2fa77c7d 100644 --- a/src/listmode_buildblock/CListModeDataROOT.cxx +++ b/src/listmode_buildblock/CListModeDataROOT.cxx @@ -265,6 +265,7 @@ set_defaults() ring_spacing = -.1f; bin_size = -1.f; view_offset = 0.f; + tof_mash_factor = 1; } Succeeded From 6b8ae0256b6ecda141de3100b49ea5da919bc4e5 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Wed, 6 Apr 2022 10:53:57 +0100 Subject: [PATCH 188/509] fix Scanner after merge onto TOF - remove scanner_orientation again - remove extra set_params() again --- src/IO/InterfileHeader.cxx | 1 - src/buildblock/Scanner.cxx | 54 -------------------------------------- src/include/stir/Scanner.h | 27 ------------------- 3 files changed, 82 deletions(-) diff --git a/src/IO/InterfileHeader.cxx b/src/IO/InterfileHeader.cxx index a74d1ac1e2..f88678030a 100644 --- a/src/IO/InterfileHeader.cxx +++ b/src/IO/InterfileHeader.cxx @@ -1443,7 +1443,6 @@ bool InterfilePDFSHeader::post_processing() max_num_timing_poss, size_of_timing_pos, timing_resolution, - scanner_orientation, scanner_geometry, static_cast(axial_distance_between_crystals_in_cm*10.), static_cast(transaxial_distance_between_crystals_in_cm*10.), diff --git a/src/buildblock/Scanner.cxx b/src/buildblock/Scanner.cxx index bf1d79f918..4c5dcb72fd 100644 --- a/src/buildblock/Scanner.cxx +++ b/src/buildblock/Scanner.cxx @@ -655,7 +655,6 @@ case PETMR_Signa: -1, //energy_resolution_v -1, //reference_energy_v (short int)0, 0.F, 0.F, // non-TOF - "", //scanner_orientation_v "", //scanner_geometry_v 2.2, //axial_crystal_spacing_v 2.2, //transaxial_crystal_spacing_v @@ -707,7 +706,6 @@ Scanner::Scanner(Type type_v, const list& list_of_names_v, short int max_num_of_timing_poss_v, float size_timing_pos_v, float timing_resolution_v, - const string& scanner_orientation_v, const string& scanner_geometry_v, float axial_crystal_spacing_v, float transaxial_crystal_spacing_v, @@ -733,7 +731,6 @@ Scanner::Scanner(Type type_v, const list& list_of_names_v, max_num_of_timing_poss_v, size_timing_pos_v, timing_resolution_v, - scanner_orientation_v, scanner_geometry_v, axial_crystal_spacing_v, transaxial_crystal_spacing_v, @@ -758,7 +755,6 @@ Scanner::Scanner(Type type_v, const string& name, short int max_num_of_timing_poss_v, float size_timing_pos_v, float timing_resolution_v, - const string& scanner_orientation_v, const string& scanner_geometry_v, float axial_crystal_spacing_v, float transaxial_crystal_spacing_v, @@ -784,7 +780,6 @@ Scanner::Scanner(Type type_v, const string& name, max_num_of_timing_poss_v, size_timing_pos_v, timing_resolution_v, - scanner_orientation_v, scanner_geometry_v, axial_crystal_spacing_v, transaxial_crystal_spacing_v, @@ -793,53 +788,6 @@ Scanner::Scanner(Type type_v, const string& name, crystal_map_file_name_v); } -void -Scanner:: -set_params(Type type_v, const std::list& list_of_names_v, - int num_rings_v, - int max_num_non_arccorrected_bins_v, - int num_detectors_per_ring_v, - float inner_ring_radius_v, - float average_depth_of_interaction_v, - float ring_spacing_v, - float bin_size_v, float intrinsic_tilt_v, - int num_axial_blocks_per_bucket_v, int num_transaxial_blocks_per_bucket_v, - int num_axial_crystals_per_block_v, int num_transaxial_crystals_per_block_v, - int num_axial_crystals_per_singles_unit_v, - int num_transaxial_crystals_per_singles_unit_v, - int num_detector_layers_v, - float energy_resolution_v, - float reference_energy_v, - const string& scanner_geometry_v, - float axial_crystal_spacing_v, - float transaxial_crystal_spacing_v, - float axial_block_spacing_v, - float transaxial_block_spacing_v, - const std::string& crystal_map_file_name_v) -{ - set_params(type_v, list_of_names_v, num_rings_v, - max_num_non_arccorrected_bins_v, - max_num_non_arccorrected_bins_v, - num_detectors_per_ring_v, - inner_ring_radius_v, - average_depth_of_interaction_v, - ring_spacing_v, bin_size_v, intrinsic_tilt_v, - num_axial_blocks_per_bucket_v, num_transaxial_blocks_per_bucket_v, - num_axial_crystals_per_block_v, num_transaxial_crystals_per_block_v, - num_axial_crystals_per_singles_unit_v, - num_transaxial_crystals_per_singles_unit_v, - num_detector_layers_v, - energy_resolution_v, - reference_energy_v, - scanner_geometry_v, - axial_crystal_spacing_v, - transaxial_crystal_spacing_v, - axial_block_spacing_v, - transaxial_block_spacing_v, - crystal_map_file_name_v); -} - - void Scanner:: set_params(Type type_v,const list& list_of_names_v, @@ -861,7 +809,6 @@ set_params(Type type_v,const list& list_of_names_v, short int max_num_of_timing_poss_v, float size_timing_pos_v, float timing_resolution_v, - const string& scanner_orientation_v, const string& scanner_geometry_v, float axial_crystal_spacing_v, float transaxial_crystal_spacing_v, @@ -1514,7 +1461,6 @@ Scanner* Scanner::ask_parameters() Num_TOF_bins, Size_TOF_bin, TOF_resolution, - ScannerOrientation, ScannerGeometry, TransaxialCrystalSpacing, AxialCrystalSpacing, diff --git a/src/include/stir/Scanner.h b/src/include/stir/Scanner.h index cf6a3d60dc..18f9e5b135 100644 --- a/src/include/stir/Scanner.h +++ b/src/include/stir/Scanner.h @@ -171,7 +171,6 @@ class Scanner short int max_num_of_timing_poss = -1.0f, float size_timing_pos = -1.0f, float timing_resolution = -1.0f, - const std::string& scanner_orientation_v = "X", const std::string& scanner_geometry_v = "Cylindrical", float axial_crystal_spacing_v = -1.0f, float transaxial_crystal_spacing_v = -1.0f, @@ -200,7 +199,6 @@ class Scanner short int max_num_of_timing_poss = -1.0f, float size_timing_pos = -1.0f, float timing_resolution = -1.0f, - const std::string& scanner_orientation_v = "X", const std::string& scanner_geometry_v = "Cylindrical", float axial_crystal_spacing_v = -1.0f, float transaxial_crystal_spacing_v = -1.0f, @@ -539,30 +537,6 @@ class Scanner // function to create the maps void read_detectormap_from_file( const std::string& filename ); - - // ! set all parameters, case where default_num_arccorrected_bins==max_num_non_arccorrected_bins - void set_params(Type type_v, const std::list& list_of_names_v, - int num_rings_v, - int max_num_non_arccorrected_bins_v, - int num_detectors_per_ring_v, - float inner_ring_radius_v, - float average_depth_of_interaction_v, - float ring_spacing_v, - float bin_size_v, float intrinsic_tilt_v, - int num_axial_blocks_per_bucket_v, int num_transaxial_blocks_per_bucket_v, - int num_axial_crystals_per_block_v, int num_transaxial_crystals_per_block_v, - int num_axial_crystals_per_singles_unit_v, - int num_transaxial_crystals_per_singles_unit_v, - int num_detector_layers_v, - float energy_resolution_v = -1.0f, - float reference_energy = -1.0f, - const std::string& scanner_geometry_v = "", - float axial_crystal_spacing_v = -1.0f, - float transaxial_crystal_spacing_v = -1.0f, - float axial_block_spacing_v = -1.0f, - float transaxial_block_spacing_v = -1.0f, - const std::string& crystal_map_file_name = ""); - // ! set all parameters void set_params(Type type_v, const std::list& list_of_names_v, int num_rings_v, @@ -583,7 +557,6 @@ class Scanner short int max_num_of_timing_poss_v = -1.0f, float size_timing_pos_v = -1.0f, float timing_resolution_v = -1.0f, - const std::string& scanner_orientation_v = "", const std::string& scanner_geometry_v = "", float axial_crystal_spacing_v = -1.0f, float transaxial_crystal_spacing_v = -1.0f, From 1a4faf2d56c32b438b48fee79d358a675fdbe09e Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Wed, 6 Apr 2022 10:55:20 +0100 Subject: [PATCH 189/509] remove remaining half_block in ROOT code --- src/IO/InputStreamFromROOTFileForCylindricalPET.cxx | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/IO/InputStreamFromROOTFileForCylindricalPET.cxx b/src/IO/InputStreamFromROOTFileForCylindricalPET.cxx index 3395b5f0fe..921d2cbc82 100644 --- a/src/IO/InputStreamFromROOTFileForCylindricalPET.cxx +++ b/src/IO/InputStreamFromROOTFileForCylindricalPET.cxx @@ -210,12 +210,6 @@ set_up(const std::string & header_path) if (nentries == 0) error("InputStreamFromROOTFileForCylindricalPET: The total number of entries in the ROOT file is zero. Abort."); - half_block = (module_repeater_y * submodule_repeater_y * crystal_repeater_y) / 2; - if (half_block < 0 ) - half_block = 0; - - offset_dets -= half_block; - return Succeeded::yes; } From aa50db55ed48af3c89c46cff0f7511067d44a47e Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Wed, 6 Apr 2022 10:55:42 +0100 Subject: [PATCH 190/509] fix ProjDataInfoSubsetByView for TOF --- src/buildblock/ProjDataInfoSubsetByView.cxx | 4 ++-- src/include/stir/ProjDataInfoSubsetByView.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/buildblock/ProjDataInfoSubsetByView.cxx b/src/buildblock/ProjDataInfoSubsetByView.cxx index 7d5889d14b..1520b9af13 100644 --- a/src/buildblock/ProjDataInfoSubsetByView.cxx +++ b/src/buildblock/ProjDataInfoSubsetByView.cxx @@ -221,9 +221,9 @@ ProjDataInfoSubsetByView::get_sampling_in_s(const Bin& bin) const } Bin -ProjDataInfoSubsetByView::get_bin(const LOR& lor) const +ProjDataInfoSubsetByView::get_bin(const LOR& lor, const double delta_time) const { - return get_bin_from_original(this->org_proj_data_info_sptr->get_bin(lor)); + return get_bin_from_original(this->org_proj_data_info_sptr->get_bin(lor, delta_time)); } bool diff --git a/src/include/stir/ProjDataInfoSubsetByView.h b/src/include/stir/ProjDataInfoSubsetByView.h index a497d07c1e..34112ab5b1 100644 --- a/src/include/stir/ProjDataInfoSubsetByView.h +++ b/src/include/stir/ProjDataInfoSubsetByView.h @@ -159,7 +159,7 @@ class ProjDataInfoSubsetByView : public ProjDataInfo //! Find the bin in the projection data that 'contains' an LOR /*! Forwards ProjDataInfo::get_bin */ - Bin get_bin(const LOR&) const override; + Bin get_bin(const LOR&, const double delta_time = 0.0) const override; //! Check if \c *this contains \c proj /*! From 60f2d3791d45173b7afd58535904c8983782faf5 Mon Sep 17 00:00:00 2001 From: Elise Emond Date: Mon, 18 Jun 2018 16:58:54 +0100 Subject: [PATCH 191/509] Some changes for positron range: one medium --- people/eliseemond/.gitignore | 39 ++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 people/eliseemond/.gitignore diff --git a/people/eliseemond/.gitignore b/people/eliseemond/.gitignore new file mode 100644 index 0000000000..603c07ff37 --- /dev/null +++ b/people/eliseemond/.gitignore @@ -0,0 +1,39 @@ +# Compiled source # +################### +*.com +*.class +*.dll +*.exe +*.o +*.pyc +*.pyo +*.so + +# Packages # +############ +# it's better to unpack these files and commit the raw source +# git has its own built in compression methods +*.7z +*.dmg +*.gz +*.iso +*.jar +*.rar +*.tar +*.zip + +# Logs and databases # +###################### +*.log +*.sql +*.sqlite + +# OS generated files # +###################### +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db From 974f1482ff8908a0ca338f1e656a6d02fbca0bef Mon Sep 17 00:00:00 2001 From: Elise Emond Date: Tue, 19 Feb 2019 18:11:01 +0000 Subject: [PATCH 192/509] Files for STIR+ROOT check (symmetries, unlisting etc.) --- .../Gate_macros/GateMaterials.db | 486 ++++++++++++++++++ .../Gate_macros/csorter_D690.mac | 5 + .../Gate_macros/digitiser_D690.mac | 20 + .../Gate_macros/geometry_D690.mac | 117 +++++ .../Gate_macros/main_D690_template.mac | 70 +++ .../Gate_macros/main_D690_test1.mac | 70 +++ .../Gate_macros/main_D690_test10.mac | 70 +++ .../Gate_macros/main_D690_test11.mac | 70 +++ .../Gate_macros/main_D690_test12.mac | 70 +++ .../Gate_macros/main_D690_test2.mac | 70 +++ .../Gate_macros/main_D690_test3.mac | 70 +++ .../Gate_macros/main_D690_test4.mac | 70 +++ .../Gate_macros/main_D690_test5.mac | 70 +++ .../Gate_macros/main_D690_test6.mac | 70 +++ .../Gate_macros/main_D690_test7.mac | 70 +++ .../Gate_macros/main_D690_test8.mac | 70 +++ .../Gate_macros/main_D690_test9.mac | 70 +++ .../Gate_macros/physics.mac | 9 + .../Gate_macros/source_test1.mac | 46 ++ .../Gate_macros/source_test10.mac | 49 ++ .../Gate_macros/source_test11.mac | 49 ++ .../Gate_macros/source_test12.mac | 49 ++ .../Gate_macros/source_test2.mac | 50 ++ .../Gate_macros/source_test3.mac | 50 ++ .../Gate_macros/source_test4.mac | 50 ++ .../Gate_macros/source_test5.mac | 50 ++ .../Gate_macros/source_test6.mac | 50 ++ .../Gate_macros/source_test7.mac | 50 ++ .../Gate_macros/source_test8.mac | 50 ++ .../Gate_macros/source_test9.mac | 49 ++ .../ROOT-STIR-consistency/generate_image1.par | 23 + .../generate_image10.par | 23 + .../generate_image11.par | 23 + .../generate_image12.par | 23 + .../ROOT-STIR-consistency/generate_image2.par | 23 + .../ROOT-STIR-consistency/generate_image3.par | 23 + .../ROOT-STIR-consistency/generate_image4.par | 23 + .../ROOT-STIR-consistency/generate_image5.par | 23 + .../ROOT-STIR-consistency/generate_image6.par | 23 + .../ROOT-STIR-consistency/generate_image7.par | 23 + .../ROOT-STIR-consistency/generate_image8.par | 23 + .../ROOT-STIR-consistency/generate_image9.par | 23 + .../lm_to_projdata_test1.par | 44 ++ .../lm_to_projdata_test10.par | 44 ++ .../lm_to_projdata_test11.par | 44 ++ .../lm_to_projdata_test12.par | 44 ++ .../lm_to_projdata_test2.par | 44 ++ .../lm_to_projdata_test3.par | 44 ++ .../lm_to_projdata_test4.par | 44 ++ .../lm_to_projdata_test5.par | 44 ++ .../lm_to_projdata_test6.par | 44 ++ .../lm_to_projdata_test7.par | 44 ++ .../lm_to_projdata_test8.par | 44 ++ .../lm_to_projdata_test9.par | 44 ++ .../ROOT-STIR-consistency/make_plot.py | 90 ++++ .../run_pretest_script.sh | 12 + 56 files changed, 3045 insertions(+) create mode 100755 people/eliseemond/ROOT-STIR-consistency/Gate_macros/GateMaterials.db create mode 100755 people/eliseemond/ROOT-STIR-consistency/Gate_macros/csorter_D690.mac create mode 100755 people/eliseemond/ROOT-STIR-consistency/Gate_macros/digitiser_D690.mac create mode 100755 people/eliseemond/ROOT-STIR-consistency/Gate_macros/geometry_D690.mac create mode 100755 people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_template.mac create mode 100755 people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test1.mac create mode 100755 people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test10.mac create mode 100755 people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test11.mac create mode 100755 people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test12.mac create mode 100755 people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test2.mac create mode 100755 people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test3.mac create mode 100755 people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test4.mac create mode 100755 people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test5.mac create mode 100755 people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test6.mac create mode 100755 people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test7.mac create mode 100755 people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test8.mac create mode 100755 people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test9.mac create mode 100755 people/eliseemond/ROOT-STIR-consistency/Gate_macros/physics.mac create mode 100755 people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test1.mac create mode 100755 people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test10.mac create mode 100755 people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test11.mac create mode 100755 people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test12.mac create mode 100755 people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test2.mac create mode 100755 people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test3.mac create mode 100755 people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test4.mac create mode 100755 people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test5.mac create mode 100755 people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test6.mac create mode 100755 people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test7.mac create mode 100755 people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test8.mac create mode 100755 people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test9.mac create mode 100755 people/eliseemond/ROOT-STIR-consistency/generate_image1.par create mode 100755 people/eliseemond/ROOT-STIR-consistency/generate_image10.par create mode 100755 people/eliseemond/ROOT-STIR-consistency/generate_image11.par create mode 100755 people/eliseemond/ROOT-STIR-consistency/generate_image12.par create mode 100755 people/eliseemond/ROOT-STIR-consistency/generate_image2.par create mode 100755 people/eliseemond/ROOT-STIR-consistency/generate_image3.par create mode 100755 people/eliseemond/ROOT-STIR-consistency/generate_image4.par create mode 100755 people/eliseemond/ROOT-STIR-consistency/generate_image5.par create mode 100755 people/eliseemond/ROOT-STIR-consistency/generate_image6.par create mode 100755 people/eliseemond/ROOT-STIR-consistency/generate_image7.par create mode 100755 people/eliseemond/ROOT-STIR-consistency/generate_image8.par create mode 100755 people/eliseemond/ROOT-STIR-consistency/generate_image9.par create mode 100755 people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test1.par create mode 100755 people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test10.par create mode 100755 people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test11.par create mode 100755 people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test12.par create mode 100755 people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test2.par create mode 100755 people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test3.par create mode 100755 people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test4.par create mode 100755 people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test5.par create mode 100755 people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test6.par create mode 100755 people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test7.par create mode 100755 people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test8.par create mode 100755 people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test9.par create mode 100755 people/eliseemond/ROOT-STIR-consistency/make_plot.py create mode 100755 people/eliseemond/ROOT-STIR-consistency/run_pretest_script.sh diff --git a/people/eliseemond/ROOT-STIR-consistency/Gate_macros/GateMaterials.db b/people/eliseemond/ROOT-STIR-consistency/Gate_macros/GateMaterials.db new file mode 100755 index 0000000000..f43d2e8b7f --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/Gate_macros/GateMaterials.db @@ -0,0 +1,486 @@ +[Elements] +Hydrogen: S= H ; Z= 1. ; A= 1.01 g/mole +Helium: S= He ; Z= 2. ; A= 4.003 g/mole +Lithium: S= Li ; Z= 3. ; A= 6.941 g/mole +Beryllium: S= Be ; Z= 4. ; A= 9.012 g/mole +Boron: S= B ; Z= 5. ; A= 10.811 g/mole +Carbon: S= C ; Z= 6. ; A= 12.01 g/mole +Nitrogen: S= N ; Z= 7. ; A= 14.01 g/mole +Oxygen: S= O ; Z= 8. ; A= 16.00 g/mole +Fluorine: S= F ; Z= 9. ; A= 18.998 g/mole +Neon: S= Ne ; Z= 10. ; A= 20.180 g/mole +Sodium: S= Na ; Z= 11. ; A= 22.99 g/mole +Magnesium: S= Mg ; Z= 12. ; A= 24.305 g/mole +Aluminium: S= Al ; Z= 13. ; A= 26.98 g/mole +Silicon: S= Si ; Z= 14. ; A= 28.09 g/mole +Phosphor: S= P ; Z= 15. ; A= 30.97 g/mole +Sulfur: S= S ; Z= 16. ; A= 32.066 g/mole +Chlorine: S= Cl ; Z= 17. ; A= 35.45 g/mole +Argon: S= Ar ; Z= 18. ; A= 39.95 g/mole +Potassium: S= K ; Z= 19. ; A= 39.098 g/mole +Calcium: S= Ca ; Z= 20. ; A= 40.08 g/mole +Scandium: S= Sc ; Z= 21. ; A= 44.956 g/mole +Titanium: S= Ti ; Z= 22. ; A= 47.867 g/mole +Vandium: S= V ; Z= 23. ; A= 50.942 g/mole +Chromium: S= Cr ; Z= 24. ; A= 51.996 g/mole +Manganese: S= Mn ; Z= 25. ; A= 54.938 g/mole +Iron: S= Fe ; Z= 26. ; A= 55.845 g/mole +Cobalt: S= Co ; Z= 27. ; A= 58.933 g/mole +Nickel: S= Ni ; Z= 28. ; A= 58.693 g/mole +Copper: S= Cu ; Z= 29. ; A= 63.39 g/mole +Zinc: S= Zn ; Z= 30. ; A= 65.39 g/mole +Gallium: S= Ga ; Z= 31. ; A= 69.723 g/mole +Germanium: S= Ge ; Z= 32. ; A= 72.61 g/mole +Yttrium: S= Y ; Z= 39. ; A= 88.91 g/mole +Silver: S= Ag ; Z= 47. ; A= 107.868 g/mole +Cadmium: S= Cd ; Z= 48. ; A= 112.41 g/mole +Tin: S= Sn ; Z= 50. ; A= 118.71 g/mole +Tellurium: S= Te ; Z= 52. ; A= 127.6 g/mole +Iodine: S= I ; Z= 53. ; A= 126.90 g/mole +Cesium: S= Cs ; Z= 55. ; A= 132.905 g/mole +Gadolinium: S= Gd ; Z= 64. ; A= 157.25 g/mole +Lutetium: S= Lu ; Z= 71. ; A= 174.97 g/mole +Tungsten: S= W ; Z= 74. ; A= 183.84 g/mole +Gold: S= Au ; Z= 79. ; A= 196.967 g/mole +Thallium: S= Tl ; Z= 81. ; A= 204.37 g/mole +Lead: S= Pb ; Z= 82. ; A= 207.20 g/mole +Bismuth: S= Bi ; Z= 83. ; A= 208.98 g/mole +Uranium: S= U ; Z= 92. ; A= 238.03 g/mole + +[Materials] +Vacuum: d=0.000001 mg/cm3 ; n=1 + +el: name=Hydrogen ; n=1 + +Aluminium: d=2.7 g/cm3 ; n=1 ; state=solid + +el: name=auto ; n=1 + +AluminiumEGS: d=2.702 g/cm3 ; n=1 ; state=solid + +el: name=Aluminium ; n=1 + +Uranium: d=18.90 g/cm3 ; n=1 ; state=solid + +el: name=auto ; n=1 + +Silicon: d=2.33 g/cm3 ; n=1 ; state=solid + +el: name=auto ; n=1 + +Germanium: d=5.32 g/cm3 ; n=1 ; state=solid + +el: name=auto ; n=1 + +Yttrium: d=4.47 g/cm3 ; n=1 + +el: name=auto ; n=1 + +Gadolinium: d=7.9 g/cm3 ; n=1 + +el: name=auto ; n=1 + +Lutetium: d=9.84 g/cm3 ; n=1 + +el: name=auto ; n=1 + +Tungsten: d=19.3 g/cm3 ; n=1 ; state=solid + +el: name=auto ; n=1 + +Lead: d=11.4 g/cm3 ; n=1 ; state=solid + +el: name=auto ; n=1 + +Bismuth: d=9.75 g/cm3 ; n=1 ; state=solid + +el: name=auto ; n=1 + +NaI: d=3.67 g/cm3; n=2; state=solid + +el: name=Sodium ; n=1 + +el: name=Iodine ; n=1 + +PWO: d=8.28 g/cm3; n=3 ; state=Solid + +el: name=Lead ; n=1 + +el: name=Tungsten ; n=1 + +el: name=Oxygen ; n=4 + +BGO: d=7.13 g/cm3; n= 3 ; state=solid + +el: name=Bismuth ; n=4 + +el: name=Germanium ; n=3 + +el: name=Oxygen ; n=12 + +LSO: d=7.4 g/cm3; n=3 ; state=Solid + +el: name=Lutetium ; n=2 + +el: name=Silicon ; n=1 + +el: name=Oxygen ; n=5 + +Plexiglass: d=1.19 g/cm3; n=3; state=solid + +el: name=Hydrogen ; f=0.080538 + +el: name=Carbon ; f=0.599848 + +el: name=Oxygen ; f=0.319614 + +GSO: d=6.7 g/cm3; n=3 ; state=Solid + +el: name=Gadolinium ; n=2 + +el: name=Silicon ; n=1 + +el: name=Oxygen ; n=5 + +LuAP: d=8.34 g/cm3; n=3 ; state=Solid + +el: name=Lutetium ; n=1 + +el: name=Aluminium ; n=1 + +el: name=Oxygen ; n=3 + +YAP: d=5.55 g/cm3; n=3 ; state=Solid + +el: name=Yttrium ; n=1 + +el: name=Aluminium ; n=1 + +el: name=Oxygen ; n=3 + +Water: d=1.00 g/cm3; n=2 ; state=liquid + +el: name=Hydrogen ; n=2 + +el: name=Oxygen ; n=1 + +Quartz: d=2.2 g/cm3; n=2 ; state=Solid + +el: name=Silicon ; n=1 + +el: name=Oxygen ; n=2 + +Breast: d=1.020 g/cm3 ; n = 8 + +el: name=Oxygen ; f=0.5270 + +el: name=Carbon ; f=0.3320 + +el: name=Hydrogen ; f=0.1060 + +el: name=Nitrogen ; f=0.0300 + +el: name=Sulfur ; f=0.0020 + +el: name=Sodium ; f=0.0010 + +el: name=Phosphor ; f=0.0010 + +el: name=Chlorine ; f=0.0010 + +Air: d=1.29 mg/cm3 ; n=4 ; state=gas + +el: name=Nitrogen ; f=0.755268 + +el: name=Oxygen ; f=0.231781 + +el: name=Argon ; f=0.012827 + +el: name=Carbon ; f=0.000124 + +Glass: d=2.5 g/cm3; n=4; state=solid + +el: name=Sodium ; f=0.1020 + +el: name=Calcium ; f=0.0510 + +el: name=Silicon ; f=0.2480 + +el: name=Oxygen ; f=0.5990 + +Scinti-C9H10: d=1.032 g/cm3 ; n=2 + +el: name=Carbon ; n=9 + +el: name=Hydrogen ; n=10 + +LuYAP-70: d=7.1 g/cm3 ; n=4 + +el: name=Lutetium ; n= 7 + +el: name=Yttrium ; n= 3 + +el: name=Aluminium ; n=10 + +el: name=Oxygen ; n=30 + +LuYAP-80: d=7.5 g/cm3 ; n=4 + +el: name=Lutetium ; n= 8 + +el: name=Yttrium ; n= 2 + +el: name=Aluminium ; n=10 + +el: name=Oxygen ; n=30 + +Plastic: d=1.18 g/cm3 ; n=3; state=solid + +el: name=Carbon ; n=5 + +el: name=Hydrogen ; n=8 + +el: name=Oxygen ; n=2 + +Biomimic: d=1.05 g/cm3 ; n=3; state=solid + +el: name=Carbon ; n=5 + +el: name=Hydrogen ; n=8 + +el: name=Oxygen ; n=2 + +FITC: d=1.0 g/cm3 ; n=1 + +el: name=Carbon ; n=1 + +RhB: d=1.0 g/cm3 ; n=1 + +el: name=Carbon ; n=1 + +CZT: d=5.68 g/cm3 ; n=3; state=solid + +el: name=Cadmium ; n=9 + +el: name=Zinc ; n=1 + +el: name=Tellurium ; n=10 + +Lung: d=0.26 g/cm3 ; n=9 + +el: name=Hydrogen ; f=0.103 + +el: name=Carbon ; f=0.105 + +el: name=Nitrogen ; f=0.031 + +el: name=Oxygen ; f=0.749 + +el: name=Sodium ; f=0.002 + +el: name=Phosphor ; f=0.002 + +el: name=Sulfur ; f=0.003 + +el: name=Chlorine ; f=0.003 + +el: name=Potassium ; f=0.002 + +Polyethylene: d=0.96 g/cm3 ; n=2 + +el: name=Hydrogen ; n=2 + +el: name=Carbon ; n=1 + +PVC: d=1.65 g/cm3 ; n=3 ; state=solid + +el: name=Hydrogen ; n=3 + +el: name=Carbon ; n=2 + +el: name=Chlorine ; n=1 + +SS304: d=7.92 g/cm3 ; n=4 ; state=solid + +el: name=Iron ; f=0.695 + +el: name=Chromium ; f=0.190 + +el: name=Nickel ; f=0.095 + +el: name=Manganese ; f=0.020 + +PTFE: d= 2.18 g/cm3 ; n=2 ; state=solid + +el: name=Carbon ; n=1 + +el: name=Fluorine ; n=2 + + +LYSO: d=5.37 g/cm3; n=4 ; state=Solid + +el: name=Lutetium ; f=0.31101534 + +el: name=Yttrium ; f=0.368765605 + +el: name=Silicon ; f=0.083209699 + +el: name=Oxygen ; f=0.237009356 + +Body: d=1.00 g/cm3 ; n=2 + +el: name=Hydrogen ; f=0.112 + +el: name=Oxygen ; f=0.888 + +Muscle: d=1.05 g/cm3 ; n=11 + +el: name=Hydrogen ; f=0.102 + +el: name=Carbon ; f=0.143 + +el: name=Nitrogen ; f=0.034 + +el: name=Oxygen ; f=0.71 + +el: name=Sodium ; f=0.001 + +el: name=Phosphor ; f=0.002 + +el: name=Sulfur ; f=0.003 + +el: name=Chlorine ; f=0.001 + +el: name=Potassium ; f=0.004 + +el: name=Calcium ; f=0.0 + +el: name=Scandium ; f=0.0 + +LungMoby: d=0.30 g/cm3 ; n=6 + +el: name=Hydrogen ; f=0.099 + +el: name=Carbon ; f=0.100 + +el: name=Nitrogen ; f=0.028 + +el: name=Oxygen ; f=0.740 + +el: name=Phosphor ; f=0.001 + +el: name=Calcium ; f=0.032 + +SpineBone: d=1.42 g/cm3 ; n=11 + +el: name=Hydrogen ; f=0.063 + +el: name=Carbon ; f=0.261 + +el: name=Nitrogen ; f=0.039 + +el: name=Oxygen ; f=0.436 + +el: name=Sodium ; f=0.001 + +el: name=Magnesium ; f=0.001 + +el: name=Phosphor ; f=0.061 + +el: name=Sulfur ; f=0.003 + +el: name=Chlorine ; f=0.001 + +el: name=Potassium ; f=0.001 + +el: name=Calcium ; f=0.133 + +RibBone: d=1.92 g/cm3 ; n=11 + +el: name=Hydrogen ; f=0.034 + +el: name=Carbon ; f=0.155 + +el: name=Nitrogen ; f=0.042 + +el: name=Oxygen ; f=0.435 + +el: name=Sodium ; f=0.001 + +el: name=Magnesium ; f=0.002 + +el: name=Phosphor ; f=0.103 + +el: name=Sulfur ; f=0.003 + +el: name=Calcium ; f=0.225 + +el: name=Scandium ; f=0.0 + +el: name=Titanium ; f=0.0 + +Adipose: d=0.92 g/cm3 ; n=11 + +el: name=Hydrogen ; f=0.120 + +el: name=Carbon ; f=0.640 + +el: name=Nitrogen ; f=0.008 + +el: name=Oxygen ; f=0.229 + +el: name=Phosphor ; f=0.002 + +el: name=Calcium ; f=0.001 + +el: name=Scandium ; f=0.0 + +el: name=Titanium ; f=0.0 + +el: name=Vandium ; f=0.0 + +el: name=Chromium ; f=0.0 + +el: name=Manganese ; f=0.0 + +Epidermis: d=0.92 g/cm3 ; n=11 + +el: name=Hydrogen ; f=0.120 + +el: name=Carbon ; f=0.640 + +el: name=Nitrogen ; f=0.008 + +el: name=Oxygen ; f=0.229 + +el: name=Phosphor ; f=0.002 + +el: name=Calcium ; f=0.001 + +el: name=Scandium ; f=0.0 + +el: name=Titanium ; f=0.0 + +el: name=Vandium ; f=0.0 + +el: name=Chromium ; f=0.0 + +el: name=Manganese ; f=0.0 + +Hypodermis: d=0.92 g/cm3 ; n=11 + +el: name=Hydrogen ; f=0.120 + +el: name=Carbon ; f=0.640 + +el: name=Nitrogen ; f=0.008 + +el: name=Oxygen ; f=0.229 + +el: name=Phosphor ; f=0.002 + +el: name=Calcium ; f=0.001 + +el: name=Scandium ; f=0.0 + +el: name=Titanium ; f=0.0 + +el: name=Vandium ; f=0.0 + +el: name=Chromium ; f=0.0 + +el: name=Manganese ; f=0.0 + +Blood: d=1.06 g/cm3 ; n=11 + +el: name=Hydrogen ; f=0.102 + +el: name=Carbon ; f=0.11 + +el: name=Nitrogen ; f=0.033 + +el: name=Oxygen ; f=0.745 + +el: name=Sodium ; f=0.001 + +el: name=Phosphor ; f=0.001 + +el: name=Sulfur ; f=0.002 + +el: name=Chlorine ; f=0.003 + +el: name=Potassium ; f=0.002 + +el: name=Iron ; f=0.001 + +el: name=Cobalt ; f=0.0 + +Heart: d=1.05 g/cm3 ; n=11 + +el: name=Hydrogen ; f=0.104 + +el: name=Carbon ; f=0.139 + +el: name=Nitrogen ; f=0.029 + +el: name=Oxygen ; f=0.718 + +el: name=Sodium ; f=0.001 + +el: name=Phosphor ; f=0.002 + +el: name=Sulfur ; f=0.002 + +el: name=Chlorine ; f=0.002 + +el: name=Potassium ; f=0.003 + +el: name=Calcium ; f=0.0 + +el: name=Scandium ; f=0.0 + +Kidney: d=1.05 g/cm3 ; n=11 + +el: name=Hydrogen ; f=0.103 + +el: name=Carbon ; f=0.132 + +el: name=Nitrogen ; f=0.03 + +el: name=Oxygen ; f=0.724 + +el: name=Sodium ; f=0.002 + +el: name=Phosphor ; f=0.002 + +el: name=Sulfur ; f=0.002 + +el: name=Chlorine ; f=0.002 + +el: name=Potassium ; f=0.002 + +el: name=Calcium ; f=0.001 + +el: name=Scandium ; f=0.0 + +Liver: d=1.06 g/cm3 ; n=11 + +el: name=Hydrogen ; f=0.102 + +el: name=Carbon ; f=0.139 + +el: name=Nitrogen ; f=0.03 + +el: name=Oxygen ; f=0.716 + +el: name=Sodium ; f=0.002 + +el: name=Phosphor ; f=0.003 + +el: name=Sulfur ; f=0.003 + +el: name=Chlorine ; f=0.002 + +el: name=Potassium ; f=0.003 + +el: name=Calcium ; f=0.0 + +el: name=Scandium ; f=0.0 + +Lymph: d=1.03 g/cm3 ; n=11 + +el: name=Hydrogen ; f=0.108 + +el: name=Carbon ; f=0.041 + +el: name=Nitrogen ; f=0.011 + +el: name=Oxygen ; f=0.832 + +el: name=Sodium ; f=0.003 + +el: name=Sulfur ; f=0.001 + +el: name=Chlorine ; f=0.004 + +el: name=Argon ; f=0.0 + +el: name=Potassium ; f=0.0 + +el: name=Calcium ; f=0.0 + +el: name=Scandium ; f=0.0 + +Pancreas: d=1.04 g/cm3 ; n=11 + +el: name=Hydrogen ; f=0.106 + +el: name=Carbon ; f=0.169 + +el: name=Nitrogen ; f=0.022 + +el: name=Oxygen ; f=0.694 + +el: name=Sodium ; f=0.002 + +el: name=Phosphor ; f=0.002 + +el: name=Sulfur ; f=0.001 + +el: name=Chlorine ; f=0.002 + +el: name=Potassium ; f=0.002 + +el: name=Calcium ; f=0.0 + +el: name=Scandium ; f=0.0 + +Intestine: d=1.03 g/cm3 ; n=11 + +el: name=Hydrogen ; f=0.106 + +el: name=Carbon ; f=0.115 + +el: name=Nitrogen ; f=0.022 + +el: name=Oxygen ; f=0.751 + +el: name=Sodium ; f=0.001 + +el: name=Phosphor ; f=0.001 + +el: name=Sulfur ; f=0.001 + +el: name=Chlorine ; f=0.002 + +el: name=Potassium ; f=0.001 + +el: name=Calcium ; f=0.0 + +el: name=Scandium ; f=0.0 + +Skull: d=1.61 g/cm3 ; n=11 + +el: name=Hydrogen ; f=0.05 + +el: name=Carbon ; f=0.212 + +el: name=Nitrogen ; f=0.04 + +el: name=Oxygen ; f=0.435 + +el: name=Sodium ; f=0.001 + +el: name=Magnesium ; f=0.002 + +el: name=Phosphor ; f=0.081 + +el: name=Sulfur ; f=0.003 + +el: name=Calcium ; f=0.176 + +el: name=Scandium ; f=0.0 + +el: name=Titanium ; f=0.0 + +Cartilage: d=1.10 g/cm3 ; n=11 + +el: name=Hydrogen ; f=0.096 + +el: name=Carbon ; f=0.099 + +el: name=Nitrogen ; f=0.022 + +el: name=Oxygen ; f=0.744 + +el: name=Sodium ; f=0.005 + +el: name=Phosphor ; f=0.022 + +el: name=Sulfur ; f=0.009 + +el: name=Chlorine ; f=0.003 + +el: name=Argon ; f=0.0 + +el: name=Potassium ; f=0.0 + +el: name=Calcium ; f=0.0 + +Brain: d=1.04 g/cm3 ; n=11 + +el: name=Hydrogen ; f=0.107 + +el: name=Carbon ; f=0.145 + +el: name=Nitrogen ; f=0.022 + +el: name=Oxygen ; f=0.712 + +el: name=Sodium ; f=0.002 + +el: name=Phosphor ; f=0.004 + +el: name=Sulfur ; f=0.002 + +el: name=Chlorine ; f=0.003 + +el: name=Potassium ; f=0.003 + +el: name=Calcium ; f=0.0 + +el: name=Scandium ; f=0.0 + +Spleen: d=1.06 g/cm3 ; n=11 + +el: name=Hydrogen ; f=0.103 + +el: name=Carbon ; f=0.113 + +el: name=Nitrogen ; f=0.032 + +el: name=Oxygen ; f=0.741 + +el: name=Sodium ; f=0.001 + +el: name=Phosphor ; f=0.003 + +el: name=Sulfur ; f=0.002 + +el: name=Chlorine ; f=0.002 + +el: name=Potassium ; f=0.003 + +el: name=Calcium ; f=0.0 + +el: name=Scandium ; f=0.0 + +Testis: d=1.04 g/cm3 ; n=9 + +el: name=Hydrogen ; f=0.106000 + +el: name=Carbon ; f=0.099000 + +el: name=Nitrogen ; f=0.020000 + +el: name=Oxygen ; f=0.766000 + +el: name=Sodium ; f=0.002000 + +el: name=Phosphor ; f=0.001000 + +el: name=Sulfur ; f=0.002000 + +el: name=Chlorine ; f=0.002000 + +el: name=Potassium ; f=0.002000 + +PMMA: d=1.195 g/cm3; n=3 ; state=Solid + +el: name=Hydrogen ; f=0.080541 + +el: name=Carbon ; f=0.599846 + +el: name=Oxygen ; f=0.319613 + +Epoxy: d=1.0 g/cm3; n=3; state=Solid + +el: name=Carbon ; n=1 + +el: name=Hydrogen ; n=1 + +el: name=Oxygen ; n=1 + +Carbide: d=15.8 g/cm3; n=2 ; state=Solid + +el: name=Tungsten ; n=1 + +el: name=Carbon ; n=1 diff --git a/people/eliseemond/ROOT-STIR-consistency/Gate_macros/csorter_D690.mac b/people/eliseemond/ROOT-STIR-consistency/Gate_macros/csorter_D690.mac new file mode 100755 index 0000000000..9758716d3b --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/Gate_macros/csorter_D690.mac @@ -0,0 +1,5 @@ +/gate/digitizer/Coincidences/setOffset 0. ns + +/gate/digitizer/Coincidences/setWindow 4.9 ns + +/gate/digitizer/insert coincidenceSorter diff --git a/people/eliseemond/ROOT-STIR-consistency/Gate_macros/digitiser_D690.mac b/people/eliseemond/ROOT-STIR-consistency/Gate_macros/digitiser_D690.mac new file mode 100755 index 0000000000..6bbec7e123 --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/Gate_macros/digitiser_D690.mac @@ -0,0 +1,20 @@ +/gate/digitizer/Singles/insert adder +/gate/digitizer/Singles/insert readout +/gate/digitizer/Singles/readout/setDepth 1 + + + +/gate/digitizer/Singles/insert blurring +/gate/digitizer/Singles/blurring/setResolution 0.124 #checked +/gate/digitizer/Singles/blurring/setEnergyOfReference 511. keV + + + +/gate/digitizer/Singles/insert thresholder +/gate/digitizer/Singles/thresholder/setThreshold 425. keV #checked +/gate/digitizer/Singles/insert upholder +/gate/digitizer/Singles/upholder/setUphold 650. keV #checked + + +/gate/digitizer/Singles/insert timeResolution +/gate/digitizer/Singles/timeResolution/setTimeResolution 53 ps # WARNING: not the correct timing resolution for D690 \ No newline at end of file diff --git a/people/eliseemond/ROOT-STIR-consistency/Gate_macros/geometry_D690.mac b/people/eliseemond/ROOT-STIR-consistency/Gate_macros/geometry_D690.mac new file mode 100755 index 0000000000..b2ab0a76a1 --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/Gate_macros/geometry_D690.mac @@ -0,0 +1,117 @@ +# CYLINDRICAL + +/gate/world/daughters/name cylindricalPET +/gate/world/daughters/insert cylinder +/gate/cylindricalPET/placement/setTranslation 0.0 0.0 0.0 cm +/gate/cylindricalPET/geometry/setRmax 43.1 cm +/gate/cylindricalPET/geometry/setRmin 40.5 cm +/gate/cylindricalPET/geometry/setHeight 15.716 cm +/gate/cylindricalPET/setMaterial Air +/gate/cylindricalPET/vis/forceWireframe +/gate/cylindricalPET/vis/setColor white + + + +# RSECTOR + +/gate/cylindricalPET/daughters/name rsector +/gate/cylindricalPET/daughters/insert box +/gate/rsector/placement/setTranslation 41.76 0.0 0.0 cm +/gate/rsector/geometry/setXLength 25 mm +/gate/rsector/geometry/setYLength 39.96 mm +/gate/rsector/geometry/setZLength 156.96 mm +/gate/rsector/setMaterial Air +/gate/rsector/vis/setVisible 0 + +# END-SHIELDING + +/gate/rsector/daughters/name endshielding +/gate/rsector/daughters/insert box +/gate/endshielding/placement/setTranslation 0.0 0.0 0.0 cm +/gate/endshielding/geometry/setXLength 25 mm +/gate/endshielding/geometry/setYLength 39.96 mm +/gate/endshielding/geometry/setZLength 1.0 mm +/gate/endshielding/setMaterial Lead +/gate/endshielding/repeaters/insert cubicArray +/gate/endshielding/cubicArray/setRepeatNumberX 1 +/gate/endshielding/cubicArray/setRepeatNumberY 1 +/gate/endshielding/cubicArray/setRepeatNumberZ 2 +/gate/endshielding/cubicArray/setRepeatVector 0.0 0.0 15.706 cm +/gate/endshielding/vis/setColor white + +# MODULE + +/gate/rsector/daughters/name module +/gate/rsector/daughters/insert box +/gate/module/placement/setTranslation 0 0.0 0.0 cm +/gate/module/geometry/setXLength 25 mm +/gate/module/geometry/setYLength 39.96 mm +/gate/module/geometry/setZLength 39.24 mm +/gate/module/setMaterial Air +/gate/module/vis/setColor green +#/gate/module/vis/setVisible 0 + + + +# C R Y S T A L + +/gate/module/daughters/name crystal +/gate/module/daughters/insert box +/gate/crystal/placement/setTranslation 0.0 0.0 0.0 cm +/gate/crystal/geometry/setXLength 25 mm +/gate/crystal/geometry/setYLength 4.44 mm +/gate/crystal/geometry/setZLength 6.54 mm # Filled the gaps between the crystals for convenience +/gate/crystal/setMaterial LYSO +/gate/crystal/vis/setColor red + + + +# R E P E A T C R Y S T A L + +/gate/crystal/repeaters/insert cubicArray +/gate/crystal/cubicArray/setRepeatNumberX 1 +/gate/crystal/cubicArray/setRepeatNumberY 9 +/gate/crystal/cubicArray/setRepeatNumberZ 6 +/gate/crystal/cubicArray/setRepeatVector 0.0 4.44 6.54 mm + + + + + +# R E P E A T MODULE + +/gate/module/repeaters/insert cubicArray +/gate/module/cubicArray/setRepeatNumberX 1 +/gate/module/cubicArray/setRepeatNumberY 1 +/gate/module/cubicArray/setRepeatNumberZ 4 +/gate/module/cubicArray/setRepeatVector 0.0 0.0 39.24 mm + + + + + +# R E P E A T RSECTOR + +/gate/rsector/repeaters/insert ring +/gate/rsector/ring/setRepeatNumber 64 + + + + + +# A T T A C H S Y S T E M + +/gate/systems/cylindricalPET/rsector/attach rsector +/gate/systems/cylindricalPET/module/attach module +/gate/systems/cylindricalPET/crystal/attach crystal + + + +# A T T A C H C R Y S T A L SD + +/gate/crystal/attachCrystalSD + + + +/gate/systems/cylindricalPET/describe + diff --git a/people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_template.mac b/people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_template.mac new file mode 100755 index 0000000000..5ad4d6db6b --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_template.mac @@ -0,0 +1,70 @@ +# Author: Elise Emond + +/vis/disable + +#-----------------# +# G E O M E T R Y # +#-----------------# + + +# Material database +/gate/geometry/setMaterialDatabase GateMaterials.db + +# World dimensions +/gate/world/geometry/setXLength 600. cm +/gate/world/geometry/setYLength 600. cm +/gate/world/geometry/setZLength 600. cm + +# Scanner +/control/execute geometry_D690.mac + + +#---------------# +# P H Y S I C S # +#---------------# + +/control/execute physics.mac + +#-----------------------------# +# I N I T I A L I S A T I O N # +#-----------------------------# + +/gate/run/initialize + +#-------------------# +# D I G I T I S E R # +#-------------------# + +/control/execute digitiser_D690.mac + +#-------------------------------------# +# C O I N C I D E N C E S O R T E R # +#-------------------------------------# + +/control/execute csorter_D690.mac + +#-------------------------------------# +# R A D I O A C T I V E S O U R C E # +#-------------------------------------# + +/control/execute source_SOURCENAME.mac + +#-----------------------# +# O U T P U T D A T A # +#-----------------------# + +/gate/output/root/enable +/gate/output/root/setFileName RootLM_D690_SOURCENAME +/gate/output/root/setRootHitFlag 0 +/gate/output/root/setRootSinglesFlag 0 +/gate/output/root/setRootCoincidencesFlag 1 + +#---------------------------------# +# A C Q U I S I T I O N T I M E # +#---------------------------------# + +/gate/application/setTimeSlice 100 ms +/gate/application/setTimeStart 0 s +/gate/application/setTimeStop 1 s + +/gate/application/startDAQ diff --git a/people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test1.mac b/people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test1.mac new file mode 100755 index 0000000000..03ddd02e23 --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test1.mac @@ -0,0 +1,70 @@ +# Author: Elise Emond + +/vis/disable + +#-----------------# +# G E O M E T R Y # +#-----------------# + + +# Material database +/gate/geometry/setMaterialDatabase GateMaterials.db + +# World dimensions +/gate/world/geometry/setXLength 600. cm +/gate/world/geometry/setYLength 600. cm +/gate/world/geometry/setZLength 600. cm + +# Scanner +/control/execute geometry_D690.mac + + +#---------------# +# P H Y S I C S # +#---------------# + +/control/execute physics.mac + +#-----------------------------# +# I N I T I A L I S A T I O N # +#-----------------------------# + +/gate/run/initialize + +#-------------------# +# D I G I T I S E R # +#-------------------# + +/control/execute digitiser_D690.mac + +#-------------------------------------# +# C O I N C I D E N C E S O R T E R # +#-------------------------------------# + +/control/execute csorter_D690.mac + +#-------------------------------------# +# R A D I O A C T I V E S O U R C E # +#-------------------------------------# + +/control/execute source_test1.mac + +#-----------------------# +# O U T P U T D A T A # +#-----------------------# + +/gate/output/root/enable +/gate/output/root/setFileName RootLM_D690_test1 +/gate/output/root/setRootHitFlag 0 +/gate/output/root/setRootSinglesFlag 0 +/gate/output/root/setRootCoincidencesFlag 1 + +#---------------------------------# +# A C Q U I S I T I O N T I M E # +#---------------------------------# + +/gate/application/setTimeSlice 100 ms +/gate/application/setTimeStart 0 s +/gate/application/setTimeStop 1 s + +/gate/application/startDAQ diff --git a/people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test10.mac b/people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test10.mac new file mode 100755 index 0000000000..c0aea936d5 --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test10.mac @@ -0,0 +1,70 @@ +# Author: Elise Emond + +/vis/disable + +#-----------------# +# G E O M E T R Y # +#-----------------# + + +# Material database +/gate/geometry/setMaterialDatabase GateMaterials.db + +# World dimensions +/gate/world/geometry/setXLength 600. cm +/gate/world/geometry/setYLength 600. cm +/gate/world/geometry/setZLength 600. cm + +# Scanner +/control/execute geometry_D690.mac + + +#---------------# +# P H Y S I C S # +#---------------# + +/control/execute physics.mac + +#-----------------------------# +# I N I T I A L I S A T I O N # +#-----------------------------# + +/gate/run/initialize + +#-------------------# +# D I G I T I S E R # +#-------------------# + +/control/execute digitiser_D690.mac + +#-------------------------------------# +# C O I N C I D E N C E S O R T E R # +#-------------------------------------# + +/control/execute csorter_D690.mac + +#-------------------------------------# +# R A D I O A C T I V E S O U R C E # +#-------------------------------------# + +/control/execute source_test10.mac + +#-----------------------# +# O U T P U T D A T A # +#-----------------------# + +/gate/output/root/enable +/gate/output/root/setFileName RootLM_D690_test10 +/gate/output/root/setRootHitFlag 0 +/gate/output/root/setRootSinglesFlag 0 +/gate/output/root/setRootCoincidencesFlag 1 + +#---------------------------------# +# A C Q U I S I T I O N T I M E # +#---------------------------------# + +/gate/application/setTimeSlice 100 ms +/gate/application/setTimeStart 0 s +/gate/application/setTimeStop 1 s + +/gate/application/startDAQ diff --git a/people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test11.mac b/people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test11.mac new file mode 100755 index 0000000000..bb308d459c --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test11.mac @@ -0,0 +1,70 @@ +# Author: Elise Emond + +/vis/disable + +#-----------------# +# G E O M E T R Y # +#-----------------# + + +# Material database +/gate/geometry/setMaterialDatabase GateMaterials.db + +# World dimensions +/gate/world/geometry/setXLength 600. cm +/gate/world/geometry/setYLength 600. cm +/gate/world/geometry/setZLength 600. cm + +# Scanner +/control/execute geometry_D690.mac + + +#---------------# +# P H Y S I C S # +#---------------# + +/control/execute physics.mac + +#-----------------------------# +# I N I T I A L I S A T I O N # +#-----------------------------# + +/gate/run/initialize + +#-------------------# +# D I G I T I S E R # +#-------------------# + +/control/execute digitiser_D690.mac + +#-------------------------------------# +# C O I N C I D E N C E S O R T E R # +#-------------------------------------# + +/control/execute csorter_D690.mac + +#-------------------------------------# +# R A D I O A C T I V E S O U R C E # +#-------------------------------------# + +/control/execute source_test11.mac + +#-----------------------# +# O U T P U T D A T A # +#-----------------------# + +/gate/output/root/enable +/gate/output/root/setFileName RootLM_D690_test11 +/gate/output/root/setRootHitFlag 0 +/gate/output/root/setRootSinglesFlag 0 +/gate/output/root/setRootCoincidencesFlag 1 + +#---------------------------------# +# A C Q U I S I T I O N T I M E # +#---------------------------------# + +/gate/application/setTimeSlice 100 ms +/gate/application/setTimeStart 0 s +/gate/application/setTimeStop 1 s + +/gate/application/startDAQ diff --git a/people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test12.mac b/people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test12.mac new file mode 100755 index 0000000000..921468cd09 --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test12.mac @@ -0,0 +1,70 @@ +# Author: Elise Emond + +/vis/disable + +#-----------------# +# G E O M E T R Y # +#-----------------# + + +# Material database +/gate/geometry/setMaterialDatabase GateMaterials.db + +# World dimensions +/gate/world/geometry/setXLength 600. cm +/gate/world/geometry/setYLength 600. cm +/gate/world/geometry/setZLength 600. cm + +# Scanner +/control/execute geometry_D690.mac + + +#---------------# +# P H Y S I C S # +#---------------# + +/control/execute physics.mac + +#-----------------------------# +# I N I T I A L I S A T I O N # +#-----------------------------# + +/gate/run/initialize + +#-------------------# +# D I G I T I S E R # +#-------------------# + +/control/execute digitiser_D690.mac + +#-------------------------------------# +# C O I N C I D E N C E S O R T E R # +#-------------------------------------# + +/control/execute csorter_D690.mac + +#-------------------------------------# +# R A D I O A C T I V E S O U R C E # +#-------------------------------------# + +/control/execute source_test12.mac + +#-----------------------# +# O U T P U T D A T A # +#-----------------------# + +/gate/output/root/enable +/gate/output/root/setFileName RootLM_D690_test12 +/gate/output/root/setRootHitFlag 0 +/gate/output/root/setRootSinglesFlag 0 +/gate/output/root/setRootCoincidencesFlag 1 + +#---------------------------------# +# A C Q U I S I T I O N T I M E # +#---------------------------------# + +/gate/application/setTimeSlice 100 ms +/gate/application/setTimeStart 0 s +/gate/application/setTimeStop 1 s + +/gate/application/startDAQ diff --git a/people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test2.mac b/people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test2.mac new file mode 100755 index 0000000000..c96311cdfb --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test2.mac @@ -0,0 +1,70 @@ +# Author: Elise Emond + +/vis/disable + +#-----------------# +# G E O M E T R Y # +#-----------------# + + +# Material database +/gate/geometry/setMaterialDatabase GateMaterials.db + +# World dimensions +/gate/world/geometry/setXLength 600. cm +/gate/world/geometry/setYLength 600. cm +/gate/world/geometry/setZLength 600. cm + +# Scanner +/control/execute geometry_D690.mac + + +#---------------# +# P H Y S I C S # +#---------------# + +/control/execute physics.mac + +#-----------------------------# +# I N I T I A L I S A T I O N # +#-----------------------------# + +/gate/run/initialize + +#-------------------# +# D I G I T I S E R # +#-------------------# + +/control/execute digitiser_D690.mac + +#-------------------------------------# +# C O I N C I D E N C E S O R T E R # +#-------------------------------------# + +/control/execute csorter_D690.mac + +#-------------------------------------# +# R A D I O A C T I V E S O U R C E # +#-------------------------------------# + +/control/execute source_test2.mac + +#-----------------------# +# O U T P U T D A T A # +#-----------------------# + +/gate/output/root/enable +/gate/output/root/setFileName RootLM_D690_test2 +/gate/output/root/setRootHitFlag 0 +/gate/output/root/setRootSinglesFlag 0 +/gate/output/root/setRootCoincidencesFlag 1 + +#---------------------------------# +# A C Q U I S I T I O N T I M E # +#---------------------------------# + +/gate/application/setTimeSlice 100 ms +/gate/application/setTimeStart 0 s +/gate/application/setTimeStop 1 s + +/gate/application/startDAQ diff --git a/people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test3.mac b/people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test3.mac new file mode 100755 index 0000000000..d268cf46cf --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test3.mac @@ -0,0 +1,70 @@ +# Author: Elise Emond + +/vis/disable + +#-----------------# +# G E O M E T R Y # +#-----------------# + + +# Material database +/gate/geometry/setMaterialDatabase GateMaterials.db + +# World dimensions +/gate/world/geometry/setXLength 600. cm +/gate/world/geometry/setYLength 600. cm +/gate/world/geometry/setZLength 600. cm + +# Scanner +/control/execute geometry_D690.mac + + +#---------------# +# P H Y S I C S # +#---------------# + +/control/execute physics.mac + +#-----------------------------# +# I N I T I A L I S A T I O N # +#-----------------------------# + +/gate/run/initialize + +#-------------------# +# D I G I T I S E R # +#-------------------# + +/control/execute digitiser_D690.mac + +#-------------------------------------# +# C O I N C I D E N C E S O R T E R # +#-------------------------------------# + +/control/execute csorter_D690.mac + +#-------------------------------------# +# R A D I O A C T I V E S O U R C E # +#-------------------------------------# + +/control/execute source_test3.mac + +#-----------------------# +# O U T P U T D A T A # +#-----------------------# + +/gate/output/root/enable +/gate/output/root/setFileName RootLM_D690_test3 +/gate/output/root/setRootHitFlag 0 +/gate/output/root/setRootSinglesFlag 0 +/gate/output/root/setRootCoincidencesFlag 1 + +#---------------------------------# +# A C Q U I S I T I O N T I M E # +#---------------------------------# + +/gate/application/setTimeSlice 100 ms +/gate/application/setTimeStart 0 s +/gate/application/setTimeStop 1 s + +/gate/application/startDAQ diff --git a/people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test4.mac b/people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test4.mac new file mode 100755 index 0000000000..d5b59f227e --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test4.mac @@ -0,0 +1,70 @@ +# Author: Elise Emond + +/vis/disable + +#-----------------# +# G E O M E T R Y # +#-----------------# + + +# Material database +/gate/geometry/setMaterialDatabase GateMaterials.db + +# World dimensions +/gate/world/geometry/setXLength 600. cm +/gate/world/geometry/setYLength 600. cm +/gate/world/geometry/setZLength 600. cm + +# Scanner +/control/execute geometry_D690.mac + + +#---------------# +# P H Y S I C S # +#---------------# + +/control/execute physics.mac + +#-----------------------------# +# I N I T I A L I S A T I O N # +#-----------------------------# + +/gate/run/initialize + +#-------------------# +# D I G I T I S E R # +#-------------------# + +/control/execute digitiser_D690.mac + +#-------------------------------------# +# C O I N C I D E N C E S O R T E R # +#-------------------------------------# + +/control/execute csorter_D690.mac + +#-------------------------------------# +# R A D I O A C T I V E S O U R C E # +#-------------------------------------# + +/control/execute source_test4.mac + +#-----------------------# +# O U T P U T D A T A # +#-----------------------# + +/gate/output/root/enable +/gate/output/root/setFileName RootLM_D690_test4 +/gate/output/root/setRootHitFlag 0 +/gate/output/root/setRootSinglesFlag 0 +/gate/output/root/setRootCoincidencesFlag 1 + +#---------------------------------# +# A C Q U I S I T I O N T I M E # +#---------------------------------# + +/gate/application/setTimeSlice 100 ms +/gate/application/setTimeStart 0 s +/gate/application/setTimeStop 1 s + +/gate/application/startDAQ diff --git a/people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test5.mac b/people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test5.mac new file mode 100755 index 0000000000..8df255b7e9 --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test5.mac @@ -0,0 +1,70 @@ +# Author: Elise Emond + +/vis/disable + +#-----------------# +# G E O M E T R Y # +#-----------------# + + +# Material database +/gate/geometry/setMaterialDatabase GateMaterials.db + +# World dimensions +/gate/world/geometry/setXLength 600. cm +/gate/world/geometry/setYLength 600. cm +/gate/world/geometry/setZLength 600. cm + +# Scanner +/control/execute geometry_D690.mac + + +#---------------# +# P H Y S I C S # +#---------------# + +/control/execute physics.mac + +#-----------------------------# +# I N I T I A L I S A T I O N # +#-----------------------------# + +/gate/run/initialize + +#-------------------# +# D I G I T I S E R # +#-------------------# + +/control/execute digitiser_D690.mac + +#-------------------------------------# +# C O I N C I D E N C E S O R T E R # +#-------------------------------------# + +/control/execute csorter_D690.mac + +#-------------------------------------# +# R A D I O A C T I V E S O U R C E # +#-------------------------------------# + +/control/execute source_test5.mac + +#-----------------------# +# O U T P U T D A T A # +#-----------------------# + +/gate/output/root/enable +/gate/output/root/setFileName RootLM_D690_test5 +/gate/output/root/setRootHitFlag 0 +/gate/output/root/setRootSinglesFlag 0 +/gate/output/root/setRootCoincidencesFlag 1 + +#---------------------------------# +# A C Q U I S I T I O N T I M E # +#---------------------------------# + +/gate/application/setTimeSlice 100 ms +/gate/application/setTimeStart 0 s +/gate/application/setTimeStop 1 s + +/gate/application/startDAQ diff --git a/people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test6.mac b/people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test6.mac new file mode 100755 index 0000000000..30853a500a --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test6.mac @@ -0,0 +1,70 @@ +# Author: Elise Emond + +/vis/disable + +#-----------------# +# G E O M E T R Y # +#-----------------# + + +# Material database +/gate/geometry/setMaterialDatabase GateMaterials.db + +# World dimensions +/gate/world/geometry/setXLength 600. cm +/gate/world/geometry/setYLength 600. cm +/gate/world/geometry/setZLength 600. cm + +# Scanner +/control/execute geometry_D690.mac + + +#---------------# +# P H Y S I C S # +#---------------# + +/control/execute physics.mac + +#-----------------------------# +# I N I T I A L I S A T I O N # +#-----------------------------# + +/gate/run/initialize + +#-------------------# +# D I G I T I S E R # +#-------------------# + +/control/execute digitiser_D690.mac + +#-------------------------------------# +# C O I N C I D E N C E S O R T E R # +#-------------------------------------# + +/control/execute csorter_D690.mac + +#-------------------------------------# +# R A D I O A C T I V E S O U R C E # +#-------------------------------------# + +/control/execute source_test6.mac + +#-----------------------# +# O U T P U T D A T A # +#-----------------------# + +/gate/output/root/enable +/gate/output/root/setFileName RootLM_D690_test6 +/gate/output/root/setRootHitFlag 0 +/gate/output/root/setRootSinglesFlag 0 +/gate/output/root/setRootCoincidencesFlag 1 + +#---------------------------------# +# A C Q U I S I T I O N T I M E # +#---------------------------------# + +/gate/application/setTimeSlice 100 ms +/gate/application/setTimeStart 0 s +/gate/application/setTimeStop 1 s + +/gate/application/startDAQ diff --git a/people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test7.mac b/people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test7.mac new file mode 100755 index 0000000000..b2cc950eb8 --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test7.mac @@ -0,0 +1,70 @@ +# Author: Elise Emond + +/vis/disable + +#-----------------# +# G E O M E T R Y # +#-----------------# + + +# Material database +/gate/geometry/setMaterialDatabase GateMaterials.db + +# World dimensions +/gate/world/geometry/setXLength 600. cm +/gate/world/geometry/setYLength 600. cm +/gate/world/geometry/setZLength 600. cm + +# Scanner +/control/execute geometry_D690.mac + + +#---------------# +# P H Y S I C S # +#---------------# + +/control/execute physics.mac + +#-----------------------------# +# I N I T I A L I S A T I O N # +#-----------------------------# + +/gate/run/initialize + +#-------------------# +# D I G I T I S E R # +#-------------------# + +/control/execute digitiser_D690.mac + +#-------------------------------------# +# C O I N C I D E N C E S O R T E R # +#-------------------------------------# + +/control/execute csorter_D690.mac + +#-------------------------------------# +# R A D I O A C T I V E S O U R C E # +#-------------------------------------# + +/control/execute source_test7.mac + +#-----------------------# +# O U T P U T D A T A # +#-----------------------# + +/gate/output/root/enable +/gate/output/root/setFileName RootLM_D690_test7 +/gate/output/root/setRootHitFlag 0 +/gate/output/root/setRootSinglesFlag 0 +/gate/output/root/setRootCoincidencesFlag 1 + +#---------------------------------# +# A C Q U I S I T I O N T I M E # +#---------------------------------# + +/gate/application/setTimeSlice 100 ms +/gate/application/setTimeStart 0 s +/gate/application/setTimeStop 1 s + +/gate/application/startDAQ diff --git a/people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test8.mac b/people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test8.mac new file mode 100755 index 0000000000..7e8decaa30 --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test8.mac @@ -0,0 +1,70 @@ +# Author: Elise Emond + +/vis/disable + +#-----------------# +# G E O M E T R Y # +#-----------------# + + +# Material database +/gate/geometry/setMaterialDatabase GateMaterials.db + +# World dimensions +/gate/world/geometry/setXLength 600. cm +/gate/world/geometry/setYLength 600. cm +/gate/world/geometry/setZLength 600. cm + +# Scanner +/control/execute geometry_D690.mac + + +#---------------# +# P H Y S I C S # +#---------------# + +/control/execute physics.mac + +#-----------------------------# +# I N I T I A L I S A T I O N # +#-----------------------------# + +/gate/run/initialize + +#-------------------# +# D I G I T I S E R # +#-------------------# + +/control/execute digitiser_D690.mac + +#-------------------------------------# +# C O I N C I D E N C E S O R T E R # +#-------------------------------------# + +/control/execute csorter_D690.mac + +#-------------------------------------# +# R A D I O A C T I V E S O U R C E # +#-------------------------------------# + +/control/execute source_test8.mac + +#-----------------------# +# O U T P U T D A T A # +#-----------------------# + +/gate/output/root/enable +/gate/output/root/setFileName RootLM_D690_test8 +/gate/output/root/setRootHitFlag 0 +/gate/output/root/setRootSinglesFlag 0 +/gate/output/root/setRootCoincidencesFlag 1 + +#---------------------------------# +# A C Q U I S I T I O N T I M E # +#---------------------------------# + +/gate/application/setTimeSlice 100 ms +/gate/application/setTimeStart 0 s +/gate/application/setTimeStop 1 s + +/gate/application/startDAQ diff --git a/people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test9.mac b/people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test9.mac new file mode 100755 index 0000000000..9375c1020d --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test9.mac @@ -0,0 +1,70 @@ +# Author: Elise Emond + +/vis/disable + +#-----------------# +# G E O M E T R Y # +#-----------------# + + +# Material database +/gate/geometry/setMaterialDatabase GateMaterials.db + +# World dimensions +/gate/world/geometry/setXLength 600. cm +/gate/world/geometry/setYLength 600. cm +/gate/world/geometry/setZLength 600. cm + +# Scanner +/control/execute geometry_D690.mac + + +#---------------# +# P H Y S I C S # +#---------------# + +/control/execute physics.mac + +#-----------------------------# +# I N I T I A L I S A T I O N # +#-----------------------------# + +/gate/run/initialize + +#-------------------# +# D I G I T I S E R # +#-------------------# + +/control/execute digitiser_D690.mac + +#-------------------------------------# +# C O I N C I D E N C E S O R T E R # +#-------------------------------------# + +/control/execute csorter_D690.mac + +#-------------------------------------# +# R A D I O A C T I V E S O U R C E # +#-------------------------------------# + +/control/execute source_test9.mac + +#-----------------------# +# O U T P U T D A T A # +#-----------------------# + +/gate/output/root/enable +/gate/output/root/setFileName RootLM_D690_test9 +/gate/output/root/setRootHitFlag 0 +/gate/output/root/setRootSinglesFlag 0 +/gate/output/root/setRootCoincidencesFlag 1 + +#---------------------------------# +# A C Q U I S I T I O N T I M E # +#---------------------------------# + +/gate/application/setTimeSlice 100 ms +/gate/application/setTimeStart 0 s +/gate/application/setTimeStop 1 s + +/gate/application/startDAQ diff --git a/people/eliseemond/ROOT-STIR-consistency/Gate_macros/physics.mac b/people/eliseemond/ROOT-STIR-consistency/Gate_macros/physics.mac new file mode 100755 index 0000000000..c444c103b0 --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/Gate_macros/physics.mac @@ -0,0 +1,9 @@ +/gate/physics/addProcess PhotoElectric +/gate/physics/processes/PhotoElectric/setModel StandardModel + +/gate/physics/addProcess Compton +/gate/physics/processes/Compton/setModel StandardModel + +/gate/physics/processList Enabled + +/gate/physics/processList Initialized diff --git a/people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test1.mac b/people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test1.mac new file mode 100755 index 0000000000..9f376ac102 --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test1.mac @@ -0,0 +1,46 @@ +#===================================================== + +# P A R T I C L E S O U R C E + +#===================================================== + +/gate/source/addSource posiFDG + + + +/gate/source/posiFDG/setType backtoback + + + +# The particles emitted by the source are gammas + +/gate/source/posiFDG/gps/particle gamma + + + +# The gammas have an energy of 511 keV + +/gate/source/posiFDG/gps/energytype Mono + +/gate/source/posiFDG/gps/monoenergy 0.511 MeV + + + +/gate/source/posiFDG/setActivity 5000000 becquerel + + + +/gate/source/posiFDG/setForcedUnstableFlag true + +/gate/source/posiFDG/setForcedHalfLife 6586 s + +/gate/source/posiFDG/gps/angtype iso + + +#Shape of the source +/gate/source/posiFDG/gps/type Point + +#Position of the source +/gate/source/posiFDG/gps/centre 19. 0. 7. cm + +/gate/source/list diff --git a/people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test10.mac b/people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test10.mac new file mode 100755 index 0000000000..c5a40693b7 --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test10.mac @@ -0,0 +1,49 @@ +#===================================================== + +# P A R T I C L E S O U R C E + +#===================================================== + +/gate/source/addSource posiFDG + + +/gate/source/posiFDG/setType backtoback + + + +# The particles emitted by the source are gammas + +/gate/source/posiFDG/gps/particle gamma + + + +# The gammas have an energy of 511 keV + +/gate/source/posiFDG/gps/energytype Mono + +/gate/source/posiFDG/gps/monoenergy 0.511 MeV + + + +/gate/source/posiFDG/setActivity 5000000 becquerel + + + +/gate/source/posiFDG/setForcedUnstableFlag true + +/gate/source/posiFDG/setForcedHalfLife 6586 s + +/gate/source/posiFDG/gps/angtype iso + + +#Shape of the source +/gate/source/posiFDG/gps/type Point +#/gate/source/posiFDG/gps/shape Ellipsoid +#/gate/source/posiFDG/gps/halfx 5. cm +#/gate/source/posiFDG/gps/halfy 5. cm +#/gate/source/posiFDG/gps/halfz 5. cm + +#Position of the source +/gate/source/posiFDG/gps/centre 0. -19. -7. cm + +/gate/source/list diff --git a/people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test11.mac b/people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test11.mac new file mode 100755 index 0000000000..89ae62390d --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test11.mac @@ -0,0 +1,49 @@ +#===================================================== + +# P A R T I C L E S O U R C E + +#===================================================== + +/gate/source/addSource posiFDG + + +/gate/source/posiFDG/setType backtoback + + + +# The particles emitted by the source are gammas + +/gate/source/posiFDG/gps/particle gamma + + + +# The gammas have an energy of 511 keV + +/gate/source/posiFDG/gps/energytype Mono + +/gate/source/posiFDG/gps/monoenergy 0.511 MeV + + + +/gate/source/posiFDG/setActivity 5000000 becquerel + + + +/gate/source/posiFDG/setForcedUnstableFlag true + +/gate/source/posiFDG/setForcedHalfLife 6586 s + +/gate/source/posiFDG/gps/angtype iso + + +#Shape of the source +/gate/source/posiFDG/gps/type Point +#/gate/source/posiFDG/gps/shape Ellipsoid +#/gate/source/posiFDG/gps/halfx 5. cm +#/gate/source/posiFDG/gps/halfy 5. cm +#/gate/source/posiFDG/gps/halfz 5. cm + +#Position of the source +/gate/source/posiFDG/gps/centre 9.5 9.5 -7. cm + +/gate/source/list diff --git a/people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test12.mac b/people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test12.mac new file mode 100755 index 0000000000..fd158ba781 --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test12.mac @@ -0,0 +1,49 @@ +#===================================================== + +# P A R T I C L E S O U R C E + +#===================================================== + +/gate/source/addSource posiFDG + + +/gate/source/posiFDG/setType backtoback + + + +# The particles emitted by the source are gammas + +/gate/source/posiFDG/gps/particle gamma + + + +# The gammas have an energy of 511 keV + +/gate/source/posiFDG/gps/energytype Mono + +/gate/source/posiFDG/gps/monoenergy 0.511 MeV + + + +/gate/source/posiFDG/setActivity 5000000 becquerel + + + +/gate/source/posiFDG/setForcedUnstableFlag true + +/gate/source/posiFDG/setForcedHalfLife 6586 s + +/gate/source/posiFDG/gps/angtype iso + + +#Shape of the source +/gate/source/posiFDG/gps/type Point +#/gate/source/posiFDG/gps/shape Ellipsoid +#/gate/source/posiFDG/gps/halfx 5. cm +#/gate/source/posiFDG/gps/halfy 5. cm +#/gate/source/posiFDG/gps/halfz 5. cm + +#Position of the source +/gate/source/posiFDG/gps/centre 9.5 -9.5 -7. cm + +/gate/source/list diff --git a/people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test2.mac b/people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test2.mac new file mode 100755 index 0000000000..ecec48786a --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test2.mac @@ -0,0 +1,50 @@ +#===================================================== + +# P A R T I C L E S O U R C E + +#===================================================== + +/gate/source/addSource posiFDG + + + +/gate/source/posiFDG/setType backtoback + + + +# The particles emitted by the source are gammas + +/gate/source/posiFDG/gps/particle gamma + + + +# The gammas have an energy of 511 keV + +/gate/source/posiFDG/gps/energytype Mono + +/gate/source/posiFDG/gps/monoenergy 0.511 MeV + + + +/gate/source/posiFDG/setActivity 5000000 becquerel + + + +/gate/source/posiFDG/setForcedUnstableFlag true + +/gate/source/posiFDG/setForcedHalfLife 6586 s + +/gate/source/posiFDG/gps/angtype iso + + +#Shape of the source +/gate/source/posiFDG/gps/type Point +#/gate/source/posiFDG/gps/shape Ellipsoid +#/gate/source/posiFDG/gps/halfx 5. cm +#/gate/source/posiFDG/gps/halfy 5. cm +#/gate/source/posiFDG/gps/halfz 5. cm + +#Position of the source +/gate/source/posiFDG/gps/centre -19. 0. 7 cm + +/gate/source/list diff --git a/people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test3.mac b/people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test3.mac new file mode 100755 index 0000000000..7abef4777f --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test3.mac @@ -0,0 +1,50 @@ +#===================================================== + +# P A R T I C L E S O U R C E + +#===================================================== + +/gate/source/addSource posiFDG + + + +/gate/source/posiFDG/setType backtoback + + + +# The particles emitted by the source are gammas + +/gate/source/posiFDG/gps/particle gamma + + + +# The gammas have an energy of 511 keV + +/gate/source/posiFDG/gps/energytype Mono + +/gate/source/posiFDG/gps/monoenergy 0.511 MeV + + + +/gate/source/posiFDG/setActivity 5000000 becquerel + + + +/gate/source/posiFDG/setForcedUnstableFlag true + +/gate/source/posiFDG/setForcedHalfLife 6586 s + +/gate/source/posiFDG/gps/angtype iso + + +#Shape of the source +/gate/source/posiFDG/gps/type Point +#/gate/source/posiFDG/gps/shape Ellipsoid +#/gate/source/posiFDG/gps/halfx 5. cm +#/gate/source/posiFDG/gps/halfy 5. cm +#/gate/source/posiFDG/gps/halfz 5. cm + +#Position of the source +/gate/source/posiFDG/gps/centre 0. 19. 7. cm + +/gate/source/list diff --git a/people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test4.mac b/people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test4.mac new file mode 100755 index 0000000000..3854e43c98 --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test4.mac @@ -0,0 +1,50 @@ +#===================================================== + +# P A R T I C L E S O U R C E + +#===================================================== + +/gate/source/addSource posiFDG + + + +/gate/source/posiFDG/setType backtoback + + + +# The particles emitted by the source are gammas + +/gate/source/posiFDG/gps/particle gamma + + + +# The gammas have an energy of 511 keV + +/gate/source/posiFDG/gps/energytype Mono + +/gate/source/posiFDG/gps/monoenergy 0.511 MeV + + + +/gate/source/posiFDG/setActivity 5000000 becquerel + + + +/gate/source/posiFDG/setForcedUnstableFlag true + +/gate/source/posiFDG/setForcedHalfLife 6586 s + +/gate/source/posiFDG/gps/angtype iso + + +#Shape of the source +/gate/source/posiFDG/gps/type Point +#/gate/source/posiFDG/gps/shape Ellipsoid +#/gate/source/posiFDG/gps/halfx 5. cm +#/gate/source/posiFDG/gps/halfy 5. cm +#/gate/source/posiFDG/gps/halfz 5. cm + +#Position of the source +/gate/source/posiFDG/gps/centre 0. -19. 7. cm + +/gate/source/list diff --git a/people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test5.mac b/people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test5.mac new file mode 100755 index 0000000000..3038af14a0 --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test5.mac @@ -0,0 +1,50 @@ +#===================================================== + +# P A R T I C L E S O U R C E + +#===================================================== + +/gate/source/addSource posiFDG + + + +/gate/source/posiFDG/setType backtoback + + + +# The particles emitted by the source are gammas + +/gate/source/posiFDG/gps/particle gamma + + + +# The gammas have an energy of 511 keV + +/gate/source/posiFDG/gps/energytype Mono + +/gate/source/posiFDG/gps/monoenergy 0.511 MeV + + + +/gate/source/posiFDG/setActivity 5000000 becquerel + + + +/gate/source/posiFDG/setForcedUnstableFlag true + +/gate/source/posiFDG/setForcedHalfLife 6586 s + +/gate/source/posiFDG/gps/angtype iso + + +#Shape of the source +/gate/source/posiFDG/gps/type Point +#/gate/source/posiFDG/gps/shape Ellipsoid +#/gate/source/posiFDG/gps/halfx 5. cm +#/gate/source/posiFDG/gps/halfy 5. cm +#/gate/source/posiFDG/gps/halfz 5. cm + +#Position of the source +/gate/source/posiFDG/gps/centre 9.5 9.5 7. cm + +/gate/source/list diff --git a/people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test6.mac b/people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test6.mac new file mode 100755 index 0000000000..e2d812e1e5 --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test6.mac @@ -0,0 +1,50 @@ +#===================================================== + +# P A R T I C L E S O U R C E + +#===================================================== + +/gate/source/addSource posiFDG + + + +/gate/source/posiFDG/setType backtoback + + + +# The particles emitted by the source are gammas + +/gate/source/posiFDG/gps/particle gamma + + + +# The gammas have an energy of 511 keV + +/gate/source/posiFDG/gps/energytype Mono + +/gate/source/posiFDG/gps/monoenergy 0.511 MeV + + + +/gate/source/posiFDG/setActivity 5000000 becquerel + + + +/gate/source/posiFDG/setForcedUnstableFlag true + +/gate/source/posiFDG/setForcedHalfLife 6586 s + +/gate/source/posiFDG/gps/angtype iso + + +#Shape of the source +/gate/source/posiFDG/gps/type Point +#/gate/source/posiFDG/gps/shape Ellipsoid +#/gate/source/posiFDG/gps/halfx 5. cm +#/gate/source/posiFDG/gps/halfy 5. cm +#/gate/source/posiFDG/gps/halfz 5. cm + +#Position of the source +/gate/source/posiFDG/gps/centre 9.5 -9.5 7. cm + +/gate/source/list diff --git a/people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test7.mac b/people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test7.mac new file mode 100755 index 0000000000..7cd82bd21c --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test7.mac @@ -0,0 +1,50 @@ +#===================================================== + +# P A R T I C L E S O U R C E + +#===================================================== + +/gate/source/addSource posiFDG + + + +/gate/source/posiFDG/setType backtoback + + + +# The particles emitted by the source are gammas + +/gate/source/posiFDG/gps/particle gamma + + + +# The gammas have an energy of 511 keV + +/gate/source/posiFDG/gps/energytype Mono + +/gate/source/posiFDG/gps/monoenergy 0.511 MeV + + + +/gate/source/posiFDG/setActivity 5000000 becquerel + + + +/gate/source/posiFDG/setForcedUnstableFlag true + +/gate/source/posiFDG/setForcedHalfLife 6586 s + +/gate/source/posiFDG/gps/angtype iso + + +#Shape of the source +/gate/source/posiFDG/gps/type Point +#/gate/source/posiFDG/gps/shape Ellipsoid +#/gate/source/posiFDG/gps/halfx 5. cm +#/gate/source/posiFDG/gps/halfy 5. cm +#/gate/source/posiFDG/gps/halfz 5. cm + +#Position of the source +/gate/source/posiFDG/gps/centre 19. 0. -7. cm + +/gate/source/list diff --git a/people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test8.mac b/people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test8.mac new file mode 100755 index 0000000000..4255cd6d11 --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test8.mac @@ -0,0 +1,50 @@ +#===================================================== + +# P A R T I C L E S O U R C E + +#===================================================== + +/gate/source/addSource posiFDG + + + +/gate/source/posiFDG/setType backtoback + + + +# The particles emitted by the source are gammas + +/gate/source/posiFDG/gps/particle gamma + + + +# The gammas have an energy of 511 keV + +/gate/source/posiFDG/gps/energytype Mono + +/gate/source/posiFDG/gps/monoenergy 0.511 MeV + + + +/gate/source/posiFDG/setActivity 5000000 becquerel + + + +/gate/source/posiFDG/setForcedUnstableFlag true + +/gate/source/posiFDG/setForcedHalfLife 6586 s + +/gate/source/posiFDG/gps/angtype iso + + +#Shape of the source +/gate/source/posiFDG/gps/type Point +#/gate/source/posiFDG/gps/shape Ellipsoid +#/gate/source/posiFDG/gps/halfx 5. cm +#/gate/source/posiFDG/gps/halfy 5. cm +#/gate/source/posiFDG/gps/halfz 5. cm + +#Position of the source +/gate/source/posiFDG/gps/centre -19. 0. -7. cm + +/gate/source/list diff --git a/people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test9.mac b/people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test9.mac new file mode 100755 index 0000000000..52b5bd3c94 --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test9.mac @@ -0,0 +1,49 @@ +#===================================================== + +# P A R T I C L E S O U R C E + +#===================================================== + +/gate/source/addSource posiFDG + + +/gate/source/posiFDG/setType backtoback + + + +# The particles emitted by the source are gammas + +/gate/source/posiFDG/gps/particle gamma + + + +# The gammas have an energy of 511 keV + +/gate/source/posiFDG/gps/energytype Mono + +/gate/source/posiFDG/gps/monoenergy 0.511 MeV + + + +/gate/source/posiFDG/setActivity 5000000 becquerel + + + +/gate/source/posiFDG/setForcedUnstableFlag true + +/gate/source/posiFDG/setForcedHalfLife 6586 s + +/gate/source/posiFDG/gps/angtype iso + + +#Shape of the source +/gate/source/posiFDG/gps/type Point +#/gate/source/posiFDG/gps/shape Ellipsoid +#/gate/source/posiFDG/gps/halfx 5. cm +#/gate/source/posiFDG/gps/halfy 5. cm +#/gate/source/posiFDG/gps/halfz 5. cm + +#Position of the source +/gate/source/posiFDG/gps/centre 0. 19. -7. cm + +/gate/source/list diff --git a/people/eliseemond/ROOT-STIR-consistency/generate_image1.par b/people/eliseemond/ROOT-STIR-consistency/generate_image1.par new file mode 100755 index 0000000000..b7f2687589 --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/generate_image1.par @@ -0,0 +1,23 @@ +generate_image Parameters := +output filename:=stir_image1.v +X output image size (in pixels):=301 +Y output image size (in pixels):=301 +Z output image size (in pixels):=47 +X voxel size (in mm):= 2.1306 +Y voxel size (in mm):= 2.1306 +Z voxel size (in mm) :=3.27 + + Z number of samples to take per voxel := 5 + Y number of samples to take per voxel := 5 + X number of samples to take per voxel := 5 + +shape type:= Ellipsoid +Ellipsoid Parameters:= + radius-x (in mm):=2 + radius-y (in mm):=2 + radius-z (in mm):=2 + origin (in mm):={145.21,0,190} + END:= +value :=1 + +END:= diff --git a/people/eliseemond/ROOT-STIR-consistency/generate_image10.par b/people/eliseemond/ROOT-STIR-consistency/generate_image10.par new file mode 100755 index 0000000000..642e9e8347 --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/generate_image10.par @@ -0,0 +1,23 @@ +generate_image Parameters := +output filename:=stir_image10.v +X output image size (in pixels):=301 +Y output image size (in pixels):=301 +Z output image size (in pixels):=47 +X voxel size (in mm):= 2.1306 +Y voxel size (in mm):= 2.1306 +Z voxel size (in mm) :=3.27 + + Z number of samples to take per voxel := 5 + Y number of samples to take per voxel := 5 + X number of samples to take per voxel := 5 + +shape type:= Ellipsoid +Ellipsoid Parameters:= + radius-x (in mm):=2 + radius-y (in mm):=2 + radius-z (in mm):=2 + origin (in mm):={5.21,-190,0} + END:= +value :=1 + +END:= diff --git a/people/eliseemond/ROOT-STIR-consistency/generate_image11.par b/people/eliseemond/ROOT-STIR-consistency/generate_image11.par new file mode 100755 index 0000000000..f7d23d23bf --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/generate_image11.par @@ -0,0 +1,23 @@ +generate_image Parameters := +output filename:=stir_image11.v +X output image size (in pixels):=301 +Y output image size (in pixels):=301 +Z output image size (in pixels):=47 +X voxel size (in mm):= 2.1306 +Y voxel size (in mm):= 2.1306 +Z voxel size (in mm) :=3.27 + + Z number of samples to take per voxel := 5 + Y number of samples to take per voxel := 5 + X number of samples to take per voxel := 5 + +shape type:= Ellipsoid +Ellipsoid Parameters:= + radius-x (in mm):=2 + radius-y (in mm):=2 + radius-z (in mm):=2 + origin (in mm):={5.21,95,95} + END:= +value :=1 + +END:= diff --git a/people/eliseemond/ROOT-STIR-consistency/generate_image12.par b/people/eliseemond/ROOT-STIR-consistency/generate_image12.par new file mode 100755 index 0000000000..2021d3a028 --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/generate_image12.par @@ -0,0 +1,23 @@ +generate_image Parameters := +output filename:=stir_image12.v +X output image size (in pixels):=301 +Y output image size (in pixels):=301 +Z output image size (in pixels):=47 +X voxel size (in mm):= 2.1306 +Y voxel size (in mm):= 2.1306 +Z voxel size (in mm) :=3.27 + + Z number of samples to take per voxel := 5 + Y number of samples to take per voxel := 5 + X number of samples to take per voxel := 5 + +shape type:= Ellipsoid +Ellipsoid Parameters:= + radius-x (in mm):=2 + radius-y (in mm):=2 + radius-z (in mm):=2 + origin (in mm):={5.21,-95,95} + END:= +value :=1 + +END:= diff --git a/people/eliseemond/ROOT-STIR-consistency/generate_image2.par b/people/eliseemond/ROOT-STIR-consistency/generate_image2.par new file mode 100755 index 0000000000..548aaef677 --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/generate_image2.par @@ -0,0 +1,23 @@ +generate_image Parameters := +output filename:=stir_image2.v +X output image size (in pixels):=301 +Y output image size (in pixels):=301 +Z output image size (in pixels):=47 +X voxel size (in mm):= 2.1306 +Y voxel size (in mm):= 2.1306 +Z voxel size (in mm) :=3.27 + + Z number of samples to take per voxel := 5 + Y number of samples to take per voxel := 5 + X number of samples to take per voxel := 5 + +shape type:= Ellipsoid +Ellipsoid Parameters:= + radius-x (in mm):=2 + radius-y (in mm):=2 + radius-z (in mm):=2 + origin (in mm):={145.21,0,-190} + END:= +value :=1 + +END:= diff --git a/people/eliseemond/ROOT-STIR-consistency/generate_image3.par b/people/eliseemond/ROOT-STIR-consistency/generate_image3.par new file mode 100755 index 0000000000..c0dfc3bce8 --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/generate_image3.par @@ -0,0 +1,23 @@ +generate_image Parameters := +output filename:=stir_image3.v +X output image size (in pixels):=301 +Y output image size (in pixels):=301 +Z output image size (in pixels):=47 +X voxel size (in mm):= 2.1306 +Y voxel size (in mm):= 2.1306 +Z voxel size (in mm) :=3.27 + + Z number of samples to take per voxel := 5 + Y number of samples to take per voxel := 5 + X number of samples to take per voxel := 5 + +shape type:= Ellipsoid +Ellipsoid Parameters:= + radius-x (in mm):=2 + radius-y (in mm):=2 + radius-z (in mm):=2 + origin (in mm):={145.21,190,0} + END:= +value :=1 + +END:= diff --git a/people/eliseemond/ROOT-STIR-consistency/generate_image4.par b/people/eliseemond/ROOT-STIR-consistency/generate_image4.par new file mode 100755 index 0000000000..dc87777ab9 --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/generate_image4.par @@ -0,0 +1,23 @@ +generate_image Parameters := +output filename:=stir_image4.v +X output image size (in pixels):=301 +Y output image size (in pixels):=301 +Z output image size (in pixels):=47 +X voxel size (in mm):= 2.1306 +Y voxel size (in mm):= 2.1306 +Z voxel size (in mm) :=3.27 + + Z number of samples to take per voxel := 5 + Y number of samples to take per voxel := 5 + X number of samples to take per voxel := 5 + +shape type:= Ellipsoid +Ellipsoid Parameters:= + radius-x (in mm):=2 + radius-y (in mm):=2 + radius-z (in mm):=2 + origin (in mm):={145.21,-190,0} + END:= +value :=1 + +END:= diff --git a/people/eliseemond/ROOT-STIR-consistency/generate_image5.par b/people/eliseemond/ROOT-STIR-consistency/generate_image5.par new file mode 100755 index 0000000000..bcde0eb929 --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/generate_image5.par @@ -0,0 +1,23 @@ +generate_image Parameters := +output filename:=stir_image5.v +X output image size (in pixels):=301 +Y output image size (in pixels):=301 +Z output image size (in pixels):=47 +X voxel size (in mm):= 2.1306 +Y voxel size (in mm):= 2.1306 +Z voxel size (in mm) :=3.27 + + Z number of samples to take per voxel := 5 + Y number of samples to take per voxel := 5 + X number of samples to take per voxel := 5 + +shape type:= Ellipsoid +Ellipsoid Parameters:= + radius-x (in mm):=2 + radius-y (in mm):=2 + radius-z (in mm):=2 + origin (in mm):={145.21,95,95} + END:= +value :=1 + +END:= diff --git a/people/eliseemond/ROOT-STIR-consistency/generate_image6.par b/people/eliseemond/ROOT-STIR-consistency/generate_image6.par new file mode 100755 index 0000000000..2e23bb8b74 --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/generate_image6.par @@ -0,0 +1,23 @@ +generate_image Parameters := +output filename:=stir_image6.v +X output image size (in pixels):=301 +Y output image size (in pixels):=301 +Z output image size (in pixels):=47 +X voxel size (in mm):= 2.1306 +Y voxel size (in mm):= 2.1306 +Z voxel size (in mm) :=3.27 + + Z number of samples to take per voxel := 5 + Y number of samples to take per voxel := 5 + X number of samples to take per voxel := 5 + +shape type:= Ellipsoid +Ellipsoid Parameters:= + radius-x (in mm):=2 + radius-y (in mm):=2 + radius-z (in mm):=2 + origin (in mm):={145.21,-95,95} + END:= +value :=1 + +END:= diff --git a/people/eliseemond/ROOT-STIR-consistency/generate_image7.par b/people/eliseemond/ROOT-STIR-consistency/generate_image7.par new file mode 100755 index 0000000000..79d7cf3476 --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/generate_image7.par @@ -0,0 +1,23 @@ +generate_image Parameters := +output filename:=stir_image7.v +X output image size (in pixels):=301 +Y output image size (in pixels):=301 +Z output image size (in pixels):=47 +X voxel size (in mm):= 2.1306 +Y voxel size (in mm):= 2.1306 +Z voxel size (in mm) :=3.27 + + Z number of samples to take per voxel := 5 + Y number of samples to take per voxel := 5 + X number of samples to take per voxel := 5 + +shape type:= Ellipsoid +Ellipsoid Parameters:= + radius-x (in mm):=2 + radius-y (in mm):=2 + radius-z (in mm):=2 + origin (in mm):={5.21,0,190} + END:= +value :=1 + +END:= diff --git a/people/eliseemond/ROOT-STIR-consistency/generate_image8.par b/people/eliseemond/ROOT-STIR-consistency/generate_image8.par new file mode 100755 index 0000000000..deab64a9a6 --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/generate_image8.par @@ -0,0 +1,23 @@ +generate_image Parameters := +output filename:=stir_image8.v +X output image size (in pixels):=301 +Y output image size (in pixels):=301 +Z output image size (in pixels):=47 +X voxel size (in mm):= 2.1306 +Y voxel size (in mm):= 2.1306 +Z voxel size (in mm) :=3.27 + + Z number of samples to take per voxel := 5 + Y number of samples to take per voxel := 5 + X number of samples to take per voxel := 5 + +shape type:= Ellipsoid +Ellipsoid Parameters:= + radius-x (in mm):=2 + radius-y (in mm):=2 + radius-z (in mm):=2 + origin (in mm):={5.21,0,-190} + END:= +value :=1 + +END:= diff --git a/people/eliseemond/ROOT-STIR-consistency/generate_image9.par b/people/eliseemond/ROOT-STIR-consistency/generate_image9.par new file mode 100755 index 0000000000..6f3effdfd4 --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/generate_image9.par @@ -0,0 +1,23 @@ +generate_image Parameters := +output filename:=stir_image9.v +X output image size (in pixels):=301 +Y output image size (in pixels):=301 +Z output image size (in pixels):=47 +X voxel size (in mm):= 2.1306 +Y voxel size (in mm):= 2.1306 +Z voxel size (in mm) :=3.27 + + Z number of samples to take per voxel := 5 + Y number of samples to take per voxel := 5 + X number of samples to take per voxel := 5 + +shape type:= Ellipsoid +Ellipsoid Parameters:= + radius-x (in mm):=2 + radius-y (in mm):=2 + radius-z (in mm):=2 + origin (in mm):={5.21,190,0} + END:= +value :=1 + +END:= diff --git a/people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test1.par b/people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test1.par new file mode 100755 index 0000000000..3f76064c73 --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test1.par @@ -0,0 +1,44 @@ +lm_to_projdata Parameters:= + + input file := root_header_test1.hroot +output filename prefix := sinogram_test1 + + ; parameters that determine the sizes etc of the output + + template_projdata := template_D690_RD0.hs + ; the next can be used to use a smaller number of segments than given + ; in the template + maximum absolute segment number to process := -1 + + ; parameters for saying which events will be stored + + ; time frames (see TimeFrameDefinitions doc for format) +; frame_definition file := frames.fdef + ; or a total number of events (if larger than 1, frame definitions will be ignored) + ; note that this normally counts the total of prompts-delayeds (see below) + ; If you don't define a time frame definition file nor the num_events_to_store + ; then the total number of events in the listmode file is used. +; num_events_to_store := 10 + + ; parameters relating to prompts and delayeds + + ; with the default values, prompts will be added and delayed subtracted + ; to give the usual estimate of the trues. + + ; store the prompts (value should be 1 or 0) + store prompts := 1 ;default + ; what to do if it's a delayed event + store delayeds := 0 ;default + + + ; miscellaneous parameters + + ; list each event on stdout and do not store any files (use only for testing!) + ; has to be 0 or 1 + List event coordinates := 0 + + ; if you're short of RAM (i.e. a single projdata does not fit into memory), + ; you can use this to process the list mode data in multiple passes. + num_segments_in_memory := -1 + +End := diff --git a/people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test10.par b/people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test10.par new file mode 100755 index 0000000000..1cc0ff25ce --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test10.par @@ -0,0 +1,44 @@ +lm_to_projdata Parameters:= + + input file := root_header_test10.hroot +output filename prefix := sinogram_test10 + + ; parameters that determine the sizes etc of the output + + template_projdata := template_D690_RD0.hs + ; the next can be used to use a smaller number of segments than given + ; in the template + maximum absolute segment number to process := -1 + + ; parameters for saying which events will be stored + + ; time frames (see TimeFrameDefinitions doc for format) +; frame_definition file := frames.fdef + ; or a total number of events (if larger than 1, frame definitions will be ignored) + ; note that this normally counts the total of prompts-delayeds (see below) + ; If you don't define a time frame definition file nor the num_events_to_store + ; then the total number of events in the listmode file is used. +; num_events_to_store := 10 + + ; parameters relating to prompts and delayeds + + ; with the default values, prompts will be added and delayed subtracted + ; to give the usual estimate of the trues. + + ; store the prompts (value should be 1 or 0) + store prompts := 1 ;default + ; what to do if it's a delayed event + store delayeds := 0 ;default + + + ; miscellaneous parameters + + ; list each event on stdout and do not store any files (use only for testing!) + ; has to be 0 or 1 + List event coordinates := 0 + + ; if you're short of RAM (i.e. a single projdata does not fit into memory), + ; you can use this to process the list mode data in multiple passes. + num_segments_in_memory := -1 + +End := diff --git a/people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test11.par b/people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test11.par new file mode 100755 index 0000000000..978304779f --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test11.par @@ -0,0 +1,44 @@ +lm_to_projdata Parameters:= + + input file := root_header_test11.hroot +output filename prefix := sinogram_test11 + + ; parameters that determine the sizes etc of the output + + template_projdata := template_D690_RD0.hs + ; the next can be used to use a smaller number of segments than given + ; in the template + maximum absolute segment number to process := -1 + + ; parameters for saying which events will be stored + + ; time frames (see TimeFrameDefinitions doc for format) +; frame_definition file := frames.fdef + ; or a total number of events (if larger than 1, frame definitions will be ignored) + ; note that this normally counts the total of prompts-delayeds (see below) + ; If you don't define a time frame definition file nor the num_events_to_store + ; then the total number of events in the listmode file is used. +; num_events_to_store := 10 + + ; parameters relating to prompts and delayeds + + ; with the default values, prompts will be added and delayed subtracted + ; to give the usual estimate of the trues. + + ; store the prompts (value should be 1 or 0) + store prompts := 1 ;default + ; what to do if it's a delayed event + store delayeds := 0 ;default + + + ; miscellaneous parameters + + ; list each event on stdout and do not store any files (use only for testing!) + ; has to be 0 or 1 + List event coordinates := 0 + + ; if you're short of RAM (i.e. a single projdata does not fit into memory), + ; you can use this to process the list mode data in multiple passes. + num_segments_in_memory := -1 + +End := diff --git a/people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test12.par b/people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test12.par new file mode 100755 index 0000000000..62b234617b --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test12.par @@ -0,0 +1,44 @@ +lm_to_projdata Parameters:= + + input file := root_header_test12.hroot +output filename prefix := sinogram_test12 + + ; parameters that determine the sizes etc of the output + + template_projdata := template_D690_RD0.hs + ; the next can be used to use a smaller number of segments than given + ; in the template + maximum absolute segment number to process := -1 + + ; parameters for saying which events will be stored + + ; time frames (see TimeFrameDefinitions doc for format) +; frame_definition file := frames.fdef + ; or a total number of events (if larger than 1, frame definitions will be ignored) + ; note that this normally counts the total of prompts-delayeds (see below) + ; If you don't define a time frame definition file nor the num_events_to_store + ; then the total number of events in the listmode file is used. +; num_events_to_store := 10 + + ; parameters relating to prompts and delayeds + + ; with the default values, prompts will be added and delayed subtracted + ; to give the usual estimate of the trues. + + ; store the prompts (value should be 1 or 0) + store prompts := 1 ;default + ; what to do if it's a delayed event + store delayeds := 0 ;default + + + ; miscellaneous parameters + + ; list each event on stdout and do not store any files (use only for testing!) + ; has to be 0 or 1 + List event coordinates := 0 + + ; if you're short of RAM (i.e. a single projdata does not fit into memory), + ; you can use this to process the list mode data in multiple passes. + num_segments_in_memory := -1 + +End := diff --git a/people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test2.par b/people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test2.par new file mode 100755 index 0000000000..ee224d8fb4 --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test2.par @@ -0,0 +1,44 @@ +lm_to_projdata Parameters:= + + input file := root_header_test2.hroot +output filename prefix := sinogram_test2 + + ; parameters that determine the sizes etc of the output + + template_projdata := template_D690_RD0.hs + ; the next can be used to use a smaller number of segments than given + ; in the template + maximum absolute segment number to process := -1 + + ; parameters for saying which events will be stored + + ; time frames (see TimeFrameDefinitions doc for format) +; frame_definition file := frames.fdef + ; or a total number of events (if larger than 1, frame definitions will be ignored) + ; note that this normally counts the total of prompts-delayeds (see below) + ; If you don't define a time frame definition file nor the num_events_to_store + ; then the total number of events in the listmode file is used. +; num_events_to_store := 10 + + ; parameters relating to prompts and delayeds + + ; with the default values, prompts will be added and delayed subtracted + ; to give the usual estimate of the trues. + + ; store the prompts (value should be 1 or 0) + store prompts := 1 ;default + ; what to do if it's a delayed event + store delayeds := 0 ;default + + + ; miscellaneous parameters + + ; list each event on stdout and do not store any files (use only for testing!) + ; has to be 0 or 1 + List event coordinates := 0 + + ; if you're short of RAM (i.e. a single projdata does not fit into memory), + ; you can use this to process the list mode data in multiple passes. + num_segments_in_memory := -1 + +End := diff --git a/people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test3.par b/people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test3.par new file mode 100755 index 0000000000..1089b114e1 --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test3.par @@ -0,0 +1,44 @@ +lm_to_projdata Parameters:= + + input file := root_header_test3.hroot +output filename prefix := sinogram_test3 + + ; parameters that determine the sizes etc of the output + + template_projdata := template_D690_RD0.hs + ; the next can be used to use a smaller number of segments than given + ; in the template + maximum absolute segment number to process := -1 + + ; parameters for saying which events will be stored + + ; time frames (see TimeFrameDefinitions doc for format) +; frame_definition file := frames.fdef + ; or a total number of events (if larger than 1, frame definitions will be ignored) + ; note that this normally counts the total of prompts-delayeds (see below) + ; If you don't define a time frame definition file nor the num_events_to_store + ; then the total number of events in the listmode file is used. +; num_events_to_store := 10 + + ; parameters relating to prompts and delayeds + + ; with the default values, prompts will be added and delayed subtracted + ; to give the usual estimate of the trues. + + ; store the prompts (value should be 1 or 0) + store prompts := 1 ;default + ; what to do if it's a delayed event + store delayeds := 0 ;default + + + ; miscellaneous parameters + + ; list each event on stdout and do not store any files (use only for testing!) + ; has to be 0 or 1 + List event coordinates := 0 + + ; if you're short of RAM (i.e. a single projdata does not fit into memory), + ; you can use this to process the list mode data in multiple passes. + num_segments_in_memory := -1 + +End := diff --git a/people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test4.par b/people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test4.par new file mode 100755 index 0000000000..1050b0044d --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test4.par @@ -0,0 +1,44 @@ +lm_to_projdata Parameters:= + + input file := root_header_test4.hroot +output filename prefix := sinogram_test4 + + ; parameters that determine the sizes etc of the output + + template_projdata := template_D690_RD0.hs + ; the next can be used to use a smaller number of segments than given + ; in the template + maximum absolute segment number to process := -1 + + ; parameters for saying which events will be stored + + ; time frames (see TimeFrameDefinitions doc for format) +; frame_definition file := frames.fdef + ; or a total number of events (if larger than 1, frame definitions will be ignored) + ; note that this normally counts the total of prompts-delayeds (see below) + ; If you don't define a time frame definition file nor the num_events_to_store + ; then the total number of events in the listmode file is used. +; num_events_to_store := 10 + + ; parameters relating to prompts and delayeds + + ; with the default values, prompts will be added and delayed subtracted + ; to give the usual estimate of the trues. + + ; store the prompts (value should be 1 or 0) + store prompts := 1 ;default + ; what to do if it's a delayed event + store delayeds := 0 ;default + + + ; miscellaneous parameters + + ; list each event on stdout and do not store any files (use only for testing!) + ; has to be 0 or 1 + List event coordinates := 0 + + ; if you're short of RAM (i.e. a single projdata does not fit into memory), + ; you can use this to process the list mode data in multiple passes. + num_segments_in_memory := -1 + +End := diff --git a/people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test5.par b/people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test5.par new file mode 100755 index 0000000000..39dd245e20 --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test5.par @@ -0,0 +1,44 @@ +lm_to_projdata Parameters:= + + input file := root_header_test5.hroot +output filename prefix := sinogram_test5 + + ; parameters that determine the sizes etc of the output + + template_projdata := template_D690_RD0.hs + ; the next can be used to use a smaller number of segments than given + ; in the template + maximum absolute segment number to process := -1 + + ; parameters for saying which events will be stored + + ; time frames (see TimeFrameDefinitions doc for format) +; frame_definition file := frames.fdef + ; or a total number of events (if larger than 1, frame definitions will be ignored) + ; note that this normally counts the total of prompts-delayeds (see below) + ; If you don't define a time frame definition file nor the num_events_to_store + ; then the total number of events in the listmode file is used. +; num_events_to_store := 10 + + ; parameters relating to prompts and delayeds + + ; with the default values, prompts will be added and delayed subtracted + ; to give the usual estimate of the trues. + + ; store the prompts (value should be 1 or 0) + store prompts := 1 ;default + ; what to do if it's a delayed event + store delayeds := 0 ;default + + + ; miscellaneous parameters + + ; list each event on stdout and do not store any files (use only for testing!) + ; has to be 0 or 1 + List event coordinates := 0 + + ; if you're short of RAM (i.e. a single projdata does not fit into memory), + ; you can use this to process the list mode data in multiple passes. + num_segments_in_memory := -1 + +End := diff --git a/people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test6.par b/people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test6.par new file mode 100755 index 0000000000..99b3d6ca8d --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test6.par @@ -0,0 +1,44 @@ +lm_to_projdata Parameters:= + + input file := root_header_test6.hroot +output filename prefix := sinogram_test6 + + ; parameters that determine the sizes etc of the output + + template_projdata := template_D690_RD0.hs + ; the next can be used to use a smaller number of segments than given + ; in the template + maximum absolute segment number to process := -1 + + ; parameters for saying which events will be stored + + ; time frames (see TimeFrameDefinitions doc for format) +; frame_definition file := frames.fdef + ; or a total number of events (if larger than 1, frame definitions will be ignored) + ; note that this normally counts the total of prompts-delayeds (see below) + ; If you don't define a time frame definition file nor the num_events_to_store + ; then the total number of events in the listmode file is used. +; num_events_to_store := 10 + + ; parameters relating to prompts and delayeds + + ; with the default values, prompts will be added and delayed subtracted + ; to give the usual estimate of the trues. + + ; store the prompts (value should be 1 or 0) + store prompts := 1 ;default + ; what to do if it's a delayed event + store delayeds := 0 ;default + + + ; miscellaneous parameters + + ; list each event on stdout and do not store any files (use only for testing!) + ; has to be 0 or 1 + List event coordinates := 0 + + ; if you're short of RAM (i.e. a single projdata does not fit into memory), + ; you can use this to process the list mode data in multiple passes. + num_segments_in_memory := -1 + +End := diff --git a/people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test7.par b/people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test7.par new file mode 100755 index 0000000000..161c49d7fa --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test7.par @@ -0,0 +1,44 @@ +lm_to_projdata Parameters:= + + input file := root_header_test7.hroot +output filename prefix := sinogram_test7 + + ; parameters that determine the sizes etc of the output + + template_projdata := template_D690_RD0.hs + ; the next can be used to use a smaller number of segments than given + ; in the template + maximum absolute segment number to process := -1 + + ; parameters for saying which events will be stored + + ; time frames (see TimeFrameDefinitions doc for format) +; frame_definition file := frames.fdef + ; or a total number of events (if larger than 1, frame definitions will be ignored) + ; note that this normally counts the total of prompts-delayeds (see below) + ; If you don't define a time frame definition file nor the num_events_to_store + ; then the total number of events in the listmode file is used. +; num_events_to_store := 10 + + ; parameters relating to prompts and delayeds + + ; with the default values, prompts will be added and delayed subtracted + ; to give the usual estimate of the trues. + + ; store the prompts (value should be 1 or 0) + store prompts := 1 ;default + ; what to do if it's a delayed event + store delayeds := 0 ;default + + + ; miscellaneous parameters + + ; list each event on stdout and do not store any files (use only for testing!) + ; has to be 0 or 1 + List event coordinates := 0 + + ; if you're short of RAM (i.e. a single projdata does not fit into memory), + ; you can use this to process the list mode data in multiple passes. + num_segments_in_memory := -1 + +End := diff --git a/people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test8.par b/people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test8.par new file mode 100755 index 0000000000..4c176f80a8 --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test8.par @@ -0,0 +1,44 @@ +lm_to_projdata Parameters:= + + input file := root_header_test8.hroot +output filename prefix := sinogram_test8 + + ; parameters that determine the sizes etc of the output + + template_projdata := template_D690_RD0.hs + ; the next can be used to use a smaller number of segments than given + ; in the template + maximum absolute segment number to process := -1 + + ; parameters for saying which events will be stored + + ; time frames (see TimeFrameDefinitions doc for format) +; frame_definition file := frames.fdef + ; or a total number of events (if larger than 1, frame definitions will be ignored) + ; note that this normally counts the total of prompts-delayeds (see below) + ; If you don't define a time frame definition file nor the num_events_to_store + ; then the total number of events in the listmode file is used. +; num_events_to_store := 10 + + ; parameters relating to prompts and delayeds + + ; with the default values, prompts will be added and delayed subtracted + ; to give the usual estimate of the trues. + + ; store the prompts (value should be 1 or 0) + store prompts := 1 ;default + ; what to do if it's a delayed event + store delayeds := 0 ;default + + + ; miscellaneous parameters + + ; list each event on stdout and do not store any files (use only for testing!) + ; has to be 0 or 1 + List event coordinates := 0 + + ; if you're short of RAM (i.e. a single projdata does not fit into memory), + ; you can use this to process the list mode data in multiple passes. + num_segments_in_memory := -1 + +End := diff --git a/people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test9.par b/people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test9.par new file mode 100755 index 0000000000..fced4cd52a --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test9.par @@ -0,0 +1,44 @@ +lm_to_projdata Parameters:= + + input file := root_header_test9.hroot +output filename prefix := sinogram_test9 + + ; parameters that determine the sizes etc of the output + + template_projdata := template_D690_RD0.hs + ; the next can be used to use a smaller number of segments than given + ; in the template + maximum absolute segment number to process := -1 + + ; parameters for saying which events will be stored + + ; time frames (see TimeFrameDefinitions doc for format) +; frame_definition file := frames.fdef + ; or a total number of events (if larger than 1, frame definitions will be ignored) + ; note that this normally counts the total of prompts-delayeds (see below) + ; If you don't define a time frame definition file nor the num_events_to_store + ; then the total number of events in the listmode file is used. +; num_events_to_store := 10 + + ; parameters relating to prompts and delayeds + + ; with the default values, prompts will be added and delayed subtracted + ; to give the usual estimate of the trues. + + ; store the prompts (value should be 1 or 0) + store prompts := 1 ;default + ; what to do if it's a delayed event + store delayeds := 0 ;default + + + ; miscellaneous parameters + + ; list each event on stdout and do not store any files (use only for testing!) + ; has to be 0 or 1 + List event coordinates := 0 + + ; if you're short of RAM (i.e. a single projdata does not fit into memory), + ; you can use this to process the list mode data in multiple passes. + num_segments_in_memory := -1 + +End := diff --git a/people/eliseemond/ROOT-STIR-consistency/make_plot.py b/people/eliseemond/ROOT-STIR-consistency/make_plot.py new file mode 100755 index 0000000000..6657dba19a --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/make_plot.py @@ -0,0 +1,90 @@ +import numpy as npy +import matplotlib.pyplot as plt +import itertools + +def extract_coordinates(filename): + with open(filename,'r') as f: + line_content = f.readlines() + f.close() + line_content = [x.split() for x in line_content] + xlist = [x[0] for x in line_content ] + ylist = [x[1] for x in line_content ] + zlist = [x[2] for x in line_content ] + weightlist = [x[3] for x in line_content ] + return [xlist,ylist,zlist,weightlist] + +def extract_coordinates2(filename): + with open(filename,'r') as f: + line_content = f.readlines() + f.close() + line_content = [x.split() for x in line_content] + listcont = [[x[0],x[1],x[2]] for x in line_content ] + return listcont + + +def make_scatter_plot(xlist,ylist): + plt.scatter(xlist,ylist) + plt.xlabel("x value (mm)") + plt.ylabel("y value (mm)") + plt.title("TOF kernel") + plt.legend() + +def threshold_values(xsource,ysource,zsource,listcont): + distance=[] + boollist=[] + for x in listcont: + distance.append(npy.sqrt(npy.power(float(x[0])-xsource,2)+npy.power(float(x[1])-ysource,2)+npy.power(float(x[2])-zsource,2))) + for d in distance: + boollist.append(d > 45) + return boollist + +#%% + +[xlist,ylist,zlist,weightlist] = extract_coordinates("stir_image1.txt") +plt.figure() +make_scatter_plot(xlist,ylist) +#%% +[xlist,ylist,zlist,weightlist] = extract_coordinates("stir_image2.txt") +plt.figure() +make_scatter_plot(xlist,ylist) +#%% +[xlist,ylist,zlist,weightlist] = extract_coordinates("stir_image3.txt") +plt.figure() +make_scatter_plot(xlist,ylist) +#%% +[xlist,ylist,zlist,weightlist] = extract_coordinates("stir_image4.txt") +plt.figure() +make_scatter_plot(xlist,ylist) +#%% +[xlist,ylist,zlist,weightlist] = extract_coordinates("stir_image5.txt") +plt.figure() +make_scatter_plot(xlist,ylist) +#%% +[xlist,ylist,zlist,weightlist] = extract_coordinates("stir_image6.txt") +plt.figure() +make_scatter_plot(xlist,ylist) +#%% +[xlist,ylist,zlist,weightlist] = extract_coordinates("stir_image7.txt") +plt.figure() +make_scatter_plot(xlist,ylist) +#%% +[xlist,ylist,zlist,weightlist] = extract_coordinates("stir_image8.txt") +plt.figure() +make_scatter_plot(xlist,ylist) +#%% +[xlist,ylist,zlist,weightlist] = extract_coordinates("stir_image9.txt") +plt.figure() +make_scatter_plot(xlist,ylist) +#%% +[xlist,ylist,zlist,weightlist] = extract_coordinates("stir_image10.txt") +plt.figure() +make_scatter_plot(xlist,ylist) +#%% +[xlist,ylist,zlist,weightlist] = extract_coordinates("stir_image11.txt") +plt.figure() +make_scatter_plot(xlist,ylist) +#%% +[xlist,ylist,zlist,weightlist] = extract_coordinates("stir_image12.txt") +plt.figure() +make_scatter_plot(xlist,ylist) +#%% \ No newline at end of file diff --git a/people/eliseemond/ROOT-STIR-consistency/run_pretest_script.sh b/people/eliseemond/ROOT-STIR-consistency/run_pretest_script.sh new file mode 100755 index 0000000000..d9fea3f814 --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/run_pretest_script.sh @@ -0,0 +1,12 @@ +#! /bin/bash + +for I in {1..12} +do +generate_image generate_image${I}.par +cd Gate_macros +sed -e s/SOURCENAME/test${I}/ main_D690_template.mac > main_D690_test${I}.mac +Gate main_D690_test${I}.mac +cd .. +done; + +mv Gate_macros/*.root . \ No newline at end of file From 581c2cd60189871ca95876956c779c2878245099 Mon Sep 17 00:00:00 2001 From: Elise Emond Date: Tue, 16 Jul 2019 15:19:49 +0100 Subject: [PATCH 193/509] The .hroot files were missing --- .../root_header_test1.hroot | 69 +++++++++++++++++++ .../root_header_test10.hroot | 69 +++++++++++++++++++ .../root_header_test11.hroot | 69 +++++++++++++++++++ .../root_header_test12.hroot | 69 +++++++++++++++++++ .../root_header_test2.hroot | 69 +++++++++++++++++++ .../root_header_test3.hroot | 69 +++++++++++++++++++ .../root_header_test4.hroot | 69 +++++++++++++++++++ .../root_header_test5.hroot | 69 +++++++++++++++++++ .../root_header_test6.hroot | 69 +++++++++++++++++++ .../root_header_test7.hroot | 69 +++++++++++++++++++ .../root_header_test8.hroot | 69 +++++++++++++++++++ .../root_header_test9.hroot | 69 +++++++++++++++++++ 12 files changed, 828 insertions(+) create mode 100644 people/eliseemond/ROOT-STIR-consistency/root_header_test1.hroot create mode 100644 people/eliseemond/ROOT-STIR-consistency/root_header_test10.hroot create mode 100644 people/eliseemond/ROOT-STIR-consistency/root_header_test11.hroot create mode 100644 people/eliseemond/ROOT-STIR-consistency/root_header_test12.hroot create mode 100644 people/eliseemond/ROOT-STIR-consistency/root_header_test2.hroot create mode 100644 people/eliseemond/ROOT-STIR-consistency/root_header_test3.hroot create mode 100644 people/eliseemond/ROOT-STIR-consistency/root_header_test4.hroot create mode 100644 people/eliseemond/ROOT-STIR-consistency/root_header_test5.hroot create mode 100644 people/eliseemond/ROOT-STIR-consistency/root_header_test6.hroot create mode 100644 people/eliseemond/ROOT-STIR-consistency/root_header_test7.hroot create mode 100644 people/eliseemond/ROOT-STIR-consistency/root_header_test8.hroot create mode 100644 people/eliseemond/ROOT-STIR-consistency/root_header_test9.hroot diff --git a/people/eliseemond/ROOT-STIR-consistency/root_header_test1.hroot b/people/eliseemond/ROOT-STIR-consistency/root_header_test1.hroot new file mode 100644 index 0000000000..a29acb965d --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/root_header_test1.hroot @@ -0,0 +1,69 @@ +ROOT header := + +originating system := User_defined_scanner +Number of rings := 24 +Number of detectors per ring := 576 +Inner ring diameter (cm) := 81.02 +Average depth of interaction (cm) := 0.94 +Distance between rings (cm) := 0.654 +Default bin size (cm) := 0.21306 +View offset (degrees) := -5.021 +Maximum number of non-arc-corrected bins := 381 +Default number of arc-corrected bins := 331 +Number of TOF time bins :=275 +Size of timing bin (ps) :=17.8 +Timing resolution (ps) :=75 + +GATE scanner type := GATE_Cylindrical_PET +GATE_Cylindrical_PET Parameters := + +name of data file := RootLM_D690_test1.root + +name of input TChain := Coincidences + +; As the GATE repeaters. +; If you skip a level in GATE's hierarchy, +; use 1. +number of Rsectors := 64 +number of modules_X := 1 +number of modules_Y := 1 +number of modules_Z := 4 +number of submodules_X := 1 +number of submodules_Y := 1 +number of submodules_Z := 1 +number of crystals_X := 1 +number of crystals_Y := 9 +number of crystals_Z := 6 + +;; From GATE's online documentation: +;; (http://wiki.opengatecollaboration.org/index.php/Users_Guide_V7.2:Digitizer_and_readout_parameters) +;; [...] the readout depth depends upon how the electronic readout functions. +;; If one PMT reads the four modules in the axial direction, +;; the depth should be set with the command: +;; /gate/digitizer/Singles/readout/setDepth 1 +; +; In STIR terminology this will be used to define the number of crystals +; per singles unit. +Singles readout depth := 1 + +; +; If set the scattered events will be skipped +exclude scattered events := 1 + +; +; If set the random events will be skipped +exclude random events := 1 + +; +; STIR will try to align the data. +; If you have used non standart GATE axes, +; rotate using: +offset (num of detectors) := 0 + +; If want to deactivate set to [0, 10000] +low energy window (keV) := 0 +upper energy window (keV):= 10000 + +End GATE_Cylindrical_PET Parameters := + +end ROOT header := diff --git a/people/eliseemond/ROOT-STIR-consistency/root_header_test10.hroot b/people/eliseemond/ROOT-STIR-consistency/root_header_test10.hroot new file mode 100644 index 0000000000..8292ec8aca --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/root_header_test10.hroot @@ -0,0 +1,69 @@ +ROOT header := + +originating system := User_defined_scanner +Number of rings := 24 +Number of detectors per ring := 576 +Inner ring diameter (cm) := 81.02 +Average depth of interaction (cm) := 0.94 +Distance between rings (cm) := 0.654 +Default bin size (cm) := 0.21306 +View offset (degrees) := -5.021 +Maximum number of non-arc-corrected bins := 381 +Default number of arc-corrected bins := 331 +Number of TOF time bins :=275 +Size of timing bin (ps) :=17.8 +Timing resolution (ps) :=75 + +GATE scanner type := GATE_Cylindrical_PET +GATE_Cylindrical_PET Parameters := + +name of data file := RootLM_D690_test10.root + +name of input TChain := Coincidences + +; As the GATE repeaters. +; If you skip a level in GATE's hierarchy, +; use 1. +number of Rsectors := 64 +number of modules_X := 1 +number of modules_Y := 1 +number of modules_Z := 4 +number of submodules_X := 1 +number of submodules_Y := 1 +number of submodules_Z := 1 +number of crystals_X := 1 +number of crystals_Y := 9 +number of crystals_Z := 6 + +;; From GATE's online documentation: +;; (http://wiki.opengatecollaboration.org/index.php/Users_Guide_V7.2:Digitizer_and_readout_parameters) +;; [...] the readout depth depends upon how the electronic readout functions. +;; If one PMT reads the four modules in the axial direction, +;; the depth should be set with the command: +;; /gate/digitizer/Singles/readout/setDepth 1 +; +; In STIR terminology this will be used to define the number of crystals +; per singles unit. +Singles readout depth := 1 + +; +; If set the scattered events will be skipped +exclude scattered events := 1 + +; +; If set the random events will be skipped +exclude random events := 1 + +; +; STIR will try to align the data. +; If you have used non standart GATE axes, +; rotate using: +offset (num of detectors) := 0 + +; If want to deactivate set to [0, 10000] +low energy window (keV) := 0 +upper energy window (keV):= 10000 + +End GATE_Cylindrical_PET Parameters := + +end ROOT header := diff --git a/people/eliseemond/ROOT-STIR-consistency/root_header_test11.hroot b/people/eliseemond/ROOT-STIR-consistency/root_header_test11.hroot new file mode 100644 index 0000000000..1435ff9be9 --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/root_header_test11.hroot @@ -0,0 +1,69 @@ +ROOT header := + +originating system := User_defined_scanner +Number of rings := 24 +Number of detectors per ring := 576 +Inner ring diameter (cm) := 81.02 +Average depth of interaction (cm) := 0.94 +Distance between rings (cm) := 0.654 +Default bin size (cm) := 0.21306 +View offset (degrees) := -5.021 +Maximum number of non-arc-corrected bins := 381 +Default number of arc-corrected bins := 331 +Number of TOF time bins :=275 +Size of timing bin (ps) :=17.8 +Timing resolution (ps) :=75 + +GATE scanner type := GATE_Cylindrical_PET +GATE_Cylindrical_PET Parameters := + +name of data file := RootLM_D690_test11.root + +name of input TChain := Coincidences + +; As the GATE repeaters. +; If you skip a level in GATE's hierarchy, +; use 1. +number of Rsectors := 64 +number of modules_X := 1 +number of modules_Y := 1 +number of modules_Z := 4 +number of submodules_X := 1 +number of submodules_Y := 1 +number of submodules_Z := 1 +number of crystals_X := 1 +number of crystals_Y := 9 +number of crystals_Z := 6 + +;; From GATE's online documentation: +;; (http://wiki.opengatecollaboration.org/index.php/Users_Guide_V7.2:Digitizer_and_readout_parameters) +;; [...] the readout depth depends upon how the electronic readout functions. +;; If one PMT reads the four modules in the axial direction, +;; the depth should be set with the command: +;; /gate/digitizer/Singles/readout/setDepth 1 +; +; In STIR terminology this will be used to define the number of crystals +; per singles unit. +Singles readout depth := 1 + +; +; If set the scattered events will be skipped +exclude scattered events := 1 + +; +; If set the random events will be skipped +exclude random events := 1 + +; +; STIR will try to align the data. +; If you have used non standart GATE axes, +; rotate using: +offset (num of detectors) := 0 + +; If want to deactivate set to [0, 10000] +low energy window (keV) := 0 +upper energy window (keV):= 10000 + +End GATE_Cylindrical_PET Parameters := + +end ROOT header := diff --git a/people/eliseemond/ROOT-STIR-consistency/root_header_test12.hroot b/people/eliseemond/ROOT-STIR-consistency/root_header_test12.hroot new file mode 100644 index 0000000000..54f96a7ac0 --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/root_header_test12.hroot @@ -0,0 +1,69 @@ +ROOT header := + +originating system := User_defined_scanner +Number of rings := 24 +Number of detectors per ring := 576 +Inner ring diameter (cm) := 81.02 +Average depth of interaction (cm) := 0.94 +Distance between rings (cm) := 0.654 +Default bin size (cm) := 0.21306 +View offset (degrees) := -5.021 +Maximum number of non-arc-corrected bins := 381 +Default number of arc-corrected bins := 331 +Number of TOF time bins :=275 +Size of timing bin (ps) :=17.8 +Timing resolution (ps) :=75 + +GATE scanner type := GATE_Cylindrical_PET +GATE_Cylindrical_PET Parameters := + +name of data file := RootLM_D690_test12.root + +name of input TChain := Coincidences + +; As the GATE repeaters. +; If you skip a level in GATE's hierarchy, +; use 1. +number of Rsectors := 64 +number of modules_X := 1 +number of modules_Y := 1 +number of modules_Z := 4 +number of submodules_X := 1 +number of submodules_Y := 1 +number of submodules_Z := 1 +number of crystals_X := 1 +number of crystals_Y := 9 +number of crystals_Z := 6 + +;; From GATE's online documentation: +;; (http://wiki.opengatecollaboration.org/index.php/Users_Guide_V7.2:Digitizer_and_readout_parameters) +;; [...] the readout depth depends upon how the electronic readout functions. +;; If one PMT reads the four modules in the axial direction, +;; the depth should be set with the command: +;; /gate/digitizer/Singles/readout/setDepth 1 +; +; In STIR terminology this will be used to define the number of crystals +; per singles unit. +Singles readout depth := 1 + +; +; If set the scattered events will be skipped +exclude scattered events := 1 + +; +; If set the random events will be skipped +exclude random events := 1 + +; +; STIR will try to align the data. +; If you have used non standart GATE axes, +; rotate using: +offset (num of detectors) := 0 + +; If want to deactivate set to [0, 10000] +low energy window (keV) := 0 +upper energy window (keV):= 10000 + +End GATE_Cylindrical_PET Parameters := + +end ROOT header := diff --git a/people/eliseemond/ROOT-STIR-consistency/root_header_test2.hroot b/people/eliseemond/ROOT-STIR-consistency/root_header_test2.hroot new file mode 100644 index 0000000000..44440618e5 --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/root_header_test2.hroot @@ -0,0 +1,69 @@ +ROOT header := + +originating system := User_defined_scanner +Number of rings := 24 +Number of detectors per ring := 576 +Inner ring diameter (cm) := 81.02 +Average depth of interaction (cm) := 0.94 +Distance between rings (cm) := 0.654 +Default bin size (cm) := 0.21306 +View offset (degrees) := -5.021 +Maximum number of non-arc-corrected bins := 381 +Default number of arc-corrected bins := 331 +Number of TOF time bins :=275 +Size of timing bin (ps) :=17.8 +Timing resolution (ps) :=75 + +GATE scanner type := GATE_Cylindrical_PET +GATE_Cylindrical_PET Parameters := + +name of data file := RootLM_D690_test2.root + +name of input TChain := Coincidences + +; As the GATE repeaters. +; If you skip a level in GATE's hierarchy, +; use 1. +number of Rsectors := 64 +number of modules_X := 1 +number of modules_Y := 1 +number of modules_Z := 4 +number of submodules_X := 1 +number of submodules_Y := 1 +number of submodules_Z := 1 +number of crystals_X := 1 +number of crystals_Y := 9 +number of crystals_Z := 6 + +;; From GATE's online documentation: +;; (http://wiki.opengatecollaboration.org/index.php/Users_Guide_V7.2:Digitizer_and_readout_parameters) +;; [...] the readout depth depends upon how the electronic readout functions. +;; If one PMT reads the four modules in the axial direction, +;; the depth should be set with the command: +;; /gate/digitizer/Singles/readout/setDepth 1 +; +; In STIR terminology this will be used to define the number of crystals +; per singles unit. +Singles readout depth := 1 + +; +; If set the scattered events will be skipped +exclude scattered events := 1 + +; +; If set the random events will be skipped +exclude random events := 1 + +; +; STIR will try to align the data. +; If you have used non standart GATE axes, +; rotate using: +offset (num of detectors) := 0 + +; If want to deactivate set to [0, 10000] +low energy window (keV) := 0 +upper energy window (keV):= 10000 + +End GATE_Cylindrical_PET Parameters := + +end ROOT header := diff --git a/people/eliseemond/ROOT-STIR-consistency/root_header_test3.hroot b/people/eliseemond/ROOT-STIR-consistency/root_header_test3.hroot new file mode 100644 index 0000000000..42333be0d6 --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/root_header_test3.hroot @@ -0,0 +1,69 @@ +ROOT header := + +originating system := User_defined_scanner +Number of rings := 24 +Number of detectors per ring := 576 +Inner ring diameter (cm) := 81.02 +Average depth of interaction (cm) := 0.94 +Distance between rings (cm) := 0.654 +Default bin size (cm) := 0.21306 +View offset (degrees) := -5.021 +Maximum number of non-arc-corrected bins := 381 +Default number of arc-corrected bins := 331 +Number of TOF time bins :=275 +Size of timing bin (ps) :=17.8 +Timing resolution (ps) :=75 + +GATE scanner type := GATE_Cylindrical_PET +GATE_Cylindrical_PET Parameters := + +name of data file := RootLM_D690_test3.root + +name of input TChain := Coincidences + +; As the GATE repeaters. +; If you skip a level in GATE's hierarchy, +; use 1. +number of Rsectors := 64 +number of modules_X := 1 +number of modules_Y := 1 +number of modules_Z := 4 +number of submodules_X := 1 +number of submodules_Y := 1 +number of submodules_Z := 1 +number of crystals_X := 1 +number of crystals_Y := 9 +number of crystals_Z := 6 + +;; From GATE's online documentation: +;; (http://wiki.opengatecollaboration.org/index.php/Users_Guide_V7.2:Digitizer_and_readout_parameters) +;; [...] the readout depth depends upon how the electronic readout functions. +;; If one PMT reads the four modules in the axial direction, +;; the depth should be set with the command: +;; /gate/digitizer/Singles/readout/setDepth 1 +; +; In STIR terminology this will be used to define the number of crystals +; per singles unit. +Singles readout depth := 1 + +; +; If set the scattered events will be skipped +exclude scattered events := 1 + +; +; If set the random events will be skipped +exclude random events := 1 + +; +; STIR will try to align the data. +; If you have used non standart GATE axes, +; rotate using: +offset (num of detectors) := 0 + +; If want to deactivate set to [0, 10000] +low energy window (keV) := 0 +upper energy window (keV):= 10000 + +End GATE_Cylindrical_PET Parameters := + +end ROOT header := diff --git a/people/eliseemond/ROOT-STIR-consistency/root_header_test4.hroot b/people/eliseemond/ROOT-STIR-consistency/root_header_test4.hroot new file mode 100644 index 0000000000..7d53b0e56a --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/root_header_test4.hroot @@ -0,0 +1,69 @@ +ROOT header := + +originating system := User_defined_scanner +Number of rings := 24 +Number of detectors per ring := 576 +Inner ring diameter (cm) := 81.02 +Average depth of interaction (cm) := 0.94 +Distance between rings (cm) := 0.654 +Default bin size (cm) := 0.21306 +View offset (degrees) := -5.021 +Maximum number of non-arc-corrected bins := 381 +Default number of arc-corrected bins := 331 +Number of TOF time bins :=275 +Size of timing bin (ps) :=17.8 +Timing resolution (ps) :=75 + +GATE scanner type := GATE_Cylindrical_PET +GATE_Cylindrical_PET Parameters := + +name of data file := RootLM_D690_test4.root + +name of input TChain := Coincidences + +; As the GATE repeaters. +; If you skip a level in GATE's hierarchy, +; use 1. +number of Rsectors := 64 +number of modules_X := 1 +number of modules_Y := 1 +number of modules_Z := 4 +number of submodules_X := 1 +number of submodules_Y := 1 +number of submodules_Z := 1 +number of crystals_X := 1 +number of crystals_Y := 9 +number of crystals_Z := 6 + +;; From GATE's online documentation: +;; (http://wiki.opengatecollaboration.org/index.php/Users_Guide_V7.2:Digitizer_and_readout_parameters) +;; [...] the readout depth depends upon how the electronic readout functions. +;; If one PMT reads the four modules in the axial direction, +;; the depth should be set with the command: +;; /gate/digitizer/Singles/readout/setDepth 1 +; +; In STIR terminology this will be used to define the number of crystals +; per singles unit. +Singles readout depth := 1 + +; +; If set the scattered events will be skipped +exclude scattered events := 1 + +; +; If set the random events will be skipped +exclude random events := 1 + +; +; STIR will try to align the data. +; If you have used non standart GATE axes, +; rotate using: +offset (num of detectors) := 0 + +; If want to deactivate set to [0, 10000] +low energy window (keV) := 0 +upper energy window (keV):= 10000 + +End GATE_Cylindrical_PET Parameters := + +end ROOT header := diff --git a/people/eliseemond/ROOT-STIR-consistency/root_header_test5.hroot b/people/eliseemond/ROOT-STIR-consistency/root_header_test5.hroot new file mode 100644 index 0000000000..9589e9c069 --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/root_header_test5.hroot @@ -0,0 +1,69 @@ +ROOT header := + +originating system := User_defined_scanner +Number of rings := 24 +Number of detectors per ring := 576 +Inner ring diameter (cm) := 81.02 +Average depth of interaction (cm) := 0.94 +Distance between rings (cm) := 0.654 +Default bin size (cm) := 0.21306 +View offset (degrees) := -5.021 +Maximum number of non-arc-corrected bins := 381 +Default number of arc-corrected bins := 331 +Number of TOF time bins :=275 +Size of timing bin (ps) :=17.8 +Timing resolution (ps) :=75 + +GATE scanner type := GATE_Cylindrical_PET +GATE_Cylindrical_PET Parameters := + +name of data file := RootLM_D690_test5.root + +name of input TChain := Coincidences + +; As the GATE repeaters. +; If you skip a level in GATE's hierarchy, +; use 1. +number of Rsectors := 64 +number of modules_X := 1 +number of modules_Y := 1 +number of modules_Z := 4 +number of submodules_X := 1 +number of submodules_Y := 1 +number of submodules_Z := 1 +number of crystals_X := 1 +number of crystals_Y := 9 +number of crystals_Z := 6 + +;; From GATE's online documentation: +;; (http://wiki.opengatecollaboration.org/index.php/Users_Guide_V7.2:Digitizer_and_readout_parameters) +;; [...] the readout depth depends upon how the electronic readout functions. +;; If one PMT reads the four modules in the axial direction, +;; the depth should be set with the command: +;; /gate/digitizer/Singles/readout/setDepth 1 +; +; In STIR terminology this will be used to define the number of crystals +; per singles unit. +Singles readout depth := 1 + +; +; If set the scattered events will be skipped +exclude scattered events := 1 + +; +; If set the random events will be skipped +exclude random events := 1 + +; +; STIR will try to align the data. +; If you have used non standart GATE axes, +; rotate using: +offset (num of detectors) := 0 + +; If want to deactivate set to [0, 10000] +low energy window (keV) := 0 +upper energy window (keV):= 10000 + +End GATE_Cylindrical_PET Parameters := + +end ROOT header := diff --git a/people/eliseemond/ROOT-STIR-consistency/root_header_test6.hroot b/people/eliseemond/ROOT-STIR-consistency/root_header_test6.hroot new file mode 100644 index 0000000000..1e2550c397 --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/root_header_test6.hroot @@ -0,0 +1,69 @@ +ROOT header := + +originating system := User_defined_scanner +Number of rings := 24 +Number of detectors per ring := 576 +Inner ring diameter (cm) := 81.02 +Average depth of interaction (cm) := 0.94 +Distance between rings (cm) := 0.654 +Default bin size (cm) := 0.21306 +View offset (degrees) := -5.021 +Maximum number of non-arc-corrected bins := 381 +Default number of arc-corrected bins := 331 +Number of TOF time bins :=275 +Size of timing bin (ps) :=17.8 +Timing resolution (ps) :=75 + +GATE scanner type := GATE_Cylindrical_PET +GATE_Cylindrical_PET Parameters := + +name of data file := RootLM_D690_test6.root + +name of input TChain := Coincidences + +; As the GATE repeaters. +; If you skip a level in GATE's hierarchy, +; use 1. +number of Rsectors := 64 +number of modules_X := 1 +number of modules_Y := 1 +number of modules_Z := 4 +number of submodules_X := 1 +number of submodules_Y := 1 +number of submodules_Z := 1 +number of crystals_X := 1 +number of crystals_Y := 9 +number of crystals_Z := 6 + +;; From GATE's online documentation: +;; (http://wiki.opengatecollaboration.org/index.php/Users_Guide_V7.2:Digitizer_and_readout_parameters) +;; [...] the readout depth depends upon how the electronic readout functions. +;; If one PMT reads the four modules in the axial direction, +;; the depth should be set with the command: +;; /gate/digitizer/Singles/readout/setDepth 1 +; +; In STIR terminology this will be used to define the number of crystals +; per singles unit. +Singles readout depth := 1 + +; +; If set the scattered events will be skipped +exclude scattered events := 1 + +; +; If set the random events will be skipped +exclude random events := 1 + +; +; STIR will try to align the data. +; If you have used non standart GATE axes, +; rotate using: +offset (num of detectors) := 0 + +; If want to deactivate set to [0, 10000] +low energy window (keV) := 0 +upper energy window (keV):= 10000 + +End GATE_Cylindrical_PET Parameters := + +end ROOT header := diff --git a/people/eliseemond/ROOT-STIR-consistency/root_header_test7.hroot b/people/eliseemond/ROOT-STIR-consistency/root_header_test7.hroot new file mode 100644 index 0000000000..3f77c1c9a7 --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/root_header_test7.hroot @@ -0,0 +1,69 @@ +ROOT header := + +originating system := User_defined_scanner +Number of rings := 24 +Number of detectors per ring := 576 +Inner ring diameter (cm) := 81.02 +Average depth of interaction (cm) := 0.94 +Distance between rings (cm) := 0.654 +Default bin size (cm) := 0.21306 +View offset (degrees) := -5.021 +Maximum number of non-arc-corrected bins := 381 +Default number of arc-corrected bins := 331 +Number of TOF time bins :=275 +Size of timing bin (ps) :=17.8 +Timing resolution (ps) :=75 + +GATE scanner type := GATE_Cylindrical_PET +GATE_Cylindrical_PET Parameters := + +name of data file := RootLM_D690_test7.root + +name of input TChain := Coincidences + +; As the GATE repeaters. +; If you skip a level in GATE's hierarchy, +; use 1. +number of Rsectors := 64 +number of modules_X := 1 +number of modules_Y := 1 +number of modules_Z := 4 +number of submodules_X := 1 +number of submodules_Y := 1 +number of submodules_Z := 1 +number of crystals_X := 1 +number of crystals_Y := 9 +number of crystals_Z := 6 + +;; From GATE's online documentation: +;; (http://wiki.opengatecollaboration.org/index.php/Users_Guide_V7.2:Digitizer_and_readout_parameters) +;; [...] the readout depth depends upon how the electronic readout functions. +;; If one PMT reads the four modules in the axial direction, +;; the depth should be set with the command: +;; /gate/digitizer/Singles/readout/setDepth 1 +; +; In STIR terminology this will be used to define the number of crystals +; per singles unit. +Singles readout depth := 1 + +; +; If set the scattered events will be skipped +exclude scattered events := 1 + +; +; If set the random events will be skipped +exclude random events := 1 + +; +; STIR will try to align the data. +; If you have used non standart GATE axes, +; rotate using: +offset (num of detectors) := 0 + +; If want to deactivate set to [0, 10000] +low energy window (keV) := 0 +upper energy window (keV):= 10000 + +End GATE_Cylindrical_PET Parameters := + +end ROOT header := diff --git a/people/eliseemond/ROOT-STIR-consistency/root_header_test8.hroot b/people/eliseemond/ROOT-STIR-consistency/root_header_test8.hroot new file mode 100644 index 0000000000..85946ac628 --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/root_header_test8.hroot @@ -0,0 +1,69 @@ +ROOT header := + +originating system := User_defined_scanner +Number of rings := 24 +Number of detectors per ring := 576 +Inner ring diameter (cm) := 81.02 +Average depth of interaction (cm) := 0.94 +Distance between rings (cm) := 0.654 +Default bin size (cm) := 0.21306 +View offset (degrees) := -5.021 +Maximum number of non-arc-corrected bins := 381 +Default number of arc-corrected bins := 331 +Number of TOF time bins :=275 +Size of timing bin (ps) :=17.8 +Timing resolution (ps) :=75 + +GATE scanner type := GATE_Cylindrical_PET +GATE_Cylindrical_PET Parameters := + +name of data file := RootLM_D690_test8.root + +name of input TChain := Coincidences + +; As the GATE repeaters. +; If you skip a level in GATE's hierarchy, +; use 1. +number of Rsectors := 64 +number of modules_X := 1 +number of modules_Y := 1 +number of modules_Z := 4 +number of submodules_X := 1 +number of submodules_Y := 1 +number of submodules_Z := 1 +number of crystals_X := 1 +number of crystals_Y := 9 +number of crystals_Z := 6 + +;; From GATE's online documentation: +;; (http://wiki.opengatecollaboration.org/index.php/Users_Guide_V7.2:Digitizer_and_readout_parameters) +;; [...] the readout depth depends upon how the electronic readout functions. +;; If one PMT reads the four modules in the axial direction, +;; the depth should be set with the command: +;; /gate/digitizer/Singles/readout/setDepth 1 +; +; In STIR terminology this will be used to define the number of crystals +; per singles unit. +Singles readout depth := 1 + +; +; If set the scattered events will be skipped +exclude scattered events := 1 + +; +; If set the random events will be skipped +exclude random events := 1 + +; +; STIR will try to align the data. +; If you have used non standart GATE axes, +; rotate using: +offset (num of detectors) := 0 + +; If want to deactivate set to [0, 10000] +low energy window (keV) := 0 +upper energy window (keV):= 10000 + +End GATE_Cylindrical_PET Parameters := + +end ROOT header := diff --git a/people/eliseemond/ROOT-STIR-consistency/root_header_test9.hroot b/people/eliseemond/ROOT-STIR-consistency/root_header_test9.hroot new file mode 100644 index 0000000000..3624c0c3a6 --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/root_header_test9.hroot @@ -0,0 +1,69 @@ +ROOT header := + +originating system := User_defined_scanner +Number of rings := 24 +Number of detectors per ring := 576 +Inner ring diameter (cm) := 81.02 +Average depth of interaction (cm) := 0.94 +Distance between rings (cm) := 0.654 +Default bin size (cm) := 0.21306 +View offset (degrees) := -5.021 +Maximum number of non-arc-corrected bins := 381 +Default number of arc-corrected bins := 331 +Number of TOF time bins :=275 +Size of timing bin (ps) :=17.8 +Timing resolution (ps) :=75 + +GATE scanner type := GATE_Cylindrical_PET +GATE_Cylindrical_PET Parameters := + +name of data file := RootLM_D690_test9.root + +name of input TChain := Coincidences + +; As the GATE repeaters. +; If you skip a level in GATE's hierarchy, +; use 1. +number of Rsectors := 64 +number of modules_X := 1 +number of modules_Y := 1 +number of modules_Z := 4 +number of submodules_X := 1 +number of submodules_Y := 1 +number of submodules_Z := 1 +number of crystals_X := 1 +number of crystals_Y := 9 +number of crystals_Z := 6 + +;; From GATE's online documentation: +;; (http://wiki.opengatecollaboration.org/index.php/Users_Guide_V7.2:Digitizer_and_readout_parameters) +;; [...] the readout depth depends upon how the electronic readout functions. +;; If one PMT reads the four modules in the axial direction, +;; the depth should be set with the command: +;; /gate/digitizer/Singles/readout/setDepth 1 +; +; In STIR terminology this will be used to define the number of crystals +; per singles unit. +Singles readout depth := 1 + +; +; If set the scattered events will be skipped +exclude scattered events := 1 + +; +; If set the random events will be skipped +exclude random events := 1 + +; +; STIR will try to align the data. +; If you have used non standart GATE axes, +; rotate using: +offset (num of detectors) := 0 + +; If want to deactivate set to [0, 10000] +low energy window (keV) := 0 +upper energy window (keV):= 10000 + +End GATE_Cylindrical_PET Parameters := + +end ROOT header := From 2884a4d3bb3db89cfd1255a6070ed52896f21db0 Mon Sep 17 00:00:00 2001 From: Elise Emond Date: Mon, 30 Sep 2019 15:12:53 +0100 Subject: [PATCH 194/509] Gate/STIR time resolution calibration For more information, please check the 2017 MIC presentation I have done during the STIR user's meeting. --- .../TOFresolutionConsistency/README.md | 17 +++ .../digitiser_D690.mac | 20 ++++ .../getTimingResolution.py | 104 +++++++++++++++++ .../main_D690_centre.mac | 105 ++++++++++++++++++ .../source_centre.mac | 50 +++++++++ 5 files changed, 296 insertions(+) create mode 100644 people/eliseemond/ROOT-STIR-consistency/TOFresolutionConsistency/README.md create mode 100644 people/eliseemond/ROOT-STIR-consistency/TOFresolutionConsistency/digitiser_D690.mac create mode 100644 people/eliseemond/ROOT-STIR-consistency/TOFresolutionConsistency/getTimingResolution.py create mode 100644 people/eliseemond/ROOT-STIR-consistency/TOFresolutionConsistency/main_D690_centre.mac create mode 100644 people/eliseemond/ROOT-STIR-consistency/TOFresolutionConsistency/source_centre.mac diff --git a/people/eliseemond/ROOT-STIR-consistency/TOFresolutionConsistency/README.md b/people/eliseemond/ROOT-STIR-consistency/TOFresolutionConsistency/README.md new file mode 100644 index 0000000000..f2865cf75d --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/TOFresolutionConsistency/README.md @@ -0,0 +1,17 @@ +# Using TOF Gate data - setting up the single detector time resolution +## Author: Elise Emond + +In Gate, you can only define a "single detector time resolution" =/= coincidence time resolution. + +If you know the expected coincidence time resolution for a given scanner (e.g. 550 ps for the Discovery 710) you can follow this to calibrate accordingly your Gate scanner. + +Command for Gate single detector time resolution: /gate/digitizer/Singles/timeResolution/setTimeResolution in digitiser. + +**What you can do:** + +1. Run a Gate simulation with perfect time resolution (in the digitiser, setTimeResolution=0 ns or just comment out that line - default value), use the ascii output in main_D690_centre.mac (it will be read in Python) +2. In getTimingResolution.py, run the last cell (starting from last #%%) - don't forget to modify the output name (it is written Output_centre2Coincidences.dat). You can get the theoretical "detector response" (some kind of triangular response). +3. Then you can deconvolve the theoretical distribution (Gaussian distribution with expected FWHM, for Discovery 710 550ps) with this detector response to get an estimate of the single detector time resolution (you will obtain a Gaussian distribution after the deconvolution with a slightly lower FWHM: use setTimeResolution = this new FWHM/sqrt(2)) +4. Check that this is correct: modify once again the setTimeResolution in the digitiser with the value you just calculated and then run the second to last Python cell in getTimingResolution.py to obtain the coincidence FWHM. + +*NB: sadly I had written a nice Mathematica script for convolution/deconvolution but lost it when my computer crashed at the MIC 2017 and forgot to recover this file... * diff --git a/people/eliseemond/ROOT-STIR-consistency/TOFresolutionConsistency/digitiser_D690.mac b/people/eliseemond/ROOT-STIR-consistency/TOFresolutionConsistency/digitiser_D690.mac new file mode 100644 index 0000000000..26934cf253 --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/TOFresolutionConsistency/digitiser_D690.mac @@ -0,0 +1,20 @@ +/gate/digitizer/Singles/insert adder +/gate/digitizer/Singles/insert readout +/gate/digitizer/Singles/readout/setDepth 1 + + + +/gate/digitizer/Singles/insert blurring +/gate/digitizer/Singles/blurring/setResolution 0.124 #checked +/gate/digitizer/Singles/blurring/setEnergyOfReference 511. keV + + + +/gate/digitizer/Singles/insert thresholder +/gate/digitizer/Singles/thresholder/setThreshold 425. keV #checked +/gate/digitizer/Singles/insert upholder +/gate/digitizer/Singles/upholder/setUphold 650. keV #checked + + +/gate/digitizer/Singles/insert timeResolution +/gate/digitizer/Singles/timeResolution/setTimeResolution 384.76 ps \ No newline at end of file diff --git a/people/eliseemond/ROOT-STIR-consistency/TOFresolutionConsistency/getTimingResolution.py b/people/eliseemond/ROOT-STIR-consistency/TOFresolutionConsistency/getTimingResolution.py new file mode 100644 index 0000000000..55a43fac52 --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/TOFresolutionConsistency/getTimingResolution.py @@ -0,0 +1,104 @@ +import numpy as npy +import matplotlib.pyplot as plt +from scipy.optimize import curve_fit +from scipy import signal + +def extract_timeStamps(filename): + with open(filename,'r') as f: + line_content = f.readlines() + f.close() + line_content = [x.split() for x in line_content] + line_content = [[x[6],x[29],x[12],x[35]] for x in line_content ] + return line_content + +def get_timeDifference(time_list): + timeDiff=[] + for x in time_list: + if (int(x[2])(x0-b) and xindex= x0 and xindex <(x0+b)): + y[nb]=a-xindex*a/b + nb=nb+1 + return y + +def get_responseFit(timedifferencearray,nbofevents): + m=0 + a=12000 + b=100 + params = curve_fit(response_noTimeRes, timedifferencearray, nbofevents, p0=[a,m,b]) + return [response_noTimeRes(timedifferencearray, params[0][0],params[0][1],params[0][2]),params] + +def reload_data_timing(name): + npzfile=npy.load(name) + # Order should be: timeDiff, nbEvents, nbEventsFit, firstparamfit, secondparamfit, tirdparamfit + return [npzfile[npzfile.files[0]],npzfile[npzfile.files[1]],npzfile[npzfile.files[2]],npzfile[npzfile.files[3]],npzfile[npzfile.files[4]],npzfile[npzfile.files[5]]] + +#%% +time_list = extract_timeStamps("Output_centreCoincidences.dat") +timeDiff=get_timeDifference(time_list) +[sampledTimeDiffTim,nbEventsTim]=sample_timeDifference_distribution(timeDiff) +plot_timeDifference_distribution(sampledTimeDiffTim,nbEventsTim) +[nbEventsFitTim,paramsTim] = get_gaussianFit(sampledTimeDiffTim,nbEventsTim) +plot_timeDifference_distribution(sampledTimeDiffTim,nbEventsFitTim) +FWHM=get_FWHM(paramsTim[0][2]) + +npy.savez("centreWithTimeResolution.npz",sampledTimeDiffTim=sampledTimeDiffTim, + nbEventsTim=nbEventsTim,nbEventsFitTim=nbEventsFitTim, a=paramsTim[0][0],x0=paramsTim[0][1],sigma=paramsTim[0][2]) +#%% +time_list = extract_timeStamps("Output_centre2Coincidences.dat") +timeDiff=get_timeDifference(time_list) +[sampledTimeDiffNoTim,nbEventsNoTim]=sample_timeDifference_distribution(timeDiff) +plt.figure() +plot_timeDifference_distribution(sampledTimeDiffNoTim,nbEventsNoTim) +[nbEventsFitNoTim,paramsNoTim]= get_responseFit(sampledTimeDiffNoTim,nbEventsNoTim) +plot_timeDifference_distribution(sampledTimeDiffNoTim,nbEventsFitNoTim) + +npy.savez("centreWithoutTimeResolution.npz",sampledTimeDiffNoTim=sampledTimeDiffNoTim, + nbEventsNoTim=nbEventsNoTim,nbEventsFitNoTim=nbEventsFitNoTim, a=paramsNoTim[0][0],x0=paramsNoTim[0][1],b=paramsNoTim[0][2]) diff --git a/people/eliseemond/ROOT-STIR-consistency/TOFresolutionConsistency/main_D690_centre.mac b/people/eliseemond/ROOT-STIR-consistency/TOFresolutionConsistency/main_D690_centre.mac new file mode 100644 index 0000000000..97c8e0db07 --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/TOFresolutionConsistency/main_D690_centre.mac @@ -0,0 +1,105 @@ +# Auteur: Elise Emond + +#-------------# +# V I S U E L # +#-------------# + +/vis/disable +#/vis/enable + +#/control/execute visualisation.mac + +#-------------------# +# G O M T R I E # +#-------------------# + + +# Base de donn‚es des mat‚riaux +/gate/geometry/setMaterialDatabase GateMaterials.db + +# Dimensions du monde +/gate/world/geometry/setXLength 600. cm +/gate/world/geometry/setYLength 600. cm +/gate/world/geometry/setZLength 600. cm + +# Scanner + +/control/execute geometry_D690.mac + + +#-----------------# +# P H Y S I Q U E # +#-----------------# + +/control/execute physics.mac + +#---------# +# C U T S # +#---------# + +/control/execute cuts.mac + +#-----------------------------# +# I N I T I A L I S A T I O N # +#-----------------------------# + +/gate/run/initialize + +#-------------------# +# D I G I T I S E R # +#-------------------# + +/control/execute digitiser_D690.mac + +#-------------------------------------# +# C O I N C I D E N C E S O R T E R # +#-------------------------------------# + +/control/execute csorter_D690.mac + +#-------------------------------------# +# S O U R C E R A D I O A C T I V E # +#-------------------------------------# + +/control/execute source_centre.mac + +#-----------------------------------# +# D O N N É E S E N S O R T I E # +#-----------------------------------# + +/gate/output/root/enable +/gate/output/root/setFileName RootLM_D690_centre +/gate/output/root/setRootHitFlag 0 +/gate/output/root/setRootSinglesFlag 0 +/gate/output/root/setRootCoincidencesFlag 1 +/gate/output/root/setRootdelayFlag 0 + +/gate/output/ascii/enable +/gate/output/ascii/setFileName Output_centre +/gate/output/ascii/setOutFileHitsFlag 0 +/gate/output/ascii/setOutFileSinglesFlag 0 +/gate/output/ascii/setOutFileCoincidencesFlag 1 +/gate/output/ascii/setOutFiledelayFlag 0 + +#---------------------------# +# R A N D O M E N G I N E # +#---------------------------# + +# JamesRandom Ranlux64 MersenneTwister +#/gate/random/setEngineName JamesRandom +#/gate/random/setEngineSeed default +#/gate/random/setEngineSeed auto +#/gate/random/setEngineSeed 123456789 +#/gate/random/setEngineSeed default +#/gate/random/resetEngineFrom fileName +#/gate/random/verbose 1 + +#---------------------------------------# +# T E M P S D ' A C Q U I S I T I O N # +#---------------------------------------# + +/gate/application/setTimeSlice 100 ms +/gate/application/setTimeStart 0 s +/gate/application/setTimeStop 10 s + +/gate/application/startDAQ diff --git a/people/eliseemond/ROOT-STIR-consistency/TOFresolutionConsistency/source_centre.mac b/people/eliseemond/ROOT-STIR-consistency/TOFresolutionConsistency/source_centre.mac new file mode 100644 index 0000000000..3089286fe7 --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/TOFresolutionConsistency/source_centre.mac @@ -0,0 +1,50 @@ +#===================================================== + +# P A R T I C L E S O U R C E + +#===================================================== + +/gate/source/addSource posiFDG + + + +/gate/source/posiFDG/setType backtoback + + + +# The particles emitted by the source are gammas + +/gate/source/posiFDG/gps/particle gamma + + + +# The gammas have an energy of 511 keV + +/gate/source/posiFDG/gps/energytype Mono + +/gate/source/posiFDG/gps/monoenergy 0.511 MeV + + + +/gate/source/posiFDG/setActivity 5000000 becquerel + + + +/gate/source/posiFDG/setForcedUnstableFlag true + +/gate/source/posiFDG/setForcedHalfLife 6586 s + +/gate/source/posiFDG/gps/angtype iso + + +#Shape of the source +/gate/source/posiFDG/gps/type Point +#/gate/source/posiFDG/gps/shape Ellipsoid +#/gate/source/posiFDG/gps/halfx 5. cm +#/gate/source/posiFDG/gps/halfy 5. cm +#/gate/source/posiFDG/gps/halfz 5. cm + +#Position of the source +/gate/source/posiFDG/gps/centre 0. 0. 0. cm + +/gate/source/list From cb75a8c1df4a8d97dd986c69dfe431155f073bd0 Mon Sep 17 00:00:00 2001 From: Elise Emond Date: Mon, 30 Sep 2019 18:53:15 +0100 Subject: [PATCH 195/509] Update README.md --- .../ROOT-STIR-consistency/TOFresolutionConsistency/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/people/eliseemond/ROOT-STIR-consistency/TOFresolutionConsistency/README.md b/people/eliseemond/ROOT-STIR-consistency/TOFresolutionConsistency/README.md index f2865cf75d..9b88c19289 100644 --- a/people/eliseemond/ROOT-STIR-consistency/TOFresolutionConsistency/README.md +++ b/people/eliseemond/ROOT-STIR-consistency/TOFresolutionConsistency/README.md @@ -14,4 +14,4 @@ Command for Gate single detector time resolution: /gate/digitizer/Singles/timeRe 3. Then you can deconvolve the theoretical distribution (Gaussian distribution with expected FWHM, for Discovery 710 550ps) with this detector response to get an estimate of the single detector time resolution (you will obtain a Gaussian distribution after the deconvolution with a slightly lower FWHM: use setTimeResolution = this new FWHM/sqrt(2)) 4. Check that this is correct: modify once again the setTimeResolution in the digitiser with the value you just calculated and then run the second to last Python cell in getTimingResolution.py to obtain the coincidence FWHM. -*NB: sadly I had written a nice Mathematica script for convolution/deconvolution but lost it when my computer crashed at the MIC 2017 and forgot to recover this file... * +*NB: sadly I had written a nice Mathematica script for convolution/deconvolution but lost it when my computer crashed at the MIC 2017 and forgot to recover this file... You can however use the file in eliseemond/Mathematica/ConvolutionEffectiveEnergy.nb as a model to convolve distributions in Mathematica.* From 7e10e2cf581ae125234b9cfe2e2ef141bd8910f9 Mon Sep 17 00:00:00 2001 From: Elise Emond Date: Mon, 30 Sep 2019 19:07:18 +0100 Subject: [PATCH 196/509] README.md for ROOT consistency check for TOF --- people/eliseemond/ROOT-STIR-consistency/README.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 people/eliseemond/ROOT-STIR-consistency/README.md diff --git a/people/eliseemond/ROOT-STIR-consistency/README.md b/people/eliseemond/ROOT-STIR-consistency/README.md new file mode 100644 index 0000000000..ab99ec2e1a --- /dev/null +++ b/people/eliseemond/ROOT-STIR-consistency/README.md @@ -0,0 +1,9 @@ +## TOF consistency checks for STIR +# Author: Elise Emond + +Files in the folder were created to check: +* Whether the TOF STIR implementation was correct. To do so you need: + 1. To run `./run_pretest_script.sh` in the terminal to create the Gate root files for different point sources. + 2. Run the STIR test: `src/recon_test/test_consistency_root`. This test should tell you whether it failed or not, using centres of mass corresponding to the maximum value for a detection bin (defined by LOR + time bin). If failed, the TOF backprojection is incorrect. + 3. The image coordinates corresponding to the centres of mass were written as a txt file by the previous test. You can plot using `make_plot.py`. +* To check if the time resolution defined in the Gate digitiser for a single detector corresponds to the time resolution in STIR scanner template. Have a look at `TOFresolutionConsistency/README.md` for more information. From f8dee6451b5486ea5b0a91904bbbaaaf8b9b7dfe Mon Sep 17 00:00:00 2001 From: Robert Twyman Date: Thu, 10 Feb 2022 17:03:08 -0800 Subject: [PATCH 197/509] Move ROOT-STIR-consistency into recon_tests and enable test [ci skip] --- people/eliseemond/.gitignore | 39 ------------------- .../Gate_macros/GateMaterials.db | 0 .../Gate_macros/csorter_D690.mac | 0 .../Gate_macros/digitiser_D690.mac | 0 .../Gate_macros/geometry_D690.mac | 0 .../Gate_macros/main_D690_template.mac | 0 .../Gate_macros/main_D690_test1.mac | 0 .../Gate_macros/main_D690_test10.mac | 0 .../Gate_macros/main_D690_test11.mac | 0 .../Gate_macros/main_D690_test12.mac | 0 .../Gate_macros/main_D690_test2.mac | 0 .../Gate_macros/main_D690_test3.mac | 0 .../Gate_macros/main_D690_test4.mac | 0 .../Gate_macros/main_D690_test5.mac | 0 .../Gate_macros/main_D690_test6.mac | 0 .../Gate_macros/main_D690_test7.mac | 0 .../Gate_macros/main_D690_test8.mac | 0 .../Gate_macros/main_D690_test9.mac | 0 .../Gate_macros/physics.mac | 0 .../Gate_macros/source_test1.mac | 0 .../Gate_macros/source_test10.mac | 0 .../Gate_macros/source_test11.mac | 0 .../Gate_macros/source_test12.mac | 0 .../Gate_macros/source_test2.mac | 0 .../Gate_macros/source_test3.mac | 0 .../Gate_macros/source_test4.mac | 0 .../Gate_macros/source_test5.mac | 0 .../Gate_macros/source_test6.mac | 0 .../Gate_macros/source_test7.mac | 0 .../Gate_macros/source_test8.mac | 0 .../Gate_macros/source_test9.mac | 0 .../ROOT_STIR_consistency}/README.md | 0 .../TOFresolutionConsistency/README.md | 0 .../digitiser_D690.mac | 0 .../getTimingResolution.py | 0 .../main_D690_centre.mac | 0 .../source_centre.mac | 0 .../generate_image1.par | 0 .../generate_image10.par | 0 .../generate_image11.par | 0 .../generate_image12.par | 0 .../generate_image2.par | 0 .../generate_image3.par | 0 .../generate_image4.par | 0 .../generate_image5.par | 0 .../generate_image6.par | 0 .../generate_image7.par | 0 .../generate_image8.par | 0 .../generate_image9.par | 0 .../lm_to_projdata_test1.par | 0 .../lm_to_projdata_test10.par | 0 .../lm_to_projdata_test11.par | 0 .../lm_to_projdata_test12.par | 0 .../lm_to_projdata_test2.par | 0 .../lm_to_projdata_test3.par | 0 .../lm_to_projdata_test4.par | 0 .../lm_to_projdata_test5.par | 0 .../lm_to_projdata_test6.par | 0 .../lm_to_projdata_test7.par | 0 .../lm_to_projdata_test8.par | 0 .../lm_to_projdata_test9.par | 0 .../ROOT_STIR_consistency}/make_plot.py | 0 .../root_header_test1.hroot | 0 .../root_header_test10.hroot | 0 .../root_header_test11.hroot | 0 .../root_header_test12.hroot | 0 .../root_header_test2.hroot | 0 .../root_header_test3.hroot | 0 .../root_header_test4.hroot | 0 .../root_header_test5.hroot | 0 .../root_header_test6.hroot | 0 .../root_header_test7.hroot | 0 .../root_header_test8.hroot | 0 .../root_header_test9.hroot | 0 .../run_pretest_script.sh | 0 75 files changed, 39 deletions(-) delete mode 100644 people/eliseemond/.gitignore rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/Gate_macros/GateMaterials.db (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/Gate_macros/csorter_D690.mac (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/Gate_macros/digitiser_D690.mac (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/Gate_macros/geometry_D690.mac (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/Gate_macros/main_D690_template.mac (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/Gate_macros/main_D690_test1.mac (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/Gate_macros/main_D690_test10.mac (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/Gate_macros/main_D690_test11.mac (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/Gate_macros/main_D690_test12.mac (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/Gate_macros/main_D690_test2.mac (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/Gate_macros/main_D690_test3.mac (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/Gate_macros/main_D690_test4.mac (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/Gate_macros/main_D690_test5.mac (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/Gate_macros/main_D690_test6.mac (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/Gate_macros/main_D690_test7.mac (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/Gate_macros/main_D690_test8.mac (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/Gate_macros/main_D690_test9.mac (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/Gate_macros/physics.mac (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/Gate_macros/source_test1.mac (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/Gate_macros/source_test10.mac (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/Gate_macros/source_test11.mac (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/Gate_macros/source_test12.mac (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/Gate_macros/source_test2.mac (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/Gate_macros/source_test3.mac (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/Gate_macros/source_test4.mac (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/Gate_macros/source_test5.mac (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/Gate_macros/source_test6.mac (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/Gate_macros/source_test7.mac (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/Gate_macros/source_test8.mac (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/Gate_macros/source_test9.mac (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/README.md (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/TOFresolutionConsistency/README.md (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/TOFresolutionConsistency/digitiser_D690.mac (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/TOFresolutionConsistency/getTimingResolution.py (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/TOFresolutionConsistency/main_D690_centre.mac (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/TOFresolutionConsistency/source_centre.mac (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/generate_image1.par (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/generate_image10.par (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/generate_image11.par (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/generate_image12.par (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/generate_image2.par (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/generate_image3.par (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/generate_image4.par (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/generate_image5.par (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/generate_image6.par (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/generate_image7.par (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/generate_image8.par (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/generate_image9.par (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/lm_to_projdata_test1.par (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/lm_to_projdata_test10.par (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/lm_to_projdata_test11.par (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/lm_to_projdata_test12.par (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/lm_to_projdata_test2.par (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/lm_to_projdata_test3.par (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/lm_to_projdata_test4.par (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/lm_to_projdata_test5.par (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/lm_to_projdata_test6.par (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/lm_to_projdata_test7.par (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/lm_to_projdata_test8.par (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/lm_to_projdata_test9.par (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/make_plot.py (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/root_header_test1.hroot (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/root_header_test10.hroot (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/root_header_test11.hroot (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/root_header_test12.hroot (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/root_header_test2.hroot (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/root_header_test3.hroot (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/root_header_test4.hroot (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/root_header_test5.hroot (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/root_header_test6.hroot (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/root_header_test7.hroot (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/root_header_test8.hroot (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/root_header_test9.hroot (100%) rename {people/eliseemond/ROOT-STIR-consistency => recon_test_pack/ROOT_STIR_consistency}/run_pretest_script.sh (100%) diff --git a/people/eliseemond/.gitignore b/people/eliseemond/.gitignore deleted file mode 100644 index 603c07ff37..0000000000 --- a/people/eliseemond/.gitignore +++ /dev/null @@ -1,39 +0,0 @@ -# Compiled source # -################### -*.com -*.class -*.dll -*.exe -*.o -*.pyc -*.pyo -*.so - -# Packages # -############ -# it's better to unpack these files and commit the raw source -# git has its own built in compression methods -*.7z -*.dmg -*.gz -*.iso -*.jar -*.rar -*.tar -*.zip - -# Logs and databases # -###################### -*.log -*.sql -*.sqlite - -# OS generated files # -###################### -.DS_Store -.DS_Store? -._* -.Spotlight-V100 -.Trashes -ehthumbs.db -Thumbs.db diff --git a/people/eliseemond/ROOT-STIR-consistency/Gate_macros/GateMaterials.db b/recon_test_pack/ROOT_STIR_consistency/Gate_macros/GateMaterials.db similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/Gate_macros/GateMaterials.db rename to recon_test_pack/ROOT_STIR_consistency/Gate_macros/GateMaterials.db diff --git a/people/eliseemond/ROOT-STIR-consistency/Gate_macros/csorter_D690.mac b/recon_test_pack/ROOT_STIR_consistency/Gate_macros/csorter_D690.mac similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/Gate_macros/csorter_D690.mac rename to recon_test_pack/ROOT_STIR_consistency/Gate_macros/csorter_D690.mac diff --git a/people/eliseemond/ROOT-STIR-consistency/Gate_macros/digitiser_D690.mac b/recon_test_pack/ROOT_STIR_consistency/Gate_macros/digitiser_D690.mac similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/Gate_macros/digitiser_D690.mac rename to recon_test_pack/ROOT_STIR_consistency/Gate_macros/digitiser_D690.mac diff --git a/people/eliseemond/ROOT-STIR-consistency/Gate_macros/geometry_D690.mac b/recon_test_pack/ROOT_STIR_consistency/Gate_macros/geometry_D690.mac similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/Gate_macros/geometry_D690.mac rename to recon_test_pack/ROOT_STIR_consistency/Gate_macros/geometry_D690.mac diff --git a/people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_template.mac b/recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_template.mac similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_template.mac rename to recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_template.mac diff --git a/people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test1.mac b/recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test1.mac similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test1.mac rename to recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test1.mac diff --git a/people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test10.mac b/recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test10.mac similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test10.mac rename to recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test10.mac diff --git a/people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test11.mac b/recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test11.mac similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test11.mac rename to recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test11.mac diff --git a/people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test12.mac b/recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test12.mac similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test12.mac rename to recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test12.mac diff --git a/people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test2.mac b/recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test2.mac similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test2.mac rename to recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test2.mac diff --git a/people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test3.mac b/recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test3.mac similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test3.mac rename to recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test3.mac diff --git a/people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test4.mac b/recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test4.mac similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test4.mac rename to recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test4.mac diff --git a/people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test5.mac b/recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test5.mac similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test5.mac rename to recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test5.mac diff --git a/people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test6.mac b/recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test6.mac similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test6.mac rename to recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test6.mac diff --git a/people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test7.mac b/recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test7.mac similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test7.mac rename to recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test7.mac diff --git a/people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test8.mac b/recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test8.mac similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test8.mac rename to recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test8.mac diff --git a/people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test9.mac b/recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test9.mac similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/Gate_macros/main_D690_test9.mac rename to recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test9.mac diff --git a/people/eliseemond/ROOT-STIR-consistency/Gate_macros/physics.mac b/recon_test_pack/ROOT_STIR_consistency/Gate_macros/physics.mac similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/Gate_macros/physics.mac rename to recon_test_pack/ROOT_STIR_consistency/Gate_macros/physics.mac diff --git a/people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test1.mac b/recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test1.mac similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test1.mac rename to recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test1.mac diff --git a/people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test10.mac b/recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test10.mac similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test10.mac rename to recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test10.mac diff --git a/people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test11.mac b/recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test11.mac similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test11.mac rename to recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test11.mac diff --git a/people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test12.mac b/recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test12.mac similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test12.mac rename to recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test12.mac diff --git a/people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test2.mac b/recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test2.mac similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test2.mac rename to recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test2.mac diff --git a/people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test3.mac b/recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test3.mac similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test3.mac rename to recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test3.mac diff --git a/people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test4.mac b/recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test4.mac similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test4.mac rename to recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test4.mac diff --git a/people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test5.mac b/recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test5.mac similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test5.mac rename to recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test5.mac diff --git a/people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test6.mac b/recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test6.mac similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test6.mac rename to recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test6.mac diff --git a/people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test7.mac b/recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test7.mac similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test7.mac rename to recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test7.mac diff --git a/people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test8.mac b/recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test8.mac similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test8.mac rename to recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test8.mac diff --git a/people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test9.mac b/recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test9.mac similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/Gate_macros/source_test9.mac rename to recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test9.mac diff --git a/people/eliseemond/ROOT-STIR-consistency/README.md b/recon_test_pack/ROOT_STIR_consistency/README.md similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/README.md rename to recon_test_pack/ROOT_STIR_consistency/README.md diff --git a/people/eliseemond/ROOT-STIR-consistency/TOFresolutionConsistency/README.md b/recon_test_pack/ROOT_STIR_consistency/TOFresolutionConsistency/README.md similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/TOFresolutionConsistency/README.md rename to recon_test_pack/ROOT_STIR_consistency/TOFresolutionConsistency/README.md diff --git a/people/eliseemond/ROOT-STIR-consistency/TOFresolutionConsistency/digitiser_D690.mac b/recon_test_pack/ROOT_STIR_consistency/TOFresolutionConsistency/digitiser_D690.mac similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/TOFresolutionConsistency/digitiser_D690.mac rename to recon_test_pack/ROOT_STIR_consistency/TOFresolutionConsistency/digitiser_D690.mac diff --git a/people/eliseemond/ROOT-STIR-consistency/TOFresolutionConsistency/getTimingResolution.py b/recon_test_pack/ROOT_STIR_consistency/TOFresolutionConsistency/getTimingResolution.py similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/TOFresolutionConsistency/getTimingResolution.py rename to recon_test_pack/ROOT_STIR_consistency/TOFresolutionConsistency/getTimingResolution.py diff --git a/people/eliseemond/ROOT-STIR-consistency/TOFresolutionConsistency/main_D690_centre.mac b/recon_test_pack/ROOT_STIR_consistency/TOFresolutionConsistency/main_D690_centre.mac similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/TOFresolutionConsistency/main_D690_centre.mac rename to recon_test_pack/ROOT_STIR_consistency/TOFresolutionConsistency/main_D690_centre.mac diff --git a/people/eliseemond/ROOT-STIR-consistency/TOFresolutionConsistency/source_centre.mac b/recon_test_pack/ROOT_STIR_consistency/TOFresolutionConsistency/source_centre.mac similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/TOFresolutionConsistency/source_centre.mac rename to recon_test_pack/ROOT_STIR_consistency/TOFresolutionConsistency/source_centre.mac diff --git a/people/eliseemond/ROOT-STIR-consistency/generate_image1.par b/recon_test_pack/ROOT_STIR_consistency/generate_image1.par similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/generate_image1.par rename to recon_test_pack/ROOT_STIR_consistency/generate_image1.par diff --git a/people/eliseemond/ROOT-STIR-consistency/generate_image10.par b/recon_test_pack/ROOT_STIR_consistency/generate_image10.par similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/generate_image10.par rename to recon_test_pack/ROOT_STIR_consistency/generate_image10.par diff --git a/people/eliseemond/ROOT-STIR-consistency/generate_image11.par b/recon_test_pack/ROOT_STIR_consistency/generate_image11.par similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/generate_image11.par rename to recon_test_pack/ROOT_STIR_consistency/generate_image11.par diff --git a/people/eliseemond/ROOT-STIR-consistency/generate_image12.par b/recon_test_pack/ROOT_STIR_consistency/generate_image12.par similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/generate_image12.par rename to recon_test_pack/ROOT_STIR_consistency/generate_image12.par diff --git a/people/eliseemond/ROOT-STIR-consistency/generate_image2.par b/recon_test_pack/ROOT_STIR_consistency/generate_image2.par similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/generate_image2.par rename to recon_test_pack/ROOT_STIR_consistency/generate_image2.par diff --git a/people/eliseemond/ROOT-STIR-consistency/generate_image3.par b/recon_test_pack/ROOT_STIR_consistency/generate_image3.par similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/generate_image3.par rename to recon_test_pack/ROOT_STIR_consistency/generate_image3.par diff --git a/people/eliseemond/ROOT-STIR-consistency/generate_image4.par b/recon_test_pack/ROOT_STIR_consistency/generate_image4.par similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/generate_image4.par rename to recon_test_pack/ROOT_STIR_consistency/generate_image4.par diff --git a/people/eliseemond/ROOT-STIR-consistency/generate_image5.par b/recon_test_pack/ROOT_STIR_consistency/generate_image5.par similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/generate_image5.par rename to recon_test_pack/ROOT_STIR_consistency/generate_image5.par diff --git a/people/eliseemond/ROOT-STIR-consistency/generate_image6.par b/recon_test_pack/ROOT_STIR_consistency/generate_image6.par similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/generate_image6.par rename to recon_test_pack/ROOT_STIR_consistency/generate_image6.par diff --git a/people/eliseemond/ROOT-STIR-consistency/generate_image7.par b/recon_test_pack/ROOT_STIR_consistency/generate_image7.par similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/generate_image7.par rename to recon_test_pack/ROOT_STIR_consistency/generate_image7.par diff --git a/people/eliseemond/ROOT-STIR-consistency/generate_image8.par b/recon_test_pack/ROOT_STIR_consistency/generate_image8.par similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/generate_image8.par rename to recon_test_pack/ROOT_STIR_consistency/generate_image8.par diff --git a/people/eliseemond/ROOT-STIR-consistency/generate_image9.par b/recon_test_pack/ROOT_STIR_consistency/generate_image9.par similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/generate_image9.par rename to recon_test_pack/ROOT_STIR_consistency/generate_image9.par diff --git a/people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test1.par b/recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test1.par similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test1.par rename to recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test1.par diff --git a/people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test10.par b/recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test10.par similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test10.par rename to recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test10.par diff --git a/people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test11.par b/recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test11.par similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test11.par rename to recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test11.par diff --git a/people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test12.par b/recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test12.par similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test12.par rename to recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test12.par diff --git a/people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test2.par b/recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test2.par similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test2.par rename to recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test2.par diff --git a/people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test3.par b/recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test3.par similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test3.par rename to recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test3.par diff --git a/people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test4.par b/recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test4.par similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test4.par rename to recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test4.par diff --git a/people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test5.par b/recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test5.par similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test5.par rename to recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test5.par diff --git a/people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test6.par b/recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test6.par similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test6.par rename to recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test6.par diff --git a/people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test7.par b/recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test7.par similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test7.par rename to recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test7.par diff --git a/people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test8.par b/recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test8.par similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test8.par rename to recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test8.par diff --git a/people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test9.par b/recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test9.par similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/lm_to_projdata_test9.par rename to recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test9.par diff --git a/people/eliseemond/ROOT-STIR-consistency/make_plot.py b/recon_test_pack/ROOT_STIR_consistency/make_plot.py similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/make_plot.py rename to recon_test_pack/ROOT_STIR_consistency/make_plot.py diff --git a/people/eliseemond/ROOT-STIR-consistency/root_header_test1.hroot b/recon_test_pack/ROOT_STIR_consistency/root_header_test1.hroot similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/root_header_test1.hroot rename to recon_test_pack/ROOT_STIR_consistency/root_header_test1.hroot diff --git a/people/eliseemond/ROOT-STIR-consistency/root_header_test10.hroot b/recon_test_pack/ROOT_STIR_consistency/root_header_test10.hroot similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/root_header_test10.hroot rename to recon_test_pack/ROOT_STIR_consistency/root_header_test10.hroot diff --git a/people/eliseemond/ROOT-STIR-consistency/root_header_test11.hroot b/recon_test_pack/ROOT_STIR_consistency/root_header_test11.hroot similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/root_header_test11.hroot rename to recon_test_pack/ROOT_STIR_consistency/root_header_test11.hroot diff --git a/people/eliseemond/ROOT-STIR-consistency/root_header_test12.hroot b/recon_test_pack/ROOT_STIR_consistency/root_header_test12.hroot similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/root_header_test12.hroot rename to recon_test_pack/ROOT_STIR_consistency/root_header_test12.hroot diff --git a/people/eliseemond/ROOT-STIR-consistency/root_header_test2.hroot b/recon_test_pack/ROOT_STIR_consistency/root_header_test2.hroot similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/root_header_test2.hroot rename to recon_test_pack/ROOT_STIR_consistency/root_header_test2.hroot diff --git a/people/eliseemond/ROOT-STIR-consistency/root_header_test3.hroot b/recon_test_pack/ROOT_STIR_consistency/root_header_test3.hroot similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/root_header_test3.hroot rename to recon_test_pack/ROOT_STIR_consistency/root_header_test3.hroot diff --git a/people/eliseemond/ROOT-STIR-consistency/root_header_test4.hroot b/recon_test_pack/ROOT_STIR_consistency/root_header_test4.hroot similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/root_header_test4.hroot rename to recon_test_pack/ROOT_STIR_consistency/root_header_test4.hroot diff --git a/people/eliseemond/ROOT-STIR-consistency/root_header_test5.hroot b/recon_test_pack/ROOT_STIR_consistency/root_header_test5.hroot similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/root_header_test5.hroot rename to recon_test_pack/ROOT_STIR_consistency/root_header_test5.hroot diff --git a/people/eliseemond/ROOT-STIR-consistency/root_header_test6.hroot b/recon_test_pack/ROOT_STIR_consistency/root_header_test6.hroot similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/root_header_test6.hroot rename to recon_test_pack/ROOT_STIR_consistency/root_header_test6.hroot diff --git a/people/eliseemond/ROOT-STIR-consistency/root_header_test7.hroot b/recon_test_pack/ROOT_STIR_consistency/root_header_test7.hroot similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/root_header_test7.hroot rename to recon_test_pack/ROOT_STIR_consistency/root_header_test7.hroot diff --git a/people/eliseemond/ROOT-STIR-consistency/root_header_test8.hroot b/recon_test_pack/ROOT_STIR_consistency/root_header_test8.hroot similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/root_header_test8.hroot rename to recon_test_pack/ROOT_STIR_consistency/root_header_test8.hroot diff --git a/people/eliseemond/ROOT-STIR-consistency/root_header_test9.hroot b/recon_test_pack/ROOT_STIR_consistency/root_header_test9.hroot similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/root_header_test9.hroot rename to recon_test_pack/ROOT_STIR_consistency/root_header_test9.hroot diff --git a/people/eliseemond/ROOT-STIR-consistency/run_pretest_script.sh b/recon_test_pack/ROOT_STIR_consistency/run_pretest_script.sh similarity index 100% rename from people/eliseemond/ROOT-STIR-consistency/run_pretest_script.sh rename to recon_test_pack/ROOT_STIR_consistency/run_pretest_script.sh From 8d8d5b4f9f89d65dcb5f17e50c9d4cd9cbd6bcef Mon Sep 17 00:00:00 2001 From: Robert Twyman Date: Wed, 6 Apr 2022 15:10:56 -0700 Subject: [PATCH 198/509] Remove consistency configuration and generation files --- .../Gate_macros/GateMaterials.db | 486 ------------------ .../Gate_macros/csorter_D690.mac | 5 - .../Gate_macros/digitiser_D690.mac | 20 - .../Gate_macros/geometry_D690.mac | 117 ----- .../Gate_macros/main_D690_template.mac | 70 --- .../Gate_macros/main_D690_test1.mac | 70 --- .../Gate_macros/main_D690_test10.mac | 70 --- .../Gate_macros/main_D690_test11.mac | 70 --- .../Gate_macros/main_D690_test12.mac | 70 --- .../Gate_macros/main_D690_test2.mac | 70 --- .../Gate_macros/main_D690_test3.mac | 70 --- .../Gate_macros/main_D690_test4.mac | 70 --- .../Gate_macros/main_D690_test5.mac | 70 --- .../Gate_macros/main_D690_test6.mac | 70 --- .../Gate_macros/main_D690_test7.mac | 70 --- .../Gate_macros/main_D690_test8.mac | 70 --- .../Gate_macros/main_D690_test9.mac | 70 --- .../Gate_macros/physics.mac | 9 - .../Gate_macros/source_test1.mac | 46 -- .../Gate_macros/source_test10.mac | 49 -- .../Gate_macros/source_test11.mac | 49 -- .../Gate_macros/source_test12.mac | 49 -- .../Gate_macros/source_test2.mac | 50 -- .../Gate_macros/source_test3.mac | 50 -- .../Gate_macros/source_test4.mac | 50 -- .../Gate_macros/source_test5.mac | 50 -- .../Gate_macros/source_test6.mac | 50 -- .../Gate_macros/source_test7.mac | 50 -- .../Gate_macros/source_test8.mac | 50 -- .../Gate_macros/source_test9.mac | 49 -- .../ROOT_STIR_consistency/README.md | 9 - .../TOFresolutionConsistency/README.md | 17 - .../digitiser_D690.mac | 20 - .../getTimingResolution.py | 104 ---- .../main_D690_centre.mac | 105 ---- .../source_centre.mac | 50 -- .../ROOT_STIR_consistency/generate_image1.par | 23 - .../generate_image10.par | 23 - .../generate_image11.par | 23 - .../generate_image12.par | 23 - .../ROOT_STIR_consistency/generate_image2.par | 23 - .../ROOT_STIR_consistency/generate_image3.par | 23 - .../ROOT_STIR_consistency/generate_image4.par | 23 - .../ROOT_STIR_consistency/generate_image5.par | 23 - .../ROOT_STIR_consistency/generate_image6.par | 23 - .../ROOT_STIR_consistency/generate_image7.par | 23 - .../ROOT_STIR_consistency/generate_image8.par | 23 - .../ROOT_STIR_consistency/generate_image9.par | 23 - .../lm_to_projdata_test1.par | 44 -- .../lm_to_projdata_test10.par | 44 -- .../lm_to_projdata_test11.par | 44 -- .../lm_to_projdata_test12.par | 44 -- .../lm_to_projdata_test2.par | 44 -- .../lm_to_projdata_test3.par | 44 -- .../lm_to_projdata_test4.par | 44 -- .../lm_to_projdata_test5.par | 44 -- .../lm_to_projdata_test6.par | 44 -- .../lm_to_projdata_test7.par | 44 -- .../lm_to_projdata_test8.par | 44 -- .../lm_to_projdata_test9.par | 44 -- .../ROOT_STIR_consistency/make_plot.py | 90 ---- .../root_header_test1.hroot | 69 --- .../root_header_test10.hroot | 69 --- .../root_header_test11.hroot | 69 --- .../root_header_test12.hroot | 69 --- .../root_header_test2.hroot | 69 --- .../root_header_test3.hroot | 69 --- .../root_header_test4.hroot | 69 --- .../root_header_test5.hroot | 69 --- .../root_header_test6.hroot | 69 --- .../root_header_test7.hroot | 69 --- .../root_header_test8.hroot | 69 --- .../root_header_test9.hroot | 69 --- .../run_pretest_script.sh | 12 - 74 files changed, 4178 deletions(-) delete mode 100755 recon_test_pack/ROOT_STIR_consistency/Gate_macros/GateMaterials.db delete mode 100755 recon_test_pack/ROOT_STIR_consistency/Gate_macros/csorter_D690.mac delete mode 100755 recon_test_pack/ROOT_STIR_consistency/Gate_macros/digitiser_D690.mac delete mode 100755 recon_test_pack/ROOT_STIR_consistency/Gate_macros/geometry_D690.mac delete mode 100755 recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_template.mac delete mode 100755 recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test1.mac delete mode 100755 recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test10.mac delete mode 100755 recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test11.mac delete mode 100755 recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test12.mac delete mode 100755 recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test2.mac delete mode 100755 recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test3.mac delete mode 100755 recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test4.mac delete mode 100755 recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test5.mac delete mode 100755 recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test6.mac delete mode 100755 recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test7.mac delete mode 100755 recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test8.mac delete mode 100755 recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test9.mac delete mode 100755 recon_test_pack/ROOT_STIR_consistency/Gate_macros/physics.mac delete mode 100755 recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test1.mac delete mode 100755 recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test10.mac delete mode 100755 recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test11.mac delete mode 100755 recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test12.mac delete mode 100755 recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test2.mac delete mode 100755 recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test3.mac delete mode 100755 recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test4.mac delete mode 100755 recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test5.mac delete mode 100755 recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test6.mac delete mode 100755 recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test7.mac delete mode 100755 recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test8.mac delete mode 100755 recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test9.mac delete mode 100644 recon_test_pack/ROOT_STIR_consistency/README.md delete mode 100644 recon_test_pack/ROOT_STIR_consistency/TOFresolutionConsistency/README.md delete mode 100644 recon_test_pack/ROOT_STIR_consistency/TOFresolutionConsistency/digitiser_D690.mac delete mode 100644 recon_test_pack/ROOT_STIR_consistency/TOFresolutionConsistency/getTimingResolution.py delete mode 100644 recon_test_pack/ROOT_STIR_consistency/TOFresolutionConsistency/main_D690_centre.mac delete mode 100644 recon_test_pack/ROOT_STIR_consistency/TOFresolutionConsistency/source_centre.mac delete mode 100755 recon_test_pack/ROOT_STIR_consistency/generate_image1.par delete mode 100755 recon_test_pack/ROOT_STIR_consistency/generate_image10.par delete mode 100755 recon_test_pack/ROOT_STIR_consistency/generate_image11.par delete mode 100755 recon_test_pack/ROOT_STIR_consistency/generate_image12.par delete mode 100755 recon_test_pack/ROOT_STIR_consistency/generate_image2.par delete mode 100755 recon_test_pack/ROOT_STIR_consistency/generate_image3.par delete mode 100755 recon_test_pack/ROOT_STIR_consistency/generate_image4.par delete mode 100755 recon_test_pack/ROOT_STIR_consistency/generate_image5.par delete mode 100755 recon_test_pack/ROOT_STIR_consistency/generate_image6.par delete mode 100755 recon_test_pack/ROOT_STIR_consistency/generate_image7.par delete mode 100755 recon_test_pack/ROOT_STIR_consistency/generate_image8.par delete mode 100755 recon_test_pack/ROOT_STIR_consistency/generate_image9.par delete mode 100755 recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test1.par delete mode 100755 recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test10.par delete mode 100755 recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test11.par delete mode 100755 recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test12.par delete mode 100755 recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test2.par delete mode 100755 recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test3.par delete mode 100755 recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test4.par delete mode 100755 recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test5.par delete mode 100755 recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test6.par delete mode 100755 recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test7.par delete mode 100755 recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test8.par delete mode 100755 recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test9.par delete mode 100755 recon_test_pack/ROOT_STIR_consistency/make_plot.py delete mode 100644 recon_test_pack/ROOT_STIR_consistency/root_header_test1.hroot delete mode 100644 recon_test_pack/ROOT_STIR_consistency/root_header_test10.hroot delete mode 100644 recon_test_pack/ROOT_STIR_consistency/root_header_test11.hroot delete mode 100644 recon_test_pack/ROOT_STIR_consistency/root_header_test12.hroot delete mode 100644 recon_test_pack/ROOT_STIR_consistency/root_header_test2.hroot delete mode 100644 recon_test_pack/ROOT_STIR_consistency/root_header_test3.hroot delete mode 100644 recon_test_pack/ROOT_STIR_consistency/root_header_test4.hroot delete mode 100644 recon_test_pack/ROOT_STIR_consistency/root_header_test5.hroot delete mode 100644 recon_test_pack/ROOT_STIR_consistency/root_header_test6.hroot delete mode 100644 recon_test_pack/ROOT_STIR_consistency/root_header_test7.hroot delete mode 100644 recon_test_pack/ROOT_STIR_consistency/root_header_test8.hroot delete mode 100644 recon_test_pack/ROOT_STIR_consistency/root_header_test9.hroot delete mode 100755 recon_test_pack/ROOT_STIR_consistency/run_pretest_script.sh diff --git a/recon_test_pack/ROOT_STIR_consistency/Gate_macros/GateMaterials.db b/recon_test_pack/ROOT_STIR_consistency/Gate_macros/GateMaterials.db deleted file mode 100755 index f43d2e8b7f..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/Gate_macros/GateMaterials.db +++ /dev/null @@ -1,486 +0,0 @@ -[Elements] -Hydrogen: S= H ; Z= 1. ; A= 1.01 g/mole -Helium: S= He ; Z= 2. ; A= 4.003 g/mole -Lithium: S= Li ; Z= 3. ; A= 6.941 g/mole -Beryllium: S= Be ; Z= 4. ; A= 9.012 g/mole -Boron: S= B ; Z= 5. ; A= 10.811 g/mole -Carbon: S= C ; Z= 6. ; A= 12.01 g/mole -Nitrogen: S= N ; Z= 7. ; A= 14.01 g/mole -Oxygen: S= O ; Z= 8. ; A= 16.00 g/mole -Fluorine: S= F ; Z= 9. ; A= 18.998 g/mole -Neon: S= Ne ; Z= 10. ; A= 20.180 g/mole -Sodium: S= Na ; Z= 11. ; A= 22.99 g/mole -Magnesium: S= Mg ; Z= 12. ; A= 24.305 g/mole -Aluminium: S= Al ; Z= 13. ; A= 26.98 g/mole -Silicon: S= Si ; Z= 14. ; A= 28.09 g/mole -Phosphor: S= P ; Z= 15. ; A= 30.97 g/mole -Sulfur: S= S ; Z= 16. ; A= 32.066 g/mole -Chlorine: S= Cl ; Z= 17. ; A= 35.45 g/mole -Argon: S= Ar ; Z= 18. ; A= 39.95 g/mole -Potassium: S= K ; Z= 19. ; A= 39.098 g/mole -Calcium: S= Ca ; Z= 20. ; A= 40.08 g/mole -Scandium: S= Sc ; Z= 21. ; A= 44.956 g/mole -Titanium: S= Ti ; Z= 22. ; A= 47.867 g/mole -Vandium: S= V ; Z= 23. ; A= 50.942 g/mole -Chromium: S= Cr ; Z= 24. ; A= 51.996 g/mole -Manganese: S= Mn ; Z= 25. ; A= 54.938 g/mole -Iron: S= Fe ; Z= 26. ; A= 55.845 g/mole -Cobalt: S= Co ; Z= 27. ; A= 58.933 g/mole -Nickel: S= Ni ; Z= 28. ; A= 58.693 g/mole -Copper: S= Cu ; Z= 29. ; A= 63.39 g/mole -Zinc: S= Zn ; Z= 30. ; A= 65.39 g/mole -Gallium: S= Ga ; Z= 31. ; A= 69.723 g/mole -Germanium: S= Ge ; Z= 32. ; A= 72.61 g/mole -Yttrium: S= Y ; Z= 39. ; A= 88.91 g/mole -Silver: S= Ag ; Z= 47. ; A= 107.868 g/mole -Cadmium: S= Cd ; Z= 48. ; A= 112.41 g/mole -Tin: S= Sn ; Z= 50. ; A= 118.71 g/mole -Tellurium: S= Te ; Z= 52. ; A= 127.6 g/mole -Iodine: S= I ; Z= 53. ; A= 126.90 g/mole -Cesium: S= Cs ; Z= 55. ; A= 132.905 g/mole -Gadolinium: S= Gd ; Z= 64. ; A= 157.25 g/mole -Lutetium: S= Lu ; Z= 71. ; A= 174.97 g/mole -Tungsten: S= W ; Z= 74. ; A= 183.84 g/mole -Gold: S= Au ; Z= 79. ; A= 196.967 g/mole -Thallium: S= Tl ; Z= 81. ; A= 204.37 g/mole -Lead: S= Pb ; Z= 82. ; A= 207.20 g/mole -Bismuth: S= Bi ; Z= 83. ; A= 208.98 g/mole -Uranium: S= U ; Z= 92. ; A= 238.03 g/mole - -[Materials] -Vacuum: d=0.000001 mg/cm3 ; n=1 - +el: name=Hydrogen ; n=1 - -Aluminium: d=2.7 g/cm3 ; n=1 ; state=solid - +el: name=auto ; n=1 - -AluminiumEGS: d=2.702 g/cm3 ; n=1 ; state=solid - +el: name=Aluminium ; n=1 - -Uranium: d=18.90 g/cm3 ; n=1 ; state=solid - +el: name=auto ; n=1 - -Silicon: d=2.33 g/cm3 ; n=1 ; state=solid - +el: name=auto ; n=1 - -Germanium: d=5.32 g/cm3 ; n=1 ; state=solid - +el: name=auto ; n=1 - -Yttrium: d=4.47 g/cm3 ; n=1 - +el: name=auto ; n=1 - -Gadolinium: d=7.9 g/cm3 ; n=1 - +el: name=auto ; n=1 - -Lutetium: d=9.84 g/cm3 ; n=1 - +el: name=auto ; n=1 - -Tungsten: d=19.3 g/cm3 ; n=1 ; state=solid - +el: name=auto ; n=1 - -Lead: d=11.4 g/cm3 ; n=1 ; state=solid - +el: name=auto ; n=1 - -Bismuth: d=9.75 g/cm3 ; n=1 ; state=solid - +el: name=auto ; n=1 - -NaI: d=3.67 g/cm3; n=2; state=solid - +el: name=Sodium ; n=1 - +el: name=Iodine ; n=1 - -PWO: d=8.28 g/cm3; n=3 ; state=Solid - +el: name=Lead ; n=1 - +el: name=Tungsten ; n=1 - +el: name=Oxygen ; n=4 - -BGO: d=7.13 g/cm3; n= 3 ; state=solid - +el: name=Bismuth ; n=4 - +el: name=Germanium ; n=3 - +el: name=Oxygen ; n=12 - -LSO: d=7.4 g/cm3; n=3 ; state=Solid - +el: name=Lutetium ; n=2 - +el: name=Silicon ; n=1 - +el: name=Oxygen ; n=5 - -Plexiglass: d=1.19 g/cm3; n=3; state=solid - +el: name=Hydrogen ; f=0.080538 - +el: name=Carbon ; f=0.599848 - +el: name=Oxygen ; f=0.319614 - -GSO: d=6.7 g/cm3; n=3 ; state=Solid - +el: name=Gadolinium ; n=2 - +el: name=Silicon ; n=1 - +el: name=Oxygen ; n=5 - -LuAP: d=8.34 g/cm3; n=3 ; state=Solid - +el: name=Lutetium ; n=1 - +el: name=Aluminium ; n=1 - +el: name=Oxygen ; n=3 - -YAP: d=5.55 g/cm3; n=3 ; state=Solid - +el: name=Yttrium ; n=1 - +el: name=Aluminium ; n=1 - +el: name=Oxygen ; n=3 - -Water: d=1.00 g/cm3; n=2 ; state=liquid - +el: name=Hydrogen ; n=2 - +el: name=Oxygen ; n=1 - -Quartz: d=2.2 g/cm3; n=2 ; state=Solid - +el: name=Silicon ; n=1 - +el: name=Oxygen ; n=2 - -Breast: d=1.020 g/cm3 ; n = 8 - +el: name=Oxygen ; f=0.5270 - +el: name=Carbon ; f=0.3320 - +el: name=Hydrogen ; f=0.1060 - +el: name=Nitrogen ; f=0.0300 - +el: name=Sulfur ; f=0.0020 - +el: name=Sodium ; f=0.0010 - +el: name=Phosphor ; f=0.0010 - +el: name=Chlorine ; f=0.0010 - -Air: d=1.29 mg/cm3 ; n=4 ; state=gas - +el: name=Nitrogen ; f=0.755268 - +el: name=Oxygen ; f=0.231781 - +el: name=Argon ; f=0.012827 - +el: name=Carbon ; f=0.000124 - -Glass: d=2.5 g/cm3; n=4; state=solid - +el: name=Sodium ; f=0.1020 - +el: name=Calcium ; f=0.0510 - +el: name=Silicon ; f=0.2480 - +el: name=Oxygen ; f=0.5990 - -Scinti-C9H10: d=1.032 g/cm3 ; n=2 - +el: name=Carbon ; n=9 - +el: name=Hydrogen ; n=10 - -LuYAP-70: d=7.1 g/cm3 ; n=4 - +el: name=Lutetium ; n= 7 - +el: name=Yttrium ; n= 3 - +el: name=Aluminium ; n=10 - +el: name=Oxygen ; n=30 - -LuYAP-80: d=7.5 g/cm3 ; n=4 - +el: name=Lutetium ; n= 8 - +el: name=Yttrium ; n= 2 - +el: name=Aluminium ; n=10 - +el: name=Oxygen ; n=30 - -Plastic: d=1.18 g/cm3 ; n=3; state=solid - +el: name=Carbon ; n=5 - +el: name=Hydrogen ; n=8 - +el: name=Oxygen ; n=2 - -Biomimic: d=1.05 g/cm3 ; n=3; state=solid - +el: name=Carbon ; n=5 - +el: name=Hydrogen ; n=8 - +el: name=Oxygen ; n=2 - -FITC: d=1.0 g/cm3 ; n=1 - +el: name=Carbon ; n=1 - -RhB: d=1.0 g/cm3 ; n=1 - +el: name=Carbon ; n=1 - -CZT: d=5.68 g/cm3 ; n=3; state=solid - +el: name=Cadmium ; n=9 - +el: name=Zinc ; n=1 - +el: name=Tellurium ; n=10 - -Lung: d=0.26 g/cm3 ; n=9 - +el: name=Hydrogen ; f=0.103 - +el: name=Carbon ; f=0.105 - +el: name=Nitrogen ; f=0.031 - +el: name=Oxygen ; f=0.749 - +el: name=Sodium ; f=0.002 - +el: name=Phosphor ; f=0.002 - +el: name=Sulfur ; f=0.003 - +el: name=Chlorine ; f=0.003 - +el: name=Potassium ; f=0.002 - -Polyethylene: d=0.96 g/cm3 ; n=2 - +el: name=Hydrogen ; n=2 - +el: name=Carbon ; n=1 - -PVC: d=1.65 g/cm3 ; n=3 ; state=solid - +el: name=Hydrogen ; n=3 - +el: name=Carbon ; n=2 - +el: name=Chlorine ; n=1 - -SS304: d=7.92 g/cm3 ; n=4 ; state=solid - +el: name=Iron ; f=0.695 - +el: name=Chromium ; f=0.190 - +el: name=Nickel ; f=0.095 - +el: name=Manganese ; f=0.020 - -PTFE: d= 2.18 g/cm3 ; n=2 ; state=solid - +el: name=Carbon ; n=1 - +el: name=Fluorine ; n=2 - - -LYSO: d=5.37 g/cm3; n=4 ; state=Solid - +el: name=Lutetium ; f=0.31101534 - +el: name=Yttrium ; f=0.368765605 - +el: name=Silicon ; f=0.083209699 - +el: name=Oxygen ; f=0.237009356 - -Body: d=1.00 g/cm3 ; n=2 - +el: name=Hydrogen ; f=0.112 - +el: name=Oxygen ; f=0.888 - -Muscle: d=1.05 g/cm3 ; n=11 - +el: name=Hydrogen ; f=0.102 - +el: name=Carbon ; f=0.143 - +el: name=Nitrogen ; f=0.034 - +el: name=Oxygen ; f=0.71 - +el: name=Sodium ; f=0.001 - +el: name=Phosphor ; f=0.002 - +el: name=Sulfur ; f=0.003 - +el: name=Chlorine ; f=0.001 - +el: name=Potassium ; f=0.004 - +el: name=Calcium ; f=0.0 - +el: name=Scandium ; f=0.0 - -LungMoby: d=0.30 g/cm3 ; n=6 - +el: name=Hydrogen ; f=0.099 - +el: name=Carbon ; f=0.100 - +el: name=Nitrogen ; f=0.028 - +el: name=Oxygen ; f=0.740 - +el: name=Phosphor ; f=0.001 - +el: name=Calcium ; f=0.032 - -SpineBone: d=1.42 g/cm3 ; n=11 - +el: name=Hydrogen ; f=0.063 - +el: name=Carbon ; f=0.261 - +el: name=Nitrogen ; f=0.039 - +el: name=Oxygen ; f=0.436 - +el: name=Sodium ; f=0.001 - +el: name=Magnesium ; f=0.001 - +el: name=Phosphor ; f=0.061 - +el: name=Sulfur ; f=0.003 - +el: name=Chlorine ; f=0.001 - +el: name=Potassium ; f=0.001 - +el: name=Calcium ; f=0.133 - -RibBone: d=1.92 g/cm3 ; n=11 - +el: name=Hydrogen ; f=0.034 - +el: name=Carbon ; f=0.155 - +el: name=Nitrogen ; f=0.042 - +el: name=Oxygen ; f=0.435 - +el: name=Sodium ; f=0.001 - +el: name=Magnesium ; f=0.002 - +el: name=Phosphor ; f=0.103 - +el: name=Sulfur ; f=0.003 - +el: name=Calcium ; f=0.225 - +el: name=Scandium ; f=0.0 - +el: name=Titanium ; f=0.0 - -Adipose: d=0.92 g/cm3 ; n=11 - +el: name=Hydrogen ; f=0.120 - +el: name=Carbon ; f=0.640 - +el: name=Nitrogen ; f=0.008 - +el: name=Oxygen ; f=0.229 - +el: name=Phosphor ; f=0.002 - +el: name=Calcium ; f=0.001 - +el: name=Scandium ; f=0.0 - +el: name=Titanium ; f=0.0 - +el: name=Vandium ; f=0.0 - +el: name=Chromium ; f=0.0 - +el: name=Manganese ; f=0.0 - -Epidermis: d=0.92 g/cm3 ; n=11 - +el: name=Hydrogen ; f=0.120 - +el: name=Carbon ; f=0.640 - +el: name=Nitrogen ; f=0.008 - +el: name=Oxygen ; f=0.229 - +el: name=Phosphor ; f=0.002 - +el: name=Calcium ; f=0.001 - +el: name=Scandium ; f=0.0 - +el: name=Titanium ; f=0.0 - +el: name=Vandium ; f=0.0 - +el: name=Chromium ; f=0.0 - +el: name=Manganese ; f=0.0 - -Hypodermis: d=0.92 g/cm3 ; n=11 - +el: name=Hydrogen ; f=0.120 - +el: name=Carbon ; f=0.640 - +el: name=Nitrogen ; f=0.008 - +el: name=Oxygen ; f=0.229 - +el: name=Phosphor ; f=0.002 - +el: name=Calcium ; f=0.001 - +el: name=Scandium ; f=0.0 - +el: name=Titanium ; f=0.0 - +el: name=Vandium ; f=0.0 - +el: name=Chromium ; f=0.0 - +el: name=Manganese ; f=0.0 - -Blood: d=1.06 g/cm3 ; n=11 - +el: name=Hydrogen ; f=0.102 - +el: name=Carbon ; f=0.11 - +el: name=Nitrogen ; f=0.033 - +el: name=Oxygen ; f=0.745 - +el: name=Sodium ; f=0.001 - +el: name=Phosphor ; f=0.001 - +el: name=Sulfur ; f=0.002 - +el: name=Chlorine ; f=0.003 - +el: name=Potassium ; f=0.002 - +el: name=Iron ; f=0.001 - +el: name=Cobalt ; f=0.0 - -Heart: d=1.05 g/cm3 ; n=11 - +el: name=Hydrogen ; f=0.104 - +el: name=Carbon ; f=0.139 - +el: name=Nitrogen ; f=0.029 - +el: name=Oxygen ; f=0.718 - +el: name=Sodium ; f=0.001 - +el: name=Phosphor ; f=0.002 - +el: name=Sulfur ; f=0.002 - +el: name=Chlorine ; f=0.002 - +el: name=Potassium ; f=0.003 - +el: name=Calcium ; f=0.0 - +el: name=Scandium ; f=0.0 - -Kidney: d=1.05 g/cm3 ; n=11 - +el: name=Hydrogen ; f=0.103 - +el: name=Carbon ; f=0.132 - +el: name=Nitrogen ; f=0.03 - +el: name=Oxygen ; f=0.724 - +el: name=Sodium ; f=0.002 - +el: name=Phosphor ; f=0.002 - +el: name=Sulfur ; f=0.002 - +el: name=Chlorine ; f=0.002 - +el: name=Potassium ; f=0.002 - +el: name=Calcium ; f=0.001 - +el: name=Scandium ; f=0.0 - -Liver: d=1.06 g/cm3 ; n=11 - +el: name=Hydrogen ; f=0.102 - +el: name=Carbon ; f=0.139 - +el: name=Nitrogen ; f=0.03 - +el: name=Oxygen ; f=0.716 - +el: name=Sodium ; f=0.002 - +el: name=Phosphor ; f=0.003 - +el: name=Sulfur ; f=0.003 - +el: name=Chlorine ; f=0.002 - +el: name=Potassium ; f=0.003 - +el: name=Calcium ; f=0.0 - +el: name=Scandium ; f=0.0 - -Lymph: d=1.03 g/cm3 ; n=11 - +el: name=Hydrogen ; f=0.108 - +el: name=Carbon ; f=0.041 - +el: name=Nitrogen ; f=0.011 - +el: name=Oxygen ; f=0.832 - +el: name=Sodium ; f=0.003 - +el: name=Sulfur ; f=0.001 - +el: name=Chlorine ; f=0.004 - +el: name=Argon ; f=0.0 - +el: name=Potassium ; f=0.0 - +el: name=Calcium ; f=0.0 - +el: name=Scandium ; f=0.0 - -Pancreas: d=1.04 g/cm3 ; n=11 - +el: name=Hydrogen ; f=0.106 - +el: name=Carbon ; f=0.169 - +el: name=Nitrogen ; f=0.022 - +el: name=Oxygen ; f=0.694 - +el: name=Sodium ; f=0.002 - +el: name=Phosphor ; f=0.002 - +el: name=Sulfur ; f=0.001 - +el: name=Chlorine ; f=0.002 - +el: name=Potassium ; f=0.002 - +el: name=Calcium ; f=0.0 - +el: name=Scandium ; f=0.0 - -Intestine: d=1.03 g/cm3 ; n=11 - +el: name=Hydrogen ; f=0.106 - +el: name=Carbon ; f=0.115 - +el: name=Nitrogen ; f=0.022 - +el: name=Oxygen ; f=0.751 - +el: name=Sodium ; f=0.001 - +el: name=Phosphor ; f=0.001 - +el: name=Sulfur ; f=0.001 - +el: name=Chlorine ; f=0.002 - +el: name=Potassium ; f=0.001 - +el: name=Calcium ; f=0.0 - +el: name=Scandium ; f=0.0 - -Skull: d=1.61 g/cm3 ; n=11 - +el: name=Hydrogen ; f=0.05 - +el: name=Carbon ; f=0.212 - +el: name=Nitrogen ; f=0.04 - +el: name=Oxygen ; f=0.435 - +el: name=Sodium ; f=0.001 - +el: name=Magnesium ; f=0.002 - +el: name=Phosphor ; f=0.081 - +el: name=Sulfur ; f=0.003 - +el: name=Calcium ; f=0.176 - +el: name=Scandium ; f=0.0 - +el: name=Titanium ; f=0.0 - -Cartilage: d=1.10 g/cm3 ; n=11 - +el: name=Hydrogen ; f=0.096 - +el: name=Carbon ; f=0.099 - +el: name=Nitrogen ; f=0.022 - +el: name=Oxygen ; f=0.744 - +el: name=Sodium ; f=0.005 - +el: name=Phosphor ; f=0.022 - +el: name=Sulfur ; f=0.009 - +el: name=Chlorine ; f=0.003 - +el: name=Argon ; f=0.0 - +el: name=Potassium ; f=0.0 - +el: name=Calcium ; f=0.0 - -Brain: d=1.04 g/cm3 ; n=11 - +el: name=Hydrogen ; f=0.107 - +el: name=Carbon ; f=0.145 - +el: name=Nitrogen ; f=0.022 - +el: name=Oxygen ; f=0.712 - +el: name=Sodium ; f=0.002 - +el: name=Phosphor ; f=0.004 - +el: name=Sulfur ; f=0.002 - +el: name=Chlorine ; f=0.003 - +el: name=Potassium ; f=0.003 - +el: name=Calcium ; f=0.0 - +el: name=Scandium ; f=0.0 - -Spleen: d=1.06 g/cm3 ; n=11 - +el: name=Hydrogen ; f=0.103 - +el: name=Carbon ; f=0.113 - +el: name=Nitrogen ; f=0.032 - +el: name=Oxygen ; f=0.741 - +el: name=Sodium ; f=0.001 - +el: name=Phosphor ; f=0.003 - +el: name=Sulfur ; f=0.002 - +el: name=Chlorine ; f=0.002 - +el: name=Potassium ; f=0.003 - +el: name=Calcium ; f=0.0 - +el: name=Scandium ; f=0.0 - -Testis: d=1.04 g/cm3 ; n=9 - +el: name=Hydrogen ; f=0.106000 - +el: name=Carbon ; f=0.099000 - +el: name=Nitrogen ; f=0.020000 - +el: name=Oxygen ; f=0.766000 - +el: name=Sodium ; f=0.002000 - +el: name=Phosphor ; f=0.001000 - +el: name=Sulfur ; f=0.002000 - +el: name=Chlorine ; f=0.002000 - +el: name=Potassium ; f=0.002000 - -PMMA: d=1.195 g/cm3; n=3 ; state=Solid - +el: name=Hydrogen ; f=0.080541 - +el: name=Carbon ; f=0.599846 - +el: name=Oxygen ; f=0.319613 - -Epoxy: d=1.0 g/cm3; n=3; state=Solid - +el: name=Carbon ; n=1 - +el: name=Hydrogen ; n=1 - +el: name=Oxygen ; n=1 - -Carbide: d=15.8 g/cm3; n=2 ; state=Solid - +el: name=Tungsten ; n=1 - +el: name=Carbon ; n=1 diff --git a/recon_test_pack/ROOT_STIR_consistency/Gate_macros/csorter_D690.mac b/recon_test_pack/ROOT_STIR_consistency/Gate_macros/csorter_D690.mac deleted file mode 100755 index 9758716d3b..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/Gate_macros/csorter_D690.mac +++ /dev/null @@ -1,5 +0,0 @@ -/gate/digitizer/Coincidences/setOffset 0. ns - -/gate/digitizer/Coincidences/setWindow 4.9 ns - -/gate/digitizer/insert coincidenceSorter diff --git a/recon_test_pack/ROOT_STIR_consistency/Gate_macros/digitiser_D690.mac b/recon_test_pack/ROOT_STIR_consistency/Gate_macros/digitiser_D690.mac deleted file mode 100755 index 6bbec7e123..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/Gate_macros/digitiser_D690.mac +++ /dev/null @@ -1,20 +0,0 @@ -/gate/digitizer/Singles/insert adder -/gate/digitizer/Singles/insert readout -/gate/digitizer/Singles/readout/setDepth 1 - - - -/gate/digitizer/Singles/insert blurring -/gate/digitizer/Singles/blurring/setResolution 0.124 #checked -/gate/digitizer/Singles/blurring/setEnergyOfReference 511. keV - - - -/gate/digitizer/Singles/insert thresholder -/gate/digitizer/Singles/thresholder/setThreshold 425. keV #checked -/gate/digitizer/Singles/insert upholder -/gate/digitizer/Singles/upholder/setUphold 650. keV #checked - - -/gate/digitizer/Singles/insert timeResolution -/gate/digitizer/Singles/timeResolution/setTimeResolution 53 ps # WARNING: not the correct timing resolution for D690 \ No newline at end of file diff --git a/recon_test_pack/ROOT_STIR_consistency/Gate_macros/geometry_D690.mac b/recon_test_pack/ROOT_STIR_consistency/Gate_macros/geometry_D690.mac deleted file mode 100755 index b2ab0a76a1..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/Gate_macros/geometry_D690.mac +++ /dev/null @@ -1,117 +0,0 @@ -# CYLINDRICAL - -/gate/world/daughters/name cylindricalPET -/gate/world/daughters/insert cylinder -/gate/cylindricalPET/placement/setTranslation 0.0 0.0 0.0 cm -/gate/cylindricalPET/geometry/setRmax 43.1 cm -/gate/cylindricalPET/geometry/setRmin 40.5 cm -/gate/cylindricalPET/geometry/setHeight 15.716 cm -/gate/cylindricalPET/setMaterial Air -/gate/cylindricalPET/vis/forceWireframe -/gate/cylindricalPET/vis/setColor white - - - -# RSECTOR - -/gate/cylindricalPET/daughters/name rsector -/gate/cylindricalPET/daughters/insert box -/gate/rsector/placement/setTranslation 41.76 0.0 0.0 cm -/gate/rsector/geometry/setXLength 25 mm -/gate/rsector/geometry/setYLength 39.96 mm -/gate/rsector/geometry/setZLength 156.96 mm -/gate/rsector/setMaterial Air -/gate/rsector/vis/setVisible 0 - -# END-SHIELDING - -/gate/rsector/daughters/name endshielding -/gate/rsector/daughters/insert box -/gate/endshielding/placement/setTranslation 0.0 0.0 0.0 cm -/gate/endshielding/geometry/setXLength 25 mm -/gate/endshielding/geometry/setYLength 39.96 mm -/gate/endshielding/geometry/setZLength 1.0 mm -/gate/endshielding/setMaterial Lead -/gate/endshielding/repeaters/insert cubicArray -/gate/endshielding/cubicArray/setRepeatNumberX 1 -/gate/endshielding/cubicArray/setRepeatNumberY 1 -/gate/endshielding/cubicArray/setRepeatNumberZ 2 -/gate/endshielding/cubicArray/setRepeatVector 0.0 0.0 15.706 cm -/gate/endshielding/vis/setColor white - -# MODULE - -/gate/rsector/daughters/name module -/gate/rsector/daughters/insert box -/gate/module/placement/setTranslation 0 0.0 0.0 cm -/gate/module/geometry/setXLength 25 mm -/gate/module/geometry/setYLength 39.96 mm -/gate/module/geometry/setZLength 39.24 mm -/gate/module/setMaterial Air -/gate/module/vis/setColor green -#/gate/module/vis/setVisible 0 - - - -# C R Y S T A L - -/gate/module/daughters/name crystal -/gate/module/daughters/insert box -/gate/crystal/placement/setTranslation 0.0 0.0 0.0 cm -/gate/crystal/geometry/setXLength 25 mm -/gate/crystal/geometry/setYLength 4.44 mm -/gate/crystal/geometry/setZLength 6.54 mm # Filled the gaps between the crystals for convenience -/gate/crystal/setMaterial LYSO -/gate/crystal/vis/setColor red - - - -# R E P E A T C R Y S T A L - -/gate/crystal/repeaters/insert cubicArray -/gate/crystal/cubicArray/setRepeatNumberX 1 -/gate/crystal/cubicArray/setRepeatNumberY 9 -/gate/crystal/cubicArray/setRepeatNumberZ 6 -/gate/crystal/cubicArray/setRepeatVector 0.0 4.44 6.54 mm - - - - - -# R E P E A T MODULE - -/gate/module/repeaters/insert cubicArray -/gate/module/cubicArray/setRepeatNumberX 1 -/gate/module/cubicArray/setRepeatNumberY 1 -/gate/module/cubicArray/setRepeatNumberZ 4 -/gate/module/cubicArray/setRepeatVector 0.0 0.0 39.24 mm - - - - - -# R E P E A T RSECTOR - -/gate/rsector/repeaters/insert ring -/gate/rsector/ring/setRepeatNumber 64 - - - - - -# A T T A C H S Y S T E M - -/gate/systems/cylindricalPET/rsector/attach rsector -/gate/systems/cylindricalPET/module/attach module -/gate/systems/cylindricalPET/crystal/attach crystal - - - -# A T T A C H C R Y S T A L SD - -/gate/crystal/attachCrystalSD - - - -/gate/systems/cylindricalPET/describe - diff --git a/recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_template.mac b/recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_template.mac deleted file mode 100755 index 5ad4d6db6b..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_template.mac +++ /dev/null @@ -1,70 +0,0 @@ -# Author: Elise Emond - -/vis/disable - -#-----------------# -# G E O M E T R Y # -#-----------------# - - -# Material database -/gate/geometry/setMaterialDatabase GateMaterials.db - -# World dimensions -/gate/world/geometry/setXLength 600. cm -/gate/world/geometry/setYLength 600. cm -/gate/world/geometry/setZLength 600. cm - -# Scanner -/control/execute geometry_D690.mac - - -#---------------# -# P H Y S I C S # -#---------------# - -/control/execute physics.mac - -#-----------------------------# -# I N I T I A L I S A T I O N # -#-----------------------------# - -/gate/run/initialize - -#-------------------# -# D I G I T I S E R # -#-------------------# - -/control/execute digitiser_D690.mac - -#-------------------------------------# -# C O I N C I D E N C E S O R T E R # -#-------------------------------------# - -/control/execute csorter_D690.mac - -#-------------------------------------# -# R A D I O A C T I V E S O U R C E # -#-------------------------------------# - -/control/execute source_SOURCENAME.mac - -#-----------------------# -# O U T P U T D A T A # -#-----------------------# - -/gate/output/root/enable -/gate/output/root/setFileName RootLM_D690_SOURCENAME -/gate/output/root/setRootHitFlag 0 -/gate/output/root/setRootSinglesFlag 0 -/gate/output/root/setRootCoincidencesFlag 1 - -#---------------------------------# -# A C Q U I S I T I O N T I M E # -#---------------------------------# - -/gate/application/setTimeSlice 100 ms -/gate/application/setTimeStart 0 s -/gate/application/setTimeStop 1 s - -/gate/application/startDAQ diff --git a/recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test1.mac b/recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test1.mac deleted file mode 100755 index 03ddd02e23..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test1.mac +++ /dev/null @@ -1,70 +0,0 @@ -# Author: Elise Emond - -/vis/disable - -#-----------------# -# G E O M E T R Y # -#-----------------# - - -# Material database -/gate/geometry/setMaterialDatabase GateMaterials.db - -# World dimensions -/gate/world/geometry/setXLength 600. cm -/gate/world/geometry/setYLength 600. cm -/gate/world/geometry/setZLength 600. cm - -# Scanner -/control/execute geometry_D690.mac - - -#---------------# -# P H Y S I C S # -#---------------# - -/control/execute physics.mac - -#-----------------------------# -# I N I T I A L I S A T I O N # -#-----------------------------# - -/gate/run/initialize - -#-------------------# -# D I G I T I S E R # -#-------------------# - -/control/execute digitiser_D690.mac - -#-------------------------------------# -# C O I N C I D E N C E S O R T E R # -#-------------------------------------# - -/control/execute csorter_D690.mac - -#-------------------------------------# -# R A D I O A C T I V E S O U R C E # -#-------------------------------------# - -/control/execute source_test1.mac - -#-----------------------# -# O U T P U T D A T A # -#-----------------------# - -/gate/output/root/enable -/gate/output/root/setFileName RootLM_D690_test1 -/gate/output/root/setRootHitFlag 0 -/gate/output/root/setRootSinglesFlag 0 -/gate/output/root/setRootCoincidencesFlag 1 - -#---------------------------------# -# A C Q U I S I T I O N T I M E # -#---------------------------------# - -/gate/application/setTimeSlice 100 ms -/gate/application/setTimeStart 0 s -/gate/application/setTimeStop 1 s - -/gate/application/startDAQ diff --git a/recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test10.mac b/recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test10.mac deleted file mode 100755 index c0aea936d5..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test10.mac +++ /dev/null @@ -1,70 +0,0 @@ -# Author: Elise Emond - -/vis/disable - -#-----------------# -# G E O M E T R Y # -#-----------------# - - -# Material database -/gate/geometry/setMaterialDatabase GateMaterials.db - -# World dimensions -/gate/world/geometry/setXLength 600. cm -/gate/world/geometry/setYLength 600. cm -/gate/world/geometry/setZLength 600. cm - -# Scanner -/control/execute geometry_D690.mac - - -#---------------# -# P H Y S I C S # -#---------------# - -/control/execute physics.mac - -#-----------------------------# -# I N I T I A L I S A T I O N # -#-----------------------------# - -/gate/run/initialize - -#-------------------# -# D I G I T I S E R # -#-------------------# - -/control/execute digitiser_D690.mac - -#-------------------------------------# -# C O I N C I D E N C E S O R T E R # -#-------------------------------------# - -/control/execute csorter_D690.mac - -#-------------------------------------# -# R A D I O A C T I V E S O U R C E # -#-------------------------------------# - -/control/execute source_test10.mac - -#-----------------------# -# O U T P U T D A T A # -#-----------------------# - -/gate/output/root/enable -/gate/output/root/setFileName RootLM_D690_test10 -/gate/output/root/setRootHitFlag 0 -/gate/output/root/setRootSinglesFlag 0 -/gate/output/root/setRootCoincidencesFlag 1 - -#---------------------------------# -# A C Q U I S I T I O N T I M E # -#---------------------------------# - -/gate/application/setTimeSlice 100 ms -/gate/application/setTimeStart 0 s -/gate/application/setTimeStop 1 s - -/gate/application/startDAQ diff --git a/recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test11.mac b/recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test11.mac deleted file mode 100755 index bb308d459c..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test11.mac +++ /dev/null @@ -1,70 +0,0 @@ -# Author: Elise Emond - -/vis/disable - -#-----------------# -# G E O M E T R Y # -#-----------------# - - -# Material database -/gate/geometry/setMaterialDatabase GateMaterials.db - -# World dimensions -/gate/world/geometry/setXLength 600. cm -/gate/world/geometry/setYLength 600. cm -/gate/world/geometry/setZLength 600. cm - -# Scanner -/control/execute geometry_D690.mac - - -#---------------# -# P H Y S I C S # -#---------------# - -/control/execute physics.mac - -#-----------------------------# -# I N I T I A L I S A T I O N # -#-----------------------------# - -/gate/run/initialize - -#-------------------# -# D I G I T I S E R # -#-------------------# - -/control/execute digitiser_D690.mac - -#-------------------------------------# -# C O I N C I D E N C E S O R T E R # -#-------------------------------------# - -/control/execute csorter_D690.mac - -#-------------------------------------# -# R A D I O A C T I V E S O U R C E # -#-------------------------------------# - -/control/execute source_test11.mac - -#-----------------------# -# O U T P U T D A T A # -#-----------------------# - -/gate/output/root/enable -/gate/output/root/setFileName RootLM_D690_test11 -/gate/output/root/setRootHitFlag 0 -/gate/output/root/setRootSinglesFlag 0 -/gate/output/root/setRootCoincidencesFlag 1 - -#---------------------------------# -# A C Q U I S I T I O N T I M E # -#---------------------------------# - -/gate/application/setTimeSlice 100 ms -/gate/application/setTimeStart 0 s -/gate/application/setTimeStop 1 s - -/gate/application/startDAQ diff --git a/recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test12.mac b/recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test12.mac deleted file mode 100755 index 921468cd09..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test12.mac +++ /dev/null @@ -1,70 +0,0 @@ -# Author: Elise Emond - -/vis/disable - -#-----------------# -# G E O M E T R Y # -#-----------------# - - -# Material database -/gate/geometry/setMaterialDatabase GateMaterials.db - -# World dimensions -/gate/world/geometry/setXLength 600. cm -/gate/world/geometry/setYLength 600. cm -/gate/world/geometry/setZLength 600. cm - -# Scanner -/control/execute geometry_D690.mac - - -#---------------# -# P H Y S I C S # -#---------------# - -/control/execute physics.mac - -#-----------------------------# -# I N I T I A L I S A T I O N # -#-----------------------------# - -/gate/run/initialize - -#-------------------# -# D I G I T I S E R # -#-------------------# - -/control/execute digitiser_D690.mac - -#-------------------------------------# -# C O I N C I D E N C E S O R T E R # -#-------------------------------------# - -/control/execute csorter_D690.mac - -#-------------------------------------# -# R A D I O A C T I V E S O U R C E # -#-------------------------------------# - -/control/execute source_test12.mac - -#-----------------------# -# O U T P U T D A T A # -#-----------------------# - -/gate/output/root/enable -/gate/output/root/setFileName RootLM_D690_test12 -/gate/output/root/setRootHitFlag 0 -/gate/output/root/setRootSinglesFlag 0 -/gate/output/root/setRootCoincidencesFlag 1 - -#---------------------------------# -# A C Q U I S I T I O N T I M E # -#---------------------------------# - -/gate/application/setTimeSlice 100 ms -/gate/application/setTimeStart 0 s -/gate/application/setTimeStop 1 s - -/gate/application/startDAQ diff --git a/recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test2.mac b/recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test2.mac deleted file mode 100755 index c96311cdfb..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test2.mac +++ /dev/null @@ -1,70 +0,0 @@ -# Author: Elise Emond - -/vis/disable - -#-----------------# -# G E O M E T R Y # -#-----------------# - - -# Material database -/gate/geometry/setMaterialDatabase GateMaterials.db - -# World dimensions -/gate/world/geometry/setXLength 600. cm -/gate/world/geometry/setYLength 600. cm -/gate/world/geometry/setZLength 600. cm - -# Scanner -/control/execute geometry_D690.mac - - -#---------------# -# P H Y S I C S # -#---------------# - -/control/execute physics.mac - -#-----------------------------# -# I N I T I A L I S A T I O N # -#-----------------------------# - -/gate/run/initialize - -#-------------------# -# D I G I T I S E R # -#-------------------# - -/control/execute digitiser_D690.mac - -#-------------------------------------# -# C O I N C I D E N C E S O R T E R # -#-------------------------------------# - -/control/execute csorter_D690.mac - -#-------------------------------------# -# R A D I O A C T I V E S O U R C E # -#-------------------------------------# - -/control/execute source_test2.mac - -#-----------------------# -# O U T P U T D A T A # -#-----------------------# - -/gate/output/root/enable -/gate/output/root/setFileName RootLM_D690_test2 -/gate/output/root/setRootHitFlag 0 -/gate/output/root/setRootSinglesFlag 0 -/gate/output/root/setRootCoincidencesFlag 1 - -#---------------------------------# -# A C Q U I S I T I O N T I M E # -#---------------------------------# - -/gate/application/setTimeSlice 100 ms -/gate/application/setTimeStart 0 s -/gate/application/setTimeStop 1 s - -/gate/application/startDAQ diff --git a/recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test3.mac b/recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test3.mac deleted file mode 100755 index d268cf46cf..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test3.mac +++ /dev/null @@ -1,70 +0,0 @@ -# Author: Elise Emond - -/vis/disable - -#-----------------# -# G E O M E T R Y # -#-----------------# - - -# Material database -/gate/geometry/setMaterialDatabase GateMaterials.db - -# World dimensions -/gate/world/geometry/setXLength 600. cm -/gate/world/geometry/setYLength 600. cm -/gate/world/geometry/setZLength 600. cm - -# Scanner -/control/execute geometry_D690.mac - - -#---------------# -# P H Y S I C S # -#---------------# - -/control/execute physics.mac - -#-----------------------------# -# I N I T I A L I S A T I O N # -#-----------------------------# - -/gate/run/initialize - -#-------------------# -# D I G I T I S E R # -#-------------------# - -/control/execute digitiser_D690.mac - -#-------------------------------------# -# C O I N C I D E N C E S O R T E R # -#-------------------------------------# - -/control/execute csorter_D690.mac - -#-------------------------------------# -# R A D I O A C T I V E S O U R C E # -#-------------------------------------# - -/control/execute source_test3.mac - -#-----------------------# -# O U T P U T D A T A # -#-----------------------# - -/gate/output/root/enable -/gate/output/root/setFileName RootLM_D690_test3 -/gate/output/root/setRootHitFlag 0 -/gate/output/root/setRootSinglesFlag 0 -/gate/output/root/setRootCoincidencesFlag 1 - -#---------------------------------# -# A C Q U I S I T I O N T I M E # -#---------------------------------# - -/gate/application/setTimeSlice 100 ms -/gate/application/setTimeStart 0 s -/gate/application/setTimeStop 1 s - -/gate/application/startDAQ diff --git a/recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test4.mac b/recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test4.mac deleted file mode 100755 index d5b59f227e..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test4.mac +++ /dev/null @@ -1,70 +0,0 @@ -# Author: Elise Emond - -/vis/disable - -#-----------------# -# G E O M E T R Y # -#-----------------# - - -# Material database -/gate/geometry/setMaterialDatabase GateMaterials.db - -# World dimensions -/gate/world/geometry/setXLength 600. cm -/gate/world/geometry/setYLength 600. cm -/gate/world/geometry/setZLength 600. cm - -# Scanner -/control/execute geometry_D690.mac - - -#---------------# -# P H Y S I C S # -#---------------# - -/control/execute physics.mac - -#-----------------------------# -# I N I T I A L I S A T I O N # -#-----------------------------# - -/gate/run/initialize - -#-------------------# -# D I G I T I S E R # -#-------------------# - -/control/execute digitiser_D690.mac - -#-------------------------------------# -# C O I N C I D E N C E S O R T E R # -#-------------------------------------# - -/control/execute csorter_D690.mac - -#-------------------------------------# -# R A D I O A C T I V E S O U R C E # -#-------------------------------------# - -/control/execute source_test4.mac - -#-----------------------# -# O U T P U T D A T A # -#-----------------------# - -/gate/output/root/enable -/gate/output/root/setFileName RootLM_D690_test4 -/gate/output/root/setRootHitFlag 0 -/gate/output/root/setRootSinglesFlag 0 -/gate/output/root/setRootCoincidencesFlag 1 - -#---------------------------------# -# A C Q U I S I T I O N T I M E # -#---------------------------------# - -/gate/application/setTimeSlice 100 ms -/gate/application/setTimeStart 0 s -/gate/application/setTimeStop 1 s - -/gate/application/startDAQ diff --git a/recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test5.mac b/recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test5.mac deleted file mode 100755 index 8df255b7e9..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test5.mac +++ /dev/null @@ -1,70 +0,0 @@ -# Author: Elise Emond - -/vis/disable - -#-----------------# -# G E O M E T R Y # -#-----------------# - - -# Material database -/gate/geometry/setMaterialDatabase GateMaterials.db - -# World dimensions -/gate/world/geometry/setXLength 600. cm -/gate/world/geometry/setYLength 600. cm -/gate/world/geometry/setZLength 600. cm - -# Scanner -/control/execute geometry_D690.mac - - -#---------------# -# P H Y S I C S # -#---------------# - -/control/execute physics.mac - -#-----------------------------# -# I N I T I A L I S A T I O N # -#-----------------------------# - -/gate/run/initialize - -#-------------------# -# D I G I T I S E R # -#-------------------# - -/control/execute digitiser_D690.mac - -#-------------------------------------# -# C O I N C I D E N C E S O R T E R # -#-------------------------------------# - -/control/execute csorter_D690.mac - -#-------------------------------------# -# R A D I O A C T I V E S O U R C E # -#-------------------------------------# - -/control/execute source_test5.mac - -#-----------------------# -# O U T P U T D A T A # -#-----------------------# - -/gate/output/root/enable -/gate/output/root/setFileName RootLM_D690_test5 -/gate/output/root/setRootHitFlag 0 -/gate/output/root/setRootSinglesFlag 0 -/gate/output/root/setRootCoincidencesFlag 1 - -#---------------------------------# -# A C Q U I S I T I O N T I M E # -#---------------------------------# - -/gate/application/setTimeSlice 100 ms -/gate/application/setTimeStart 0 s -/gate/application/setTimeStop 1 s - -/gate/application/startDAQ diff --git a/recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test6.mac b/recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test6.mac deleted file mode 100755 index 30853a500a..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test6.mac +++ /dev/null @@ -1,70 +0,0 @@ -# Author: Elise Emond - -/vis/disable - -#-----------------# -# G E O M E T R Y # -#-----------------# - - -# Material database -/gate/geometry/setMaterialDatabase GateMaterials.db - -# World dimensions -/gate/world/geometry/setXLength 600. cm -/gate/world/geometry/setYLength 600. cm -/gate/world/geometry/setZLength 600. cm - -# Scanner -/control/execute geometry_D690.mac - - -#---------------# -# P H Y S I C S # -#---------------# - -/control/execute physics.mac - -#-----------------------------# -# I N I T I A L I S A T I O N # -#-----------------------------# - -/gate/run/initialize - -#-------------------# -# D I G I T I S E R # -#-------------------# - -/control/execute digitiser_D690.mac - -#-------------------------------------# -# C O I N C I D E N C E S O R T E R # -#-------------------------------------# - -/control/execute csorter_D690.mac - -#-------------------------------------# -# R A D I O A C T I V E S O U R C E # -#-------------------------------------# - -/control/execute source_test6.mac - -#-----------------------# -# O U T P U T D A T A # -#-----------------------# - -/gate/output/root/enable -/gate/output/root/setFileName RootLM_D690_test6 -/gate/output/root/setRootHitFlag 0 -/gate/output/root/setRootSinglesFlag 0 -/gate/output/root/setRootCoincidencesFlag 1 - -#---------------------------------# -# A C Q U I S I T I O N T I M E # -#---------------------------------# - -/gate/application/setTimeSlice 100 ms -/gate/application/setTimeStart 0 s -/gate/application/setTimeStop 1 s - -/gate/application/startDAQ diff --git a/recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test7.mac b/recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test7.mac deleted file mode 100755 index b2cc950eb8..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test7.mac +++ /dev/null @@ -1,70 +0,0 @@ -# Author: Elise Emond - -/vis/disable - -#-----------------# -# G E O M E T R Y # -#-----------------# - - -# Material database -/gate/geometry/setMaterialDatabase GateMaterials.db - -# World dimensions -/gate/world/geometry/setXLength 600. cm -/gate/world/geometry/setYLength 600. cm -/gate/world/geometry/setZLength 600. cm - -# Scanner -/control/execute geometry_D690.mac - - -#---------------# -# P H Y S I C S # -#---------------# - -/control/execute physics.mac - -#-----------------------------# -# I N I T I A L I S A T I O N # -#-----------------------------# - -/gate/run/initialize - -#-------------------# -# D I G I T I S E R # -#-------------------# - -/control/execute digitiser_D690.mac - -#-------------------------------------# -# C O I N C I D E N C E S O R T E R # -#-------------------------------------# - -/control/execute csorter_D690.mac - -#-------------------------------------# -# R A D I O A C T I V E S O U R C E # -#-------------------------------------# - -/control/execute source_test7.mac - -#-----------------------# -# O U T P U T D A T A # -#-----------------------# - -/gate/output/root/enable -/gate/output/root/setFileName RootLM_D690_test7 -/gate/output/root/setRootHitFlag 0 -/gate/output/root/setRootSinglesFlag 0 -/gate/output/root/setRootCoincidencesFlag 1 - -#---------------------------------# -# A C Q U I S I T I O N T I M E # -#---------------------------------# - -/gate/application/setTimeSlice 100 ms -/gate/application/setTimeStart 0 s -/gate/application/setTimeStop 1 s - -/gate/application/startDAQ diff --git a/recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test8.mac b/recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test8.mac deleted file mode 100755 index 7e8decaa30..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test8.mac +++ /dev/null @@ -1,70 +0,0 @@ -# Author: Elise Emond - -/vis/disable - -#-----------------# -# G E O M E T R Y # -#-----------------# - - -# Material database -/gate/geometry/setMaterialDatabase GateMaterials.db - -# World dimensions -/gate/world/geometry/setXLength 600. cm -/gate/world/geometry/setYLength 600. cm -/gate/world/geometry/setZLength 600. cm - -# Scanner -/control/execute geometry_D690.mac - - -#---------------# -# P H Y S I C S # -#---------------# - -/control/execute physics.mac - -#-----------------------------# -# I N I T I A L I S A T I O N # -#-----------------------------# - -/gate/run/initialize - -#-------------------# -# D I G I T I S E R # -#-------------------# - -/control/execute digitiser_D690.mac - -#-------------------------------------# -# C O I N C I D E N C E S O R T E R # -#-------------------------------------# - -/control/execute csorter_D690.mac - -#-------------------------------------# -# R A D I O A C T I V E S O U R C E # -#-------------------------------------# - -/control/execute source_test8.mac - -#-----------------------# -# O U T P U T D A T A # -#-----------------------# - -/gate/output/root/enable -/gate/output/root/setFileName RootLM_D690_test8 -/gate/output/root/setRootHitFlag 0 -/gate/output/root/setRootSinglesFlag 0 -/gate/output/root/setRootCoincidencesFlag 1 - -#---------------------------------# -# A C Q U I S I T I O N T I M E # -#---------------------------------# - -/gate/application/setTimeSlice 100 ms -/gate/application/setTimeStart 0 s -/gate/application/setTimeStop 1 s - -/gate/application/startDAQ diff --git a/recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test9.mac b/recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test9.mac deleted file mode 100755 index 9375c1020d..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/Gate_macros/main_D690_test9.mac +++ /dev/null @@ -1,70 +0,0 @@ -# Author: Elise Emond - -/vis/disable - -#-----------------# -# G E O M E T R Y # -#-----------------# - - -# Material database -/gate/geometry/setMaterialDatabase GateMaterials.db - -# World dimensions -/gate/world/geometry/setXLength 600. cm -/gate/world/geometry/setYLength 600. cm -/gate/world/geometry/setZLength 600. cm - -# Scanner -/control/execute geometry_D690.mac - - -#---------------# -# P H Y S I C S # -#---------------# - -/control/execute physics.mac - -#-----------------------------# -# I N I T I A L I S A T I O N # -#-----------------------------# - -/gate/run/initialize - -#-------------------# -# D I G I T I S E R # -#-------------------# - -/control/execute digitiser_D690.mac - -#-------------------------------------# -# C O I N C I D E N C E S O R T E R # -#-------------------------------------# - -/control/execute csorter_D690.mac - -#-------------------------------------# -# R A D I O A C T I V E S O U R C E # -#-------------------------------------# - -/control/execute source_test9.mac - -#-----------------------# -# O U T P U T D A T A # -#-----------------------# - -/gate/output/root/enable -/gate/output/root/setFileName RootLM_D690_test9 -/gate/output/root/setRootHitFlag 0 -/gate/output/root/setRootSinglesFlag 0 -/gate/output/root/setRootCoincidencesFlag 1 - -#---------------------------------# -# A C Q U I S I T I O N T I M E # -#---------------------------------# - -/gate/application/setTimeSlice 100 ms -/gate/application/setTimeStart 0 s -/gate/application/setTimeStop 1 s - -/gate/application/startDAQ diff --git a/recon_test_pack/ROOT_STIR_consistency/Gate_macros/physics.mac b/recon_test_pack/ROOT_STIR_consistency/Gate_macros/physics.mac deleted file mode 100755 index c444c103b0..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/Gate_macros/physics.mac +++ /dev/null @@ -1,9 +0,0 @@ -/gate/physics/addProcess PhotoElectric -/gate/physics/processes/PhotoElectric/setModel StandardModel - -/gate/physics/addProcess Compton -/gate/physics/processes/Compton/setModel StandardModel - -/gate/physics/processList Enabled - -/gate/physics/processList Initialized diff --git a/recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test1.mac b/recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test1.mac deleted file mode 100755 index 9f376ac102..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test1.mac +++ /dev/null @@ -1,46 +0,0 @@ -#===================================================== - -# P A R T I C L E S O U R C E - -#===================================================== - -/gate/source/addSource posiFDG - - - -/gate/source/posiFDG/setType backtoback - - - -# The particles emitted by the source are gammas - -/gate/source/posiFDG/gps/particle gamma - - - -# The gammas have an energy of 511 keV - -/gate/source/posiFDG/gps/energytype Mono - -/gate/source/posiFDG/gps/monoenergy 0.511 MeV - - - -/gate/source/posiFDG/setActivity 5000000 becquerel - - - -/gate/source/posiFDG/setForcedUnstableFlag true - -/gate/source/posiFDG/setForcedHalfLife 6586 s - -/gate/source/posiFDG/gps/angtype iso - - -#Shape of the source -/gate/source/posiFDG/gps/type Point - -#Position of the source -/gate/source/posiFDG/gps/centre 19. 0. 7. cm - -/gate/source/list diff --git a/recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test10.mac b/recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test10.mac deleted file mode 100755 index c5a40693b7..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test10.mac +++ /dev/null @@ -1,49 +0,0 @@ -#===================================================== - -# P A R T I C L E S O U R C E - -#===================================================== - -/gate/source/addSource posiFDG - - -/gate/source/posiFDG/setType backtoback - - - -# The particles emitted by the source are gammas - -/gate/source/posiFDG/gps/particle gamma - - - -# The gammas have an energy of 511 keV - -/gate/source/posiFDG/gps/energytype Mono - -/gate/source/posiFDG/gps/monoenergy 0.511 MeV - - - -/gate/source/posiFDG/setActivity 5000000 becquerel - - - -/gate/source/posiFDG/setForcedUnstableFlag true - -/gate/source/posiFDG/setForcedHalfLife 6586 s - -/gate/source/posiFDG/gps/angtype iso - - -#Shape of the source -/gate/source/posiFDG/gps/type Point -#/gate/source/posiFDG/gps/shape Ellipsoid -#/gate/source/posiFDG/gps/halfx 5. cm -#/gate/source/posiFDG/gps/halfy 5. cm -#/gate/source/posiFDG/gps/halfz 5. cm - -#Position of the source -/gate/source/posiFDG/gps/centre 0. -19. -7. cm - -/gate/source/list diff --git a/recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test11.mac b/recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test11.mac deleted file mode 100755 index 89ae62390d..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test11.mac +++ /dev/null @@ -1,49 +0,0 @@ -#===================================================== - -# P A R T I C L E S O U R C E - -#===================================================== - -/gate/source/addSource posiFDG - - -/gate/source/posiFDG/setType backtoback - - - -# The particles emitted by the source are gammas - -/gate/source/posiFDG/gps/particle gamma - - - -# The gammas have an energy of 511 keV - -/gate/source/posiFDG/gps/energytype Mono - -/gate/source/posiFDG/gps/monoenergy 0.511 MeV - - - -/gate/source/posiFDG/setActivity 5000000 becquerel - - - -/gate/source/posiFDG/setForcedUnstableFlag true - -/gate/source/posiFDG/setForcedHalfLife 6586 s - -/gate/source/posiFDG/gps/angtype iso - - -#Shape of the source -/gate/source/posiFDG/gps/type Point -#/gate/source/posiFDG/gps/shape Ellipsoid -#/gate/source/posiFDG/gps/halfx 5. cm -#/gate/source/posiFDG/gps/halfy 5. cm -#/gate/source/posiFDG/gps/halfz 5. cm - -#Position of the source -/gate/source/posiFDG/gps/centre 9.5 9.5 -7. cm - -/gate/source/list diff --git a/recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test12.mac b/recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test12.mac deleted file mode 100755 index fd158ba781..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test12.mac +++ /dev/null @@ -1,49 +0,0 @@ -#===================================================== - -# P A R T I C L E S O U R C E - -#===================================================== - -/gate/source/addSource posiFDG - - -/gate/source/posiFDG/setType backtoback - - - -# The particles emitted by the source are gammas - -/gate/source/posiFDG/gps/particle gamma - - - -# The gammas have an energy of 511 keV - -/gate/source/posiFDG/gps/energytype Mono - -/gate/source/posiFDG/gps/monoenergy 0.511 MeV - - - -/gate/source/posiFDG/setActivity 5000000 becquerel - - - -/gate/source/posiFDG/setForcedUnstableFlag true - -/gate/source/posiFDG/setForcedHalfLife 6586 s - -/gate/source/posiFDG/gps/angtype iso - - -#Shape of the source -/gate/source/posiFDG/gps/type Point -#/gate/source/posiFDG/gps/shape Ellipsoid -#/gate/source/posiFDG/gps/halfx 5. cm -#/gate/source/posiFDG/gps/halfy 5. cm -#/gate/source/posiFDG/gps/halfz 5. cm - -#Position of the source -/gate/source/posiFDG/gps/centre 9.5 -9.5 -7. cm - -/gate/source/list diff --git a/recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test2.mac b/recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test2.mac deleted file mode 100755 index ecec48786a..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test2.mac +++ /dev/null @@ -1,50 +0,0 @@ -#===================================================== - -# P A R T I C L E S O U R C E - -#===================================================== - -/gate/source/addSource posiFDG - - - -/gate/source/posiFDG/setType backtoback - - - -# The particles emitted by the source are gammas - -/gate/source/posiFDG/gps/particle gamma - - - -# The gammas have an energy of 511 keV - -/gate/source/posiFDG/gps/energytype Mono - -/gate/source/posiFDG/gps/monoenergy 0.511 MeV - - - -/gate/source/posiFDG/setActivity 5000000 becquerel - - - -/gate/source/posiFDG/setForcedUnstableFlag true - -/gate/source/posiFDG/setForcedHalfLife 6586 s - -/gate/source/posiFDG/gps/angtype iso - - -#Shape of the source -/gate/source/posiFDG/gps/type Point -#/gate/source/posiFDG/gps/shape Ellipsoid -#/gate/source/posiFDG/gps/halfx 5. cm -#/gate/source/posiFDG/gps/halfy 5. cm -#/gate/source/posiFDG/gps/halfz 5. cm - -#Position of the source -/gate/source/posiFDG/gps/centre -19. 0. 7 cm - -/gate/source/list diff --git a/recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test3.mac b/recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test3.mac deleted file mode 100755 index 7abef4777f..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test3.mac +++ /dev/null @@ -1,50 +0,0 @@ -#===================================================== - -# P A R T I C L E S O U R C E - -#===================================================== - -/gate/source/addSource posiFDG - - - -/gate/source/posiFDG/setType backtoback - - - -# The particles emitted by the source are gammas - -/gate/source/posiFDG/gps/particle gamma - - - -# The gammas have an energy of 511 keV - -/gate/source/posiFDG/gps/energytype Mono - -/gate/source/posiFDG/gps/monoenergy 0.511 MeV - - - -/gate/source/posiFDG/setActivity 5000000 becquerel - - - -/gate/source/posiFDG/setForcedUnstableFlag true - -/gate/source/posiFDG/setForcedHalfLife 6586 s - -/gate/source/posiFDG/gps/angtype iso - - -#Shape of the source -/gate/source/posiFDG/gps/type Point -#/gate/source/posiFDG/gps/shape Ellipsoid -#/gate/source/posiFDG/gps/halfx 5. cm -#/gate/source/posiFDG/gps/halfy 5. cm -#/gate/source/posiFDG/gps/halfz 5. cm - -#Position of the source -/gate/source/posiFDG/gps/centre 0. 19. 7. cm - -/gate/source/list diff --git a/recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test4.mac b/recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test4.mac deleted file mode 100755 index 3854e43c98..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test4.mac +++ /dev/null @@ -1,50 +0,0 @@ -#===================================================== - -# P A R T I C L E S O U R C E - -#===================================================== - -/gate/source/addSource posiFDG - - - -/gate/source/posiFDG/setType backtoback - - - -# The particles emitted by the source are gammas - -/gate/source/posiFDG/gps/particle gamma - - - -# The gammas have an energy of 511 keV - -/gate/source/posiFDG/gps/energytype Mono - -/gate/source/posiFDG/gps/monoenergy 0.511 MeV - - - -/gate/source/posiFDG/setActivity 5000000 becquerel - - - -/gate/source/posiFDG/setForcedUnstableFlag true - -/gate/source/posiFDG/setForcedHalfLife 6586 s - -/gate/source/posiFDG/gps/angtype iso - - -#Shape of the source -/gate/source/posiFDG/gps/type Point -#/gate/source/posiFDG/gps/shape Ellipsoid -#/gate/source/posiFDG/gps/halfx 5. cm -#/gate/source/posiFDG/gps/halfy 5. cm -#/gate/source/posiFDG/gps/halfz 5. cm - -#Position of the source -/gate/source/posiFDG/gps/centre 0. -19. 7. cm - -/gate/source/list diff --git a/recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test5.mac b/recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test5.mac deleted file mode 100755 index 3038af14a0..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test5.mac +++ /dev/null @@ -1,50 +0,0 @@ -#===================================================== - -# P A R T I C L E S O U R C E - -#===================================================== - -/gate/source/addSource posiFDG - - - -/gate/source/posiFDG/setType backtoback - - - -# The particles emitted by the source are gammas - -/gate/source/posiFDG/gps/particle gamma - - - -# The gammas have an energy of 511 keV - -/gate/source/posiFDG/gps/energytype Mono - -/gate/source/posiFDG/gps/monoenergy 0.511 MeV - - - -/gate/source/posiFDG/setActivity 5000000 becquerel - - - -/gate/source/posiFDG/setForcedUnstableFlag true - -/gate/source/posiFDG/setForcedHalfLife 6586 s - -/gate/source/posiFDG/gps/angtype iso - - -#Shape of the source -/gate/source/posiFDG/gps/type Point -#/gate/source/posiFDG/gps/shape Ellipsoid -#/gate/source/posiFDG/gps/halfx 5. cm -#/gate/source/posiFDG/gps/halfy 5. cm -#/gate/source/posiFDG/gps/halfz 5. cm - -#Position of the source -/gate/source/posiFDG/gps/centre 9.5 9.5 7. cm - -/gate/source/list diff --git a/recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test6.mac b/recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test6.mac deleted file mode 100755 index e2d812e1e5..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test6.mac +++ /dev/null @@ -1,50 +0,0 @@ -#===================================================== - -# P A R T I C L E S O U R C E - -#===================================================== - -/gate/source/addSource posiFDG - - - -/gate/source/posiFDG/setType backtoback - - - -# The particles emitted by the source are gammas - -/gate/source/posiFDG/gps/particle gamma - - - -# The gammas have an energy of 511 keV - -/gate/source/posiFDG/gps/energytype Mono - -/gate/source/posiFDG/gps/monoenergy 0.511 MeV - - - -/gate/source/posiFDG/setActivity 5000000 becquerel - - - -/gate/source/posiFDG/setForcedUnstableFlag true - -/gate/source/posiFDG/setForcedHalfLife 6586 s - -/gate/source/posiFDG/gps/angtype iso - - -#Shape of the source -/gate/source/posiFDG/gps/type Point -#/gate/source/posiFDG/gps/shape Ellipsoid -#/gate/source/posiFDG/gps/halfx 5. cm -#/gate/source/posiFDG/gps/halfy 5. cm -#/gate/source/posiFDG/gps/halfz 5. cm - -#Position of the source -/gate/source/posiFDG/gps/centre 9.5 -9.5 7. cm - -/gate/source/list diff --git a/recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test7.mac b/recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test7.mac deleted file mode 100755 index 7cd82bd21c..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test7.mac +++ /dev/null @@ -1,50 +0,0 @@ -#===================================================== - -# P A R T I C L E S O U R C E - -#===================================================== - -/gate/source/addSource posiFDG - - - -/gate/source/posiFDG/setType backtoback - - - -# The particles emitted by the source are gammas - -/gate/source/posiFDG/gps/particle gamma - - - -# The gammas have an energy of 511 keV - -/gate/source/posiFDG/gps/energytype Mono - -/gate/source/posiFDG/gps/monoenergy 0.511 MeV - - - -/gate/source/posiFDG/setActivity 5000000 becquerel - - - -/gate/source/posiFDG/setForcedUnstableFlag true - -/gate/source/posiFDG/setForcedHalfLife 6586 s - -/gate/source/posiFDG/gps/angtype iso - - -#Shape of the source -/gate/source/posiFDG/gps/type Point -#/gate/source/posiFDG/gps/shape Ellipsoid -#/gate/source/posiFDG/gps/halfx 5. cm -#/gate/source/posiFDG/gps/halfy 5. cm -#/gate/source/posiFDG/gps/halfz 5. cm - -#Position of the source -/gate/source/posiFDG/gps/centre 19. 0. -7. cm - -/gate/source/list diff --git a/recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test8.mac b/recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test8.mac deleted file mode 100755 index 4255cd6d11..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test8.mac +++ /dev/null @@ -1,50 +0,0 @@ -#===================================================== - -# P A R T I C L E S O U R C E - -#===================================================== - -/gate/source/addSource posiFDG - - - -/gate/source/posiFDG/setType backtoback - - - -# The particles emitted by the source are gammas - -/gate/source/posiFDG/gps/particle gamma - - - -# The gammas have an energy of 511 keV - -/gate/source/posiFDG/gps/energytype Mono - -/gate/source/posiFDG/gps/monoenergy 0.511 MeV - - - -/gate/source/posiFDG/setActivity 5000000 becquerel - - - -/gate/source/posiFDG/setForcedUnstableFlag true - -/gate/source/posiFDG/setForcedHalfLife 6586 s - -/gate/source/posiFDG/gps/angtype iso - - -#Shape of the source -/gate/source/posiFDG/gps/type Point -#/gate/source/posiFDG/gps/shape Ellipsoid -#/gate/source/posiFDG/gps/halfx 5. cm -#/gate/source/posiFDG/gps/halfy 5. cm -#/gate/source/posiFDG/gps/halfz 5. cm - -#Position of the source -/gate/source/posiFDG/gps/centre -19. 0. -7. cm - -/gate/source/list diff --git a/recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test9.mac b/recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test9.mac deleted file mode 100755 index 52b5bd3c94..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/Gate_macros/source_test9.mac +++ /dev/null @@ -1,49 +0,0 @@ -#===================================================== - -# P A R T I C L E S O U R C E - -#===================================================== - -/gate/source/addSource posiFDG - - -/gate/source/posiFDG/setType backtoback - - - -# The particles emitted by the source are gammas - -/gate/source/posiFDG/gps/particle gamma - - - -# The gammas have an energy of 511 keV - -/gate/source/posiFDG/gps/energytype Mono - -/gate/source/posiFDG/gps/monoenergy 0.511 MeV - - - -/gate/source/posiFDG/setActivity 5000000 becquerel - - - -/gate/source/posiFDG/setForcedUnstableFlag true - -/gate/source/posiFDG/setForcedHalfLife 6586 s - -/gate/source/posiFDG/gps/angtype iso - - -#Shape of the source -/gate/source/posiFDG/gps/type Point -#/gate/source/posiFDG/gps/shape Ellipsoid -#/gate/source/posiFDG/gps/halfx 5. cm -#/gate/source/posiFDG/gps/halfy 5. cm -#/gate/source/posiFDG/gps/halfz 5. cm - -#Position of the source -/gate/source/posiFDG/gps/centre 0. 19. -7. cm - -/gate/source/list diff --git a/recon_test_pack/ROOT_STIR_consistency/README.md b/recon_test_pack/ROOT_STIR_consistency/README.md deleted file mode 100644 index ab99ec2e1a..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/README.md +++ /dev/null @@ -1,9 +0,0 @@ -## TOF consistency checks for STIR -# Author: Elise Emond - -Files in the folder were created to check: -* Whether the TOF STIR implementation was correct. To do so you need: - 1. To run `./run_pretest_script.sh` in the terminal to create the Gate root files for different point sources. - 2. Run the STIR test: `src/recon_test/test_consistency_root`. This test should tell you whether it failed or not, using centres of mass corresponding to the maximum value for a detection bin (defined by LOR + time bin). If failed, the TOF backprojection is incorrect. - 3. The image coordinates corresponding to the centres of mass were written as a txt file by the previous test. You can plot using `make_plot.py`. -* To check if the time resolution defined in the Gate digitiser for a single detector corresponds to the time resolution in STIR scanner template. Have a look at `TOFresolutionConsistency/README.md` for more information. diff --git a/recon_test_pack/ROOT_STIR_consistency/TOFresolutionConsistency/README.md b/recon_test_pack/ROOT_STIR_consistency/TOFresolutionConsistency/README.md deleted file mode 100644 index 9b88c19289..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/TOFresolutionConsistency/README.md +++ /dev/null @@ -1,17 +0,0 @@ -# Using TOF Gate data - setting up the single detector time resolution -## Author: Elise Emond - -In Gate, you can only define a "single detector time resolution" =/= coincidence time resolution. - -If you know the expected coincidence time resolution for a given scanner (e.g. 550 ps for the Discovery 710) you can follow this to calibrate accordingly your Gate scanner. - -Command for Gate single detector time resolution: /gate/digitizer/Singles/timeResolution/setTimeResolution in digitiser. - -**What you can do:** - -1. Run a Gate simulation with perfect time resolution (in the digitiser, setTimeResolution=0 ns or just comment out that line - default value), use the ascii output in main_D690_centre.mac (it will be read in Python) -2. In getTimingResolution.py, run the last cell (starting from last #%%) - don't forget to modify the output name (it is written Output_centre2Coincidences.dat). You can get the theoretical "detector response" (some kind of triangular response). -3. Then you can deconvolve the theoretical distribution (Gaussian distribution with expected FWHM, for Discovery 710 550ps) with this detector response to get an estimate of the single detector time resolution (you will obtain a Gaussian distribution after the deconvolution with a slightly lower FWHM: use setTimeResolution = this new FWHM/sqrt(2)) -4. Check that this is correct: modify once again the setTimeResolution in the digitiser with the value you just calculated and then run the second to last Python cell in getTimingResolution.py to obtain the coincidence FWHM. - -*NB: sadly I had written a nice Mathematica script for convolution/deconvolution but lost it when my computer crashed at the MIC 2017 and forgot to recover this file... You can however use the file in eliseemond/Mathematica/ConvolutionEffectiveEnergy.nb as a model to convolve distributions in Mathematica.* diff --git a/recon_test_pack/ROOT_STIR_consistency/TOFresolutionConsistency/digitiser_D690.mac b/recon_test_pack/ROOT_STIR_consistency/TOFresolutionConsistency/digitiser_D690.mac deleted file mode 100644 index 26934cf253..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/TOFresolutionConsistency/digitiser_D690.mac +++ /dev/null @@ -1,20 +0,0 @@ -/gate/digitizer/Singles/insert adder -/gate/digitizer/Singles/insert readout -/gate/digitizer/Singles/readout/setDepth 1 - - - -/gate/digitizer/Singles/insert blurring -/gate/digitizer/Singles/blurring/setResolution 0.124 #checked -/gate/digitizer/Singles/blurring/setEnergyOfReference 511. keV - - - -/gate/digitizer/Singles/insert thresholder -/gate/digitizer/Singles/thresholder/setThreshold 425. keV #checked -/gate/digitizer/Singles/insert upholder -/gate/digitizer/Singles/upholder/setUphold 650. keV #checked - - -/gate/digitizer/Singles/insert timeResolution -/gate/digitizer/Singles/timeResolution/setTimeResolution 384.76 ps \ No newline at end of file diff --git a/recon_test_pack/ROOT_STIR_consistency/TOFresolutionConsistency/getTimingResolution.py b/recon_test_pack/ROOT_STIR_consistency/TOFresolutionConsistency/getTimingResolution.py deleted file mode 100644 index 55a43fac52..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/TOFresolutionConsistency/getTimingResolution.py +++ /dev/null @@ -1,104 +0,0 @@ -import numpy as npy -import matplotlib.pyplot as plt -from scipy.optimize import curve_fit -from scipy import signal - -def extract_timeStamps(filename): - with open(filename,'r') as f: - line_content = f.readlines() - f.close() - line_content = [x.split() for x in line_content] - line_content = [[x[6],x[29],x[12],x[35]] for x in line_content ] - return line_content - -def get_timeDifference(time_list): - timeDiff=[] - for x in time_list: - if (int(x[2])(x0-b) and xindex= x0 and xindex <(x0+b)): - y[nb]=a-xindex*a/b - nb=nb+1 - return y - -def get_responseFit(timedifferencearray,nbofevents): - m=0 - a=12000 - b=100 - params = curve_fit(response_noTimeRes, timedifferencearray, nbofevents, p0=[a,m,b]) - return [response_noTimeRes(timedifferencearray, params[0][0],params[0][1],params[0][2]),params] - -def reload_data_timing(name): - npzfile=npy.load(name) - # Order should be: timeDiff, nbEvents, nbEventsFit, firstparamfit, secondparamfit, tirdparamfit - return [npzfile[npzfile.files[0]],npzfile[npzfile.files[1]],npzfile[npzfile.files[2]],npzfile[npzfile.files[3]],npzfile[npzfile.files[4]],npzfile[npzfile.files[5]]] - -#%% -time_list = extract_timeStamps("Output_centreCoincidences.dat") -timeDiff=get_timeDifference(time_list) -[sampledTimeDiffTim,nbEventsTim]=sample_timeDifference_distribution(timeDiff) -plot_timeDifference_distribution(sampledTimeDiffTim,nbEventsTim) -[nbEventsFitTim,paramsTim] = get_gaussianFit(sampledTimeDiffTim,nbEventsTim) -plot_timeDifference_distribution(sampledTimeDiffTim,nbEventsFitTim) -FWHM=get_FWHM(paramsTim[0][2]) - -npy.savez("centreWithTimeResolution.npz",sampledTimeDiffTim=sampledTimeDiffTim, - nbEventsTim=nbEventsTim,nbEventsFitTim=nbEventsFitTim, a=paramsTim[0][0],x0=paramsTim[0][1],sigma=paramsTim[0][2]) -#%% -time_list = extract_timeStamps("Output_centre2Coincidences.dat") -timeDiff=get_timeDifference(time_list) -[sampledTimeDiffNoTim,nbEventsNoTim]=sample_timeDifference_distribution(timeDiff) -plt.figure() -plot_timeDifference_distribution(sampledTimeDiffNoTim,nbEventsNoTim) -[nbEventsFitNoTim,paramsNoTim]= get_responseFit(sampledTimeDiffNoTim,nbEventsNoTim) -plot_timeDifference_distribution(sampledTimeDiffNoTim,nbEventsFitNoTim) - -npy.savez("centreWithoutTimeResolution.npz",sampledTimeDiffNoTim=sampledTimeDiffNoTim, - nbEventsNoTim=nbEventsNoTim,nbEventsFitNoTim=nbEventsFitNoTim, a=paramsNoTim[0][0],x0=paramsNoTim[0][1],b=paramsNoTim[0][2]) diff --git a/recon_test_pack/ROOT_STIR_consistency/TOFresolutionConsistency/main_D690_centre.mac b/recon_test_pack/ROOT_STIR_consistency/TOFresolutionConsistency/main_D690_centre.mac deleted file mode 100644 index 97c8e0db07..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/TOFresolutionConsistency/main_D690_centre.mac +++ /dev/null @@ -1,105 +0,0 @@ -# Auteur: Elise Emond - -#-------------# -# V I S U E L # -#-------------# - -/vis/disable -#/vis/enable - -#/control/execute visualisation.mac - -#-------------------# -# G O M T R I E # -#-------------------# - - -# Base de donn‚es des mat‚riaux -/gate/geometry/setMaterialDatabase GateMaterials.db - -# Dimensions du monde -/gate/world/geometry/setXLength 600. cm -/gate/world/geometry/setYLength 600. cm -/gate/world/geometry/setZLength 600. cm - -# Scanner - -/control/execute geometry_D690.mac - - -#-----------------# -# P H Y S I Q U E # -#-----------------# - -/control/execute physics.mac - -#---------# -# C U T S # -#---------# - -/control/execute cuts.mac - -#-----------------------------# -# I N I T I A L I S A T I O N # -#-----------------------------# - -/gate/run/initialize - -#-------------------# -# D I G I T I S E R # -#-------------------# - -/control/execute digitiser_D690.mac - -#-------------------------------------# -# C O I N C I D E N C E S O R T E R # -#-------------------------------------# - -/control/execute csorter_D690.mac - -#-------------------------------------# -# S O U R C E R A D I O A C T I V E # -#-------------------------------------# - -/control/execute source_centre.mac - -#-----------------------------------# -# D O N N É E S E N S O R T I E # -#-----------------------------------# - -/gate/output/root/enable -/gate/output/root/setFileName RootLM_D690_centre -/gate/output/root/setRootHitFlag 0 -/gate/output/root/setRootSinglesFlag 0 -/gate/output/root/setRootCoincidencesFlag 1 -/gate/output/root/setRootdelayFlag 0 - -/gate/output/ascii/enable -/gate/output/ascii/setFileName Output_centre -/gate/output/ascii/setOutFileHitsFlag 0 -/gate/output/ascii/setOutFileSinglesFlag 0 -/gate/output/ascii/setOutFileCoincidencesFlag 1 -/gate/output/ascii/setOutFiledelayFlag 0 - -#---------------------------# -# R A N D O M E N G I N E # -#---------------------------# - -# JamesRandom Ranlux64 MersenneTwister -#/gate/random/setEngineName JamesRandom -#/gate/random/setEngineSeed default -#/gate/random/setEngineSeed auto -#/gate/random/setEngineSeed 123456789 -#/gate/random/setEngineSeed default -#/gate/random/resetEngineFrom fileName -#/gate/random/verbose 1 - -#---------------------------------------# -# T E M P S D ' A C Q U I S I T I O N # -#---------------------------------------# - -/gate/application/setTimeSlice 100 ms -/gate/application/setTimeStart 0 s -/gate/application/setTimeStop 10 s - -/gate/application/startDAQ diff --git a/recon_test_pack/ROOT_STIR_consistency/TOFresolutionConsistency/source_centre.mac b/recon_test_pack/ROOT_STIR_consistency/TOFresolutionConsistency/source_centre.mac deleted file mode 100644 index 3089286fe7..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/TOFresolutionConsistency/source_centre.mac +++ /dev/null @@ -1,50 +0,0 @@ -#===================================================== - -# P A R T I C L E S O U R C E - -#===================================================== - -/gate/source/addSource posiFDG - - - -/gate/source/posiFDG/setType backtoback - - - -# The particles emitted by the source are gammas - -/gate/source/posiFDG/gps/particle gamma - - - -# The gammas have an energy of 511 keV - -/gate/source/posiFDG/gps/energytype Mono - -/gate/source/posiFDG/gps/monoenergy 0.511 MeV - - - -/gate/source/posiFDG/setActivity 5000000 becquerel - - - -/gate/source/posiFDG/setForcedUnstableFlag true - -/gate/source/posiFDG/setForcedHalfLife 6586 s - -/gate/source/posiFDG/gps/angtype iso - - -#Shape of the source -/gate/source/posiFDG/gps/type Point -#/gate/source/posiFDG/gps/shape Ellipsoid -#/gate/source/posiFDG/gps/halfx 5. cm -#/gate/source/posiFDG/gps/halfy 5. cm -#/gate/source/posiFDG/gps/halfz 5. cm - -#Position of the source -/gate/source/posiFDG/gps/centre 0. 0. 0. cm - -/gate/source/list diff --git a/recon_test_pack/ROOT_STIR_consistency/generate_image1.par b/recon_test_pack/ROOT_STIR_consistency/generate_image1.par deleted file mode 100755 index b7f2687589..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/generate_image1.par +++ /dev/null @@ -1,23 +0,0 @@ -generate_image Parameters := -output filename:=stir_image1.v -X output image size (in pixels):=301 -Y output image size (in pixels):=301 -Z output image size (in pixels):=47 -X voxel size (in mm):= 2.1306 -Y voxel size (in mm):= 2.1306 -Z voxel size (in mm) :=3.27 - - Z number of samples to take per voxel := 5 - Y number of samples to take per voxel := 5 - X number of samples to take per voxel := 5 - -shape type:= Ellipsoid -Ellipsoid Parameters:= - radius-x (in mm):=2 - radius-y (in mm):=2 - radius-z (in mm):=2 - origin (in mm):={145.21,0,190} - END:= -value :=1 - -END:= diff --git a/recon_test_pack/ROOT_STIR_consistency/generate_image10.par b/recon_test_pack/ROOT_STIR_consistency/generate_image10.par deleted file mode 100755 index 642e9e8347..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/generate_image10.par +++ /dev/null @@ -1,23 +0,0 @@ -generate_image Parameters := -output filename:=stir_image10.v -X output image size (in pixels):=301 -Y output image size (in pixels):=301 -Z output image size (in pixels):=47 -X voxel size (in mm):= 2.1306 -Y voxel size (in mm):= 2.1306 -Z voxel size (in mm) :=3.27 - - Z number of samples to take per voxel := 5 - Y number of samples to take per voxel := 5 - X number of samples to take per voxel := 5 - -shape type:= Ellipsoid -Ellipsoid Parameters:= - radius-x (in mm):=2 - radius-y (in mm):=2 - radius-z (in mm):=2 - origin (in mm):={5.21,-190,0} - END:= -value :=1 - -END:= diff --git a/recon_test_pack/ROOT_STIR_consistency/generate_image11.par b/recon_test_pack/ROOT_STIR_consistency/generate_image11.par deleted file mode 100755 index f7d23d23bf..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/generate_image11.par +++ /dev/null @@ -1,23 +0,0 @@ -generate_image Parameters := -output filename:=stir_image11.v -X output image size (in pixels):=301 -Y output image size (in pixels):=301 -Z output image size (in pixels):=47 -X voxel size (in mm):= 2.1306 -Y voxel size (in mm):= 2.1306 -Z voxel size (in mm) :=3.27 - - Z number of samples to take per voxel := 5 - Y number of samples to take per voxel := 5 - X number of samples to take per voxel := 5 - -shape type:= Ellipsoid -Ellipsoid Parameters:= - radius-x (in mm):=2 - radius-y (in mm):=2 - radius-z (in mm):=2 - origin (in mm):={5.21,95,95} - END:= -value :=1 - -END:= diff --git a/recon_test_pack/ROOT_STIR_consistency/generate_image12.par b/recon_test_pack/ROOT_STIR_consistency/generate_image12.par deleted file mode 100755 index 2021d3a028..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/generate_image12.par +++ /dev/null @@ -1,23 +0,0 @@ -generate_image Parameters := -output filename:=stir_image12.v -X output image size (in pixels):=301 -Y output image size (in pixels):=301 -Z output image size (in pixels):=47 -X voxel size (in mm):= 2.1306 -Y voxel size (in mm):= 2.1306 -Z voxel size (in mm) :=3.27 - - Z number of samples to take per voxel := 5 - Y number of samples to take per voxel := 5 - X number of samples to take per voxel := 5 - -shape type:= Ellipsoid -Ellipsoid Parameters:= - radius-x (in mm):=2 - radius-y (in mm):=2 - radius-z (in mm):=2 - origin (in mm):={5.21,-95,95} - END:= -value :=1 - -END:= diff --git a/recon_test_pack/ROOT_STIR_consistency/generate_image2.par b/recon_test_pack/ROOT_STIR_consistency/generate_image2.par deleted file mode 100755 index 548aaef677..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/generate_image2.par +++ /dev/null @@ -1,23 +0,0 @@ -generate_image Parameters := -output filename:=stir_image2.v -X output image size (in pixels):=301 -Y output image size (in pixels):=301 -Z output image size (in pixels):=47 -X voxel size (in mm):= 2.1306 -Y voxel size (in mm):= 2.1306 -Z voxel size (in mm) :=3.27 - - Z number of samples to take per voxel := 5 - Y number of samples to take per voxel := 5 - X number of samples to take per voxel := 5 - -shape type:= Ellipsoid -Ellipsoid Parameters:= - radius-x (in mm):=2 - radius-y (in mm):=2 - radius-z (in mm):=2 - origin (in mm):={145.21,0,-190} - END:= -value :=1 - -END:= diff --git a/recon_test_pack/ROOT_STIR_consistency/generate_image3.par b/recon_test_pack/ROOT_STIR_consistency/generate_image3.par deleted file mode 100755 index c0dfc3bce8..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/generate_image3.par +++ /dev/null @@ -1,23 +0,0 @@ -generate_image Parameters := -output filename:=stir_image3.v -X output image size (in pixels):=301 -Y output image size (in pixels):=301 -Z output image size (in pixels):=47 -X voxel size (in mm):= 2.1306 -Y voxel size (in mm):= 2.1306 -Z voxel size (in mm) :=3.27 - - Z number of samples to take per voxel := 5 - Y number of samples to take per voxel := 5 - X number of samples to take per voxel := 5 - -shape type:= Ellipsoid -Ellipsoid Parameters:= - radius-x (in mm):=2 - radius-y (in mm):=2 - radius-z (in mm):=2 - origin (in mm):={145.21,190,0} - END:= -value :=1 - -END:= diff --git a/recon_test_pack/ROOT_STIR_consistency/generate_image4.par b/recon_test_pack/ROOT_STIR_consistency/generate_image4.par deleted file mode 100755 index dc87777ab9..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/generate_image4.par +++ /dev/null @@ -1,23 +0,0 @@ -generate_image Parameters := -output filename:=stir_image4.v -X output image size (in pixels):=301 -Y output image size (in pixels):=301 -Z output image size (in pixels):=47 -X voxel size (in mm):= 2.1306 -Y voxel size (in mm):= 2.1306 -Z voxel size (in mm) :=3.27 - - Z number of samples to take per voxel := 5 - Y number of samples to take per voxel := 5 - X number of samples to take per voxel := 5 - -shape type:= Ellipsoid -Ellipsoid Parameters:= - radius-x (in mm):=2 - radius-y (in mm):=2 - radius-z (in mm):=2 - origin (in mm):={145.21,-190,0} - END:= -value :=1 - -END:= diff --git a/recon_test_pack/ROOT_STIR_consistency/generate_image5.par b/recon_test_pack/ROOT_STIR_consistency/generate_image5.par deleted file mode 100755 index bcde0eb929..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/generate_image5.par +++ /dev/null @@ -1,23 +0,0 @@ -generate_image Parameters := -output filename:=stir_image5.v -X output image size (in pixels):=301 -Y output image size (in pixels):=301 -Z output image size (in pixels):=47 -X voxel size (in mm):= 2.1306 -Y voxel size (in mm):= 2.1306 -Z voxel size (in mm) :=3.27 - - Z number of samples to take per voxel := 5 - Y number of samples to take per voxel := 5 - X number of samples to take per voxel := 5 - -shape type:= Ellipsoid -Ellipsoid Parameters:= - radius-x (in mm):=2 - radius-y (in mm):=2 - radius-z (in mm):=2 - origin (in mm):={145.21,95,95} - END:= -value :=1 - -END:= diff --git a/recon_test_pack/ROOT_STIR_consistency/generate_image6.par b/recon_test_pack/ROOT_STIR_consistency/generate_image6.par deleted file mode 100755 index 2e23bb8b74..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/generate_image6.par +++ /dev/null @@ -1,23 +0,0 @@ -generate_image Parameters := -output filename:=stir_image6.v -X output image size (in pixels):=301 -Y output image size (in pixels):=301 -Z output image size (in pixels):=47 -X voxel size (in mm):= 2.1306 -Y voxel size (in mm):= 2.1306 -Z voxel size (in mm) :=3.27 - - Z number of samples to take per voxel := 5 - Y number of samples to take per voxel := 5 - X number of samples to take per voxel := 5 - -shape type:= Ellipsoid -Ellipsoid Parameters:= - radius-x (in mm):=2 - radius-y (in mm):=2 - radius-z (in mm):=2 - origin (in mm):={145.21,-95,95} - END:= -value :=1 - -END:= diff --git a/recon_test_pack/ROOT_STIR_consistency/generate_image7.par b/recon_test_pack/ROOT_STIR_consistency/generate_image7.par deleted file mode 100755 index 79d7cf3476..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/generate_image7.par +++ /dev/null @@ -1,23 +0,0 @@ -generate_image Parameters := -output filename:=stir_image7.v -X output image size (in pixels):=301 -Y output image size (in pixels):=301 -Z output image size (in pixels):=47 -X voxel size (in mm):= 2.1306 -Y voxel size (in mm):= 2.1306 -Z voxel size (in mm) :=3.27 - - Z number of samples to take per voxel := 5 - Y number of samples to take per voxel := 5 - X number of samples to take per voxel := 5 - -shape type:= Ellipsoid -Ellipsoid Parameters:= - radius-x (in mm):=2 - radius-y (in mm):=2 - radius-z (in mm):=2 - origin (in mm):={5.21,0,190} - END:= -value :=1 - -END:= diff --git a/recon_test_pack/ROOT_STIR_consistency/generate_image8.par b/recon_test_pack/ROOT_STIR_consistency/generate_image8.par deleted file mode 100755 index deab64a9a6..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/generate_image8.par +++ /dev/null @@ -1,23 +0,0 @@ -generate_image Parameters := -output filename:=stir_image8.v -X output image size (in pixels):=301 -Y output image size (in pixels):=301 -Z output image size (in pixels):=47 -X voxel size (in mm):= 2.1306 -Y voxel size (in mm):= 2.1306 -Z voxel size (in mm) :=3.27 - - Z number of samples to take per voxel := 5 - Y number of samples to take per voxel := 5 - X number of samples to take per voxel := 5 - -shape type:= Ellipsoid -Ellipsoid Parameters:= - radius-x (in mm):=2 - radius-y (in mm):=2 - radius-z (in mm):=2 - origin (in mm):={5.21,0,-190} - END:= -value :=1 - -END:= diff --git a/recon_test_pack/ROOT_STIR_consistency/generate_image9.par b/recon_test_pack/ROOT_STIR_consistency/generate_image9.par deleted file mode 100755 index 6f3effdfd4..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/generate_image9.par +++ /dev/null @@ -1,23 +0,0 @@ -generate_image Parameters := -output filename:=stir_image9.v -X output image size (in pixels):=301 -Y output image size (in pixels):=301 -Z output image size (in pixels):=47 -X voxel size (in mm):= 2.1306 -Y voxel size (in mm):= 2.1306 -Z voxel size (in mm) :=3.27 - - Z number of samples to take per voxel := 5 - Y number of samples to take per voxel := 5 - X number of samples to take per voxel := 5 - -shape type:= Ellipsoid -Ellipsoid Parameters:= - radius-x (in mm):=2 - radius-y (in mm):=2 - radius-z (in mm):=2 - origin (in mm):={5.21,190,0} - END:= -value :=1 - -END:= diff --git a/recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test1.par b/recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test1.par deleted file mode 100755 index 3f76064c73..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test1.par +++ /dev/null @@ -1,44 +0,0 @@ -lm_to_projdata Parameters:= - - input file := root_header_test1.hroot -output filename prefix := sinogram_test1 - - ; parameters that determine the sizes etc of the output - - template_projdata := template_D690_RD0.hs - ; the next can be used to use a smaller number of segments than given - ; in the template - maximum absolute segment number to process := -1 - - ; parameters for saying which events will be stored - - ; time frames (see TimeFrameDefinitions doc for format) -; frame_definition file := frames.fdef - ; or a total number of events (if larger than 1, frame definitions will be ignored) - ; note that this normally counts the total of prompts-delayeds (see below) - ; If you don't define a time frame definition file nor the num_events_to_store - ; then the total number of events in the listmode file is used. -; num_events_to_store := 10 - - ; parameters relating to prompts and delayeds - - ; with the default values, prompts will be added and delayed subtracted - ; to give the usual estimate of the trues. - - ; store the prompts (value should be 1 or 0) - store prompts := 1 ;default - ; what to do if it's a delayed event - store delayeds := 0 ;default - - - ; miscellaneous parameters - - ; list each event on stdout and do not store any files (use only for testing!) - ; has to be 0 or 1 - List event coordinates := 0 - - ; if you're short of RAM (i.e. a single projdata does not fit into memory), - ; you can use this to process the list mode data in multiple passes. - num_segments_in_memory := -1 - -End := diff --git a/recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test10.par b/recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test10.par deleted file mode 100755 index 1cc0ff25ce..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test10.par +++ /dev/null @@ -1,44 +0,0 @@ -lm_to_projdata Parameters:= - - input file := root_header_test10.hroot -output filename prefix := sinogram_test10 - - ; parameters that determine the sizes etc of the output - - template_projdata := template_D690_RD0.hs - ; the next can be used to use a smaller number of segments than given - ; in the template - maximum absolute segment number to process := -1 - - ; parameters for saying which events will be stored - - ; time frames (see TimeFrameDefinitions doc for format) -; frame_definition file := frames.fdef - ; or a total number of events (if larger than 1, frame definitions will be ignored) - ; note that this normally counts the total of prompts-delayeds (see below) - ; If you don't define a time frame definition file nor the num_events_to_store - ; then the total number of events in the listmode file is used. -; num_events_to_store := 10 - - ; parameters relating to prompts and delayeds - - ; with the default values, prompts will be added and delayed subtracted - ; to give the usual estimate of the trues. - - ; store the prompts (value should be 1 or 0) - store prompts := 1 ;default - ; what to do if it's a delayed event - store delayeds := 0 ;default - - - ; miscellaneous parameters - - ; list each event on stdout and do not store any files (use only for testing!) - ; has to be 0 or 1 - List event coordinates := 0 - - ; if you're short of RAM (i.e. a single projdata does not fit into memory), - ; you can use this to process the list mode data in multiple passes. - num_segments_in_memory := -1 - -End := diff --git a/recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test11.par b/recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test11.par deleted file mode 100755 index 978304779f..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test11.par +++ /dev/null @@ -1,44 +0,0 @@ -lm_to_projdata Parameters:= - - input file := root_header_test11.hroot -output filename prefix := sinogram_test11 - - ; parameters that determine the sizes etc of the output - - template_projdata := template_D690_RD0.hs - ; the next can be used to use a smaller number of segments than given - ; in the template - maximum absolute segment number to process := -1 - - ; parameters for saying which events will be stored - - ; time frames (see TimeFrameDefinitions doc for format) -; frame_definition file := frames.fdef - ; or a total number of events (if larger than 1, frame definitions will be ignored) - ; note that this normally counts the total of prompts-delayeds (see below) - ; If you don't define a time frame definition file nor the num_events_to_store - ; then the total number of events in the listmode file is used. -; num_events_to_store := 10 - - ; parameters relating to prompts and delayeds - - ; with the default values, prompts will be added and delayed subtracted - ; to give the usual estimate of the trues. - - ; store the prompts (value should be 1 or 0) - store prompts := 1 ;default - ; what to do if it's a delayed event - store delayeds := 0 ;default - - - ; miscellaneous parameters - - ; list each event on stdout and do not store any files (use only for testing!) - ; has to be 0 or 1 - List event coordinates := 0 - - ; if you're short of RAM (i.e. a single projdata does not fit into memory), - ; you can use this to process the list mode data in multiple passes. - num_segments_in_memory := -1 - -End := diff --git a/recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test12.par b/recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test12.par deleted file mode 100755 index 62b234617b..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test12.par +++ /dev/null @@ -1,44 +0,0 @@ -lm_to_projdata Parameters:= - - input file := root_header_test12.hroot -output filename prefix := sinogram_test12 - - ; parameters that determine the sizes etc of the output - - template_projdata := template_D690_RD0.hs - ; the next can be used to use a smaller number of segments than given - ; in the template - maximum absolute segment number to process := -1 - - ; parameters for saying which events will be stored - - ; time frames (see TimeFrameDefinitions doc for format) -; frame_definition file := frames.fdef - ; or a total number of events (if larger than 1, frame definitions will be ignored) - ; note that this normally counts the total of prompts-delayeds (see below) - ; If you don't define a time frame definition file nor the num_events_to_store - ; then the total number of events in the listmode file is used. -; num_events_to_store := 10 - - ; parameters relating to prompts and delayeds - - ; with the default values, prompts will be added and delayed subtracted - ; to give the usual estimate of the trues. - - ; store the prompts (value should be 1 or 0) - store prompts := 1 ;default - ; what to do if it's a delayed event - store delayeds := 0 ;default - - - ; miscellaneous parameters - - ; list each event on stdout and do not store any files (use only for testing!) - ; has to be 0 or 1 - List event coordinates := 0 - - ; if you're short of RAM (i.e. a single projdata does not fit into memory), - ; you can use this to process the list mode data in multiple passes. - num_segments_in_memory := -1 - -End := diff --git a/recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test2.par b/recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test2.par deleted file mode 100755 index ee224d8fb4..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test2.par +++ /dev/null @@ -1,44 +0,0 @@ -lm_to_projdata Parameters:= - - input file := root_header_test2.hroot -output filename prefix := sinogram_test2 - - ; parameters that determine the sizes etc of the output - - template_projdata := template_D690_RD0.hs - ; the next can be used to use a smaller number of segments than given - ; in the template - maximum absolute segment number to process := -1 - - ; parameters for saying which events will be stored - - ; time frames (see TimeFrameDefinitions doc for format) -; frame_definition file := frames.fdef - ; or a total number of events (if larger than 1, frame definitions will be ignored) - ; note that this normally counts the total of prompts-delayeds (see below) - ; If you don't define a time frame definition file nor the num_events_to_store - ; then the total number of events in the listmode file is used. -; num_events_to_store := 10 - - ; parameters relating to prompts and delayeds - - ; with the default values, prompts will be added and delayed subtracted - ; to give the usual estimate of the trues. - - ; store the prompts (value should be 1 or 0) - store prompts := 1 ;default - ; what to do if it's a delayed event - store delayeds := 0 ;default - - - ; miscellaneous parameters - - ; list each event on stdout and do not store any files (use only for testing!) - ; has to be 0 or 1 - List event coordinates := 0 - - ; if you're short of RAM (i.e. a single projdata does not fit into memory), - ; you can use this to process the list mode data in multiple passes. - num_segments_in_memory := -1 - -End := diff --git a/recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test3.par b/recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test3.par deleted file mode 100755 index 1089b114e1..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test3.par +++ /dev/null @@ -1,44 +0,0 @@ -lm_to_projdata Parameters:= - - input file := root_header_test3.hroot -output filename prefix := sinogram_test3 - - ; parameters that determine the sizes etc of the output - - template_projdata := template_D690_RD0.hs - ; the next can be used to use a smaller number of segments than given - ; in the template - maximum absolute segment number to process := -1 - - ; parameters for saying which events will be stored - - ; time frames (see TimeFrameDefinitions doc for format) -; frame_definition file := frames.fdef - ; or a total number of events (if larger than 1, frame definitions will be ignored) - ; note that this normally counts the total of prompts-delayeds (see below) - ; If you don't define a time frame definition file nor the num_events_to_store - ; then the total number of events in the listmode file is used. -; num_events_to_store := 10 - - ; parameters relating to prompts and delayeds - - ; with the default values, prompts will be added and delayed subtracted - ; to give the usual estimate of the trues. - - ; store the prompts (value should be 1 or 0) - store prompts := 1 ;default - ; what to do if it's a delayed event - store delayeds := 0 ;default - - - ; miscellaneous parameters - - ; list each event on stdout and do not store any files (use only for testing!) - ; has to be 0 or 1 - List event coordinates := 0 - - ; if you're short of RAM (i.e. a single projdata does not fit into memory), - ; you can use this to process the list mode data in multiple passes. - num_segments_in_memory := -1 - -End := diff --git a/recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test4.par b/recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test4.par deleted file mode 100755 index 1050b0044d..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test4.par +++ /dev/null @@ -1,44 +0,0 @@ -lm_to_projdata Parameters:= - - input file := root_header_test4.hroot -output filename prefix := sinogram_test4 - - ; parameters that determine the sizes etc of the output - - template_projdata := template_D690_RD0.hs - ; the next can be used to use a smaller number of segments than given - ; in the template - maximum absolute segment number to process := -1 - - ; parameters for saying which events will be stored - - ; time frames (see TimeFrameDefinitions doc for format) -; frame_definition file := frames.fdef - ; or a total number of events (if larger than 1, frame definitions will be ignored) - ; note that this normally counts the total of prompts-delayeds (see below) - ; If you don't define a time frame definition file nor the num_events_to_store - ; then the total number of events in the listmode file is used. -; num_events_to_store := 10 - - ; parameters relating to prompts and delayeds - - ; with the default values, prompts will be added and delayed subtracted - ; to give the usual estimate of the trues. - - ; store the prompts (value should be 1 or 0) - store prompts := 1 ;default - ; what to do if it's a delayed event - store delayeds := 0 ;default - - - ; miscellaneous parameters - - ; list each event on stdout and do not store any files (use only for testing!) - ; has to be 0 or 1 - List event coordinates := 0 - - ; if you're short of RAM (i.e. a single projdata does not fit into memory), - ; you can use this to process the list mode data in multiple passes. - num_segments_in_memory := -1 - -End := diff --git a/recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test5.par b/recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test5.par deleted file mode 100755 index 39dd245e20..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test5.par +++ /dev/null @@ -1,44 +0,0 @@ -lm_to_projdata Parameters:= - - input file := root_header_test5.hroot -output filename prefix := sinogram_test5 - - ; parameters that determine the sizes etc of the output - - template_projdata := template_D690_RD0.hs - ; the next can be used to use a smaller number of segments than given - ; in the template - maximum absolute segment number to process := -1 - - ; parameters for saying which events will be stored - - ; time frames (see TimeFrameDefinitions doc for format) -; frame_definition file := frames.fdef - ; or a total number of events (if larger than 1, frame definitions will be ignored) - ; note that this normally counts the total of prompts-delayeds (see below) - ; If you don't define a time frame definition file nor the num_events_to_store - ; then the total number of events in the listmode file is used. -; num_events_to_store := 10 - - ; parameters relating to prompts and delayeds - - ; with the default values, prompts will be added and delayed subtracted - ; to give the usual estimate of the trues. - - ; store the prompts (value should be 1 or 0) - store prompts := 1 ;default - ; what to do if it's a delayed event - store delayeds := 0 ;default - - - ; miscellaneous parameters - - ; list each event on stdout and do not store any files (use only for testing!) - ; has to be 0 or 1 - List event coordinates := 0 - - ; if you're short of RAM (i.e. a single projdata does not fit into memory), - ; you can use this to process the list mode data in multiple passes. - num_segments_in_memory := -1 - -End := diff --git a/recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test6.par b/recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test6.par deleted file mode 100755 index 99b3d6ca8d..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test6.par +++ /dev/null @@ -1,44 +0,0 @@ -lm_to_projdata Parameters:= - - input file := root_header_test6.hroot -output filename prefix := sinogram_test6 - - ; parameters that determine the sizes etc of the output - - template_projdata := template_D690_RD0.hs - ; the next can be used to use a smaller number of segments than given - ; in the template - maximum absolute segment number to process := -1 - - ; parameters for saying which events will be stored - - ; time frames (see TimeFrameDefinitions doc for format) -; frame_definition file := frames.fdef - ; or a total number of events (if larger than 1, frame definitions will be ignored) - ; note that this normally counts the total of prompts-delayeds (see below) - ; If you don't define a time frame definition file nor the num_events_to_store - ; then the total number of events in the listmode file is used. -; num_events_to_store := 10 - - ; parameters relating to prompts and delayeds - - ; with the default values, prompts will be added and delayed subtracted - ; to give the usual estimate of the trues. - - ; store the prompts (value should be 1 or 0) - store prompts := 1 ;default - ; what to do if it's a delayed event - store delayeds := 0 ;default - - - ; miscellaneous parameters - - ; list each event on stdout and do not store any files (use only for testing!) - ; has to be 0 or 1 - List event coordinates := 0 - - ; if you're short of RAM (i.e. a single projdata does not fit into memory), - ; you can use this to process the list mode data in multiple passes. - num_segments_in_memory := -1 - -End := diff --git a/recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test7.par b/recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test7.par deleted file mode 100755 index 161c49d7fa..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test7.par +++ /dev/null @@ -1,44 +0,0 @@ -lm_to_projdata Parameters:= - - input file := root_header_test7.hroot -output filename prefix := sinogram_test7 - - ; parameters that determine the sizes etc of the output - - template_projdata := template_D690_RD0.hs - ; the next can be used to use a smaller number of segments than given - ; in the template - maximum absolute segment number to process := -1 - - ; parameters for saying which events will be stored - - ; time frames (see TimeFrameDefinitions doc for format) -; frame_definition file := frames.fdef - ; or a total number of events (if larger than 1, frame definitions will be ignored) - ; note that this normally counts the total of prompts-delayeds (see below) - ; If you don't define a time frame definition file nor the num_events_to_store - ; then the total number of events in the listmode file is used. -; num_events_to_store := 10 - - ; parameters relating to prompts and delayeds - - ; with the default values, prompts will be added and delayed subtracted - ; to give the usual estimate of the trues. - - ; store the prompts (value should be 1 or 0) - store prompts := 1 ;default - ; what to do if it's a delayed event - store delayeds := 0 ;default - - - ; miscellaneous parameters - - ; list each event on stdout and do not store any files (use only for testing!) - ; has to be 0 or 1 - List event coordinates := 0 - - ; if you're short of RAM (i.e. a single projdata does not fit into memory), - ; you can use this to process the list mode data in multiple passes. - num_segments_in_memory := -1 - -End := diff --git a/recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test8.par b/recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test8.par deleted file mode 100755 index 4c176f80a8..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test8.par +++ /dev/null @@ -1,44 +0,0 @@ -lm_to_projdata Parameters:= - - input file := root_header_test8.hroot -output filename prefix := sinogram_test8 - - ; parameters that determine the sizes etc of the output - - template_projdata := template_D690_RD0.hs - ; the next can be used to use a smaller number of segments than given - ; in the template - maximum absolute segment number to process := -1 - - ; parameters for saying which events will be stored - - ; time frames (see TimeFrameDefinitions doc for format) -; frame_definition file := frames.fdef - ; or a total number of events (if larger than 1, frame definitions will be ignored) - ; note that this normally counts the total of prompts-delayeds (see below) - ; If you don't define a time frame definition file nor the num_events_to_store - ; then the total number of events in the listmode file is used. -; num_events_to_store := 10 - - ; parameters relating to prompts and delayeds - - ; with the default values, prompts will be added and delayed subtracted - ; to give the usual estimate of the trues. - - ; store the prompts (value should be 1 or 0) - store prompts := 1 ;default - ; what to do if it's a delayed event - store delayeds := 0 ;default - - - ; miscellaneous parameters - - ; list each event on stdout and do not store any files (use only for testing!) - ; has to be 0 or 1 - List event coordinates := 0 - - ; if you're short of RAM (i.e. a single projdata does not fit into memory), - ; you can use this to process the list mode data in multiple passes. - num_segments_in_memory := -1 - -End := diff --git a/recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test9.par b/recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test9.par deleted file mode 100755 index fced4cd52a..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/lm_to_projdata_test9.par +++ /dev/null @@ -1,44 +0,0 @@ -lm_to_projdata Parameters:= - - input file := root_header_test9.hroot -output filename prefix := sinogram_test9 - - ; parameters that determine the sizes etc of the output - - template_projdata := template_D690_RD0.hs - ; the next can be used to use a smaller number of segments than given - ; in the template - maximum absolute segment number to process := -1 - - ; parameters for saying which events will be stored - - ; time frames (see TimeFrameDefinitions doc for format) -; frame_definition file := frames.fdef - ; or a total number of events (if larger than 1, frame definitions will be ignored) - ; note that this normally counts the total of prompts-delayeds (see below) - ; If you don't define a time frame definition file nor the num_events_to_store - ; then the total number of events in the listmode file is used. -; num_events_to_store := 10 - - ; parameters relating to prompts and delayeds - - ; with the default values, prompts will be added and delayed subtracted - ; to give the usual estimate of the trues. - - ; store the prompts (value should be 1 or 0) - store prompts := 1 ;default - ; what to do if it's a delayed event - store delayeds := 0 ;default - - - ; miscellaneous parameters - - ; list each event on stdout and do not store any files (use only for testing!) - ; has to be 0 or 1 - List event coordinates := 0 - - ; if you're short of RAM (i.e. a single projdata does not fit into memory), - ; you can use this to process the list mode data in multiple passes. - num_segments_in_memory := -1 - -End := diff --git a/recon_test_pack/ROOT_STIR_consistency/make_plot.py b/recon_test_pack/ROOT_STIR_consistency/make_plot.py deleted file mode 100755 index 6657dba19a..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/make_plot.py +++ /dev/null @@ -1,90 +0,0 @@ -import numpy as npy -import matplotlib.pyplot as plt -import itertools - -def extract_coordinates(filename): - with open(filename,'r') as f: - line_content = f.readlines() - f.close() - line_content = [x.split() for x in line_content] - xlist = [x[0] for x in line_content ] - ylist = [x[1] for x in line_content ] - zlist = [x[2] for x in line_content ] - weightlist = [x[3] for x in line_content ] - return [xlist,ylist,zlist,weightlist] - -def extract_coordinates2(filename): - with open(filename,'r') as f: - line_content = f.readlines() - f.close() - line_content = [x.split() for x in line_content] - listcont = [[x[0],x[1],x[2]] for x in line_content ] - return listcont - - -def make_scatter_plot(xlist,ylist): - plt.scatter(xlist,ylist) - plt.xlabel("x value (mm)") - plt.ylabel("y value (mm)") - plt.title("TOF kernel") - plt.legend() - -def threshold_values(xsource,ysource,zsource,listcont): - distance=[] - boollist=[] - for x in listcont: - distance.append(npy.sqrt(npy.power(float(x[0])-xsource,2)+npy.power(float(x[1])-ysource,2)+npy.power(float(x[2])-zsource,2))) - for d in distance: - boollist.append(d > 45) - return boollist - -#%% - -[xlist,ylist,zlist,weightlist] = extract_coordinates("stir_image1.txt") -plt.figure() -make_scatter_plot(xlist,ylist) -#%% -[xlist,ylist,zlist,weightlist] = extract_coordinates("stir_image2.txt") -plt.figure() -make_scatter_plot(xlist,ylist) -#%% -[xlist,ylist,zlist,weightlist] = extract_coordinates("stir_image3.txt") -plt.figure() -make_scatter_plot(xlist,ylist) -#%% -[xlist,ylist,zlist,weightlist] = extract_coordinates("stir_image4.txt") -plt.figure() -make_scatter_plot(xlist,ylist) -#%% -[xlist,ylist,zlist,weightlist] = extract_coordinates("stir_image5.txt") -plt.figure() -make_scatter_plot(xlist,ylist) -#%% -[xlist,ylist,zlist,weightlist] = extract_coordinates("stir_image6.txt") -plt.figure() -make_scatter_plot(xlist,ylist) -#%% -[xlist,ylist,zlist,weightlist] = extract_coordinates("stir_image7.txt") -plt.figure() -make_scatter_plot(xlist,ylist) -#%% -[xlist,ylist,zlist,weightlist] = extract_coordinates("stir_image8.txt") -plt.figure() -make_scatter_plot(xlist,ylist) -#%% -[xlist,ylist,zlist,weightlist] = extract_coordinates("stir_image9.txt") -plt.figure() -make_scatter_plot(xlist,ylist) -#%% -[xlist,ylist,zlist,weightlist] = extract_coordinates("stir_image10.txt") -plt.figure() -make_scatter_plot(xlist,ylist) -#%% -[xlist,ylist,zlist,weightlist] = extract_coordinates("stir_image11.txt") -plt.figure() -make_scatter_plot(xlist,ylist) -#%% -[xlist,ylist,zlist,weightlist] = extract_coordinates("stir_image12.txt") -plt.figure() -make_scatter_plot(xlist,ylist) -#%% \ No newline at end of file diff --git a/recon_test_pack/ROOT_STIR_consistency/root_header_test1.hroot b/recon_test_pack/ROOT_STIR_consistency/root_header_test1.hroot deleted file mode 100644 index a29acb965d..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/root_header_test1.hroot +++ /dev/null @@ -1,69 +0,0 @@ -ROOT header := - -originating system := User_defined_scanner -Number of rings := 24 -Number of detectors per ring := 576 -Inner ring diameter (cm) := 81.02 -Average depth of interaction (cm) := 0.94 -Distance between rings (cm) := 0.654 -Default bin size (cm) := 0.21306 -View offset (degrees) := -5.021 -Maximum number of non-arc-corrected bins := 381 -Default number of arc-corrected bins := 331 -Number of TOF time bins :=275 -Size of timing bin (ps) :=17.8 -Timing resolution (ps) :=75 - -GATE scanner type := GATE_Cylindrical_PET -GATE_Cylindrical_PET Parameters := - -name of data file := RootLM_D690_test1.root - -name of input TChain := Coincidences - -; As the GATE repeaters. -; If you skip a level in GATE's hierarchy, -; use 1. -number of Rsectors := 64 -number of modules_X := 1 -number of modules_Y := 1 -number of modules_Z := 4 -number of submodules_X := 1 -number of submodules_Y := 1 -number of submodules_Z := 1 -number of crystals_X := 1 -number of crystals_Y := 9 -number of crystals_Z := 6 - -;; From GATE's online documentation: -;; (http://wiki.opengatecollaboration.org/index.php/Users_Guide_V7.2:Digitizer_and_readout_parameters) -;; [...] the readout depth depends upon how the electronic readout functions. -;; If one PMT reads the four modules in the axial direction, -;; the depth should be set with the command: -;; /gate/digitizer/Singles/readout/setDepth 1 -; -; In STIR terminology this will be used to define the number of crystals -; per singles unit. -Singles readout depth := 1 - -; -; If set the scattered events will be skipped -exclude scattered events := 1 - -; -; If set the random events will be skipped -exclude random events := 1 - -; -; STIR will try to align the data. -; If you have used non standart GATE axes, -; rotate using: -offset (num of detectors) := 0 - -; If want to deactivate set to [0, 10000] -low energy window (keV) := 0 -upper energy window (keV):= 10000 - -End GATE_Cylindrical_PET Parameters := - -end ROOT header := diff --git a/recon_test_pack/ROOT_STIR_consistency/root_header_test10.hroot b/recon_test_pack/ROOT_STIR_consistency/root_header_test10.hroot deleted file mode 100644 index 8292ec8aca..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/root_header_test10.hroot +++ /dev/null @@ -1,69 +0,0 @@ -ROOT header := - -originating system := User_defined_scanner -Number of rings := 24 -Number of detectors per ring := 576 -Inner ring diameter (cm) := 81.02 -Average depth of interaction (cm) := 0.94 -Distance between rings (cm) := 0.654 -Default bin size (cm) := 0.21306 -View offset (degrees) := -5.021 -Maximum number of non-arc-corrected bins := 381 -Default number of arc-corrected bins := 331 -Number of TOF time bins :=275 -Size of timing bin (ps) :=17.8 -Timing resolution (ps) :=75 - -GATE scanner type := GATE_Cylindrical_PET -GATE_Cylindrical_PET Parameters := - -name of data file := RootLM_D690_test10.root - -name of input TChain := Coincidences - -; As the GATE repeaters. -; If you skip a level in GATE's hierarchy, -; use 1. -number of Rsectors := 64 -number of modules_X := 1 -number of modules_Y := 1 -number of modules_Z := 4 -number of submodules_X := 1 -number of submodules_Y := 1 -number of submodules_Z := 1 -number of crystals_X := 1 -number of crystals_Y := 9 -number of crystals_Z := 6 - -;; From GATE's online documentation: -;; (http://wiki.opengatecollaboration.org/index.php/Users_Guide_V7.2:Digitizer_and_readout_parameters) -;; [...] the readout depth depends upon how the electronic readout functions. -;; If one PMT reads the four modules in the axial direction, -;; the depth should be set with the command: -;; /gate/digitizer/Singles/readout/setDepth 1 -; -; In STIR terminology this will be used to define the number of crystals -; per singles unit. -Singles readout depth := 1 - -; -; If set the scattered events will be skipped -exclude scattered events := 1 - -; -; If set the random events will be skipped -exclude random events := 1 - -; -; STIR will try to align the data. -; If you have used non standart GATE axes, -; rotate using: -offset (num of detectors) := 0 - -; If want to deactivate set to [0, 10000] -low energy window (keV) := 0 -upper energy window (keV):= 10000 - -End GATE_Cylindrical_PET Parameters := - -end ROOT header := diff --git a/recon_test_pack/ROOT_STIR_consistency/root_header_test11.hroot b/recon_test_pack/ROOT_STIR_consistency/root_header_test11.hroot deleted file mode 100644 index 1435ff9be9..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/root_header_test11.hroot +++ /dev/null @@ -1,69 +0,0 @@ -ROOT header := - -originating system := User_defined_scanner -Number of rings := 24 -Number of detectors per ring := 576 -Inner ring diameter (cm) := 81.02 -Average depth of interaction (cm) := 0.94 -Distance between rings (cm) := 0.654 -Default bin size (cm) := 0.21306 -View offset (degrees) := -5.021 -Maximum number of non-arc-corrected bins := 381 -Default number of arc-corrected bins := 331 -Number of TOF time bins :=275 -Size of timing bin (ps) :=17.8 -Timing resolution (ps) :=75 - -GATE scanner type := GATE_Cylindrical_PET -GATE_Cylindrical_PET Parameters := - -name of data file := RootLM_D690_test11.root - -name of input TChain := Coincidences - -; As the GATE repeaters. -; If you skip a level in GATE's hierarchy, -; use 1. -number of Rsectors := 64 -number of modules_X := 1 -number of modules_Y := 1 -number of modules_Z := 4 -number of submodules_X := 1 -number of submodules_Y := 1 -number of submodules_Z := 1 -number of crystals_X := 1 -number of crystals_Y := 9 -number of crystals_Z := 6 - -;; From GATE's online documentation: -;; (http://wiki.opengatecollaboration.org/index.php/Users_Guide_V7.2:Digitizer_and_readout_parameters) -;; [...] the readout depth depends upon how the electronic readout functions. -;; If one PMT reads the four modules in the axial direction, -;; the depth should be set with the command: -;; /gate/digitizer/Singles/readout/setDepth 1 -; -; In STIR terminology this will be used to define the number of crystals -; per singles unit. -Singles readout depth := 1 - -; -; If set the scattered events will be skipped -exclude scattered events := 1 - -; -; If set the random events will be skipped -exclude random events := 1 - -; -; STIR will try to align the data. -; If you have used non standart GATE axes, -; rotate using: -offset (num of detectors) := 0 - -; If want to deactivate set to [0, 10000] -low energy window (keV) := 0 -upper energy window (keV):= 10000 - -End GATE_Cylindrical_PET Parameters := - -end ROOT header := diff --git a/recon_test_pack/ROOT_STIR_consistency/root_header_test12.hroot b/recon_test_pack/ROOT_STIR_consistency/root_header_test12.hroot deleted file mode 100644 index 54f96a7ac0..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/root_header_test12.hroot +++ /dev/null @@ -1,69 +0,0 @@ -ROOT header := - -originating system := User_defined_scanner -Number of rings := 24 -Number of detectors per ring := 576 -Inner ring diameter (cm) := 81.02 -Average depth of interaction (cm) := 0.94 -Distance between rings (cm) := 0.654 -Default bin size (cm) := 0.21306 -View offset (degrees) := -5.021 -Maximum number of non-arc-corrected bins := 381 -Default number of arc-corrected bins := 331 -Number of TOF time bins :=275 -Size of timing bin (ps) :=17.8 -Timing resolution (ps) :=75 - -GATE scanner type := GATE_Cylindrical_PET -GATE_Cylindrical_PET Parameters := - -name of data file := RootLM_D690_test12.root - -name of input TChain := Coincidences - -; As the GATE repeaters. -; If you skip a level in GATE's hierarchy, -; use 1. -number of Rsectors := 64 -number of modules_X := 1 -number of modules_Y := 1 -number of modules_Z := 4 -number of submodules_X := 1 -number of submodules_Y := 1 -number of submodules_Z := 1 -number of crystals_X := 1 -number of crystals_Y := 9 -number of crystals_Z := 6 - -;; From GATE's online documentation: -;; (http://wiki.opengatecollaboration.org/index.php/Users_Guide_V7.2:Digitizer_and_readout_parameters) -;; [...] the readout depth depends upon how the electronic readout functions. -;; If one PMT reads the four modules in the axial direction, -;; the depth should be set with the command: -;; /gate/digitizer/Singles/readout/setDepth 1 -; -; In STIR terminology this will be used to define the number of crystals -; per singles unit. -Singles readout depth := 1 - -; -; If set the scattered events will be skipped -exclude scattered events := 1 - -; -; If set the random events will be skipped -exclude random events := 1 - -; -; STIR will try to align the data. -; If you have used non standart GATE axes, -; rotate using: -offset (num of detectors) := 0 - -; If want to deactivate set to [0, 10000] -low energy window (keV) := 0 -upper energy window (keV):= 10000 - -End GATE_Cylindrical_PET Parameters := - -end ROOT header := diff --git a/recon_test_pack/ROOT_STIR_consistency/root_header_test2.hroot b/recon_test_pack/ROOT_STIR_consistency/root_header_test2.hroot deleted file mode 100644 index 44440618e5..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/root_header_test2.hroot +++ /dev/null @@ -1,69 +0,0 @@ -ROOT header := - -originating system := User_defined_scanner -Number of rings := 24 -Number of detectors per ring := 576 -Inner ring diameter (cm) := 81.02 -Average depth of interaction (cm) := 0.94 -Distance between rings (cm) := 0.654 -Default bin size (cm) := 0.21306 -View offset (degrees) := -5.021 -Maximum number of non-arc-corrected bins := 381 -Default number of arc-corrected bins := 331 -Number of TOF time bins :=275 -Size of timing bin (ps) :=17.8 -Timing resolution (ps) :=75 - -GATE scanner type := GATE_Cylindrical_PET -GATE_Cylindrical_PET Parameters := - -name of data file := RootLM_D690_test2.root - -name of input TChain := Coincidences - -; As the GATE repeaters. -; If you skip a level in GATE's hierarchy, -; use 1. -number of Rsectors := 64 -number of modules_X := 1 -number of modules_Y := 1 -number of modules_Z := 4 -number of submodules_X := 1 -number of submodules_Y := 1 -number of submodules_Z := 1 -number of crystals_X := 1 -number of crystals_Y := 9 -number of crystals_Z := 6 - -;; From GATE's online documentation: -;; (http://wiki.opengatecollaboration.org/index.php/Users_Guide_V7.2:Digitizer_and_readout_parameters) -;; [...] the readout depth depends upon how the electronic readout functions. -;; If one PMT reads the four modules in the axial direction, -;; the depth should be set with the command: -;; /gate/digitizer/Singles/readout/setDepth 1 -; -; In STIR terminology this will be used to define the number of crystals -; per singles unit. -Singles readout depth := 1 - -; -; If set the scattered events will be skipped -exclude scattered events := 1 - -; -; If set the random events will be skipped -exclude random events := 1 - -; -; STIR will try to align the data. -; If you have used non standart GATE axes, -; rotate using: -offset (num of detectors) := 0 - -; If want to deactivate set to [0, 10000] -low energy window (keV) := 0 -upper energy window (keV):= 10000 - -End GATE_Cylindrical_PET Parameters := - -end ROOT header := diff --git a/recon_test_pack/ROOT_STIR_consistency/root_header_test3.hroot b/recon_test_pack/ROOT_STIR_consistency/root_header_test3.hroot deleted file mode 100644 index 42333be0d6..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/root_header_test3.hroot +++ /dev/null @@ -1,69 +0,0 @@ -ROOT header := - -originating system := User_defined_scanner -Number of rings := 24 -Number of detectors per ring := 576 -Inner ring diameter (cm) := 81.02 -Average depth of interaction (cm) := 0.94 -Distance between rings (cm) := 0.654 -Default bin size (cm) := 0.21306 -View offset (degrees) := -5.021 -Maximum number of non-arc-corrected bins := 381 -Default number of arc-corrected bins := 331 -Number of TOF time bins :=275 -Size of timing bin (ps) :=17.8 -Timing resolution (ps) :=75 - -GATE scanner type := GATE_Cylindrical_PET -GATE_Cylindrical_PET Parameters := - -name of data file := RootLM_D690_test3.root - -name of input TChain := Coincidences - -; As the GATE repeaters. -; If you skip a level in GATE's hierarchy, -; use 1. -number of Rsectors := 64 -number of modules_X := 1 -number of modules_Y := 1 -number of modules_Z := 4 -number of submodules_X := 1 -number of submodules_Y := 1 -number of submodules_Z := 1 -number of crystals_X := 1 -number of crystals_Y := 9 -number of crystals_Z := 6 - -;; From GATE's online documentation: -;; (http://wiki.opengatecollaboration.org/index.php/Users_Guide_V7.2:Digitizer_and_readout_parameters) -;; [...] the readout depth depends upon how the electronic readout functions. -;; If one PMT reads the four modules in the axial direction, -;; the depth should be set with the command: -;; /gate/digitizer/Singles/readout/setDepth 1 -; -; In STIR terminology this will be used to define the number of crystals -; per singles unit. -Singles readout depth := 1 - -; -; If set the scattered events will be skipped -exclude scattered events := 1 - -; -; If set the random events will be skipped -exclude random events := 1 - -; -; STIR will try to align the data. -; If you have used non standart GATE axes, -; rotate using: -offset (num of detectors) := 0 - -; If want to deactivate set to [0, 10000] -low energy window (keV) := 0 -upper energy window (keV):= 10000 - -End GATE_Cylindrical_PET Parameters := - -end ROOT header := diff --git a/recon_test_pack/ROOT_STIR_consistency/root_header_test4.hroot b/recon_test_pack/ROOT_STIR_consistency/root_header_test4.hroot deleted file mode 100644 index 7d53b0e56a..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/root_header_test4.hroot +++ /dev/null @@ -1,69 +0,0 @@ -ROOT header := - -originating system := User_defined_scanner -Number of rings := 24 -Number of detectors per ring := 576 -Inner ring diameter (cm) := 81.02 -Average depth of interaction (cm) := 0.94 -Distance between rings (cm) := 0.654 -Default bin size (cm) := 0.21306 -View offset (degrees) := -5.021 -Maximum number of non-arc-corrected bins := 381 -Default number of arc-corrected bins := 331 -Number of TOF time bins :=275 -Size of timing bin (ps) :=17.8 -Timing resolution (ps) :=75 - -GATE scanner type := GATE_Cylindrical_PET -GATE_Cylindrical_PET Parameters := - -name of data file := RootLM_D690_test4.root - -name of input TChain := Coincidences - -; As the GATE repeaters. -; If you skip a level in GATE's hierarchy, -; use 1. -number of Rsectors := 64 -number of modules_X := 1 -number of modules_Y := 1 -number of modules_Z := 4 -number of submodules_X := 1 -number of submodules_Y := 1 -number of submodules_Z := 1 -number of crystals_X := 1 -number of crystals_Y := 9 -number of crystals_Z := 6 - -;; From GATE's online documentation: -;; (http://wiki.opengatecollaboration.org/index.php/Users_Guide_V7.2:Digitizer_and_readout_parameters) -;; [...] the readout depth depends upon how the electronic readout functions. -;; If one PMT reads the four modules in the axial direction, -;; the depth should be set with the command: -;; /gate/digitizer/Singles/readout/setDepth 1 -; -; In STIR terminology this will be used to define the number of crystals -; per singles unit. -Singles readout depth := 1 - -; -; If set the scattered events will be skipped -exclude scattered events := 1 - -; -; If set the random events will be skipped -exclude random events := 1 - -; -; STIR will try to align the data. -; If you have used non standart GATE axes, -; rotate using: -offset (num of detectors) := 0 - -; If want to deactivate set to [0, 10000] -low energy window (keV) := 0 -upper energy window (keV):= 10000 - -End GATE_Cylindrical_PET Parameters := - -end ROOT header := diff --git a/recon_test_pack/ROOT_STIR_consistency/root_header_test5.hroot b/recon_test_pack/ROOT_STIR_consistency/root_header_test5.hroot deleted file mode 100644 index 9589e9c069..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/root_header_test5.hroot +++ /dev/null @@ -1,69 +0,0 @@ -ROOT header := - -originating system := User_defined_scanner -Number of rings := 24 -Number of detectors per ring := 576 -Inner ring diameter (cm) := 81.02 -Average depth of interaction (cm) := 0.94 -Distance between rings (cm) := 0.654 -Default bin size (cm) := 0.21306 -View offset (degrees) := -5.021 -Maximum number of non-arc-corrected bins := 381 -Default number of arc-corrected bins := 331 -Number of TOF time bins :=275 -Size of timing bin (ps) :=17.8 -Timing resolution (ps) :=75 - -GATE scanner type := GATE_Cylindrical_PET -GATE_Cylindrical_PET Parameters := - -name of data file := RootLM_D690_test5.root - -name of input TChain := Coincidences - -; As the GATE repeaters. -; If you skip a level in GATE's hierarchy, -; use 1. -number of Rsectors := 64 -number of modules_X := 1 -number of modules_Y := 1 -number of modules_Z := 4 -number of submodules_X := 1 -number of submodules_Y := 1 -number of submodules_Z := 1 -number of crystals_X := 1 -number of crystals_Y := 9 -number of crystals_Z := 6 - -;; From GATE's online documentation: -;; (http://wiki.opengatecollaboration.org/index.php/Users_Guide_V7.2:Digitizer_and_readout_parameters) -;; [...] the readout depth depends upon how the electronic readout functions. -;; If one PMT reads the four modules in the axial direction, -;; the depth should be set with the command: -;; /gate/digitizer/Singles/readout/setDepth 1 -; -; In STIR terminology this will be used to define the number of crystals -; per singles unit. -Singles readout depth := 1 - -; -; If set the scattered events will be skipped -exclude scattered events := 1 - -; -; If set the random events will be skipped -exclude random events := 1 - -; -; STIR will try to align the data. -; If you have used non standart GATE axes, -; rotate using: -offset (num of detectors) := 0 - -; If want to deactivate set to [0, 10000] -low energy window (keV) := 0 -upper energy window (keV):= 10000 - -End GATE_Cylindrical_PET Parameters := - -end ROOT header := diff --git a/recon_test_pack/ROOT_STIR_consistency/root_header_test6.hroot b/recon_test_pack/ROOT_STIR_consistency/root_header_test6.hroot deleted file mode 100644 index 1e2550c397..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/root_header_test6.hroot +++ /dev/null @@ -1,69 +0,0 @@ -ROOT header := - -originating system := User_defined_scanner -Number of rings := 24 -Number of detectors per ring := 576 -Inner ring diameter (cm) := 81.02 -Average depth of interaction (cm) := 0.94 -Distance between rings (cm) := 0.654 -Default bin size (cm) := 0.21306 -View offset (degrees) := -5.021 -Maximum number of non-arc-corrected bins := 381 -Default number of arc-corrected bins := 331 -Number of TOF time bins :=275 -Size of timing bin (ps) :=17.8 -Timing resolution (ps) :=75 - -GATE scanner type := GATE_Cylindrical_PET -GATE_Cylindrical_PET Parameters := - -name of data file := RootLM_D690_test6.root - -name of input TChain := Coincidences - -; As the GATE repeaters. -; If you skip a level in GATE's hierarchy, -; use 1. -number of Rsectors := 64 -number of modules_X := 1 -number of modules_Y := 1 -number of modules_Z := 4 -number of submodules_X := 1 -number of submodules_Y := 1 -number of submodules_Z := 1 -number of crystals_X := 1 -number of crystals_Y := 9 -number of crystals_Z := 6 - -;; From GATE's online documentation: -;; (http://wiki.opengatecollaboration.org/index.php/Users_Guide_V7.2:Digitizer_and_readout_parameters) -;; [...] the readout depth depends upon how the electronic readout functions. -;; If one PMT reads the four modules in the axial direction, -;; the depth should be set with the command: -;; /gate/digitizer/Singles/readout/setDepth 1 -; -; In STIR terminology this will be used to define the number of crystals -; per singles unit. -Singles readout depth := 1 - -; -; If set the scattered events will be skipped -exclude scattered events := 1 - -; -; If set the random events will be skipped -exclude random events := 1 - -; -; STIR will try to align the data. -; If you have used non standart GATE axes, -; rotate using: -offset (num of detectors) := 0 - -; If want to deactivate set to [0, 10000] -low energy window (keV) := 0 -upper energy window (keV):= 10000 - -End GATE_Cylindrical_PET Parameters := - -end ROOT header := diff --git a/recon_test_pack/ROOT_STIR_consistency/root_header_test7.hroot b/recon_test_pack/ROOT_STIR_consistency/root_header_test7.hroot deleted file mode 100644 index 3f77c1c9a7..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/root_header_test7.hroot +++ /dev/null @@ -1,69 +0,0 @@ -ROOT header := - -originating system := User_defined_scanner -Number of rings := 24 -Number of detectors per ring := 576 -Inner ring diameter (cm) := 81.02 -Average depth of interaction (cm) := 0.94 -Distance between rings (cm) := 0.654 -Default bin size (cm) := 0.21306 -View offset (degrees) := -5.021 -Maximum number of non-arc-corrected bins := 381 -Default number of arc-corrected bins := 331 -Number of TOF time bins :=275 -Size of timing bin (ps) :=17.8 -Timing resolution (ps) :=75 - -GATE scanner type := GATE_Cylindrical_PET -GATE_Cylindrical_PET Parameters := - -name of data file := RootLM_D690_test7.root - -name of input TChain := Coincidences - -; As the GATE repeaters. -; If you skip a level in GATE's hierarchy, -; use 1. -number of Rsectors := 64 -number of modules_X := 1 -number of modules_Y := 1 -number of modules_Z := 4 -number of submodules_X := 1 -number of submodules_Y := 1 -number of submodules_Z := 1 -number of crystals_X := 1 -number of crystals_Y := 9 -number of crystals_Z := 6 - -;; From GATE's online documentation: -;; (http://wiki.opengatecollaboration.org/index.php/Users_Guide_V7.2:Digitizer_and_readout_parameters) -;; [...] the readout depth depends upon how the electronic readout functions. -;; If one PMT reads the four modules in the axial direction, -;; the depth should be set with the command: -;; /gate/digitizer/Singles/readout/setDepth 1 -; -; In STIR terminology this will be used to define the number of crystals -; per singles unit. -Singles readout depth := 1 - -; -; If set the scattered events will be skipped -exclude scattered events := 1 - -; -; If set the random events will be skipped -exclude random events := 1 - -; -; STIR will try to align the data. -; If you have used non standart GATE axes, -; rotate using: -offset (num of detectors) := 0 - -; If want to deactivate set to [0, 10000] -low energy window (keV) := 0 -upper energy window (keV):= 10000 - -End GATE_Cylindrical_PET Parameters := - -end ROOT header := diff --git a/recon_test_pack/ROOT_STIR_consistency/root_header_test8.hroot b/recon_test_pack/ROOT_STIR_consistency/root_header_test8.hroot deleted file mode 100644 index 85946ac628..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/root_header_test8.hroot +++ /dev/null @@ -1,69 +0,0 @@ -ROOT header := - -originating system := User_defined_scanner -Number of rings := 24 -Number of detectors per ring := 576 -Inner ring diameter (cm) := 81.02 -Average depth of interaction (cm) := 0.94 -Distance between rings (cm) := 0.654 -Default bin size (cm) := 0.21306 -View offset (degrees) := -5.021 -Maximum number of non-arc-corrected bins := 381 -Default number of arc-corrected bins := 331 -Number of TOF time bins :=275 -Size of timing bin (ps) :=17.8 -Timing resolution (ps) :=75 - -GATE scanner type := GATE_Cylindrical_PET -GATE_Cylindrical_PET Parameters := - -name of data file := RootLM_D690_test8.root - -name of input TChain := Coincidences - -; As the GATE repeaters. -; If you skip a level in GATE's hierarchy, -; use 1. -number of Rsectors := 64 -number of modules_X := 1 -number of modules_Y := 1 -number of modules_Z := 4 -number of submodules_X := 1 -number of submodules_Y := 1 -number of submodules_Z := 1 -number of crystals_X := 1 -number of crystals_Y := 9 -number of crystals_Z := 6 - -;; From GATE's online documentation: -;; (http://wiki.opengatecollaboration.org/index.php/Users_Guide_V7.2:Digitizer_and_readout_parameters) -;; [...] the readout depth depends upon how the electronic readout functions. -;; If one PMT reads the four modules in the axial direction, -;; the depth should be set with the command: -;; /gate/digitizer/Singles/readout/setDepth 1 -; -; In STIR terminology this will be used to define the number of crystals -; per singles unit. -Singles readout depth := 1 - -; -; If set the scattered events will be skipped -exclude scattered events := 1 - -; -; If set the random events will be skipped -exclude random events := 1 - -; -; STIR will try to align the data. -; If you have used non standart GATE axes, -; rotate using: -offset (num of detectors) := 0 - -; If want to deactivate set to [0, 10000] -low energy window (keV) := 0 -upper energy window (keV):= 10000 - -End GATE_Cylindrical_PET Parameters := - -end ROOT header := diff --git a/recon_test_pack/ROOT_STIR_consistency/root_header_test9.hroot b/recon_test_pack/ROOT_STIR_consistency/root_header_test9.hroot deleted file mode 100644 index 3624c0c3a6..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/root_header_test9.hroot +++ /dev/null @@ -1,69 +0,0 @@ -ROOT header := - -originating system := User_defined_scanner -Number of rings := 24 -Number of detectors per ring := 576 -Inner ring diameter (cm) := 81.02 -Average depth of interaction (cm) := 0.94 -Distance between rings (cm) := 0.654 -Default bin size (cm) := 0.21306 -View offset (degrees) := -5.021 -Maximum number of non-arc-corrected bins := 381 -Default number of arc-corrected bins := 331 -Number of TOF time bins :=275 -Size of timing bin (ps) :=17.8 -Timing resolution (ps) :=75 - -GATE scanner type := GATE_Cylindrical_PET -GATE_Cylindrical_PET Parameters := - -name of data file := RootLM_D690_test9.root - -name of input TChain := Coincidences - -; As the GATE repeaters. -; If you skip a level in GATE's hierarchy, -; use 1. -number of Rsectors := 64 -number of modules_X := 1 -number of modules_Y := 1 -number of modules_Z := 4 -number of submodules_X := 1 -number of submodules_Y := 1 -number of submodules_Z := 1 -number of crystals_X := 1 -number of crystals_Y := 9 -number of crystals_Z := 6 - -;; From GATE's online documentation: -;; (http://wiki.opengatecollaboration.org/index.php/Users_Guide_V7.2:Digitizer_and_readout_parameters) -;; [...] the readout depth depends upon how the electronic readout functions. -;; If one PMT reads the four modules in the axial direction, -;; the depth should be set with the command: -;; /gate/digitizer/Singles/readout/setDepth 1 -; -; In STIR terminology this will be used to define the number of crystals -; per singles unit. -Singles readout depth := 1 - -; -; If set the scattered events will be skipped -exclude scattered events := 1 - -; -; If set the random events will be skipped -exclude random events := 1 - -; -; STIR will try to align the data. -; If you have used non standart GATE axes, -; rotate using: -offset (num of detectors) := 0 - -; If want to deactivate set to [0, 10000] -low energy window (keV) := 0 -upper energy window (keV):= 10000 - -End GATE_Cylindrical_PET Parameters := - -end ROOT header := diff --git a/recon_test_pack/ROOT_STIR_consistency/run_pretest_script.sh b/recon_test_pack/ROOT_STIR_consistency/run_pretest_script.sh deleted file mode 100755 index d9fea3f814..0000000000 --- a/recon_test_pack/ROOT_STIR_consistency/run_pretest_script.sh +++ /dev/null @@ -1,12 +0,0 @@ -#! /bin/bash - -for I in {1..12} -do -generate_image generate_image${I}.par -cd Gate_macros -sed -e s/SOURCENAME/test${I}/ main_D690_template.mac > main_D690_test${I}.mac -Gate main_D690_test${I}.mac -cd .. -done; - -mv Gate_macros/*.root . \ No newline at end of file From f59a3cebe4b71737fb208f4c478725d118204552 Mon Sep 17 00:00:00 2001 From: Robert Twyman Date: Sat, 9 Apr 2022 09:52:00 -0700 Subject: [PATCH 199/509] Redesign of test_view_offset_root to include TOF tests Also required a lot of method/variable renaming to make this logically work. --- src/recon_test/test_view_offset_root.cxx | 589 +++++++++++++++++------ 1 file changed, 431 insertions(+), 158 deletions(-) diff --git a/src/recon_test/test_view_offset_root.cxx b/src/recon_test/test_view_offset_root.cxx index 4c3c73505e..51507e9395 100644 --- a/src/recon_test/test_view_offset_root.cxx +++ b/src/recon_test/test_view_offset_root.cxx @@ -51,152 +51,228 @@ using std::ifstream; START_NAMESPACE_STIR -class ROOTconsistency_Tests : public RunTests +class WeightedCoordinate +// This class is used to store the coordinates and weight { public: - ROOTconsistency_Tests(const char * const root_header_filename, const char * const generate_image_parameter_filename) - : root_header_filename(root_header_filename), generate_image_parameter_filename(generate_image_parameter_filename) - {} - void run_tests(); + WeightedCoordinate() + : coord(CartesianCoordinate3D(0.f,0.f,0.f)), value(0.f) + {} + WeightedCoordinate(const CartesianCoordinate3D& voxel_centre_v, const float value_v) + : coord(voxel_centre_v), value(value_v) + {} + + //! Center of mass position in mm + CartesianCoordinate3D coord; + //! Mass of all weights + float value; + //! COM standard deviation. +}; + + +class ROOTConsistencyTests : public RunTests +{ +public: + ROOTConsistencyTests()= default; + + /*! + * \brief Run the tests with ROOT data + + * \returns 0 if all tests passed, 1 otherwise + */ + void run_tests() override; private: - /*! Reads listmode event by event, computes the ProjMatrixElemsForOneBin (probabilities - * along a bin LOR). This method should include all tests/ setup for additional test such that the list data is - * read only once. - * - * Passes ProjMatrixElemsForOneBin (LOR) to test_LOR_closest_approach() and if fails, - * add 1 to `failed_events` (LOR's closest voxel was not within tolerance). - * Check if the number of `failed_events` is greater than half the number of tested events to pass the test. - * @param test_discretised_density_sptr Density containing a point source. - * @param original_coords Precomputed coordinates of the point source - * @param grid_spacing Precomputed voxel sizes - */ - void process_list_data( - const shared_ptr > &test_discretised_density_sptr, - const CartesianCoordinate3D original_coords, CartesianCoordinate3D grid_spacing); - - /*! Given a ProjMatrixElemsForOneBin (probabilities), test if the closest voxel in the LOR to the original_coords - * is within tolerance distance. If it is, pass with true, otherwise fales. - * @param probabilities ProjMatrixElemsForOneBin object of a list mode event - * @param test_discretised_density_sptr Density containing a point source. - * @param original_coords Precomputed coordinates of the point source - * @param grid_spacing Precomputed voxel sizes - used in setting tolerance - * @return True if test passes, false if failed. - */ - bool test_LOR_closest_approach(const ProjMatrixElemsForOneBin &probabilities, - const shared_ptr > &test_discretised_density_sptr, - const CartesianCoordinate3D original_coords, - const float grid_spacing); - - std::string root_header_filename; - const char * const generate_image_parameter_filename; - - //! A vector to store the coordinates of the closest approach voxels. - std::vector> min_distances_coords_list; + + void setup(); + /*! Reads listmode event by event, computes the ProjMatrixElemsForOneBin (probabilities + * along a bin LOR). This method should include all tests/ setup for additional test such that the list data is + * read only once. + * + * Passes ProjMatrixElemsForOneBin (LOR) to test_nonTOF_LOR_closest_approach() and if fails, + * add 1 to `failed_events` (LOR's closest voxel was not within nonTOF_distance_threshold). + * Check if the number of `failed_events` is greater than half the number of tested events to pass the test. + */ + void process_list_data(); + + void loop_through_list_events(shared_ptr lm_data_sptr, const shared_ptr& proj_matrix_sptr, + const CartesianCoordinate3D original_coords); + /*! Given a ProjMatrixElemsForOneBin (probabilities), test if the closest voxel in the LOR to the original_coords + * is within nonTOF_distance_threshold distance. If it is, pass with true, otherwise fales. + * @param probabilities ProjMatrixElemsForOneBin object of a list mode event + * @param test_discretised_density_sptr Density containing a point source. + * @param original_coords Precomputed coordinates of the point source + * @param tolerance Precomputed voxel sizes - used in setting nonTOF_distance_threshold + * @return True if test passes, false if failed. + */ + bool test_nonTOF_LOR_closest_approach(const ProjMatrixElemsForOneBin& probabilities); + void post_processing_nonTOF(); + + + //! Selects and stores the highest probability elements of ProjMatrixElemsForOneBin. + bool test_TOF_max_lor_voxel(const ProjMatrixElemsForOneBin& probabilities); + + //! Checks if original and calculated coordinates are close enough and saves data to file. + void post_processing_TOF(); + + //! Modified version of check_if_equal for this test + bool check_if_almost_equal(const float a, const float b, const std::string str, const float tolerance); + + // Auxilary methods + std::string get_root_header_filename() { return "pretest_output/root_header_test" + std::to_string(test_index) + ".hroot"; } + std::string get_generate_image_par_filename() { return "SourceFiles/generate_image" + std::to_string(test_index) + ".par"; } + + + // Class VARIABLES + + int test_index; + int num_tests; + std::vector test_results_nonTOF; + std::vector test_results_TOF; + + shared_ptr > discretised_density_sptr; + CartesianCoordinate3D original_coords; // Stored in class because of dynamic_cast + CartesianCoordinate3D grid_spacing; // Stored in class because of dynamic_cast + + int num_events_tested = 0; + const float failure_tolerance_nonTOF = 0.05; + const float failure_tolerance_TOF = 0.05; + + /// NON TOF VARIABLES + //! A vector to store the coordinates of the closest approach voxels. + std::vector> nonTOF_closest_voxels_list; + int num_failed_nonTOF_lor_events = 0; + double nonTOF_distance_threshold; + + /// TOF VARIABLES + //! A copy of the scanner timing resolution in mm + float gauss_sigma_in_mm; + std::vector max_coords_for_each_event; + double TOF_distance_threshold; + int num_failed_TOF_lor_events = 0; }; void -ROOTconsistency_Tests::run_tests() +ROOTConsistencyTests::run_tests() { - // Create the point source (discretised_density_sptr) with GenerateImage. - GenerateImage image_gen_application(this->generate_image_parameter_filename); - image_gen_application.compute(); - shared_ptr > discretised_density_sptr = image_gen_application.get_output_sptr(); - - // needs to be cast to VoxelsOnCartesianGrid to be able to calculate the centre of gravity, - // hence the location of the original source, stored in test_original_coords. - const VoxelsOnCartesianGrid& discretised_cartesian_grid = - dynamic_cast &>(*discretised_density_sptr); + // Loop over each of the ROOT files in the test_data directory + int num_test = 8; + test_results_nonTOF = std::vector (num_tests, true); + test_results_TOF = std::vector (num_tests, true); + + cerr << "Testing the view offset consistency between GATE/ROOT and STIR. \n"; + for (int i = 1; i <= num_test; ++i) + { + test_index = i; + cerr << "\nTesting dataset " << std::to_string(test_index) << "...\n"; - // Find the center of mass of the original data - CartesianCoordinate3D original_coords = find_centre_of_gravity_in_mm(discretised_cartesian_grid); - CartesianCoordinate3D grid_spacing = discretised_cartesian_grid.get_grid_spacing(); + setup(); + process_list_data(); + + cerr << "\nResults for dataset: " << std::to_string(test_index) << std::endl; + post_processing_nonTOF(); + post_processing_TOF(); + } - // Iterate through the list mode data and perform all needed operations. - process_list_data(discretised_density_sptr, original_coords, grid_spacing); + // Print results for easy readability + cerr << "\nTest Results\n" + "------------------------------------------\n" + "\tTest Index\t|\tnonTOF\t|\tTOF\n" + "------------------------------------------\n"; + for (int j = 0; j < num_test; ++j) + cerr << "\t\t" << std::to_string(j + 1) << "\t\t|\t" + << ((test_results_nonTOF[j]) ? "Pass" : "Fail") << "\t|\t" + << ((test_results_TOF[j]) ? "Pass" : "Fail") << std::endl; } void -ROOTconsistency_Tests:: -process_list_data( - const shared_ptr > &test_discretised_density_sptr, - const CartesianCoordinate3D original_coords, CartesianCoordinate3D grid_spacing) +ROOTConsistencyTests:: +setup() { - shared_ptr lm_data_sptr(read_from_file(root_header_filename)); - - shared_ptr proj_matrix_sptr(new ProjMatrixByBinUsingRayTracing()); + { + // Create the point source (discretised_density_sptr) with GenerateImage from parameter file + // GenerateImage requires `const char* const par_filename`. + GenerateImage image_gen_application(get_generate_image_par_filename().c_str()); + image_gen_application.compute(); + discretised_density_sptr = image_gen_application.get_output_sptr(); + } - proj_matrix_sptr.get()->set_up(lm_data_sptr->get_proj_data_info_sptr(), - test_discretised_density_sptr); + // Needs to be cast to VoxelsOnCartesianGrid to be able to calculate the centre of gravity + const VoxelsOnCartesianGrid &discretised_cartesian_grid = + dynamic_cast &>(*discretised_density_sptr); - ProjMatrixElemsForOneBin proj_matrix_row; + // Find the center of mass and grid spacing of the original data + original_coords = find_centre_of_gravity_in_mm(discretised_cartesian_grid); + grid_spacing = discretised_cartesian_grid.get_grid_spacing(); - // For debugging, create a list of the closest LOR coords. Also first entry is the original. - min_distances_coords_list.empty(); - min_distances_coords_list.push_back(original_coords); + // For post_processing and debugging, create a list of the closest LOR coords + nonTOF_closest_voxels_list.empty(); + max_coords_for_each_event.empty(); // The number of LORs with closes approach greater than the threshold. - int failed_events = 0; - int tested_events = 0; - const auto tolerance = 1.5 * norm(grid_spacing); // Using norm(grid_spacing) as a tolerance - cerr << "Tolerance is set to " << tolerance << std::endl; + num_failed_nonTOF_lor_events = 0; + num_failed_TOF_lor_events = 0; + num_events_tested = 0; + + // Failure conditioner and recording + nonTOF_distance_threshold = 1.5 * norm(grid_spacing); // Using norm(grid_spacing) as a nonTOF_distance_threshold + TOF_distance_threshold = 1.5 * norm(grid_spacing); // Using norm(grid_spacing) as a tof_distance_threshold +} + +void +ROOTConsistencyTests::process_list_data() +{ + // Configure the list mode reader + shared_ptr lm_data_sptr(read_from_file(get_root_header_filename())); + shared_ptr proj_matrix_sptr(new ProjMatrixByBinUsingRayTracing()); + proj_matrix_sptr.get()->set_up(lm_data_sptr->get_proj_data_info_sptr(), + discretised_density_sptr); + + // TOF requirements + proj_matrix_sptr->enable_tof(lm_data_sptr->get_proj_data_info_sptr()); + gauss_sigma_in_mm = ProjDataInfo::tof_delta_time_to_mm(lm_data_sptr->get_proj_data_info_sptr()->get_scanner_ptr()->get_timing_resolution()) / 2.355f; + // loop over all events in the listmode file + shared_ptr record_sptr = lm_data_sptr->get_empty_record_sptr(); + CListRecord& record = *record_sptr; + ProjMatrixElemsForOneBin proj_matrix_row; + while (lm_data_sptr->get_next_record(record) == Succeeded::yes) { - // loop over all events in the listmode file - shared_ptr record_sptr = lm_data_sptr->get_empty_record_sptr(); - CListRecord& record = *record_sptr; - while (lm_data_sptr->get_next_record(record) == Succeeded::yes) - { - // only stores prompts - if (record.is_event() && record.event().is_prompt()) - { - Bin bin; - bin.set_bin_value(1.f); - // gets the bin corresponding to the event - record.event().get_bin(bin, *lm_data_sptr->get_proj_data_info_sptr()); - if ( bin.get_bin_value()>0 ) + // only stores prompts + if (record.is_event() && record.event().is_prompt()) + { + Bin bin; + bin.set_bin_value(1.f); + // gets the bin corresponding to the event + record.event().get_bin(bin, *lm_data_sptr->get_proj_data_info_sptr()); + if (bin.get_bin_value() > 0) { - // computes the TOF probabilities along the bin LOR + // computes the non-TOF probabilities along the bin LOR proj_matrix_sptr->get_proj_matrix_elems_for_one_bin(proj_matrix_row, bin); - // adds coordinates and weights of the elements with highest probability along LOR - if (!test_LOR_closest_approach(proj_matrix_row, test_discretised_density_sptr, - original_coords, tolerance)) - { - failed_events += 1; - } - tested_events += 1; - } - } - } - } - cerr << "\nNUMBER OF FAILED EVENTS = " << failed_events << "\t NUMBER OF TESTED EVENTS = " << tested_events << std::endl; - check_if_less(failed_events, 0.05 * tested_events, - "the number of failed events is more than half the number of tested events."); + // Test event for non-TOF consistency + if (!test_nonTOF_LOR_closest_approach(proj_matrix_row)) + num_failed_nonTOF_lor_events += 1; - { // Save the closest coordinate for each LOR to file. - std::string lor_pos_filename = root_header_filename.substr(0, root_header_filename.size() - 6) + "_lor_pos.txt"; - cerr << "\nSaving debug information as: " << lor_pos_filename << - "\nThe first entry is the original coordinate position." << std::endl; - std::ofstream myfile; - myfile.open(lor_pos_filename.c_str()); - for (std::vector>::iterator coord_entry = min_distances_coords_list.begin(); - coord_entry != min_distances_coords_list.end(); ++coord_entry) - myfile << coord_entry->x() << " " << coord_entry->y() << " " << coord_entry->z() << std::endl; - myfile.close(); + // Test TOF aspects of the LOR + if (!test_TOF_max_lor_voxel(proj_matrix_row)) + num_failed_TOF_lor_events += 1; + + // Record as tested event + num_events_tested += 1; + } + } } } + +//// NON-TOF TESTING METHODS //// bool -ROOTconsistency_Tests:: -test_LOR_closest_approach(const ProjMatrixElemsForOneBin &probabilities, - const shared_ptr > &test_discretised_density_sptr, - const CartesianCoordinate3D original_coords, - const float tolerance) +ROOTConsistencyTests::test_nonTOF_LOR_closest_approach(const ProjMatrixElemsForOneBin& probabilities) { // Loop variables CartesianCoordinate3D closest_LOR_voxel_to_origin; - float min_distance; + float min_distance; // shortest distance to the origin along the LOR bool first_entry = true; // Use this to populate with the initial value ProjMatrixElemsForOneBin::const_iterator element_ptr = probabilities.begin(); @@ -204,7 +280,7 @@ test_LOR_closest_approach(const ProjMatrixElemsForOneBin &probabilities, while (element_ptr != probabilities.end()) { CartesianCoordinate3D voxel_coords = - test_discretised_density_sptr->get_physical_coordinates_for_indices(element_ptr->get_coords()); + discretised_density_sptr->get_physical_coordinates_for_indices(element_ptr->get_coords()); float dist_to_original = norm(voxel_coords - original_coords); if (dist_to_original < min_distance || first_entry) @@ -215,60 +291,257 @@ test_LOR_closest_approach(const ProjMatrixElemsForOneBin &probabilities, } ++element_ptr; } - // Log position in vector - min_distances_coords_list.push_back(closest_LOR_voxel_to_origin); -// if (!check_if_less(min_distance, 3*norm(grid_spacing), "ERR msgs")) - if (min_distance > tolerance) - { -// cerr << "min_distance = " << min_distance << "\t tolerance = " << tolerance << std::endl; - return false; // Test failed - LOR closest voxel beyond tolerance + // Log closest voxel to origin in vector (value doesn't matter) + nonTOF_closest_voxels_list.push_back(closest_LOR_voxel_to_origin); + + if (min_distance > nonTOF_distance_threshold) + return false; // Test failed - LOR closest voxel beyond nonTOF_distance_threshold + return true; // Test passed - LOR closest voxel within nonTOF_distance_threshold +} + + +void +ROOTConsistencyTests:: +post_processing_nonTOF() +{ + cerr << "\nNon-TOF number of failed events: " << num_failed_nonTOF_lor_events + << "\tNumber of tested events: = " << num_events_tested << std::endl; + + test_results_nonTOF[test_index] = check_if_less(num_failed_nonTOF_lor_events, failure_tolerance_nonTOF * num_events_tested, + "The number of failed TOF events is more than the tolerance(" + + std::to_string(100*failure_tolerance_nonTOF)+ "%)"); + + { // Save the closest coordinate for each LOR to file. + std::string lor_pos_filename = "non_TOF_voxel_data_" + std::to_string(test_index) + ".txt"; + cerr << "Saving debug information as: " << lor_pos_filename << + "\nThe first entry is the original coordinate position." << std::endl; + std::ofstream myfile; + myfile.open(lor_pos_filename.c_str()); + // The first entry is the original coords + myfile << original_coords.x() << " " << original_coords.y() << " " << original_coords.z() << std::endl; + for (std::vector>::iterator coord_entry = nonTOF_closest_voxels_list.begin(); + coord_entry != nonTOF_closest_voxels_list.end(); ++coord_entry) + myfile << coord_entry->x() << " " << coord_entry->y() << " " << coord_entry->z() << std::endl; + myfile.close(); } - return true; // Test passed - LOR closest voxel within tolerance } -END_NAMESPACE_STIR -int main(int argc, char **argv) +//// TOF TESTING METHODS //// +bool +ROOTConsistencyTests:: +test_TOF_max_lor_voxel(const ProjMatrixElemsForOneBin& probabilities) { - USING_NAMESPACE_STIR - // Should be called from `STIR/examples/ROOT_files/ROOT_STIR_consistency` + // Along a TOF LOR (probabilities), values assigned to voxels (or positions used here). + // This method finds highest value voxel(s). + // There may be more than one voxel with the highest value. + // Everytime a new highest voxel value is found, sum_weighted_position is re-set to that value. + // If a voxel is found with the same value as in sum_weighted_position, the coordinates are added. + // At the end of the loop, divide the coordinates by `num_max_value_elements` and add to the list of max_value_voxels, 1 value per event. - int num_test = 8; - bool exit_status = EXIT_SUCCESS; - cerr << "Testing the view offset consistency between GATE/ROOT and STIR. \n"; - for (int i = 1; i <= num_test; ++i) - { - const std::string root_header_filename = "pretest_output/root_header_test" + std::to_string(i) + ".hroot"; - const std::string generate_image_filename = "SourceFiles/generate_image" + std::to_string(i) + ".par"; - const char *root_filename_as_char = root_header_filename.c_str(); - const char *gen_image_par_filename_as_char = generate_image_filename.c_str(); + // Loop variables + ProjMatrixElemsForOneBin::const_iterator element_ptr = probabilities.begin(); + int num_max_value_elements = 0; + WeightedCoordinate sum_weighted_position(CartesianCoordinate3D(0, 0, 0), 0); - // Check the program arguments exist + while (element_ptr != probabilities.end()) + { + if (element_ptr->get_value() > sum_weighted_position.value) { - ifstream in1(root_header_filename); - if (!in1) - { - cerr << argv[0] << ": Error opening root header file: " << root_header_filename << "\nExiting.\n"; - return EXIT_FAILURE; - } - ifstream in2(generate_image_filename); - if (!in2) - { - cerr << argv[0] << ": Error opening generate image parameter filename: " << generate_image_filename << "\nExiting.\n"; - return EXIT_FAILURE; - } + // New highest value found, set sum_weighted_position to new value + sum_weighted_position.coord = discretised_density_sptr->get_physical_coordinates_for_indices(element_ptr->get_coords()); + sum_weighted_position.value = element_ptr->get_value(); + num_max_value_elements = 1; } - cerr << "Testing point source " << std::to_string(i) << " of " << std::to_string(num_test) << std::endl; - ROOTconsistency_Tests test(root_filename_as_char, gen_image_par_filename_as_char); - test.run_tests(); - - // Rather than using the exit status of each test, use the exit status of the program - if (test.main_return_value() != EXIT_SUCCESS) + else if (element_ptr->get_value() == sum_weighted_position.value) { - cerr << "Test " << std::to_string(i) << " FAILED!\n"; - exit_status = EXIT_FAILURE; + // Same value found, add coordinates to sum_weighted_position (will divide later) + sum_weighted_position.coord += discretised_density_sptr->get_physical_coordinates_for_indices(element_ptr->get_coords()); + num_max_value_elements++; } + ++element_ptr; + } + // Divide sum_weighted_position by num_max_value_elements to get the COM position + sum_weighted_position.coord /= num_max_value_elements; + + // Add the COM position to the list of max_value_voxels + max_coords_for_each_event.push_back(sum_weighted_position); + + // Check if the COM position is within the TOF_distance_threshold tolerance + if (norm(original_coords - sum_weighted_position.coord) > TOF_distance_threshold) + return false; // Test failed - LOR closest voxel beyond tof_distance_threshold + return true; // Test passed - LOR closest voxel within tof_distance_threshold +} + + +void +ROOTConsistencyTests:: +post_processing_TOF() +{ + cerr << "\nTOF number of failed events: " << num_failed_TOF_lor_events + << "\tNumber of tested events: = " << num_events_tested << std::endl; + + test_results_TOF[test_index] = check_if_less(num_failed_TOF_lor_events, failure_tolerance_TOF * num_events_tested, + "The number of failed TOF events is more than the tolerance(" + + std::to_string(100*failure_tolerance_nonTOF)+ "%)"); + + { // Save the closest coordinate for each LOR to file. + std::string lor_pos_filename = "TOF_voxel_data_" + std::to_string(test_index) + ".txt"; + cerr << "Saving debug information as: " << lor_pos_filename << + "\nThe first entry is the original coordinate position." << std::endl; + + std::ofstream myfile; + myfile.open(lor_pos_filename.c_str()); + myfile << original_coords.x() << " " << original_coords.y() << " " << original_coords.z() << std::endl; + for (std::vector::iterator coord_entry = max_coords_for_each_event.begin(); + coord_entry != max_coords_for_each_event.end(); ++coord_entry) + myfile << coord_entry->coord.x() << " " << coord_entry->coord.y() << " " << coord_entry->coord.z() << std::endl; + myfile.close(); + } + +#if 0 // old code, will not run. Used for records but can be deleted... + // This old code was used to check if the COM of all the max voxels was a distance_threshold of the original coordinate. + // There were also checks on the standard deviation of the max voxels. + // This is not the case anymore because this kind of analysis is done via python scripts. + + // creation of a file with all LOR maxima, to be able to plot them + std::ofstream myfile; + std::string max_lor_position_file_name = + current_root_header_filename.substr(0,current_root_header_filename.size()-3) + "_lor_tof_pos.txt"; + myfile.open (max_lor_position_file_name.c_str()); + + // Reset all COM values to 0.f + CartesianCoordinate3D max_lor_COM(0, 0, 0); + CartesianCoordinate3D max_lor_COM(0, 0, 0); + max_lor_COM.x() = 0.f; + max_lor_COM.x() = 0.f; + max_lor_COM.z() = 0.f; + max_lor_COM.weighted_standard_deviation.x() = 0.f; + max_lor_COM.weighted_standard_deviation.y() = 0.f; + max_lor_COM.weighted_standard_deviation.z() = 0.f; + + + // computes centre of mass (weighted mean value) = sum(x_i * v_i) + for (std::vector::iterator lor_element_ptr= max_coords_for_each_event.begin(); + lor_element_ptr != max_coords_for_each_event.end();++lor_element_ptr) + { + max_lor_COM.coord.x() += lor_element_ptr->coord.x()*lor_element_ptr->value; + max_lor_COM.coord.y() += lor_element_ptr->coord.y()*lor_element_ptr->value; + max_lor_COM.coord.z() += lor_element_ptr->coord.z()*lor_element_ptr->value; + max_lor_COM.value += lor_element_ptr->value; + + myfile << lor_element_ptr->coord.x() << " " + << lor_element_ptr->coord.y() << " " + << lor_element_ptr->coord.z() << " " + << lor_element_ptr->value << std::endl; + + } + myfile.close(); + + // needs to divide by the weights + if (max_lor_COM.value != 0) + { + max_lor_COM.coord.x()= max_lor_COM.coord.x()/ max_lor_COM.value; + max_lor_COM.coord.y()= max_lor_COM.coord.y()/ max_lor_COM.value; + max_lor_COM.coord.z()= max_lor_COM.coord.z()/ max_lor_COM.value; + } + else + { + warning("Total weight of the centre of mass equal to 0. Please check your data."); + max_lor_COM.coord.x()=0; + max_lor_COM.coord.y()=0; + max_lor_COM.coord.z()=0; } - return exit_status; + + // Compute the weighted standard deviation https://stats.stackexchange.com/a/6536 + for (std::vector::iterator lor_element_ptr= max_coords_for_each_event.begin(); + lor_element_ptr != max_coords_for_each_event.end();++lor_element_ptr) + { + CartesianCoordinate3D diff = lor_element_ptr->coord - max_lor_COM.coord; + max_lor_COM.weighted_standard_deviation.x() += lor_element_ptr->value * pow(diff.x(),2); + max_lor_COM.weighted_standard_deviation.y() += lor_element_ptr->value * pow(diff.y(), 2); + max_lor_COM.weighted_standard_deviation.z() += lor_element_ptr->value * pow(diff.z(), 2); + } + + float M = static_cast(max_coords_for_each_event.size()); + const float weighted_stddev_scalar = (M-1)/M * max_lor_COM.value; + max_lor_COM.weighted_standard_deviation.x() = sqrt(max_lor_COM.weighted_standard_deviation.x()/weighted_stddev_scalar); + max_lor_COM.weighted_standard_deviation.y() = sqrt(max_lor_COM.weighted_standard_deviation.y()/weighted_stddev_scalar); + max_lor_COM.weighted_standard_deviation.z() = sqrt(max_lor_COM.weighted_standard_deviation.z()/weighted_stddev_scalar); + + cerr << "Original coordinates: \t\t\t\t\t[x] " << original_coords.x() <<"\t[y] " + << original_coords.y() << "\t[z] " << original_coords.z() << std::endl; + + cerr << "Centre of gravity coordinates: \t\t\t[x] " << max_lor_COM.coord.x() <<"\t[y] " + << max_lor_COM.coord.y() << "\t[z] " << max_lor_COM.coord.z() << std::endl; + + cerr << "Centre of gravity coordinates (stddev): [x] " << max_lor_COM.weighted_standard_deviation.x() <<"\t[y] " + << max_lor_COM.weighted_standard_deviation.y() << "\t[z] " << max_lor_COM.weighted_standard_deviation.z() << "\n\n"<< std::endl; + + + // Check if max_lor_COM is within grid spacing nonTOF_distance_threshold with the original coordinates + check_if_almost_equal(original_coords.x(), max_lor_COM.coord.x(), + "COM not within grid spacing (x) of Original",grid_spacing[3]); + check_if_almost_equal(original_coords.y(), max_lor_COM.coord.y(), + "COM not within grid spacing (y) of Original",grid_spacing[2]); + check_if_almost_equal(original_coords.z(), max_lor_COM.coord.z(), + "COM not within grid spacing (z) of Original",grid_spacing[1]); + + // Check if weighted standard deviation is less than 2x the tof timing resolution + check_if_less(max_lor_COM.weighted_standard_deviation.x(), 2*gauss_sigma_in_mm, + "COM weighted stddev[x] is greater than 2x TOF timing resolution (mm)"); + check_if_less(max_lor_COM.weighted_standard_deviation.y(), 2*gauss_sigma_in_mm, + "COM weighted stddev[y] is greater than 2x TOF timing resolution (mm)"); + check_if_less(max_lor_COM.weighted_standard_deviation.z(), 2*gauss_sigma_in_mm, + "COM weighted stddev[z] is greater than 2x TOF timing resolution (mm)"); + + + // Save the origin and Center of mass data to a separate file. + std::ofstream myfile; + std::string origin_and_COM_file_name = current_root_header_filename.substr(0,current_root_header_filename.size()-6) + "_tof_ORIG_and_COM.txt"; + myfile.open (origin_and_COM_file_name.c_str()); + // Original coordinates + myfile << original_coords.x() << " " + << original_coords.y() << " " + << original_coords.z() << std::endl; + // max LOR mean coordinates + myfile << max_lor_COM.coord.x() << " " + << max_lor_COM.coord.y() << " " + << max_lor_COM.coord.z() << std::endl; + // max LOR weighted stddev + myfile << max_lor_COM.weighted_standard_deviation.x() << " " + << max_lor_COM.weighted_standard_deviation.y() << " " + << max_lor_COM.weighted_standard_deviation.z() << std::endl; + + myfile.close(); +#endif +} + +bool +ROOTConsistencyTests:: +check_if_almost_equal(const float a, const float b, const std::string str, const float tolerance) +{ + if ((fabs(a-b) > tolerance)) + { + std::cerr << "Error : unequal values are " << a << " and " << b + << ". " << str << std::endl; + everything_ok = false; + return false; + } + else + return true; +} + +END_NAMESPACE_STIR + +int main(int argc, char **argv) +{ + USING_NAMESPACE_STIR + // Should be called from `STIR/examples/ROOT_files/ROOT_STIR_consistency` + + // Tests in class ROOTConsistencyTests + ROOTConsistencyTests test; + test.run_tests(); + return test.main_return_value(); } From 140b2177d94ef0773e80fa4af187ccf1f1193f08 Mon Sep 17 00:00:00 2001 From: Robert Twyman Date: Sat, 9 Apr 2022 09:52:42 -0700 Subject: [PATCH 200/509] Rename test_view_offset_root -> test_consistency_with_root --- src/recon_test/CMakeLists.txt | 10 +++++----- ..._offset_root.cxx => test_consistency_with_root.cxx} | 0 2 files changed, 5 insertions(+), 5 deletions(-) rename src/recon_test/{test_view_offset_root.cxx => test_consistency_with_root.cxx} (100%) diff --git a/src/recon_test/CMakeLists.txt b/src/recon_test/CMakeLists.txt index c3031c1a8a..2592070e2c 100644 --- a/src/recon_test/CMakeLists.txt +++ b/src/recon_test/CMakeLists.txt @@ -107,13 +107,13 @@ if (HAVE_CERN_ROOT) # Add test if the data is downloaded (only checking root_header_test1.hroot) if(EXISTS "${ROOT_STIR_consistency_data_dir}/root_header_test1.hroot") - list(APPEND ${dir_INVOLVED_TEST_EXE_SOURCES} test_view_offset_root) - set(test_name test_view_offset_root) - # test_view_offset_root requires working directory to be examples/ROOT_files/ROOT_STIR_consistency + list(APPEND ${dir_INVOLVED_TEST_EXE_SOURCES} test_consistency_with_root) + # test_consistency_with_root requires working directory to be examples/ROOT_files/ROOT_STIR_consistency # for output files to be written there - ADD_TEST(NAME ${test_name} + ADD_TEST(NAME test_consistency_with_root WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/examples/ROOT_files/ROOT_STIR_consistency - COMMAND ${CMAKE_CURRENT_BINARY_DIR}/test_view_offset_root) + COMMAND ${CMAKE_CURRENT_BINARY_DIR}/test_consistency_with_root) + endif() endif() diff --git a/src/recon_test/test_view_offset_root.cxx b/src/recon_test/test_consistency_with_root.cxx similarity index 100% rename from src/recon_test/test_view_offset_root.cxx rename to src/recon_test/test_consistency_with_root.cxx From b44974871ff61d371e3f6030c511943188e29bc7 Mon Sep 17 00:00:00 2001 From: Robert Twyman Date: Sat, 9 Apr 2022 09:53:19 -0700 Subject: [PATCH 201/509] Remove old test test_consistency_root --- src/recon_test/test_consistency_root.cxx | 319 ----------------------- 1 file changed, 319 deletions(-) delete mode 100644 src/recon_test/test_consistency_root.cxx diff --git a/src/recon_test/test_consistency_root.cxx b/src/recon_test/test_consistency_root.cxx deleted file mode 100644 index e34201c38c..0000000000 --- a/src/recon_test/test_consistency_root.cxx +++ /dev/null @@ -1,319 +0,0 @@ -/* - Copyright (C) 2017, UCL - 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 -*/ -/*! - \ingroup recon_test - Implementation of stir::test_consistency_root -*/ -#include "stir/ProjDataInfoCylindricalNoArcCorr.h" -#include "stir/recon_buildblock/ProjMatrixByBin.h" -#include "stir/recon_buildblock/ProjMatrixByBinUsingRayTracing.h" -#include "stir/recon_buildblock/ProjMatrixElemsForOneBin.h" -#include "stir/centre_of_gravity.h" -#include "stir/listmode/LmToProjData.h" -#include "stir/listmode/CListModeDataROOT.h" -#include "stir/listmode/CListRecord.h" -#include "stir/IO/read_from_file.h" -#include "stir/HighResWallClockTimer.h" -#include "stir/DiscretisedDensity.h" -#include "stir/VoxelsOnCartesianGrid.h" -#include "stir/Succeeded.h" -#include "stir/shared_ptr.h" -#include "stir/RunTests.h" -#include "boost/lexical_cast.hpp" - -#include "stir/info.h" -#include "stir/warning.h" - -#include - -using std::cerr; -using std::ifstream; - -START_NAMESPACE_STIR - -/*! - * \ingroup recon_test - * \brief Test class to check the consistency between ROOT listmode and STIR backprojection for high resolution TOF - * \author Elise Emond - * - * This test currently uses Root listmodes of single point sources. Scatters are not - * considered. It could be extended to actual scanner data, for which we would need - * to exclude scatter events. A way to do so would be to compute the distance between - * the bin LOR and the (non-TOF?) LOR calculated from the original data and exclude - * the events for which the distance would be more than a chosen threshold. - * - */ - -class ROOTconsistency_Tests : public RunTests -{ -public: - ROOTconsistency_Tests(const std::string& in, const std::string& image) - : root_header_filename(in), image_filename(image) - {} - void run_tests(); - - // Class to store the coordinates and weights of the maxima of the Lines-of-Response - // used to calculate the centre of gravity (see below). - class LORMax{ - public: - LORMax() : voxel_centre(CartesianCoordinate3D(0.f,0.f,0.f)), value(0.f) - {} - CartesianCoordinate3D voxel_centre; - float value; - }; - -private: - //! Reads listmode event by event, computes the ProjMatrixElemsForOneBin (probabilities - //! along a bin LOR) and stores in a vector the coordinates and weights of the - //! LOR maxima (vector::LORMax) prior to computing the centre of mass of those. - void construct_list_of_LOR_max( - const shared_ptr >& test_discretised_density_sptr); - - //! Selects and stores the highest probability elements of ProjMatrixElemsForOneBin. - void get_LOR_of_max(const ProjMatrixElemsForOneBin& probabilities, - const shared_ptr >& test_discretised_density_sptr); - - //! Given a vector::LORMax, computes the centre of mass. - CartesianCoordinate3D compute_centre_of_mass(); - - //! Checks if original and calculated coordinates are close enough. - void compare_original_and_calculated_coordinates( - const CartesianCoordinate3D& original_coords, - const CartesianCoordinate3D& centre_of_mass, - const BasicCoordinate<3, float>& grid_spacing); - - //! Modified version of check_if_equal for this test - bool check_if_almost_equal(const double a, const double b, const std::string& str, const double tolerance); - - std::string root_header_filename; - std::string image_filename; - std::vector max_lor; -}; - -void -ROOTconsistency_Tests::run_tests() -{ - // DiscretisedDensity for original image - shared_ptr > discretised_density_sptr(DiscretisedDensity<3,float>::read_from_file(image_filename)); - - // needs to be cast to VoxelsOnCartesianGrid to be able to calculate the centre of gravity, - // hence the location of the original source, stored in test_original_coords. - const VoxelsOnCartesianGrid& discretised_cartesian_grid = - dynamic_cast &>(*discretised_density_sptr); - CartesianCoordinate3D original_coords = find_centre_of_gravity_in_mm(discretised_cartesian_grid); - - construct_list_of_LOR_max(discretised_density_sptr); - - CartesianCoordinate3D centreofmass = compute_centre_of_mass(); - - compare_original_and_calculated_coordinates(original_coords,centreofmass,discretised_cartesian_grid.get_grid_spacing()); -} - -void -ROOTconsistency_Tests:: -construct_list_of_LOR_max(const shared_ptr >& discretised_density_sptr) -{ - shared_ptr lm_data_sptr(read_from_file(root_header_filename)); - - shared_ptr proj_matrix_sptr(new ProjMatrixByBinUsingRayTracing()); - - proj_matrix_sptr.get()->set_up(lm_data_sptr->get_proj_data_info_sptr(), - discretised_density_sptr); - proj_matrix_sptr->enable_tof(lm_data_sptr->get_proj_data_info_sptr()); - - ProjMatrixElemsForOneBin proj_matrix_row; - - { - // loop over all events in the listmode file - shared_ptr record_sptr = lm_data_sptr->get_empty_record_sptr(); - CListRecord& record = *record_sptr; - while (lm_data_sptr->get_next_record(record) == Succeeded::yes) - { - // only stores prompts - if (record.is_event() && record.event().is_prompt()) - { - Bin bin; - bin.set_bin_value(1.f); - // gets the bin corresponding to the event - record.event().get_bin(bin, *lm_data_sptr->get_proj_data_info_sptr()); - if ( bin.get_bin_value()>0 ) - { - // computes the TOF probabilities along the bin LOR - proj_matrix_sptr->get_proj_matrix_elems_for_one_bin(proj_matrix_row, bin); - // adds coordinates and weights of the elements with highest probability along LOR - get_LOR_of_max(proj_matrix_row, discretised_density_sptr); - } - } - } - } - -} - -void -ROOTconsistency_Tests::get_LOR_of_max(const ProjMatrixElemsForOneBin& probabilities, const shared_ptr >& test_discretised_density_sptr) -{ - std::stack tmp_max_lor; - - CartesianCoordinate3D voxel_centre; - - float maxLOR = 0; - - ProjMatrixElemsForOneBin::const_iterator element_ptr = probabilities.begin(); - // iterative calculation of highest probability and corresponding elements along the LOR - while (element_ptr != probabilities.end()) - { - if (element_ptr->get_value() >= maxLOR) - { - maxLOR = element_ptr->get_value(); - voxel_centre = - test_discretised_density_sptr->get_physical_coordinates_for_indices(element_ptr->get_coords()); - LORMax tmp; - tmp.value = element_ptr->get_value(); - tmp.voxel_centre = voxel_centre; - tmp_max_lor.push(tmp); - } - ++element_ptr; - } - - // only selects the elements on top of the stack, corresponding to the highest probability - if (maxLOR !=0) - { - while(!tmp_max_lor.empty()) - { - if (tmp_max_lor.top().value == maxLOR) - { - max_lor.push_back(tmp_max_lor.top()); - tmp_max_lor.pop(); - } - else break; - } - } -} - -CartesianCoordinate3D ROOTconsistency_Tests::compute_centre_of_mass() -{ - - // creation of a file with all LOR maxima, to be able to plot them - std::ofstream myfile; - std::string file_name = image_filename.substr(0,image_filename.size()-3) + ".txt"; - myfile.open (file_name.c_str()); - - LORMax centreofmass; - - // computes centre of mass - for (std::vector::iterator lor_element_ptr=max_lor.begin(); - lor_element_ptr != max_lor.end();++lor_element_ptr) - { - centreofmass.voxel_centre.x() += lor_element_ptr->voxel_centre.x()*lor_element_ptr->value; - centreofmass.voxel_centre.y() += lor_element_ptr->voxel_centre.y()*lor_element_ptr->value; - centreofmass.voxel_centre.z() += lor_element_ptr->voxel_centre.z()*lor_element_ptr->value; - centreofmass.value += lor_element_ptr->value; - - myfile << lor_element_ptr->voxel_centre.x() << " " - << lor_element_ptr->voxel_centre.y() << " " - << lor_element_ptr->voxel_centre.z() << " " - << lor_element_ptr->value << std::endl; - - } - - // needs to divide by the weights - if (centreofmass.value != 0) - { - centreofmass.voxel_centre.x()=centreofmass.voxel_centre.x()/centreofmass.value; - centreofmass.voxel_centre.y()=centreofmass.voxel_centre.y()/centreofmass.value; - centreofmass.voxel_centre.z()=centreofmass.voxel_centre.z()/centreofmass.value; - } - else - { - warning("Total weight of the centre of mass equal to 0. Please check your data."); - centreofmass.voxel_centre.x()=0; - centreofmass.voxel_centre.y()=0; - centreofmass.voxel_centre.z()=0; - } - - cerr << "Centre of gravity coordinates: " << centreofmass.voxel_centre.x() << " " - << centreofmass.voxel_centre.y() << " " << centreofmass.voxel_centre.z() << std::endl; - - myfile.close(); - - return centreofmass.voxel_centre; - -} - -// TODO change this -void ROOTconsistency_Tests::compare_original_and_calculated_coordinates(const CartesianCoordinate3D& original_coords, - const CartesianCoordinate3D& centre_of_mass, const BasicCoordinate<3, float>& grid_spacing) -{ - check_if_almost_equal(static_cast(original_coords.x()),static_cast(centre_of_mass.x()),"x",grid_spacing[1]); - check_if_almost_equal(static_cast(original_coords.y()),static_cast(centre_of_mass.y()),"y",grid_spacing[2]); - check_if_almost_equal(static_cast(original_coords.z()),static_cast(centre_of_mass.z()),"z",grid_spacing[3]); - - cerr << "Original coordinates: " << original_coords.x() << " " - << original_coords.y() << " " << original_coords.z() << std::endl; -} - -bool -ROOTconsistency_Tests::check_if_almost_equal(const double a, const double b, std::string str, const double tolerance) -{ - if ((fabs(a-b) > tolerance)) - { - std::cerr << "Error : unequal values are " << a << " and " << b - << ". " << str << std::endl; - everything_ok = false; - return false; - } - else - return true; -} - -END_NAMESPACE_STIR - -int main(int argc, char **argv) -{ - USING_NAMESPACE_STIR - - if (argc != 3) - { - cerr << "Usage : " << argv[1] << " filename " - << argv[2] << "original image \n" - << "See source file for the format of this file.\n\n"; - return EXIT_FAILURE; - } - - - ifstream in(argv[1]); - if (!in) - { - cerr << argv[0] - << ": Error opening root file " << argv[1] << "\nExiting.\n"; - - return EXIT_FAILURE; - } - ifstream in2(argv[2]); - if (!in2) - { - cerr << argv[0] - << ": Error opening original image " << argv[2] << "\nExiting.\n"; - - return EXIT_FAILURE; - } - std::string filename(argv[1]); - std::string image(argv[2]); - ROOTconsistency_Tests tests(filename,image); - tests.run_tests(); - return tests.main_return_value(); -} From ecb4fd41612f509fdd7173fa6f11a457060eb665 Mon Sep 17 00:00:00 2001 From: Robert Twyman Date: Sat, 9 Apr 2022 14:07:19 -0700 Subject: [PATCH 202/509] [ci skip] Significant fixes for test_consistency_with_root tests - Updates some documentation, - removes `check_if_almost_equal` method because unused, - renames `masx_coords_for_each_event` to `TOF_LOR_peak_value_coords`, -fix issue with `num_test` and `num_tests` being different variables, - fix issue with `empty()` being used to clear a std::vector (correct method is `clear()`, - increase `TOF_distance_threshold` to 2.5*norm(grid_spacing), - fix indexing issue in `test_results_nonTOF` and `test_results_TOF`, and - remove unused code block --- src/recon_test/test_consistency_with_root.cxx | 194 +++--------------- 1 file changed, 27 insertions(+), 167 deletions(-) diff --git a/src/recon_test/test_consistency_with_root.cxx b/src/recon_test/test_consistency_with_root.cxx index 51507e9395..96d3e92121 100644 --- a/src/recon_test/test_consistency_with_root.cxx +++ b/src/recon_test/test_consistency_with_root.cxx @@ -43,8 +43,6 @@ #include "stir/warning.h" -#include - using std::cerr; using std::ifstream; @@ -62,11 +60,10 @@ class WeightedCoordinate : coord(voxel_centre_v), value(value_v) {} - //! Center of mass position in mm + //! Coordinate position CartesianCoordinate3D coord; - //! Mass of all weights + //! Weight value float value; - //! COM standard deviation. }; @@ -100,9 +97,6 @@ class ROOTConsistencyTests : public RunTests /*! Given a ProjMatrixElemsForOneBin (probabilities), test if the closest voxel in the LOR to the original_coords * is within nonTOF_distance_threshold distance. If it is, pass with true, otherwise fales. * @param probabilities ProjMatrixElemsForOneBin object of a list mode event - * @param test_discretised_density_sptr Density containing a point source. - * @param original_coords Precomputed coordinates of the point source - * @param tolerance Precomputed voxel sizes - used in setting nonTOF_distance_threshold * @return True if test passes, false if failed. */ bool test_nonTOF_LOR_closest_approach(const ProjMatrixElemsForOneBin& probabilities); @@ -115,18 +109,18 @@ class ROOTConsistencyTests : public RunTests //! Checks if original and calculated coordinates are close enough and saves data to file. void post_processing_TOF(); - //! Modified version of check_if_equal for this test - bool check_if_almost_equal(const float a, const float b, const std::string str, const float tolerance); - // Auxilary methods std::string get_root_header_filename() { return "pretest_output/root_header_test" + std::to_string(test_index) + ".hroot"; } std::string get_generate_image_par_filename() { return "SourceFiles/generate_image" + std::to_string(test_index) + ".par"; } - // Class VARIABLES + ///// Class VARIABLES ///// + // Data set variables int test_index; int num_tests; + + // Test results storage. Records if each test failed or not. E.g.`test_index` result is stored in `test_results_nonTOF[test_index -1]` std::vector test_results_nonTOF; std::vector test_results_TOF; @@ -135,19 +129,19 @@ class ROOTConsistencyTests : public RunTests CartesianCoordinate3D grid_spacing; // Stored in class because of dynamic_cast int num_events_tested = 0; - const float failure_tolerance_nonTOF = 0.05; - const float failure_tolerance_TOF = 0.05; /// NON TOF VARIABLES //! A vector to store the coordinates of the closest approach voxels. + const float failure_tolerance_nonTOF = 0.05; std::vector> nonTOF_closest_voxels_list; int num_failed_nonTOF_lor_events = 0; double nonTOF_distance_threshold; /// TOF VARIABLES //! A copy of the scanner timing resolution in mm - float gauss_sigma_in_mm; - std::vector max_coords_for_each_event; + const float failure_tolerance_TOF = 0.05; + float gauss_sigma_in_mm; // Currently unused. + std::vector TOF_LOR_peak_value_coords; double TOF_distance_threshold; int num_failed_TOF_lor_events = 0; }; @@ -156,12 +150,12 @@ void ROOTConsistencyTests::run_tests() { // Loop over each of the ROOT files in the test_data directory - int num_test = 8; + num_tests = 8; test_results_nonTOF = std::vector (num_tests, true); test_results_TOF = std::vector (num_tests, true); cerr << "Testing the view offset consistency between GATE/ROOT and STIR. \n"; - for (int i = 1; i <= num_test; ++i) + for (int i = 1; i <= num_tests; ++i) { test_index = i; cerr << "\nTesting dataset " << std::to_string(test_index) << "...\n"; @@ -179,7 +173,7 @@ ROOTConsistencyTests::run_tests() "------------------------------------------\n" "\tTest Index\t|\tnonTOF\t|\tTOF\n" "------------------------------------------\n"; - for (int j = 0; j < num_test; ++j) + for (int j = 0; j < num_tests; ++j) cerr << "\t\t" << std::to_string(j + 1) << "\t\t|\t" << ((test_results_nonTOF[j]) ? "Pass" : "Fail") << "\t|\t" << ((test_results_TOF[j]) ? "Pass" : "Fail") << std::endl; @@ -189,13 +183,11 @@ void ROOTConsistencyTests:: setup() { - { - // Create the point source (discretised_density_sptr) with GenerateImage from parameter file - // GenerateImage requires `const char* const par_filename`. - GenerateImage image_gen_application(get_generate_image_par_filename().c_str()); - image_gen_application.compute(); - discretised_density_sptr = image_gen_application.get_output_sptr(); - } + // Create the point source (discretised_density_sptr) with GenerateImage from parameter file + // GenerateImage requires `const char* const par_filename`. + GenerateImage image_gen_application(get_generate_image_par_filename().c_str()); + image_gen_application.compute(); + discretised_density_sptr = image_gen_application.get_output_sptr(); // Needs to be cast to VoxelsOnCartesianGrid to be able to calculate the centre of gravity const VoxelsOnCartesianGrid &discretised_cartesian_grid = @@ -206,17 +198,17 @@ setup() grid_spacing = discretised_cartesian_grid.get_grid_spacing(); // For post_processing and debugging, create a list of the closest LOR coords - nonTOF_closest_voxels_list.empty(); - max_coords_for_each_event.empty(); + nonTOF_closest_voxels_list.clear(); + TOF_LOR_peak_value_coords.clear(); - // The number of LORs with closes approach greater than the threshold. + // Reset the number of processed and failed events num_failed_nonTOF_lor_events = 0; num_failed_TOF_lor_events = 0; num_events_tested = 0; // Failure conditioner and recording nonTOF_distance_threshold = 1.5 * norm(grid_spacing); // Using norm(grid_spacing) as a nonTOF_distance_threshold - TOF_distance_threshold = 1.5 * norm(grid_spacing); // Using norm(grid_spacing) as a tof_distance_threshold + TOF_distance_threshold = 2.5 * norm(grid_spacing); // Using norm(grid_spacing) as a tof_distance_threshold } void @@ -308,7 +300,7 @@ post_processing_nonTOF() cerr << "\nNon-TOF number of failed events: " << num_failed_nonTOF_lor_events << "\tNumber of tested events: = " << num_events_tested << std::endl; - test_results_nonTOF[test_index] = check_if_less(num_failed_nonTOF_lor_events, failure_tolerance_nonTOF * num_events_tested, + test_results_nonTOF[test_index-1] = check_if_less(num_failed_nonTOF_lor_events, failure_tolerance_nonTOF * num_events_tested, "The number of failed TOF events is more than the tolerance(" + std::to_string(100*failure_tolerance_nonTOF)+ "%)"); @@ -366,7 +358,7 @@ test_TOF_max_lor_voxel(const ProjMatrixElemsForOneBin& probabilities) sum_weighted_position.coord /= num_max_value_elements; // Add the COM position to the list of max_value_voxels - max_coords_for_each_event.push_back(sum_weighted_position); + TOF_LOR_peak_value_coords.push_back(sum_weighted_position); // Check if the COM position is within the TOF_distance_threshold tolerance if (norm(original_coords - sum_weighted_position.coord) > TOF_distance_threshold) @@ -382,7 +374,7 @@ post_processing_TOF() cerr << "\nTOF number of failed events: " << num_failed_TOF_lor_events << "\tNumber of tested events: = " << num_events_tested << std::endl; - test_results_TOF[test_index] = check_if_less(num_failed_TOF_lor_events, failure_tolerance_TOF * num_events_tested, + test_results_TOF[test_index-1] = check_if_less(num_failed_TOF_lor_events, failure_tolerance_TOF * num_events_tested, "The number of failed TOF events is more than the tolerance(" + std::to_string(100*failure_tolerance_nonTOF)+ "%)"); @@ -394,143 +386,11 @@ post_processing_TOF() std::ofstream myfile; myfile.open(lor_pos_filename.c_str()); myfile << original_coords.x() << " " << original_coords.y() << " " << original_coords.z() << std::endl; - for (std::vector::iterator coord_entry = max_coords_for_each_event.begin(); - coord_entry != max_coords_for_each_event.end(); ++coord_entry) + for (std::vector::iterator coord_entry = TOF_LOR_peak_value_coords.begin(); + coord_entry != TOF_LOR_peak_value_coords.end(); ++coord_entry) myfile << coord_entry->coord.x() << " " << coord_entry->coord.y() << " " << coord_entry->coord.z() << std::endl; myfile.close(); } - -#if 0 // old code, will not run. Used for records but can be deleted... - // This old code was used to check if the COM of all the max voxels was a distance_threshold of the original coordinate. - // There were also checks on the standard deviation of the max voxels. - // This is not the case anymore because this kind of analysis is done via python scripts. - - // creation of a file with all LOR maxima, to be able to plot them - std::ofstream myfile; - std::string max_lor_position_file_name = - current_root_header_filename.substr(0,current_root_header_filename.size()-3) + "_lor_tof_pos.txt"; - myfile.open (max_lor_position_file_name.c_str()); - - // Reset all COM values to 0.f - CartesianCoordinate3D max_lor_COM(0, 0, 0); - CartesianCoordinate3D max_lor_COM(0, 0, 0); - max_lor_COM.x() = 0.f; - max_lor_COM.x() = 0.f; - max_lor_COM.z() = 0.f; - max_lor_COM.weighted_standard_deviation.x() = 0.f; - max_lor_COM.weighted_standard_deviation.y() = 0.f; - max_lor_COM.weighted_standard_deviation.z() = 0.f; - - - // computes centre of mass (weighted mean value) = sum(x_i * v_i) - for (std::vector::iterator lor_element_ptr= max_coords_for_each_event.begin(); - lor_element_ptr != max_coords_for_each_event.end();++lor_element_ptr) - { - max_lor_COM.coord.x() += lor_element_ptr->coord.x()*lor_element_ptr->value; - max_lor_COM.coord.y() += lor_element_ptr->coord.y()*lor_element_ptr->value; - max_lor_COM.coord.z() += lor_element_ptr->coord.z()*lor_element_ptr->value; - max_lor_COM.value += lor_element_ptr->value; - - myfile << lor_element_ptr->coord.x() << " " - << lor_element_ptr->coord.y() << " " - << lor_element_ptr->coord.z() << " " - << lor_element_ptr->value << std::endl; - - } - myfile.close(); - - // needs to divide by the weights - if (max_lor_COM.value != 0) - { - max_lor_COM.coord.x()= max_lor_COM.coord.x()/ max_lor_COM.value; - max_lor_COM.coord.y()= max_lor_COM.coord.y()/ max_lor_COM.value; - max_lor_COM.coord.z()= max_lor_COM.coord.z()/ max_lor_COM.value; - } - else - { - warning("Total weight of the centre of mass equal to 0. Please check your data."); - max_lor_COM.coord.x()=0; - max_lor_COM.coord.y()=0; - max_lor_COM.coord.z()=0; - } - - // Compute the weighted standard deviation https://stats.stackexchange.com/a/6536 - for (std::vector::iterator lor_element_ptr= max_coords_for_each_event.begin(); - lor_element_ptr != max_coords_for_each_event.end();++lor_element_ptr) - { - CartesianCoordinate3D diff = lor_element_ptr->coord - max_lor_COM.coord; - max_lor_COM.weighted_standard_deviation.x() += lor_element_ptr->value * pow(diff.x(),2); - max_lor_COM.weighted_standard_deviation.y() += lor_element_ptr->value * pow(diff.y(), 2); - max_lor_COM.weighted_standard_deviation.z() += lor_element_ptr->value * pow(diff.z(), 2); - } - - float M = static_cast(max_coords_for_each_event.size()); - const float weighted_stddev_scalar = (M-1)/M * max_lor_COM.value; - max_lor_COM.weighted_standard_deviation.x() = sqrt(max_lor_COM.weighted_standard_deviation.x()/weighted_stddev_scalar); - max_lor_COM.weighted_standard_deviation.y() = sqrt(max_lor_COM.weighted_standard_deviation.y()/weighted_stddev_scalar); - max_lor_COM.weighted_standard_deviation.z() = sqrt(max_lor_COM.weighted_standard_deviation.z()/weighted_stddev_scalar); - - cerr << "Original coordinates: \t\t\t\t\t[x] " << original_coords.x() <<"\t[y] " - << original_coords.y() << "\t[z] " << original_coords.z() << std::endl; - - cerr << "Centre of gravity coordinates: \t\t\t[x] " << max_lor_COM.coord.x() <<"\t[y] " - << max_lor_COM.coord.y() << "\t[z] " << max_lor_COM.coord.z() << std::endl; - - cerr << "Centre of gravity coordinates (stddev): [x] " << max_lor_COM.weighted_standard_deviation.x() <<"\t[y] " - << max_lor_COM.weighted_standard_deviation.y() << "\t[z] " << max_lor_COM.weighted_standard_deviation.z() << "\n\n"<< std::endl; - - - // Check if max_lor_COM is within grid spacing nonTOF_distance_threshold with the original coordinates - check_if_almost_equal(original_coords.x(), max_lor_COM.coord.x(), - "COM not within grid spacing (x) of Original",grid_spacing[3]); - check_if_almost_equal(original_coords.y(), max_lor_COM.coord.y(), - "COM not within grid spacing (y) of Original",grid_spacing[2]); - check_if_almost_equal(original_coords.z(), max_lor_COM.coord.z(), - "COM not within grid spacing (z) of Original",grid_spacing[1]); - - // Check if weighted standard deviation is less than 2x the tof timing resolution - check_if_less(max_lor_COM.weighted_standard_deviation.x(), 2*gauss_sigma_in_mm, - "COM weighted stddev[x] is greater than 2x TOF timing resolution (mm)"); - check_if_less(max_lor_COM.weighted_standard_deviation.y(), 2*gauss_sigma_in_mm, - "COM weighted stddev[y] is greater than 2x TOF timing resolution (mm)"); - check_if_less(max_lor_COM.weighted_standard_deviation.z(), 2*gauss_sigma_in_mm, - "COM weighted stddev[z] is greater than 2x TOF timing resolution (mm)"); - - - // Save the origin and Center of mass data to a separate file. - std::ofstream myfile; - std::string origin_and_COM_file_name = current_root_header_filename.substr(0,current_root_header_filename.size()-6) + "_tof_ORIG_and_COM.txt"; - myfile.open (origin_and_COM_file_name.c_str()); - // Original coordinates - myfile << original_coords.x() << " " - << original_coords.y() << " " - << original_coords.z() << std::endl; - // max LOR mean coordinates - myfile << max_lor_COM.coord.x() << " " - << max_lor_COM.coord.y() << " " - << max_lor_COM.coord.z() << std::endl; - // max LOR weighted stddev - myfile << max_lor_COM.weighted_standard_deviation.x() << " " - << max_lor_COM.weighted_standard_deviation.y() << " " - << max_lor_COM.weighted_standard_deviation.z() << std::endl; - - myfile.close(); -#endif -} - -bool -ROOTConsistencyTests:: -check_if_almost_equal(const float a, const float b, const std::string str, const float tolerance) -{ - if ((fabs(a-b) > tolerance)) - { - std::cerr << "Error : unequal values are " << a << " and " << b - << ". " << str << std::endl; - everything_ok = false; - return false; - } - else - return true; } END_NAMESPACE_STIR From a7ac52f678fc715de4ab7f8eded6fc3224010368 Mon Sep 17 00:00:00 2001 From: Robert Twyman Date: Sat, 9 Apr 2022 14:24:09 -0700 Subject: [PATCH 203/509] [ci skip] The data is TOF by default and guass_sigma_in_mm is not needed for test. --- src/recon_test/test_consistency_with_root.cxx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/recon_test/test_consistency_with_root.cxx b/src/recon_test/test_consistency_with_root.cxx index 96d3e92121..d02d01dc41 100644 --- a/src/recon_test/test_consistency_with_root.cxx +++ b/src/recon_test/test_consistency_with_root.cxx @@ -140,7 +140,6 @@ class ROOTConsistencyTests : public RunTests /// TOF VARIABLES //! A copy of the scanner timing resolution in mm const float failure_tolerance_TOF = 0.05; - float gauss_sigma_in_mm; // Currently unused. std::vector TOF_LOR_peak_value_coords; double TOF_distance_threshold; int num_failed_TOF_lor_events = 0; @@ -220,10 +219,6 @@ ROOTConsistencyTests::process_list_data() proj_matrix_sptr.get()->set_up(lm_data_sptr->get_proj_data_info_sptr(), discretised_density_sptr); - // TOF requirements - proj_matrix_sptr->enable_tof(lm_data_sptr->get_proj_data_info_sptr()); - gauss_sigma_in_mm = ProjDataInfo::tof_delta_time_to_mm(lm_data_sptr->get_proj_data_info_sptr()->get_scanner_ptr()->get_timing_resolution()) / 2.355f; - // loop over all events in the listmode file shared_ptr record_sptr = lm_data_sptr->get_empty_record_sptr(); CListRecord& record = *record_sptr; From 3ae07306065613365e7e3b2440e9666485a5b8a4 Mon Sep 17 00:00:00 2001 From: Robert Twyman Date: Sun, 10 Apr 2022 10:02:00 -0700 Subject: [PATCH 204/509] Rename debug tool and cleanup content for non-TOF tests --- ...ency.py => debug_consistency_with_root.py} | 60 ++++++++++--------- 1 file changed, 31 insertions(+), 29 deletions(-) rename examples/ROOT_files/ROOT_STIR_consistency/DebugScripts/{debug_view_offset_consistency.py => debug_consistency_with_root.py} (86%) diff --git a/examples/ROOT_files/ROOT_STIR_consistency/DebugScripts/debug_view_offset_consistency.py b/examples/ROOT_files/ROOT_STIR_consistency/DebugScripts/debug_consistency_with_root.py similarity index 86% rename from examples/ROOT_files/ROOT_STIR_consistency/DebugScripts/debug_view_offset_consistency.py rename to examples/ROOT_files/ROOT_STIR_consistency/DebugScripts/debug_consistency_with_root.py index 927e253027..3d541a30b5 100644 --- a/examples/ROOT_files/ROOT_STIR_consistency/DebugScripts/debug_view_offset_consistency.py +++ b/examples/ROOT_files/ROOT_STIR_consistency/DebugScripts/debug_consistency_with_root.py @@ -1,5 +1,5 @@ import matplotlib.pyplot as plt -from os import getcwd +from os import chdir import sys import numpy as np from math import log10, floor @@ -74,12 +74,12 @@ def plot_1D_distance_histogram(distances, n_bins=100, logy=False, pretitle=""): f"{pretitle} l2-norm of distance to origin: Mean = {round_sig(mean)} and Median = {round_sig(median)}") -class ViewOffsetConsistencyClosestLORInfo: +class ROOTConsistencyDataHandler: """ Helper class. - Loads and converts the data outputs by the `test_view_offset_root` test in STIR into coordinates. + Loads and converts the data output by the `test_consistency_with_root` test. The first line of the text file should be the original coordinate of the point source and - the rest the closest voxel along the LOR to the original. + the rest correspond to a selected voxel position along the LOR. Each line is expected to be formatted as [ x y z ], e.g., '190.048 0 145.172\n' """ @@ -143,9 +143,8 @@ def get_num_events(self): return len(self.voxel_coords) def set_tolerance(self, tolerance): - if tolerance is not None: - print(f"Overwriting tolerance value as {tolerance}") - self.tolerance = tolerance + print(f"Overwriting tolerance value as {tolerance}") + self.tolerance = tolerance def get_num_failed_events(self, tolerance=None): """ @@ -161,12 +160,10 @@ def get_failure_percentage(self, tolerance=None): Returns the percentage of events that are outside the tolerance :param tolerance: l2-norm tolerance that classified a "failed" event. """ - if tolerance is None: - tolerance = self.tolerance return self.get_num_failed_events(tolerance) / self.get_num_events() -def print_pass_and_fail(allowed_fraction_of_failed_events=0.05): +def print_pass_and_fail(point_sources_data, allowed_fraction_of_failed_events=0.05): """ Prints the number of events, how many events failed and the percentage :param allowed_fraction_of_failed_events: Fraction of events that could "fail" before the test failed @@ -197,7 +194,7 @@ def print_pass_and_fail(allowed_fraction_of_failed_events=0.05): print(f"{row_string} {warning_msg}") -def print_axis_biases(): +def print_axis_biases(point_sources_data): """ Prints the LOR COM and the axis bias for each point source and the overall bias in each axis. :return: None @@ -264,26 +261,31 @@ def print_axis_biases(): # ===================================================================================================== # Main Script # ===================================================================================================== -print("\nUSAGE: After `make test` or `test_view_offset_root` has been run,\n" - "run `debug_view_offset_consistency` from `pretest_output` directory or input that directory as an argument.\n") +def main(): + print("\nUSAGE: After `make test` or `test_view_offset_root` has been run,\n" + "run `debug_view_offset_consistency` from `ROOT_STIR_consistency` directory or input that directory as an " + "argument.\n") + + # Optional argument to set the directory of the output of the test_consistency_with_root CTest. + if len(sys.argv) > 1: + chdir(sys.argv[1]) + + # Assuming the prefix's and suffix's of the file names + filename_prefix = "non_TOF_voxel_data_" + filename_suffix = ".txt" -# Optional argument to set the directory of the output of the view_offset_root test. -working_directory = getcwd() -if len(sys.argv) > 1: - working_directory = sys.argv[1] + # Loop over all files in the working directory and load the data into the point_sources_data dictionary + point_sources_data = dict() + for i in range(1, 9, 1): + point_sources_data[i] = ROOTConsistencyDataHandler(f"{filename_prefix}{i}{filename_suffix}") -point_sources_data = dict() -# Assumeing the prefix's and suffix's of the file names -filename_prefix = "root_header_test" -filename_suffix = "_lor_pos.txt" + # Print the number of events, number of failed events and failure percentage for each point source + print_pass_and_fail(point_sources_data) + # Print the mean offset in each axis (x,y,z) for each point source and the total bias in each axis + print_axis_biases(point_sources_data) -# Loop over all files in the working directory and load the data into the point_sources_data dictionary -for i in range(1, 9, 1): - point_sources_data[i] = ViewOffsetConsistencyClosestLORInfo(f"{filename_prefix}{i}{filename_suffix}") + print("Done") -# Print the number of events, number of failed events and failure percentage for each point source -print_pass_and_fail() -# Print the mean offset in each axis (x,y,z) for each point source and the total bias in each axis -print_axis_biases() -print("Done") +if __name__ == "__main__": + main() From 21ec98ecb959a9501b376f75d647e23e13243a92 Mon Sep 17 00:00:00 2001 From: Robert Twyman Date: Sun, 10 Apr 2022 10:02:43 -0700 Subject: [PATCH 205/509] Update test data output ti *voxel_data*.txt in .gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 02e19409b8..3b9ae581c8 100644 --- a/.gitignore +++ b/.gitignore @@ -90,6 +90,6 @@ install_manifest.txt examples/ROOT_files/ROOT_STIR_consistency/Gate_macros/main_GATE_macro_test*.mac examples/ROOT_files/ROOT_STIR_consistency/pretest_output/root_data_test*.root examples/ROOT_files/ROOT_STIR_consistency/pretest_output/root_header_test*.hroot -examples/ROOT_files/ROOT_STIR_consistency/pretest_output/root_header_test*_lor_pos.txt +examples/ROOT_files/ROOT_STIR_consistency/*voxel_data_*.txt examples/ROOT_files/ROOT_STIR_consistency/pretest_output/stir_image*.* From 9f4f2f78054301aa98669e3faf25e2d31da88373 Mon Sep 17 00:00:00 2001 From: Robert Twyman Date: Mon, 16 May 2022 08:21:40 -0700 Subject: [PATCH 206/509] [ci skip] TOF debug_consistency_with_root improvements Adds PointCloud3D() and changes filename checks? --- .../debug_consistency_with_root.py | 80 ++++++++++++++++--- 1 file changed, 67 insertions(+), 13 deletions(-) diff --git a/examples/ROOT_files/ROOT_STIR_consistency/DebugScripts/debug_consistency_with_root.py b/examples/ROOT_files/ROOT_STIR_consistency/DebugScripts/debug_consistency_with_root.py index 3d541a30b5..88ad1769eb 100644 --- a/examples/ROOT_files/ROOT_STIR_consistency/DebugScripts/debug_consistency_with_root.py +++ b/examples/ROOT_files/ROOT_STIR_consistency/DebugScripts/debug_consistency_with_root.py @@ -73,6 +73,44 @@ def plot_1D_distance_histogram(distances, n_bins=100, logy=False, pretitle=""): axs.title.set_text( f"{pretitle} l2-norm of distance to origin: Mean = {round_sig(mean)} and Median = {round_sig(median)}") +def PointCloud3D(DataHandler): + # import matplotlib.pyplot as plt + # import random + + fig = plt.figure(figsize=(10, 10)) + ax = fig.add_subplot(projection='3d') + + # Plot all the points (intensity increases for multiple points) + ax.scatter(DataHandler.voxel_coords[:, 0], DataHandler.voxel_coords[:, 1], DataHandler.voxel_coords[:, 2]) + + # Plot the original point and tolerance + ox = DataHandler.original_coord[0] + oy = DataHandler.original_coord[1] + oz = DataHandler.original_coord[2] + tol = DataHandler.tolerance + ax.plot(ox, oy, oz, c='r', marker='o') + #plot tolerence around original point + ax.plot([ox+tol, ox-tol], [oy, oy], [oz, oz], c='r', marker="_", label='_nolegend_') + ax.plot([ox, ox], [oy+tol, oy-tol], [oz, oz], c='r', marker="_", label='_nolegend_') + ax.plot([ox, ox], [oy, oy], [oz+tol, oz-tol], c='r', marker="_", label='_nolegend_') + + #plot Mean position and standard deviation + fx = DataHandler.mean_coord[0] + fy = DataHandler.mean_coord[1] + fz = DataHandler.mean_coord[2] + xerror = np.std(DataHandler.voxel_coords[:, 0]) + yerror = np.std(DataHandler.voxel_coords[:, 1]) + zerror = np.std(DataHandler.voxel_coords[:, 2]) + ax.plot(fx, fy, fz, linestyle="None", marker="o", c='g') + ax.plot([fx+xerror, fx-xerror], [fy, fy], [fz, fz], marker="_", c='g', label='_nolegend_') + ax.plot([fx, fx], [fy+yerror, fy-yerror], [fz, fz], marker="_", c='g', label='_nolegend_') + ax.plot([fx, fx], [fy, fy], [fz+zerror, fz-zerror], marker="_", c='g', label='_nolegend_') + + ax.set_xlabel('x (mm)') + ax.set_ylabel('y (mm)') + ax.set_zlabel('z (mm)') + ax.legend(['Origin and Tolerance', 'Mean coords and stddev ', 'Voxel Positions']) + plt.show() class ROOTConsistencyDataHandler: """ @@ -83,10 +121,11 @@ class ROOTConsistencyDataHandler: Each line is expected to be formatted as [ x y z ], e.g., '190.048 0 145.172\n' """ - def __init__(self, filename, tolerance=6.66983): + def __init__(self, filename, tolerance=1.5 * 4.447): """ :param filename: Filename of the file output by `test_view_offset_root` (.txt file) - :param tolerance: l2-norm tolerance that classified a "failed" event. Default = 6.66983, from previous studies. + :param tolerance: l2-norm tolerance that classified a "failed" event. Default = 6.66983 = 1.5 * 4.447mm, + from previous studies. """ self.filename = filename print(f"Loading data: {self.filename}") @@ -258,6 +297,30 @@ def print_axis_biases(point_sources_data): print(f"{row_string}") +def nonTOF_evaluation(filename_prefix, file_extension=".txt"): + # Loop over all files in the working directory and load the data into the point_sources_data dictionary + point_sources_data = dict() + for i in range(1, 9, 1): + point_sources_data[i] = ROOTConsistencyDataHandler(f"{filename_prefix}{i}{file_extension}") + + # Print the number of events, number of failed events and failure percentage for each point source + print_pass_and_fail(point_sources_data) + # Print the mean offset in each axis (x,y,z) for each point source and the total bias in each axis + print_axis_biases(point_sources_data) + +def TOF_evaluation(filename_prefix, file_extension=".txt"): + # Loop over all files in the working directory and load the data into the point_sources_data dictionary + point_sources_data = dict() + for i in range(1, 9, 1): + point_sources_data[i] = ROOTConsistencyDataHandler(f"{filename_prefix}{i}{file_extension}", tolerance=2.5*4.447) + + # Print the number of events, number of failed events and failure percentage for each point source + print_pass_and_fail(point_sources_data) + # Print the mean offset in each axis (x,y,z) for each point source and the total bias in each axis + print_axis_biases(point_sources_data) + + PointCloud3D(point_sources_data[1]) + # ===================================================================================================== # Main Script # ===================================================================================================== @@ -270,19 +333,10 @@ def main(): if len(sys.argv) > 1: chdir(sys.argv[1]) - # Assuming the prefix's and suffix's of the file names - filename_prefix = "non_TOF_voxel_data_" - filename_suffix = ".txt" + nonTOF_evaluation("non_TOF_voxel_data_") - # Loop over all files in the working directory and load the data into the point_sources_data dictionary - point_sources_data = dict() - for i in range(1, 9, 1): - point_sources_data[i] = ROOTConsistencyDataHandler(f"{filename_prefix}{i}{filename_suffix}") + TOF_evaluation("TOF_voxel_data_") - # Print the number of events, number of failed events and failure percentage for each point source - print_pass_and_fail(point_sources_data) - # Print the mean offset in each axis (x,y,z) for each point source and the total bias in each axis - print_axis_biases(point_sources_data) print("Done") From e244cb42036af68ed4c6da55a742473700967d1f Mon Sep 17 00:00:00 2001 From: NicoleJurjew Date: Fri, 12 Aug 2022 13:15:19 +0100 Subject: [PATCH 207/509] changed TOF-test threshold to 3D & added 3D plot of all sources in 1 plot --- .../debug_consistency_with_root.py | 64 ++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/examples/ROOT_files/ROOT_STIR_consistency/DebugScripts/debug_consistency_with_root.py b/examples/ROOT_files/ROOT_STIR_consistency/DebugScripts/debug_consistency_with_root.py index 88ad1769eb..7f9f4656a2 100644 --- a/examples/ROOT_files/ROOT_STIR_consistency/DebugScripts/debug_consistency_with_root.py +++ b/examples/ROOT_files/ROOT_STIR_consistency/DebugScripts/debug_consistency_with_root.py @@ -110,6 +110,67 @@ def PointCloud3D(DataHandler): ax.set_ylabel('y (mm)') ax.set_zlabel('z (mm)') ax.legend(['Origin and Tolerance', 'Mean coords and stddev ', 'Voxel Positions']) + + plt.show() + +def PointCloud3D_all(point_sources_data): + # import matplotlib.pyplot as plt + # import random + + fig = plt.figure(figsize=(10, 10)) + ax = fig.add_subplot(projection='3d') + + for key in point_sources_data.keys(): + DataHandler = point_sources_data[key] + + # Plot all the points (intensity increases for multiple points) + ax.scatter(DataHandler.voxel_coords[:, 0], DataHandler.voxel_coords[:, 1], DataHandler.voxel_coords[:, 2], label = 'Source'+str(key)) + + # Plot the original point and tolerance + ox = DataHandler.original_coord[0] + oy = DataHandler.original_coord[1] + oz = DataHandler.original_coord[2] + tol = DataHandler.tolerance + ax.plot(ox, oy, oz, c='r', marker='o') + #plot tolerence around original point + ax.plot([ox+tol, ox-tol], [oy, oy], [oz, oz], c='r', marker="_", label='_nolegend_') + ax.plot([ox, ox], [oy+tol, oy-tol], [oz, oz], c='r', marker="_", label='_nolegend_') + ax.plot([ox, ox], [oy, oy], [oz+tol, oz-tol], c='r', marker="_", label='_nolegend_') + + #plot Mean position and standard deviation + fx = DataHandler.mean_coord[0] + fy = DataHandler.mean_coord[1] + fz = DataHandler.mean_coord[2] + xerror = np.std(DataHandler.voxel_coords[:, 0]) + yerror = np.std(DataHandler.voxel_coords[:, 1]) + zerror = np.std(DataHandler.voxel_coords[:, 2]) + ax.plot(fx, fy, fz, linestyle="None", marker="o", c='g') + ax.plot([fx+xerror, fx-xerror], [fy, fy], [fz, fz], marker="_", c='g', label='_nolegend_') + ax.plot([fx, fx], [fy+yerror, fy-yerror], [fz, fz], marker="_", c='g', label='_nolegend_') + ax.plot([fx, fx], [fy, fy], [fz+zerror, fz-zerror], marker="_", c='g', label='_nolegend_') + + ax.set_xlabel('x (mm)') + ax.set_ylabel('y (mm)') + ax.set_zlabel('z (mm)') + # set the x-spine (see below for more info on `set_position`) + ax.spines['left'].set_position('zero') + + # turn off the right spine/ticks + #ax.spines['right'].set_color('none') + #ax.yaxis.tick_left() + + # set the y-spine + ax.spines['bottom'].set_position('zero') + + # turn off the top spine/ticks + #ax.spines['top'].set_color('none') + #ax.xaxis.tick_bottom() + + #ax.legend(['Origin and Tolerance', 'Mean coords and stddev ', 'Voxel Positions']) + + + + plt.legend() plt.show() class ROOTConsistencyDataHandler: @@ -312,7 +373,7 @@ def TOF_evaluation(filename_prefix, file_extension=".txt"): # Loop over all files in the working directory and load the data into the point_sources_data dictionary point_sources_data = dict() for i in range(1, 9, 1): - point_sources_data[i] = ROOTConsistencyDataHandler(f"{filename_prefix}{i}{file_extension}", tolerance=2.5*4.447) + point_sources_data[i] = ROOTConsistencyDataHandler(f"{filename_prefix}{i}{file_extension}", tolerance=3.3*4.447) # Print the number of events, number of failed events and failure percentage for each point source print_pass_and_fail(point_sources_data) @@ -320,6 +381,7 @@ def TOF_evaluation(filename_prefix, file_extension=".txt"): print_axis_biases(point_sources_data) PointCloud3D(point_sources_data[1]) + PointCloud3D_all(point_sources_data) # ===================================================================================================== # Main Script From 84cee72ea3e3746994b885fde51138cdcdf8d76b Mon Sep 17 00:00:00 2001 From: NicoleJurjew Date: Fri, 12 Aug 2022 13:15:34 +0100 Subject: [PATCH 208/509] adapted TOF-test threshold to 3.3 --- src/recon_test/test_consistency_with_root.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/recon_test/test_consistency_with_root.cxx b/src/recon_test/test_consistency_with_root.cxx index d02d01dc41..81f7d01eb2 100644 --- a/src/recon_test/test_consistency_with_root.cxx +++ b/src/recon_test/test_consistency_with_root.cxx @@ -207,7 +207,7 @@ setup() // Failure conditioner and recording nonTOF_distance_threshold = 1.5 * norm(grid_spacing); // Using norm(grid_spacing) as a nonTOF_distance_threshold - TOF_distance_threshold = 2.5 * norm(grid_spacing); // Using norm(grid_spacing) as a tof_distance_threshold + TOF_distance_threshold = 3.3 * norm(grid_spacing); // Using norm(grid_spacing) as a tof_distance_threshold } void From 4cc2120f806e6be41c3572400ff66d184d678cd6 Mon Sep 17 00:00:00 2001 From: NicoleJurjew Date: Fri, 12 Aug 2022 16:24:08 +0100 Subject: [PATCH 209/509] add documentation and changee variable names and plot legend --- .../debug_consistency_with_root.py | 86 +++++++++---------- src/recon_test/test_consistency_with_root.cxx | 2 +- 2 files changed, 42 insertions(+), 46 deletions(-) diff --git a/examples/ROOT_files/ROOT_STIR_consistency/DebugScripts/debug_consistency_with_root.py b/examples/ROOT_files/ROOT_STIR_consistency/DebugScripts/debug_consistency_with_root.py index 7f9f4656a2..6c3083e1c9 100644 --- a/examples/ROOT_files/ROOT_STIR_consistency/DebugScripts/debug_consistency_with_root.py +++ b/examples/ROOT_files/ROOT_STIR_consistency/DebugScripts/debug_consistency_with_root.py @@ -73,21 +73,24 @@ def plot_1D_distance_histogram(distances, n_bins=100, logy=False, pretitle=""): axs.title.set_text( f"{pretitle} l2-norm of distance to origin: Mean = {round_sig(mean)} and Median = {round_sig(median)}") -def PointCloud3D(DataHandler): - # import matplotlib.pyplot as plt - # import random +def point_cloud_3D(data_handler): + """ + Plot the pointcloud of generated LOR-positions, original source-position and tolerance-whiskers for each source in a seperate scatter plot. + Args: + data_handler (ROOTConsistencyDataHandler): Object containing all relevant data for the plot + """ fig = plt.figure(figsize=(10, 10)) ax = fig.add_subplot(projection='3d') # Plot all the points (intensity increases for multiple points) - ax.scatter(DataHandler.voxel_coords[:, 0], DataHandler.voxel_coords[:, 1], DataHandler.voxel_coords[:, 2]) + ax.scatter(data_handler.voxel_coords[:, 0], data_handler.voxel_coords[:, 1], data_handler.voxel_coords[:, 2]) # Plot the original point and tolerance - ox = DataHandler.original_coord[0] - oy = DataHandler.original_coord[1] - oz = DataHandler.original_coord[2] - tol = DataHandler.tolerance + ox = data_handler.original_coord[0] + oy = data_handler.original_coord[1] + oz = data_handler.original_coord[2] + tol = data_handler.tolerance ax.plot(ox, oy, oz, c='r', marker='o') #plot tolerence around original point ax.plot([ox+tol, ox-tol], [oy, oy], [oz, oz], c='r', marker="_", label='_nolegend_') @@ -95,12 +98,12 @@ def PointCloud3D(DataHandler): ax.plot([ox, ox], [oy, oy], [oz+tol, oz-tol], c='r', marker="_", label='_nolegend_') #plot Mean position and standard deviation - fx = DataHandler.mean_coord[0] - fy = DataHandler.mean_coord[1] - fz = DataHandler.mean_coord[2] - xerror = np.std(DataHandler.voxel_coords[:, 0]) - yerror = np.std(DataHandler.voxel_coords[:, 1]) - zerror = np.std(DataHandler.voxel_coords[:, 2]) + fx = data_handler.mean_coord[0] + fy = data_handler.mean_coord[1] + fz = data_handler.mean_coord[2] + xerror = np.std(data_handler.voxel_coords[:, 0]) + yerror = np.std(data_handler.voxel_coords[:, 1]) + zerror = np.std(data_handler.voxel_coords[:, 2]) ax.plot(fx, fy, fz, linestyle="None", marker="o", c='g') ax.plot([fx+xerror, fx-xerror], [fy, fy], [fz, fz], marker="_", c='g', label='_nolegend_') ax.plot([fx, fx], [fy+yerror, fy-yerror], [fz, fz], marker="_", c='g', label='_nolegend_') @@ -109,28 +112,31 @@ def PointCloud3D(DataHandler): ax.set_xlabel('x (mm)') ax.set_ylabel('y (mm)') ax.set_zlabel('z (mm)') - ax.legend(['Origin and Tolerance', 'Mean coords and stddev ', 'Voxel Positions']) + ax.legend(['Voxel Positions', 'Origin and Tolerance','Mean coords and stddev']) plt.show() -def PointCloud3D_all(point_sources_data): - # import matplotlib.pyplot as plt - # import random +def point_cloud_3D_all(dict_of_data_handlers): + """ + generates a single plot with pointclouds of generated LOR-positions, original source-positions and tolerance-whiskers for all sources + Args: + dict_of_data_handlers (dictionary containing objects of type ROOTConsistencyDataHandler): dictionary of objects containing all relevant data for the plot + """ fig = plt.figure(figsize=(10, 10)) ax = fig.add_subplot(projection='3d') - for key in point_sources_data.keys(): - DataHandler = point_sources_data[key] + for key in dict_of_data_handlers.keys(): + data_handler = dict_of_data_handlers[key] # Plot all the points (intensity increases for multiple points) - ax.scatter(DataHandler.voxel_coords[:, 0], DataHandler.voxel_coords[:, 1], DataHandler.voxel_coords[:, 2], label = 'Source'+str(key)) + ax.scatter(data_handler.voxel_coords[:, 0], data_handler.voxel_coords[:, 1], data_handler.voxel_coords[:, 2], label = 'Source'+str(key)) # Plot the original point and tolerance - ox = DataHandler.original_coord[0] - oy = DataHandler.original_coord[1] - oz = DataHandler.original_coord[2] - tol = DataHandler.tolerance + ox = data_handler.original_coord[0] + oy = data_handler.original_coord[1] + oz = data_handler.original_coord[2] + tol = data_handler.tolerance ax.plot(ox, oy, oz, c='r', marker='o') #plot tolerence around original point ax.plot([ox+tol, ox-tol], [oy, oy], [oz, oz], c='r', marker="_", label='_nolegend_') @@ -138,12 +144,12 @@ def PointCloud3D_all(point_sources_data): ax.plot([ox, ox], [oy, oy], [oz+tol, oz-tol], c='r', marker="_", label='_nolegend_') #plot Mean position and standard deviation - fx = DataHandler.mean_coord[0] - fy = DataHandler.mean_coord[1] - fz = DataHandler.mean_coord[2] - xerror = np.std(DataHandler.voxel_coords[:, 0]) - yerror = np.std(DataHandler.voxel_coords[:, 1]) - zerror = np.std(DataHandler.voxel_coords[:, 2]) + fx = data_handler.mean_coord[0] + fy = data_handler.mean_coord[1] + fz = data_handler.mean_coord[2] + xerror = np.std(data_handler.voxel_coords[:, 0]) + yerror = np.std(data_handler.voxel_coords[:, 1]) + zerror = np.std(data_handler.voxel_coords[:, 2]) ax.plot(fx, fy, fz, linestyle="None", marker="o", c='g') ax.plot([fx+xerror, fx-xerror], [fy, fy], [fz, fz], marker="_", c='g', label='_nolegend_') ax.plot([fx, fx], [fy+yerror, fy-yerror], [fz, fz], marker="_", c='g', label='_nolegend_') @@ -152,21 +158,11 @@ def PointCloud3D_all(point_sources_data): ax.set_xlabel('x (mm)') ax.set_ylabel('y (mm)') ax.set_zlabel('z (mm)') - # set the x-spine (see below for more info on `set_position`) - ax.spines['left'].set_position('zero') - - # turn off the right spine/ticks - #ax.spines['right'].set_color('none') - #ax.yaxis.tick_left() + # set the x-spine + ax.spines['left'].set_position('zero') # set the y-spine ax.spines['bottom'].set_position('zero') - - # turn off the top spine/ticks - #ax.spines['top'].set_color('none') - #ax.xaxis.tick_bottom() - - #ax.legend(['Origin and Tolerance', 'Mean coords and stddev ', 'Voxel Positions']) @@ -380,8 +376,8 @@ def TOF_evaluation(filename_prefix, file_extension=".txt"): # Print the mean offset in each axis (x,y,z) for each point source and the total bias in each axis print_axis_biases(point_sources_data) - PointCloud3D(point_sources_data[1]) - PointCloud3D_all(point_sources_data) + point_cloud_3D(point_sources_data[1]) + point_cloud_3D_all(point_sources_data) # ===================================================================================================== # Main Script diff --git a/src/recon_test/test_consistency_with_root.cxx b/src/recon_test/test_consistency_with_root.cxx index 81f7d01eb2..6cbe2a444b 100644 --- a/src/recon_test/test_consistency_with_root.cxx +++ b/src/recon_test/test_consistency_with_root.cxx @@ -207,7 +207,7 @@ setup() // Failure conditioner and recording nonTOF_distance_threshold = 1.5 * norm(grid_spacing); // Using norm(grid_spacing) as a nonTOF_distance_threshold - TOF_distance_threshold = 3.3 * norm(grid_spacing); // Using norm(grid_spacing) as a tof_distance_threshold + TOF_distance_threshold = 3.3 * norm(grid_spacing); // With the value 3.3*norm(grid_spacing), test_root_consistency passes for all sources } void From 85d94723c9e7bfedcbb9cd494b4ba4855b70f156 Mon Sep 17 00:00:00 2001 From: Robert Twyman Skelly Date: Wed, 17 Aug 2022 14:21:56 -0700 Subject: [PATCH 210/509] Rename `test_consistency_with_root` to `test_consistency_with_GATE` Filename and class name change --- src/recon_test/CMakeLists.txt | 8 +++---- ...oot.cxx => test_consistency_with_GATE.cxx} | 22 +++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) rename src/recon_test/{test_consistency_with_root.cxx => test_consistency_with_GATE.cxx} (97%) diff --git a/src/recon_test/CMakeLists.txt b/src/recon_test/CMakeLists.txt index 2592070e2c..b83df21742 100644 --- a/src/recon_test/CMakeLists.txt +++ b/src/recon_test/CMakeLists.txt @@ -107,12 +107,12 @@ if (HAVE_CERN_ROOT) # Add test if the data is downloaded (only checking root_header_test1.hroot) if(EXISTS "${ROOT_STIR_consistency_data_dir}/root_header_test1.hroot") - list(APPEND ${dir_INVOLVED_TEST_EXE_SOURCES} test_consistency_with_root) - # test_consistency_with_root requires working directory to be examples/ROOT_files/ROOT_STIR_consistency + list(APPEND ${dir_INVOLVED_TEST_EXE_SOURCES} test_consistency_with_GATE.cxx) + # test_consistency_with_GATE requires working directory to be examples/ROOT_files/ROOT_STIR_consistency # for output files to be written there - ADD_TEST(NAME test_consistency_with_root + ADD_TEST(NAME test_consistency_with_GATE WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/examples/ROOT_files/ROOT_STIR_consistency - COMMAND ${CMAKE_CURRENT_BINARY_DIR}/test_consistency_with_root) + COMMAND ${CMAKE_CURRENT_BINARY_DIR}/test_consistency_with_GATE) endif() endif() diff --git a/src/recon_test/test_consistency_with_root.cxx b/src/recon_test/test_consistency_with_GATE.cxx similarity index 97% rename from src/recon_test/test_consistency_with_root.cxx rename to src/recon_test/test_consistency_with_GATE.cxx index 6cbe2a444b..26682ae1b3 100644 --- a/src/recon_test/test_consistency_with_root.cxx +++ b/src/recon_test/test_consistency_with_GATE.cxx @@ -67,10 +67,10 @@ class WeightedCoordinate }; -class ROOTConsistencyTests : public RunTests +class GATEConsistencyTests : public RunTests { public: - ROOTConsistencyTests()= default; + GATEConsistencyTests()= default; /*! * \brief Run the tests with ROOT data @@ -146,7 +146,7 @@ class ROOTConsistencyTests : public RunTests }; void -ROOTConsistencyTests::run_tests() +GATEConsistencyTests::run_tests() { // Loop over each of the ROOT files in the test_data directory num_tests = 8; @@ -179,7 +179,7 @@ ROOTConsistencyTests::run_tests() } void -ROOTConsistencyTests:: +GATEConsistencyTests:: setup() { // Create the point source (discretised_density_sptr) with GenerateImage from parameter file @@ -211,7 +211,7 @@ setup() } void -ROOTConsistencyTests::process_list_data() +GATEConsistencyTests::process_list_data() { // Configure the list mode reader shared_ptr lm_data_sptr(read_from_file(get_root_header_filename())); @@ -255,7 +255,7 @@ ROOTConsistencyTests::process_list_data() //// NON-TOF TESTING METHODS //// bool -ROOTConsistencyTests::test_nonTOF_LOR_closest_approach(const ProjMatrixElemsForOneBin& probabilities) +GATEConsistencyTests::test_nonTOF_LOR_closest_approach(const ProjMatrixElemsForOneBin& probabilities) { // Loop variables CartesianCoordinate3D closest_LOR_voxel_to_origin; @@ -289,7 +289,7 @@ ROOTConsistencyTests::test_nonTOF_LOR_closest_approach(const ProjMatrixElemsForO void -ROOTConsistencyTests:: +GATEConsistencyTests:: post_processing_nonTOF() { cerr << "\nNon-TOF number of failed events: " << num_failed_nonTOF_lor_events @@ -317,7 +317,7 @@ post_processing_nonTOF() //// TOF TESTING METHODS //// bool -ROOTConsistencyTests:: +GATEConsistencyTests:: test_TOF_max_lor_voxel(const ProjMatrixElemsForOneBin& probabilities) { // Along a TOF LOR (probabilities), values assigned to voxels (or positions used here). @@ -363,7 +363,7 @@ test_TOF_max_lor_voxel(const ProjMatrixElemsForOneBin& probabilities) void -ROOTConsistencyTests:: +GATEConsistencyTests:: post_processing_TOF() { cerr << "\nTOF number of failed events: " << num_failed_TOF_lor_events @@ -395,8 +395,8 @@ int main(int argc, char **argv) USING_NAMESPACE_STIR // Should be called from `STIR/examples/ROOT_files/ROOT_STIR_consistency` - // Tests in class ROOTConsistencyTests - ROOTConsistencyTests test; + // Tests in class GATEConsistencyTests + GATEConsistencyTests test; test.run_tests(); return test.main_return_value(); } From 855de84e09ba91ec2774da6db3e2bcd801eb9094 Mon Sep 17 00:00:00 2001 From: Robert Twyman Skelly Date: Wed, 17 Aug 2022 15:14:56 -0700 Subject: [PATCH 211/509] Class and method documentation and code->method refactoring --- src/recon_test/test_consistency_with_GATE.cxx | 138 ++++++++++++------ 1 file changed, 93 insertions(+), 45 deletions(-) diff --git a/src/recon_test/test_consistency_with_GATE.cxx b/src/recon_test/test_consistency_with_GATE.cxx index 26682ae1b3..99f4565e70 100644 --- a/src/recon_test/test_consistency_with_GATE.cxx +++ b/src/recon_test/test_consistency_with_GATE.cxx @@ -15,17 +15,31 @@ */ /*! * \ingroup recon_test - * \brief Test class to check the consistency between ROOT listmode and STIR backprojection + * \brief Test class to check the consistency between GATE and STIR * \author Elise Emond * \author Robert Twyman * - * This test currently uses Root listmodes of single point sources. + * This test currently uses ROOT list mode data of single point sources to assess the consistency between GATE and STIR. + * Each test aims to verify that each list mode event back projection is reasonably close to the point source position used + * to generate the list mode data. + * Currently there are 8 point sources, one in each of 8 list mode files, tested. + * + * This test is designed to run from ${STIR_SOURCE_PATH}/examples/ROOT_files/ROOT_STIR_consistency + * + * Non-TOF tests * This test computes the distance between the original point source position and the closes voxel that the list mode * event passes through. Ideally each event would travel directly through the original point source position but * error may be present. Therefore we test that the majority of LORs travel close enough. + * + * TOF tests + * Follows approximately the same procedure as non-TOF tests but, instead of closest approach voxel along the LOR, the voxel + * with maximum value is tested. If that voxel exceeds the threshold distance, the test is considered a failer. + * Again, if the majority of LORs travel close enough, the test is considered a success. + * + * In both cases, the logs of voxel positions (origin and closest approach or maximum voxel intensity) + * are written to files for later analysis. */ -#include "stir/ProjDataInfoCylindricalNoArcCorr.h" #include "stir/recon_buildblock/ProjMatrixByBin.h" #include "stir/recon_buildblock/ProjMatrixByBinUsingRayTracing.h" #include "stir/recon_buildblock/ProjMatrixElemsForOneBin.h" @@ -33,14 +47,12 @@ #include "stir/listmode/CListModeDataROOT.h" #include "stir/listmode/CListRecord.h" #include "stir/IO/read_from_file.h" -#include "stir/HighResWallClockTimer.h" #include "stir/DiscretisedDensity.h" #include "stir/VoxelsOnCartesianGrid.h" #include "stir/Succeeded.h" #include "stir/RunTests.h" #include "stir/Shape/GenerateImage.h" #include "boost/lexical_cast.hpp" - #include "stir/warning.h" using std::cerr; @@ -74,108 +86,120 @@ class GATEConsistencyTests : public RunTests /*! * \brief Run the tests with ROOT data - + * Main method for running the tests. + * For each list mode file, setups up the original point source positions and runs the tests and post processes the results. + * Records the pass failure of each file and logs to console. * \returns 0 if all tests passed, 1 otherwise */ void run_tests() override; private: + /*! Initialise the original point source position by generating image and finding the centre of gravity + * and sets TOF and non-TOF threshold distances and the number of events passed and failed counters + */ void setup(); + /*! Reads listmode event by event, computes the ProjMatrixElemsForOneBin (probabilities - * along a bin LOR). This method should include all tests/ setup for additional test such that the list data is - * read only once. - * - * Passes ProjMatrixElemsForOneBin (LOR) to test_nonTOF_LOR_closest_approach() and if fails, - * add 1 to `failed_events` (LOR's closest voxel was not within nonTOF_distance_threshold). - * Check if the number of `failed_events` is greater than half the number of tested events to pass the test. + * along a bin LOR). This method should setup the proj_matrix_sptr. + * The list data is processes once and each proj_matrix_row is passes to relevant non-TOF and TOF test methods. */ void process_list_data(); - void loop_through_list_events(shared_ptr lm_data_sptr, const shared_ptr& proj_matrix_sptr, - const CartesianCoordinate3D original_coords); - /*! Given a ProjMatrixElemsForOneBin (probabilities), test if the closest voxel in the LOR to the original_coords + /*! + * Performs the analysis steps for each test after the list mode data has been processed. + */ + void post_processing(); + + /*! Test if the closest voxel in the LOR (probabilities) to the original_coords * is within nonTOF_distance_threshold distance. If it is, pass with true, otherwise fales. * @param probabilities ProjMatrixElemsForOneBin object of a list mode event * @return True if test passes, false if failed. */ bool test_nonTOF_LOR_closest_approach(const ProjMatrixElemsForOneBin& probabilities); + + /*! If enough of the non-TOF LORs pass the closest approach test, pass the test, otherwise fail. + * Additionally, save the original and closest approach voxel positions to file. + */ void post_processing_nonTOF(); - //! Selects and stores the highest probability elements of ProjMatrixElemsForOneBin. + /*! Test if the voxel with the highest value in the LOR (probabilities) is within TOF_distance_threshold to the original_coords. + * If it is, pass with true, otherwise fales. + * @param probabilities ProjMatrixElemsForOneBin object of a list mode event + * @return True if within TOF_distance_threshold of original_coords, else false. + */ bool test_TOF_max_lor_voxel(const ProjMatrixElemsForOneBin& probabilities); - //! Checks if original and calculated coordinates are close enough and saves data to file. + /*! If enough of the TOF LORs pass the max voxel test distance test, pass, otherwise fail. + * Additionally, save the original and max voxel positions of the LOR to file. + */ void post_processing_TOF(); - // Auxilary methods + //! Logs the results of each test to console + void log_results_to_console(); + + // Auxiliary methods std::string get_root_header_filename() { return "pretest_output/root_header_test" + std::to_string(test_index) + ".hroot"; } std::string get_generate_image_par_filename() { return "SourceFiles/generate_image" + std::to_string(test_index) + ".par"; } ///// Class VARIABLES ///// +private: // Data set variables - int test_index; - int num_tests; + int test_index = 0; + int num_tests = 8; // Test results storage. Records if each test failed or not. E.g.`test_index` result is stored in `test_results_nonTOF[test_index -1]` std::vector test_results_nonTOF; std::vector test_results_TOF; + // Original point source position variables shared_ptr > discretised_density_sptr; CartesianCoordinate3D original_coords; // Stored in class because of dynamic_cast CartesianCoordinate3D grid_spacing; // Stored in class because of dynamic_cast + //! The number of events/LORs have been tested int num_events_tested = 0; /// NON TOF VARIABLES //! A vector to store the coordinates of the closest approach voxels. - const float failure_tolerance_nonTOF = 0.05; std::vector> nonTOF_closest_voxels_list; + //! Fraction of nonTOF LORs that can fail the closest approach test and data set still passes. + const float failure_tolerance_nonTOF = 0.05; + //! The number of nonTOF LORs that failed the closest approach test. int num_failed_nonTOF_lor_events = 0; + //! The threshold distance for the closest approach test. double nonTOF_distance_threshold; /// TOF VARIABLES - //! A copy of the scanner timing resolution in mm - const float failure_tolerance_TOF = 0.05; + //! A vector to store the coordinates of the maximum voxels for each LOR. std::vector TOF_LOR_peak_value_coords; - double TOF_distance_threshold; + //! Fraction of TOF LORs that can fail the maximum LOR intensity test and data set still passes. + const float failure_tolerance_TOF = 0.05; + //! The number of TOF LORs that failed the maximum LOR voxel intensity test. int num_failed_TOF_lor_events = 0; + //! The threshold distance for the maximum LOR voxel intensity test. + double TOF_distance_threshold; }; void GATEConsistencyTests::run_tests() { - // Loop over each of the ROOT files in the test_data directory - num_tests = 8; test_results_nonTOF = std::vector (num_tests, true); test_results_TOF = std::vector (num_tests, true); - cerr << "Testing the view offset consistency between GATE/ROOT and STIR. \n"; + cerr << "Testing consistency between GATE/ROOT and STIR. \n"; for (int i = 1; i <= num_tests; ++i) { - test_index = i; + test_index = i; // set the class variable to keep track of which test is being run cerr << "\nTesting dataset " << std::to_string(test_index) << "...\n"; - setup(); process_list_data(); - - cerr << "\nResults for dataset: " << std::to_string(test_index) << std::endl; - post_processing_nonTOF(); - post_processing_TOF(); + post_processing(); } - - // Print results for easy readability - cerr << "\nTest Results\n" - "------------------------------------------\n" - "\tTest Index\t|\tnonTOF\t|\tTOF\n" - "------------------------------------------\n"; - for (int j = 0; j < num_tests; ++j) - cerr << "\t\t" << std::to_string(j + 1) << "\t\t|\t" - << ((test_results_nonTOF[j]) ? "Pass" : "Fail") << "\t|\t" - << ((test_results_TOF[j]) ? "Pass" : "Fail") << std::endl; + log_results_to_console(); } void @@ -211,7 +235,8 @@ setup() } void -GATEConsistencyTests::process_list_data() +GATEConsistencyTests:: +process_list_data() { // Configure the list mode reader shared_ptr lm_data_sptr(read_from_file(get_root_header_filename())); @@ -314,6 +339,14 @@ post_processing_nonTOF() } } +void +GATEConsistencyTests:: +post_processing() +{ + cerr << "\nResults for dataset: " << std::to_string(this->test_index) << std::endl; + post_processing_nonTOF(); + post_processing_TOF(); +}; //// TOF TESTING METHODS //// bool @@ -388,12 +421,27 @@ post_processing_TOF() } } +void +GATEConsistencyTests:: +log_results_to_console() +{ + // Print results for easy readability + cerr << "\nTest Results\n" + "------------------------------------------\n" + "\tTest Index\t|\tnonTOF\t|\tTOF\n" + "------------------------------------------\n"; + for (int j = 0; j < num_tests; ++j) + cerr << "\t\t" << std::to_string(j + 1) << "\t\t|\t" + << ((test_results_nonTOF[j]) ? "Pass" : "Fail") << "\t|\t" + << ((test_results_TOF[j]) ? "Pass" : "Fail") << std::endl; +} + END_NAMESPACE_STIR int main(int argc, char **argv) { USING_NAMESPACE_STIR - // Should be called from `STIR/examples/ROOT_files/ROOT_STIR_consistency` + // Should be called from `${STIR_SOURCE_PATH}/examples/ROOT_files/ROOT_STIR_consistency` // Tests in class GATEConsistencyTests GATEConsistencyTests test; From 3df95b16fdf8c8973428cca72b81ab0998f830dd Mon Sep 17 00:00:00 2001 From: Robert Twyman Skelly Date: Fri, 19 Aug 2022 14:39:06 -0700 Subject: [PATCH 212/509] [ci skip] CTests save data as CSV with tolerance information. The first column of the output files is now a key to the row information. Also included a file reformat --- .gitignore | 2 +- .../debug_consistency_with_root.py | 89 ++++++++++--------- src/recon_test/test_consistency_with_GATE.cxx | 43 +++++---- 3 files changed, 76 insertions(+), 58 deletions(-) diff --git a/.gitignore b/.gitignore index 3b9ae581c8..c265edfd63 100644 --- a/.gitignore +++ b/.gitignore @@ -90,6 +90,6 @@ install_manifest.txt examples/ROOT_files/ROOT_STIR_consistency/Gate_macros/main_GATE_macro_test*.mac examples/ROOT_files/ROOT_STIR_consistency/pretest_output/root_data_test*.root examples/ROOT_files/ROOT_STIR_consistency/pretest_output/root_header_test*.hroot -examples/ROOT_files/ROOT_STIR_consistency/*voxel_data_*.txt +examples/ROOT_files/ROOT_STIR_consistency/*voxel_data_*.* examples/ROOT_files/ROOT_STIR_consistency/pretest_output/stir_image*.* diff --git a/examples/ROOT_files/ROOT_STIR_consistency/DebugScripts/debug_consistency_with_root.py b/examples/ROOT_files/ROOT_STIR_consistency/DebugScripts/debug_consistency_with_root.py index 6c3083e1c9..47f0b35447 100644 --- a/examples/ROOT_files/ROOT_STIR_consistency/DebugScripts/debug_consistency_with_root.py +++ b/examples/ROOT_files/ROOT_STIR_consistency/DebugScripts/debug_consistency_with_root.py @@ -73,6 +73,7 @@ def plot_1D_distance_histogram(distances, n_bins=100, logy=False, pretitle=""): axs.title.set_text( f"{pretitle} l2-norm of distance to origin: Mean = {round_sig(mean)} and Median = {round_sig(median)}") + def point_cloud_3D(data_handler): """ Plot the pointcloud of generated LOR-positions, original source-position and tolerance-whiskers for each source in a seperate scatter plot. @@ -92,12 +93,12 @@ def point_cloud_3D(data_handler): oz = data_handler.original_coord[2] tol = data_handler.tolerance ax.plot(ox, oy, oz, c='r', marker='o') - #plot tolerence around original point - ax.plot([ox+tol, ox-tol], [oy, oy], [oz, oz], c='r', marker="_", label='_nolegend_') - ax.plot([ox, ox], [oy+tol, oy-tol], [oz, oz], c='r', marker="_", label='_nolegend_') - ax.plot([ox, ox], [oy, oy], [oz+tol, oz-tol], c='r', marker="_", label='_nolegend_') + # plot tolerence around original point + ax.plot([ox + tol, ox - tol], [oy, oy], [oz, oz], c='r', marker="_", label='_nolegend_') + ax.plot([ox, ox], [oy + tol, oy - tol], [oz, oz], c='r', marker="_", label='_nolegend_') + ax.plot([ox, ox], [oy, oy], [oz + tol, oz - tol], c='r', marker="_", label='_nolegend_') - #plot Mean position and standard deviation + # plot Mean position and standard deviation fx = data_handler.mean_coord[0] fy = data_handler.mean_coord[1] fz = data_handler.mean_coord[2] @@ -105,17 +106,18 @@ def point_cloud_3D(data_handler): yerror = np.std(data_handler.voxel_coords[:, 1]) zerror = np.std(data_handler.voxel_coords[:, 2]) ax.plot(fx, fy, fz, linestyle="None", marker="o", c='g') - ax.plot([fx+xerror, fx-xerror], [fy, fy], [fz, fz], marker="_", c='g', label='_nolegend_') - ax.plot([fx, fx], [fy+yerror, fy-yerror], [fz, fz], marker="_", c='g', label='_nolegend_') - ax.plot([fx, fx], [fy, fy], [fz+zerror, fz-zerror], marker="_", c='g', label='_nolegend_') + ax.plot([fx + xerror, fx - xerror], [fy, fy], [fz, fz], marker="_", c='g', label='_nolegend_') + ax.plot([fx, fx], [fy + yerror, fy - yerror], [fz, fz], marker="_", c='g', label='_nolegend_') + ax.plot([fx, fx], [fy, fy], [fz + zerror, fz - zerror], marker="_", c='g', label='_nolegend_') ax.set_xlabel('x (mm)') ax.set_ylabel('y (mm)') ax.set_zlabel('z (mm)') - ax.legend(['Voxel Positions', 'Origin and Tolerance','Mean coords and stddev']) - + ax.legend(['Voxel Positions', 'Origin and Tolerance', 'Mean coords and stddev']) + plt.show() + def point_cloud_3D_all(dict_of_data_handlers): """ generates a single plot with pointclouds of generated LOR-positions, original source-positions and tolerance-whiskers for all sources @@ -125,12 +127,13 @@ def point_cloud_3D_all(dict_of_data_handlers): fig = plt.figure(figsize=(10, 10)) ax = fig.add_subplot(projection='3d') - + for key in dict_of_data_handlers.keys(): data_handler = dict_of_data_handlers[key] # Plot all the points (intensity increases for multiple points) - ax.scatter(data_handler.voxel_coords[:, 0], data_handler.voxel_coords[:, 1], data_handler.voxel_coords[:, 2], label = 'Source'+str(key)) + ax.scatter(data_handler.voxel_coords[:, 0], data_handler.voxel_coords[:, 1], data_handler.voxel_coords[:, 2], + label='Source' + str(key)) # Plot the original point and tolerance ox = data_handler.original_coord[0] @@ -138,12 +141,12 @@ def point_cloud_3D_all(dict_of_data_handlers): oz = data_handler.original_coord[2] tol = data_handler.tolerance ax.plot(ox, oy, oz, c='r', marker='o') - #plot tolerence around original point - ax.plot([ox+tol, ox-tol], [oy, oy], [oz, oz], c='r', marker="_", label='_nolegend_') - ax.plot([ox, ox], [oy+tol, oy-tol], [oz, oz], c='r', marker="_", label='_nolegend_') - ax.plot([ox, ox], [oy, oy], [oz+tol, oz-tol], c='r', marker="_", label='_nolegend_') + # plot tolerence around original point + ax.plot([ox + tol, ox - tol], [oy, oy], [oz, oz], c='r', marker="_", label='_nolegend_') + ax.plot([ox, ox], [oy + tol, oy - tol], [oz, oz], c='r', marker="_", label='_nolegend_') + ax.plot([ox, ox], [oy, oy], [oz + tol, oz - tol], c='r', marker="_", label='_nolegend_') - #plot Mean position and standard deviation + # plot Mean position and standard deviation fx = data_handler.mean_coord[0] fy = data_handler.mean_coord[1] fz = data_handler.mean_coord[2] @@ -151,9 +154,9 @@ def point_cloud_3D_all(dict_of_data_handlers): yerror = np.std(data_handler.voxel_coords[:, 1]) zerror = np.std(data_handler.voxel_coords[:, 2]) ax.plot(fx, fy, fz, linestyle="None", marker="o", c='g') - ax.plot([fx+xerror, fx-xerror], [fy, fy], [fz, fz], marker="_", c='g', label='_nolegend_') - ax.plot([fx, fx], [fy+yerror, fy-yerror], [fz, fz], marker="_", c='g', label='_nolegend_') - ax.plot([fx, fx], [fy, fy], [fz+zerror, fz-zerror], marker="_", c='g', label='_nolegend_') + ax.plot([fx + xerror, fx - xerror], [fy, fy], [fz, fz], marker="_", c='g', label='_nolegend_') + ax.plot([fx, fx], [fy + yerror, fy - yerror], [fz, fz], marker="_", c='g', label='_nolegend_') + ax.plot([fx, fx], [fy, fy], [fz + zerror, fz - zerror], marker="_", c='g', label='_nolegend_') ax.set_xlabel('x (mm)') ax.set_ylabel('y (mm)') @@ -163,12 +166,11 @@ def point_cloud_3D_all(dict_of_data_handlers): ax.spines['left'].set_position('zero') # set the y-spine ax.spines['bottom'].set_position('zero') - - plt.legend() plt.show() + class ROOTConsistencyDataHandler: """ Helper class. @@ -178,10 +180,9 @@ class ROOTConsistencyDataHandler: Each line is expected to be formatted as [ x y z ], e.g., '190.048 0 145.172\n' """ - def __init__(self, filename, tolerance=1.5 * 4.447): + def __init__(self, filename): """ :param filename: Filename of the file output by `test_view_offset_root` (.txt file) - :param tolerance: l2-norm tolerance that classified a "failed" event. Default = 6.66983 = 1.5 * 4.447mm, from previous studies. """ self.filename = filename @@ -190,7 +191,10 @@ def __init__(self, filename, tolerance=1.5 * 4.447): self.lines = f.readlines() # Extract the original coordinate and voxel coordinates as 2D numpy arrays - self.original_coord, self.voxel_coords = self.__extract_coords_from_lines(self.lines) + self.original_coord, self.voxel_coords, self.tolerance = self.__extract_data_from_lines(self.lines) + + if self.tolerance is None: + raise Exception("No tolerance information found in file") if self.voxel_coords.size == 0: raise Exception("No voxel coordinates found in file") @@ -202,18 +206,16 @@ def __init__(self, filename, tolerance=1.5 * 4.447): self.entrywise_l2_norm = np.linalg.norm(self.voxel_offset, axis=1) self.l2 = np.linalg.norm(self.voxel_offset) - self.tolerance = tolerance - def __process_line(self, line): """ Given a line (e.g., '190.048 0 145.172\n'), strips the " " and "\n" and returns the coordinate as a numpy array :param line: string input from a line in the file. :return: numpy array of (x,y,z) """ - coord = line[:-2].split(" ") + coord = line[:-2].split(",")[1:] return np.array([float(coord[0]), float(coord[1]), float(coord[2])]) - def __extract_coords_from_lines(self, lines): + def __extract_data_from_lines(self, lines): """ Assumes that lines comes in as a string with three numbers, each split by a space. Iterates through each file to get coordinate data for each entry. @@ -222,18 +224,20 @@ def __extract_coords_from_lines(self, lines): :param lines: Input lines, loaded from the file :return: A tuple of the original coordinate numpy array and a list of coordinates of closes voxels in the LOR """ - is_first = True + tolerance = None entry_coords = np.zeros(shape=(len(lines) - 1, 3)) original_coord = np.zeros(shape=3) line_index = 0 for line in lines: - if is_first: + split_line = line.split(",") + if split_line[0] == "tolerance": + tolerance = float(split_line[1]) + elif split_line[0] == "original coordinates": original_coord = self.__process_line(line) - is_first = False - continue - entry_coords[line_index] = self.__process_line(line) - line_index += 1 - return original_coord, entry_coords + else: + entry_coords[line_index] = self.__process_line(line) + line_index += 1 + return original_coord, entry_coords, tolerance def get_num_events(self): return len(self.voxel_coords) @@ -354,7 +358,7 @@ def print_axis_biases(point_sources_data): print(f"{row_string}") -def nonTOF_evaluation(filename_prefix, file_extension=".txt"): +def nonTOF_evaluation(filename_prefix, file_extension=".csv"): # Loop over all files in the working directory and load the data into the point_sources_data dictionary point_sources_data = dict() for i in range(1, 9, 1): @@ -365,11 +369,12 @@ def nonTOF_evaluation(filename_prefix, file_extension=".txt"): # Print the mean offset in each axis (x,y,z) for each point source and the total bias in each axis print_axis_biases(point_sources_data) -def TOF_evaluation(filename_prefix, file_extension=".txt"): + +def TOF_evaluation(filename_prefix, file_extension=".csv"): # Loop over all files in the working directory and load the data into the point_sources_data dictionary point_sources_data = dict() for i in range(1, 9, 1): - point_sources_data[i] = ROOTConsistencyDataHandler(f"{filename_prefix}{i}{file_extension}", tolerance=3.3*4.447) + point_sources_data[i] = ROOTConsistencyDataHandler(f"{filename_prefix}{i}{file_extension}") # Print the number of events, number of failed events and failure percentage for each point source print_pass_and_fail(point_sources_data) @@ -379,6 +384,7 @@ def TOF_evaluation(filename_prefix, file_extension=".txt"): point_cloud_3D(point_sources_data[1]) point_cloud_3D_all(point_sources_data) + # ===================================================================================================== # Main Script # ===================================================================================================== @@ -391,10 +397,9 @@ def main(): if len(sys.argv) > 1: chdir(sys.argv[1]) - nonTOF_evaluation("non_TOF_voxel_data_") - - TOF_evaluation("TOF_voxel_data_") + nonTOF_evaluation("non_TOF_voxel_data_", file_extension=".csv") + TOF_evaluation("TOF_voxel_data_", file_extension=".csv") print("Done") diff --git a/src/recon_test/test_consistency_with_GATE.cxx b/src/recon_test/test_consistency_with_GATE.cxx index 99f4565e70..f6ca99a513 100644 --- a/src/recon_test/test_consistency_with_GATE.cxx +++ b/src/recon_test/test_consistency_with_GATE.cxx @@ -325,16 +325,22 @@ post_processing_nonTOF() std::to_string(100*failure_tolerance_nonTOF)+ "%)"); { // Save the closest coordinate for each LOR to file. - std::string lor_pos_filename = "non_TOF_voxel_data_" + std::to_string(test_index) + ".txt"; - cerr << "Saving debug information as: " << lor_pos_filename << - "\nThe first entry is the original coordinate position." << std::endl; + std::string lor_pos_filename = "non_TOF_voxel_data_" + std::to_string(test_index) + ".csv"; + cerr << "Saving debug information as: " << lor_pos_filename << "\n" + << "The first entry is the distance tolerance granted.\n" + << "The second entry is the original coordinate position.\n"; std::ofstream myfile; myfile.open(lor_pos_filename.c_str()); - // The first entry is the original coords - myfile << original_coords.x() << " " << original_coords.y() << " " << original_coords.z() << std::endl; - for (std::vector>::iterator coord_entry = nonTOF_closest_voxels_list.begin(); - coord_entry != nonTOF_closest_voxels_list.end(); ++coord_entry) - myfile << coord_entry->x() << " " << coord_entry->y() << " " << coord_entry->z() << std::endl; + // The first entry is the tolerance granted + myfile << "tolerance," << nonTOF_distance_threshold << "\n"; + // The second entry is the original coords + myfile << "original coordinates," << original_coords.x() << "," << original_coords.y() << "," << original_coords.z() << "\n"; + int i = 0; + for (auto & entry : nonTOF_closest_voxels_list) + { + myfile << i << "," << entry.x() << "," << entry.y() << "," << entry.z() << "\n"; + i++; + } myfile.close(); } } @@ -407,16 +413,23 @@ post_processing_TOF() std::to_string(100*failure_tolerance_nonTOF)+ "%)"); { // Save the closest coordinate for each LOR to file. - std::string lor_pos_filename = "TOF_voxel_data_" + std::to_string(test_index) + ".txt"; - cerr << "Saving debug information as: " << lor_pos_filename << - "\nThe first entry is the original coordinate position." << std::endl; + std::string lor_pos_filename = "TOF_voxel_data_" + std::to_string(test_index) + ".csv"; + cerr << "Saving debug information as: " << lor_pos_filename << "\n" + << "The first entry is the distance tolerance granted.\n" + << "The second entry is the original coordinate position.\n"; std::ofstream myfile; myfile.open(lor_pos_filename.c_str()); - myfile << original_coords.x() << " " << original_coords.y() << " " << original_coords.z() << std::endl; - for (std::vector::iterator coord_entry = TOF_LOR_peak_value_coords.begin(); - coord_entry != TOF_LOR_peak_value_coords.end(); ++coord_entry) - myfile << coord_entry->coord.x() << " " << coord_entry->coord.y() << " " << coord_entry->coord.z() << std::endl; + // The first entry is the tolerance granted + myfile << "tolerance," << TOF_distance_threshold << "\n"; + // The second entry is the original coords + myfile << "original coordinates," << original_coords.x() << "," << original_coords.y() << "," << original_coords.z() << "\n"; + int i = 0; + for (auto & entry : TOF_LOR_peak_value_coords) + { + myfile << i << "," << entry.coord.x() << "," << entry.coord.y() << "," << entry.coord.z() << "\n"; + i++; + } myfile.close(); } } From c43e68f5e7dc9056208499324c0456759f524950 Mon Sep 17 00:00:00 2001 From: Robert Twyman Skelly Date: Fri, 19 Aug 2022 14:57:24 -0700 Subject: [PATCH 213/509] [ci skip] Change python data extraction to use csv --- .../debug_consistency_with_root.py | 71 +++++++++---------- 1 file changed, 35 insertions(+), 36 deletions(-) diff --git a/examples/ROOT_files/ROOT_STIR_consistency/DebugScripts/debug_consistency_with_root.py b/examples/ROOT_files/ROOT_STIR_consistency/DebugScripts/debug_consistency_with_root.py index 47f0b35447..fa523a3dd4 100644 --- a/examples/ROOT_files/ROOT_STIR_consistency/DebugScripts/debug_consistency_with_root.py +++ b/examples/ROOT_files/ROOT_STIR_consistency/DebugScripts/debug_consistency_with_root.py @@ -1,3 +1,5 @@ +import csv + import matplotlib.pyplot as plt from os import chdir import sys @@ -171,6 +173,38 @@ def point_cloud_3D_all(dict_of_data_handlers): plt.show() +def __extract_data_from_csv_file(filename): + """ + Loads data from a csv file and returns a numpy array containing the data. + Assumes first column is the row's key. + Each row is a separate entry in the array. + Generally, the first row is the tolerance and the second row is the original point. + Remainder of rows are the voxel positions of measured data. + :param filename: Name of the csv file to load the data from. + :return: A tuple of the original coordinate numpy array and a list of coordinates of closes voxels in the LOR + """ + + # Read the file and extract the csv data + with open(filename, newline='') as csvfile: + data = list(csv.reader(csvfile)) + + # Setup the tolerance and arrays for the data + tolerance = None + original_coord = np.zeros(shape=3) + entry_coords = np.zeros(shape=(len(data) - 2, 3)) # -2 because first 2 lines are tolerance and original coord + + line_index = 0 + for entry in data: + if entry[0] == "tolerance": + tolerance = float(entry[1]) + elif entry[0] == "original coordinates": + original_coord = np.array([float(entry[1]), float(entry[2]), float(entry[3])]) + else: + entry_coords[line_index] = np.array([float(entry[1]), float(entry[2]), float(entry[3])]) + line_index += 1 + return original_coord, entry_coords, tolerance + + class ROOTConsistencyDataHandler: """ Helper class. @@ -187,11 +221,9 @@ def __init__(self, filename): """ self.filename = filename print(f"Loading data: {self.filename}") - with open(filename) as f: - self.lines = f.readlines() # Extract the original coordinate and voxel coordinates as 2D numpy arrays - self.original_coord, self.voxel_coords, self.tolerance = self.__extract_data_from_lines(self.lines) + self.original_coord, self.voxel_coords, self.tolerance = __extract_data_from_csv_file(filename) if self.tolerance is None: raise Exception("No tolerance information found in file") @@ -206,39 +238,6 @@ def __init__(self, filename): self.entrywise_l2_norm = np.linalg.norm(self.voxel_offset, axis=1) self.l2 = np.linalg.norm(self.voxel_offset) - def __process_line(self, line): - """ - Given a line (e.g., '190.048 0 145.172\n'), strips the " " and "\n" and returns the coordinate as a numpy array - :param line: string input from a line in the file. - :return: numpy array of (x,y,z) - """ - coord = line[:-2].split(",")[1:] - return np.array([float(coord[0]), float(coord[1]), float(coord[2])]) - - def __extract_data_from_lines(self, lines): - """ - Assumes that lines comes in as a string with three numbers, each split by a space. - Iterates through each file to get coordinate data for each entry. - First line is original coordinate of the point source and - following lines are the closest voxel to the origin for each LOR in the root file. - :param lines: Input lines, loaded from the file - :return: A tuple of the original coordinate numpy array and a list of coordinates of closes voxels in the LOR - """ - tolerance = None - entry_coords = np.zeros(shape=(len(lines) - 1, 3)) - original_coord = np.zeros(shape=3) - line_index = 0 - for line in lines: - split_line = line.split(",") - if split_line[0] == "tolerance": - tolerance = float(split_line[1]) - elif split_line[0] == "original coordinates": - original_coord = self.__process_line(line) - else: - entry_coords[line_index] = self.__process_line(line) - line_index += 1 - return original_coord, entry_coords, tolerance - def get_num_events(self): return len(self.voxel_coords) From ee649954e4d4695eba68fa27dddc7afe6815a488 Mon Sep 17 00:00:00 2001 From: Robert Twyman Skelly Date: Fri, 19 Aug 2022 16:05:05 -0700 Subject: [PATCH 214/509] TOF threshold is a function of TOF resolution. This required the earlier initialization of the lm_data_sptr --- src/recon_test/test_consistency_with_GATE.cxx | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/recon_test/test_consistency_with_GATE.cxx b/src/recon_test/test_consistency_with_GATE.cxx index f6ca99a513..502c9bb8ed 100644 --- a/src/recon_test/test_consistency_with_GATE.cxx +++ b/src/recon_test/test_consistency_with_GATE.cxx @@ -155,6 +155,8 @@ class GATEConsistencyTests : public RunTests std::vector test_results_nonTOF; std::vector test_results_TOF; + shared_ptr lm_data_sptr; + // Original point source position variables shared_ptr > discretised_density_sptr; CartesianCoordinate3D original_coords; // Stored in class because of dynamic_cast @@ -206,6 +208,9 @@ void GATEConsistencyTests:: setup() { + // Initialise the list mode data object + lm_data_sptr = read_from_file(get_root_header_filename()); + // Create the point source (discretised_density_sptr) with GenerateImage from parameter file // GenerateImage requires `const char* const par_filename`. GenerateImage image_gen_application(get_generate_image_par_filename().c_str()); @@ -231,7 +236,17 @@ setup() // Failure conditioner and recording nonTOF_distance_threshold = 1.5 * norm(grid_spacing); // Using norm(grid_spacing) as a nonTOF_distance_threshold - TOF_distance_threshold = 3.3 * norm(grid_spacing); // With the value 3.3*norm(grid_spacing), test_root_consistency passes for all sources + { + // With the default files, we found that 3.3*norm(grid_spacing) is a reasonable limit. + // We try to generalise this to other data (although it will likely need further + // adjustment for cases with very different TOF resolution). + const float org_threshold = 3.3F / 75; // Through trial and error, this is about the minimum threshold + + const auto& pdi = *lm_data_sptr->get_proj_data_info_sptr(); + const float approx_tof_resolution = // 75 in initial configuration + pdi.get_scanner_ptr()->get_timing_resolution() * pdi.get_tof_mash_factor(); + this->TOF_distance_threshold = approx_tof_resolution * org_threshold * norm(grid_spacing); + } } void @@ -239,10 +254,9 @@ GATEConsistencyTests:: process_list_data() { // Configure the list mode reader - shared_ptr lm_data_sptr(read_from_file(get_root_header_filename())); shared_ptr proj_matrix_sptr(new ProjMatrixByBinUsingRayTracing()); - proj_matrix_sptr.get()->set_up(lm_data_sptr->get_proj_data_info_sptr(), - discretised_density_sptr); + proj_matrix_sptr->set_up(lm_data_sptr->get_proj_data_info_sptr(), + discretised_density_sptr); // loop over all events in the listmode file shared_ptr record_sptr = lm_data_sptr->get_empty_record_sptr(); From e8022198f7d86e2458048bd2289ed62dd74248c0 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sat, 20 Aug 2022 20:13:00 +0100 Subject: [PATCH 215/509] fixes to comments [ci skip] --- .../DebugScripts/debug_consistency_with_root.py | 7 +++---- src/recon_test/test_consistency_with_GATE.cxx | 15 +++------------ 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/examples/ROOT_files/ROOT_STIR_consistency/DebugScripts/debug_consistency_with_root.py b/examples/ROOT_files/ROOT_STIR_consistency/DebugScripts/debug_consistency_with_root.py index fa523a3dd4..819da54428 100644 --- a/examples/ROOT_files/ROOT_STIR_consistency/DebugScripts/debug_consistency_with_root.py +++ b/examples/ROOT_files/ROOT_STIR_consistency/DebugScripts/debug_consistency_with_root.py @@ -216,8 +216,7 @@ class ROOTConsistencyDataHandler: def __init__(self, filename): """ - :param filename: Filename of the file output by `test_view_offset_root` (.txt file) - from previous studies. + :param filename: Filename of the file output by `test_view_offset_GATE` (.csv file) """ self.filename = filename print(f"Loading data: {self.filename}") @@ -242,7 +241,7 @@ def get_num_events(self): return len(self.voxel_coords) def set_tolerance(self, tolerance): - print(f"Overwriting tolerance value as {tolerance}") + print(f"Overriding tolerance value as {tolerance}") self.tolerance = tolerance def get_num_failed_events(self, tolerance=None): @@ -388,7 +387,7 @@ def TOF_evaluation(filename_prefix, file_extension=".csv"): # Main Script # ===================================================================================================== def main(): - print("\nUSAGE: After `make test` or `test_view_offset_root` has been run,\n" + print("\nUSAGE: After `make test` or `test_view_offset_GATE` has been run,\n" "run `debug_view_offset_consistency` from `ROOT_STIR_consistency` directory or input that directory as an " "argument.\n") diff --git a/src/recon_test/test_consistency_with_GATE.cxx b/src/recon_test/test_consistency_with_GATE.cxx index 502c9bb8ed..fffc8f1636 100644 --- a/src/recon_test/test_consistency_with_GATE.cxx +++ b/src/recon_test/test_consistency_with_GATE.cxx @@ -2,15 +2,7 @@ Copyright (C) 2017, 2022, UCL 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. + SPDX-License-Identifier: Apache-2.0 AND License-ref-PARAPET-license See STIR/LICENSE.txt for details */ /*! @@ -33,7 +25,7 @@ * * TOF tests * Follows approximately the same procedure as non-TOF tests but, instead of closest approach voxel along the LOR, the voxel - * with maximum value is tested. If that voxel exceeds the threshold distance, the test is considered a failer. + * with maximum value is tested. If that voxel exceeds the threshold distance, the test is considered a failure. * Again, if the majority of LORs travel close enough, the test is considered a success. * * In both cases, the logs of voxel positions (origin and closest approach or maximum voxel intensity) @@ -89,7 +81,6 @@ class GATEConsistencyTests : public RunTests * Main method for running the tests. * For each list mode file, setups up the original point source positions and runs the tests and post processes the results. * Records the pass failure of each file and logs to console. - * \returns 0 if all tests passed, 1 otherwise */ void run_tests() override; @@ -127,7 +118,7 @@ class GATEConsistencyTests : public RunTests /*! Test if the voxel with the highest value in the LOR (probabilities) is within TOF_distance_threshold to the original_coords. * If it is, pass with true, otherwise fales. * @param probabilities ProjMatrixElemsForOneBin object of a list mode event - * @return True if within TOF_distance_threshold of original_coords, else false. + * @return \c true if within TOF_distance_threshold of original_coords, else \c false. */ bool test_TOF_max_lor_voxel(const ProjMatrixElemsForOneBin& probabilities); From 0d59a1d33524ae9ec7d717416183bddca4d78c8b Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sat, 20 Aug 2022 20:18:59 +0100 Subject: [PATCH 216/509] minor fixes to comments [ci skip] --- .../DebugScripts/debug_consistency_with_root.py | 2 +- src/recon_test/test_consistency_with_GATE.cxx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/ROOT_files/ROOT_STIR_consistency/DebugScripts/debug_consistency_with_root.py b/examples/ROOT_files/ROOT_STIR_consistency/DebugScripts/debug_consistency_with_root.py index 819da54428..82b0cb163e 100644 --- a/examples/ROOT_files/ROOT_STIR_consistency/DebugScripts/debug_consistency_with_root.py +++ b/examples/ROOT_files/ROOT_STIR_consistency/DebugScripts/debug_consistency_with_root.py @@ -208,7 +208,7 @@ def __extract_data_from_csv_file(filename): class ROOTConsistencyDataHandler: """ Helper class. - Loads and converts the data output by the `test_consistency_with_root` test. + Loads and converts the data output by the `test_consistency_with_GATE` test. The first line of the text file should be the original coordinate of the point source and the rest correspond to a selected voxel position along the LOR. Each line is expected to be formatted as [ x y z ], e.g., '190.048 0 145.172\n' diff --git a/src/recon_test/test_consistency_with_GATE.cxx b/src/recon_test/test_consistency_with_GATE.cxx index fffc8f1636..9bdc4693b4 100644 --- a/src/recon_test/test_consistency_with_GATE.cxx +++ b/src/recon_test/test_consistency_with_GATE.cxx @@ -225,7 +225,7 @@ setup() num_failed_TOF_lor_events = 0; num_events_tested = 0; - // Failure conditioner and recording + // Find threshold for failure nonTOF_distance_threshold = 1.5 * norm(grid_spacing); // Using norm(grid_spacing) as a nonTOF_distance_threshold { // With the default files, we found that 3.3*norm(grid_spacing) is a reasonable limit. From dc5016c926d6b0f6ebf21486e8c0c30cc0523a3b Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sat, 20 Aug 2022 21:51:06 +0100 Subject: [PATCH 217/509] fix TOF get_det_pos_pair_for_bin ProjdataInfoCylindricalNoArccorr::get_det_pos_pair_for_bin forgot to swap detectors for a negative TOF bin. Fixes https://github.com/NikEfth/STIR/issues/26 --- .../stir/ProjDataInfoCylindricalNoArcCorr.inl | 38 ++++++++++--------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/src/include/stir/ProjDataInfoCylindricalNoArcCorr.inl b/src/include/stir/ProjDataInfoCylindricalNoArcCorr.inl index bafbbeab31..b850c21f9e 100644 --- a/src/include/stir/ProjDataInfoCylindricalNoArcCorr.inl +++ b/src/include/stir/ProjDataInfoCylindricalNoArcCorr.inl @@ -183,27 +183,29 @@ get_det_pos_pair_for_bin( DetectionPositionPair<>& dp, const Bin& bin) const { - //lousy work around because types don't match TODO remove! -#if 1 - int t1=dp.pos1().tangential_coord(), - a1=dp.pos1().axial_coord(), - t2=dp.pos2().tangential_coord(), - a2=dp.pos2().axial_coord(); + // This is a bit complicated as DetectionPositionPair<>::timing_pos() is an unsigned int, + // while Bin uses an int. So we need to swap detectors around. + + //lousy work around because types don't match (short/int). TODO remove! + int t1, a1, t2, a2; get_det_pair_for_bin(t1, a1, t2, a2, bin); - dp.pos1().tangential_coord()=t1; - dp.pos1().axial_coord()=a1; - dp.pos2().tangential_coord()=t2; - dp.pos2().axial_coord()=a2; + if (bin.timing_pos_num()>=0) + { + dp.pos1().tangential_coord()=t1; + dp.pos1().axial_coord()=a1; + dp.pos2().tangential_coord()=t2; + dp.pos2().axial_coord()=a2; + } + else + { + dp.pos1().tangential_coord()=t2; + dp.pos1().axial_coord()=a2; + dp.pos2().tangential_coord()=t1; + dp.pos2().axial_coord()=a1; + } + dp.timing_pos() = std::abs(bin.timing_pos_num())*this->get_tof_mash_factor(); -#else - - get_det_pair_for_bin(dp.pos1().tangential_coord(), - dp.pos1().axial_coord(), - dp.pos2().tangential_coord(), - dp.pos2().axial_coord(), - bin); -#endif } END_NAMESPACE_STIR From e57698107b3d6aeb141106640303786cf977544c Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sun, 21 Aug 2022 13:58:41 +0100 Subject: [PATCH 218/509] minor fixes for CListEventCylindricalScannerWithViewTangRingRingEncoding for TOF - get_bin only initialised the timing_pos_num if there are more than one TOF bins - set_detection_position now errors out for TOF data as it hasn't been properly implemented yet - removed get_uncompressed_bin(). was incorrect for TOF, but is unused (fixes part of https://github.com/NikEfth/STIR/issues/32) - updated copyright --- ...ricalScannerWithViewTangRingRingEncoding.h | 2 -- ...calScannerWithViewTangRingRingEncoding.inl | 21 ++++++------------- 2 files changed, 6 insertions(+), 17 deletions(-) diff --git a/src/include/stir/listmode/CListEventCylindricalScannerWithViewTangRingRingEncoding.h b/src/include/stir/listmode/CListEventCylindricalScannerWithViewTangRingRingEncoding.h index be72690a9e..a90d8fa783 100644 --- a/src/include/stir/listmode/CListEventCylindricalScannerWithViewTangRingRingEncoding.h +++ b/src/include/stir/listmode/CListEventCylindricalScannerWithViewTangRingRingEncoding.h @@ -85,8 +85,6 @@ public CListEventCylindricalScannerWithDiscreteDetectors */ inline virtual bool is_valid_template(const ProjDataInfo&) const; - inline void get_uncompressed_bin(Bin& bin) const; - }; END_NAMESPACE_STIR diff --git a/src/include/stir/listmode/CListEventCylindricalScannerWithViewTangRingRingEncoding.inl b/src/include/stir/listmode/CListEventCylindricalScannerWithViewTangRingRingEncoding.inl index a7ec441f53..aedc72710b 100644 --- a/src/include/stir/listmode/CListEventCylindricalScannerWithViewTangRingRingEncoding.inl +++ b/src/include/stir/listmode/CListEventCylindricalScannerWithViewTangRingRingEncoding.inl @@ -2,6 +2,7 @@ // /* Copyright (C) 2003- 2011, Hammersmith Imanet Ltd + Copyright (C) 2018, 2022, University College London This file is part of STIR. SPDX-License-Identifier: Apache-2.0 @@ -14,6 +15,8 @@ \brief Implementation for stir::CListEventCylindricalScannerWithViewTangRingRingEncoding \author Kris Thielemans + \author Elise Emond + \author Nikos Efthimiou */ @@ -70,6 +73,8 @@ set_detection_position(const DetectionPositionPair<>& det_pos) det_pos.pos2().axial_coord(), det_pos.pos1().axial_coord()); } + if (this->get_uncompressed_proj_data_info_sptr()->is_tof_data()) + error("TODO: CListEventCylindricalScannerWithViewTangRingRingEncoding::set_detection_position needs to be implemented for TOF"); } static void @@ -103,8 +108,7 @@ get_bin(Bin& bin, const ProjDataInfo& proj_data_info) const get_sinogram_and_ring_coordinates(view_num, tangential_pos_num, ring_a, ring_b); sinogram_coordinates_to_bin(bin, view_num, tangential_pos_num, ring_a, ring_b, static_cast(proj_data_info)); - if (proj_data_info.get_num_tof_poss() > 1) - bin.timing_pos_num() = proj_data_info.get_tof_bin(delta_time); + bin.timing_pos_num() = proj_data_info.get_tof_bin(delta_time); } template @@ -117,18 +121,5 @@ is_valid_template(const ProjDataInfo& proj_data_info) const return false; } - -template -void -CListEventCylindricalScannerWithViewTangRingRingEncoding:: -get_uncompressed_bin(Bin& bin) const -{ - unsigned int ring_a; - unsigned int ring_b; - this->get_sinogram_and_ring_coordinates(bin.view_num(), bin.tangential_pos_num(), ring_a, ring_b); - this->get_uncompressed_proj_data_info_sptr()-> - get_segment_axial_pos_num_for_ring_pair(bin.segment_num(), bin.axial_pos_num(), - ring_a, ring_b); -} END_NAMESPACE_STIR From 3c9545b1967adb15ceaab5ef335498ce5110a419 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sun, 21 Aug 2022 16:32:08 +0100 Subject: [PATCH 219/509] fixes for Bin - doxygen corrections to Bin reflecting TOF - initialise time_frame to 1 in all constructors (including the one without arguments) - compare time_frame in operator== --- src/include/stir/Bin.h | 7 ++----- src/include/stir/Bin.inl | 5 +++-- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/include/stir/Bin.h b/src/include/stir/Bin.h index 24ff209911..2fa506ec40 100644 --- a/src/include/stir/Bin.h +++ b/src/include/stir/Bin.h @@ -36,17 +36,14 @@ START_NAMESPACE_STIR \brief A class for storing coordinates and value of a single projection bin. - The timing position reflect the detection time difference between the two events. - It is a multiple of the delta t of the least significant clock bit. + The timing position reflects the detection time difference between the two events for TOF. + It is an "index" into the projection data like the other values. The \c time_frame member defaults to 1 and needs to be set explicitly, e.g. when handling list mode data. \warning N.E: Constructors with default values were removed. I faced many problems with ambguity. I had to make changes to all the framework, when one set a float value, it has to be as 'x.f' - - \warning Temporarily the timing_pos_num is not taken into account when comparing two bins, - Until were are actually able to cache LORs based on timing location this could be let off. */ class Bin diff --git a/src/include/stir/Bin.inl b/src/include/stir/Bin.inl index 425a76288b..d6bd083b37 100644 --- a/src/include/stir/Bin.inl +++ b/src/include/stir/Bin.inl @@ -27,7 +27,7 @@ START_NAMESPACE_STIR Bin::Bin():segment(0),view(0), - axial_pos(0),tangential_pos(0),timing_pos(0), bin_value(0.0f) + axial_pos(0),tangential_pos(0),timing_pos(0), bin_value(0.0f),time_frame(1) {} @@ -48,7 +48,7 @@ Bin::Bin(int segment_num,int view_num, int axial_pos_num,int tangential_pos_num, Bin::Bin(int segment_num,int view_num, int axial_pos_num,int tangential_pos_num, int timing_pos_num) :segment(segment_num),view(view_num), - axial_pos(axial_pos_num),tangential_pos(tangential_pos_num), timing_pos(timing_pos_num), bin_value(0.0f) + axial_pos(axial_pos_num),tangential_pos(tangential_pos_num), timing_pos(timing_pos_num), bin_value(0.0f), time_frame(1) {} @@ -138,6 +138,7 @@ Bin::operator==(const Bin& bin2) const segment == bin2.segment && view == bin2.view && axial_pos == bin2.axial_pos && tangential_pos == bin2.tangential_pos && timing_pos == bin2.timing_pos && + time_frame == bin2.time_frame && bin_value == bin2.bin_value; } From c3630f1b26dc164b089e6e77ceef95a4185638bd Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sun, 21 Aug 2022 22:53:49 +0100 Subject: [PATCH 220/509] add RunTests functionality for Bin - add operator>>(ostream&, Bin) in stream.h - add check_if_equal for Bins - start TOF release notes --- documentation/release_notes_TOF.htm | 131 ++++++++++++++++++++++++++++ src/include/stir/RunTests.h | 6 +- src/include/stir/stream.h | 10 +++ 3 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 documentation/release_notes_TOF.htm diff --git a/documentation/release_notes_TOF.htm b/documentation/release_notes_TOF.htm new file mode 100644 index 0000000000..96aadb367e --- /dev/null +++ b/documentation/release_notes_TOF.htm @@ -0,0 +1,131 @@ + + + + Summary of changes in STIR release 6.0 + + + +

    Summary of changes in STIR release 6.0

    + +

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

    +

    Overall summary

    +

    +

    + + +

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

    + +

    This release contains mainly code written by Kris Thielemans (UCL) and Richard Brown (UCL). +

    + +

    Patch release info

    +
      +
    • 5.0.0 released ?/?/2020
    • + +
    + +

    Summary for end users (also to be read by developers)

    + +

    Changes breaking backwards compatibility from a user-perspective

    +

    Deprecated functionality

    diff --git a/examples/python/recon_demo.py b/examples/python/recon_demo.py index 1a65ecbd37..8151905737 100755 --- a/examples/python/recon_demo.py +++ b/examples/python/recon_demo.py @@ -48,7 +48,7 @@ print("Enabling interactive-mode for plotting failed. Continuing.") s = recon.set_up(target) -if (s == stir.Succeeded(stir.Succeeded.yes)): +if (s.succeeded()): pylab.figure() for iter in range(1,num_subiterations+1): print('\n--------------------- Subiteration ', iter) diff --git a/src/include/stir/Succeeded.h b/src/include/stir/Succeeded.h index 2b6c2d0da2..57306aec68 100644 --- a/src/include/stir/Succeeded.h +++ b/src/include/stir/Succeeded.h @@ -16,6 +16,7 @@ /* Copyright (C) 2000 PARAPET partners Copyright (C) 2000- 2009, Hammersmith Imanet Ltd + Copyright (C) 2023, University College London This file is part of STIR. SPDX-License-Identifier: Apache-2.0 AND License-ref-PARAPET-license @@ -35,15 +36,19 @@ START_NAMESPACE_STIR \code Succeeded f() { do_something; return Succeeded::yes; } void g() { if (f() == Succeeded::no) error("Error calling f"); } + // the latter can also be written as + void g2() { if (!f().succeeded()) error("Error calling f"); } \endcode */ class Succeeded { public: enum value { yes, no }; - Succeeded(const value& v) : v(v) {} + Succeeded(const value& v = yes) : v(v) {} bool operator==(const Succeeded &v2) const { return v == v2.v; } bool operator!=(const Succeeded &v2) const { return v != v2.v; } + //! convenience function returns if it is equal to Succeeded::yes + bool succeeded() const { return this->v == yes; } private: value v; }; From f053903fa7a62adca1b8187ddb3b2a3276ecc9a2 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sat, 23 Sep 2023 22:04:10 +0100 Subject: [PATCH 323/509] [SWIG] add DetectionPosition(Pair) and reduce warnings Also make Bin.time_frame_num a variable in Python like other members --- documentation/release_5.2.htm | 12 ++++-- src/swig/stir.i | 51 ++++++++++++++++++++----- src/swig/stir_projdata.i | 30 +++++++++++++-- src/swig/stir_reconstruction.i | 2 +- src/swig/test/python/test_buildblock.py | 23 ++++++++--- 5 files changed, 95 insertions(+), 23 deletions(-) diff --git a/documentation/release_5.2.htm b/documentation/release_5.2.htm index 8e1c1e483f..98e6a4609d 100644 --- a/documentation/release_5.2.htm +++ b/documentation/release_5.2.htm @@ -63,14 +63,18 @@

    New functionality

diff --git a/recon_test_pack/simulate_PET_data_for_tests.sh b/recon_test_pack/simulate_PET_data_for_tests.sh index cf711ee1df..e261877eb6 100755 --- a/recon_test_pack/simulate_PET_data_for_tests.sh +++ b/recon_test_pack/simulate_PET_data_for_tests.sh @@ -77,7 +77,7 @@ generate_image generate_uniform_cylinder.par echo "=== make attenuation image" generate_image generate_atten_cylinder.par if [ "$TOF" -eq 0 ]; then - echo "=== create template sinogram (DSTE in 3D with max ring diff 3 to save time)" + echo "=== create template sinogram (DSTE in 3D with max ring diff 2 to save time)" template_sino=my_DSTE_3D_rd3_template.hs cat > my_input.txt <get_num_rings()-1, scanner_ptr->get_num_rings()-1); - - ProjDataInfo * pdi_ptr = - ProjDataInfoCTI(scanner_ptr,span,max_delta,num_views,num_tangential_poss,arc_corrected, tof_mash_factor); + ProjDataInfo * pdi_ptr = + span==0 + ? ProjDataInfoGE(scanner_ptr,max_delta,num_views,num_tangential_poss,arc_corrected, tof_mash_factor) + : ProjDataInfoCTI(scanner_ptr,span,max_delta,num_views,num_tangential_poss,arc_corrected, tof_mash_factor); cout << pdi_ptr->parameter_info() < Date: Sat, 6 Jan 2024 00:56:27 +0000 Subject: [PATCH 426/509] updated release notes for v6.0 [ci skip] moved content of release_notes_TOF to 6.0 and completed. --- documentation/release_6.0.htm | 245 ++++++++++++++++++++++++++++ documentation/release_notes_TOF.htm | 163 ------------------ 2 files changed, 245 insertions(+), 163 deletions(-) create mode 100644 documentation/release_6.0.htm delete mode 100644 documentation/release_notes_TOF.htm diff --git a/documentation/release_6.0.htm b/documentation/release_6.0.htm new file mode 100644 index 0000000000..1ada7b2132 --- /dev/null +++ b/documentation/release_6.0.htm @@ -0,0 +1,245 @@ + + + + Summary of changes in STIR release 6.0 + + + +

Summary of changes in STIR release 6.0

+ +

This version is 99% backwards compatible with STIR 5.x for the user (see below). + Developers might need to make code changes as + detailed below. +

+

Overall summary

+

+ This release is a major upgrade adding Time of Flight (TOF) capabilities to STIR. + See also the (enormous) PR #304. +

+ + +

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

+ +

Overall code management and assistance was Kris Thielemans (UCL and ASC). Other main contributors + include: Nikos Efthimious (UCL, University of Hull, UPenn, MGH) for the TOF framework and list-mode + reconstruction, Elise Emond (UCL) for adapting TOF framework for projection-data, + Palak Wadhwa (University of Leeds) for adaptations and testing on GE Signa PET/MR data, + Robert Twyman for extending projector symmetries to TOF and formalising ROOT-based testing, + Nicole Jurjew (UCL) for adaptations and testing on Siemens Vision 600 data. + Non-TOF contributors include Daniel Deidda (NPL) and Markus Jehl (Positrigo). +

+ +

Patch release info

+
    +
  • 6.0.0 released ?/?/2024
  • + +
+ +

Summary for end users (also to be read by developers)

+ +

Changes breaking backwards compatibility from a user-perspective

+
    +
  • +
+ +

Bug fixes

+
    +
  • +
  • +
+ +

New functionality

+

General

+
    +
  • + TOF of course. This is mostly transparent, i.e. normally no changes are required + to the reconstruction code etc. When using Interfile or ROOT files, certain new keywords + are required, see examples/samples/PET_TOF_Interfile_header_Signa_PETMR.hs + and examples/samples/root_header.hroot. See also the updated STIR_glossary. +

    + Limitations
    +
      +
    • + Currently on the matrix based projectors support TOF. + Note that the implementation is generic but slow: a non-TOF row + is computed and then multiplied with the TOF kernel. + This is somewhat alleviated by the use of caching. However, as not all + symmetries are supported yet, caching of the projection matrix + needs substantially more memory than in the non-TOF situation. +
    • +
    • + We do not have TOF scatter simulation/estimation yet. +
    • +
    +
  • +
+ +

Python (and MATLAB)

+
    +
  • PR #1288 +
      +
    • + exposed ListRecord etc, such that loops over list-mode data can + now be performed in Python (although this will be somewhat slow). + See examples/python/listmode_loop_demo.py. +
    • +
    • + added LORAs2Points,LORInCylinderCoordinates, + LORInAxialAndSinogramCoordinates and PointOnCylinder. +
      + Warning: renamed FloatLOR to LOR, and same for + derived classes. +
    • +
    +
+ + +

Changed functionality breaking backwards incompatibility

+

General

+
    +
  • + ProjDataInfo::ask_parameters() and therefore create_projdata_template + has changed: +
      +
    1. If the scanner definition in STIR has TOF capabilities, it will ask for the TOF mashing factor.
    2. +
    3. The default for arc-correction has changed to N, i.e. false.
    4. +
    5. Default value for span is now 11 for Siemens and 2 for GE scanners.
    6. +
    7. The span=0 case (i.e. span-3 for segment 0, span=1 for oblique ones, erroneously + by STIR used for the GE Advance) is no deprecated. GE uses span=2.
      + (Reading a "span=0" case is still supported)
    8. +
    +
  • +
  • + Projection-data related classes have accessors with an optional make_num_tangential_poss_odd argument + (defaulting to false), which made the returned argument a different size. + This has been deprecated since version 5.0. Setting this argument to true will now raise an error. +
  • +
+ +

Python (and MATLAB)

+
    +
  • renamed FloatLOR to LOR, and same for derived classes.
  • +
+ + +

Changed functionality

+
    +
  • + We now always check (in ProjDataInfo*NoArcCorr) if number of tangential positions in the projection data exceeds the maximum number + of non arc-corrected bins set for the scanner. If it is, an error is raised. +
  • +
  • + Write STIR6.0 as Interfile key version to denote TOF changes. + This is currently ignored for parsing though. +
  • +
+ +

Build system

+ No major changes. + +

Known problems

+

See our issue tracker.

+ +

Minor bug fixes

+
    +
  • +
  • +
+ +

Documentation changes

+
    +
  • + Added (some) documentation on TOF features +
  • +
+ +

test changes

+

recon_test_pack changes

+
    +
  • + additional tests for TOF, expansion of some existing tests for TOF +
  • +
  • + updated version number and added some clarification to the README.txt +
  • +
+ +

C++ tests

+
    +
  • + additional tests for TOF, expansion of some existing tests for TOF +
  • +
+ +

What's new for developers (aside from what should be obvious +from the above):

+ +

Major bugs fixed

+
    +
  • see above
  • +
+ +

Backward incompatibities

+
    +
  • + ListModeData now has a shared_ptr proj_data_info_sptr + protected member, and the scanner_sptr member has been removed.
    + Warning: If your derived class had its own proj_data_info_sptr, it should be removed. +
  • +
  • + virtual ListModeData::get_scanner_ptr() is replaced by ListModeData::get_scanner(). +
  • +
  • + ProjDataInfo*NoArcCorr::get_bin_for_det_pair is now private. + Use get_bin_for_det_pos_pair instead. +
  • +
+ +

New functionality

+ +

TOF related

+
    +
  • Scanner now allows storing TOF information. This is currently not yet done for all + TOF-capable scanners though. Contributions welcome! +
  • +
  • + All projection-data related classes and their members now have a TOF bin index and related information. + At present, old-style accessors are in an awkward format such as +
    +      auto sino = proj_data.get_sinogram(ax_pos_num, segment_num, false, timing_pos);
    +    
    + These are deprecated since version 5.2 and should be replaced by +
    +      const SinogramIndices sinogram_idxs{ax_pos_num, segment_num, timing_pos};
    +      auto sino = proj_data.get_sinogram(sinogram_idxs);
    +    
    +
  • +
  • + List-mode data for TOF-capable scanners need to pass this through appropriately of course. +
  • +
+ +

Non-TOF related

+
    +
  • + projectors now have a clone() member, currently returning a bare pointer (like other STIR classes) +
  • +
  • Bin can now be output to stream as text
  • +
  • added RunTests::check_if_equal for Bin
  • +
  • + KeyParser has a new facility to add an alias to a keyword. This can be used to rename a keyword + for instance while remaining backwards compatible. By default, a warning will be written, but this can be disabled. +
  • +
+ + + + + diff --git a/documentation/release_notes_TOF.htm b/documentation/release_notes_TOF.htm deleted file mode 100644 index 6356d8e788..0000000000 --- a/documentation/release_notes_TOF.htm +++ /dev/null @@ -1,163 +0,0 @@ - - - - Summary of changes in STIR release 6.0 - - - -

Summary of changes in STIR release 6.0

- -

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

-

Overall summary

-

This release is a major upgrade adding Time of Flight (TOF) capabilities to STIR. -

- - -

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

- -

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

- -

Patch release info

-
    -
  • 5.0.0 released ?/?/2020
  • - -
- -

Summary for end users (also to be read by developers)

- -

Changes breaking backwards compatibility from a user-perspective

-
    -
  • -
      - - -

      Bug fixes

      -
        -
      • -
      • -
      - -

      New functionality

      -
        -
      • - projectors now have a clone() member, currently returning a bare pointer (like other STIR classes) -
      • -
      - - -

      Changed functionality

      -
        -
      • - We now always check (ProjDataInfo*NoArcCorr) if number of tangential positions in the projection data exceeds the maximum number - of non arc-corrected bins set for the scanner. If it is, an error is raised. -
      • -
      • - Write STIR6.0 as Interfile key version to denote TOF changes. - This is currently ignored for parsing though. -
      • -
      - -

      Changed functionality breaking backwards incompatibility

      -
        -
      • - ProjDataInfo::ask_parameters() and therefore create_projdata_template - has changed: -
          -
        1. If the scanner definition in STIR has TOF capabilities, it will ask for the TOF mashing factor.
        2. -
        3. The default for arc-correction has changed to N, i.e. false.
        4. -
        5. Default value for span is now 11 for Siemens and 2 for GE scanners.
        6. -
        7. The span=0 case (i.e. span-3 for segment 0, span=1 for oblique ones, erroneously - by STIR used for the GE Advance) is no deprecated. GE uses span=2.
          - (Reading a "span=0" case is still supported")
        8. -
        -
      • -
      - - -

      Build system

      -
        -
      • -
      • -
      - - -

      Known problems

      -
        -
      • -
      • -
      - -

      Minor bug fixes

      -
        -
      • -
      • -
      - -

      Documentation changes

      -
        -
      • Added documentation on new features
      • -
      • Also check the wiki in addition to the provided PDFs. -
      • -
      - -

      recon_test_pack changes

      -
        -
      • updated version number and added some clarification to the README.txt
      • -
      - -

      Other changes to tests

      -
        -
      • -
      • -
      - -

      What's new for developers (aside from what should be obvious -from the above):

      - -

      Major bugs fixed

      -
        -
      • see above
      • -
      - -

      Backward incompatibities

      -
        -
      • - virtual ListModeData::get_scanner_ptr() is replaced by ListModeData::get_scanner(). -
      • -
      • - ProjDataInfo*NoArcCorr::get_bin_for_det_pair is now private. - Use get_bin_for_det_pos_pair instead. -
      • -
      - -

      New functionality

      -
        -
      • Bin can now be output to stream as text
      • -
      • added RunTests::check_if_equal for Bin
      • -
      • - KeyParser has a new facility to add an alias to a keyword. This can be used to rename a keyword - for instance while remaining backwards compatible. By default, a warning will be written, but this can be disabled. -
      • -
      - - -

      Other code changes

      -
        -
      • -
      • -
      - - - - From b6fc7fc01d143005cb6671b9a06335a56ad16fa8 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sat, 6 Jan 2024 09:08:38 +0000 Subject: [PATCH 427/509] updated 6.0 release notes [ci skip] added papers, better indentatino, ran through https://validator.w3.org/ --- documentation/release_6.0.htm | 175 +++++++++++++++++++--------------- 1 file changed, 97 insertions(+), 78 deletions(-) diff --git a/documentation/release_6.0.htm b/documentation/release_6.0.htm index 1ada7b2132..2138fc505c 100644 --- a/documentation/release_6.0.htm +++ b/documentation/release_6.0.htm @@ -1,5 +1,5 @@ - - + + Summary of changes in STIR release 6.0 @@ -14,7 +14,6 @@

      Summary of changes in STIR release 6.0

      Overall summary

      This release is a major upgrade adding Time of Flight (TOF) capabilities to STIR. - See also the (enormous) PR #304.

      @@ -23,7 +22,7 @@

      Overall summary

      Overall code management and assistance was Kris Thielemans (UCL and ASC). Other main contributors - include: Nikos Efthimious (UCL, University of Hull, UPenn, MGH) for the TOF framework and list-mode + include: Nikos Efthimiou (UCL, University of Hull, UPenn, MGH) for the TOF framework and list-mode reconstruction, Elise Emond (UCL) for adapting TOF framework for projection-data, Palak Wadhwa (University of Leeds) for adaptations and testing on GE Signa PET/MR data, Robert Twyman for extending projector symmetries to TOF and formalising ROOT-based testing, @@ -45,7 +44,7 @@

      Patch release info

      Summary for end users (also to be read by developers)

      Changes breaking backwards compatibility from a user-perspective

      -
        +
        @@ -59,12 +58,33 @@

        New functionality

        General

        • - TOF of course. This is mostly transparent, i.e. normally no changes are required +

          + TOF of course! This is mostly transparent, i.e. normally no changes are required to the reconstruction code etc. When using Interfile or ROOT files, certain new keywords are required, see examples/samples/PET_TOF_Interfile_header_Signa_PETMR.hs - and examples/samples/root_header.hroot. See also the updated STIR_glossary. -

          - Limitations
          + and examples/samples/root_header.hroot. + See also the updated STIR_glossary. +
          + Please cite the following papers: +

          +
            +
          • + Efthimiou, N., Emond, E., Wadhwa, P., Cawthorne, C., Tsoumpas, C., Thielemans, K., 2019. + Implementation and validation of time-of-flight PET image reconstruction module for listmode and sinogram projection data in the STIR library. + Phys Med Biol 64, 035004. DOI: 10.1088/1361-6560/aaf9b9. +
          • +
          • + Wadhwa, P., Thielemans, K., Efthimiou, N., Wangerin, K., Keat, N., Emond, E., Deller, T., Bertolli, O., Deidda, D., Delso, G., Tohme, M., Jansen, F., Gunn, R.N., Hallett, W., Tsoumpas, C., 2021. + PET image reconstruction using physical and mathematical modelling for time of flight PET-MR scanners in the STIR library. + Methods, Methods on simulation in biomedicine 185, 110–119. DOI: 10.1016/j.ymeth.2020.01.005 +
          • +
          +

          + See also the (enormous) PR #304. +

          +

          + Limitations
          +

          • Currently on the matrix based projectors support TOF. @@ -93,7 +113,7 @@

            Python (and MATLAB)

          • added LORAs2Points,LORInCylinderCoordinates, LORInAxialAndSinogramCoordinates and PointOnCylinder. -
            +
            Warning: renamed FloatLOR to LOR, and same for derived classes.
          • @@ -112,7 +132,7 @@

            General

          • The default for arc-correction has changed to N, i.e. false.
          • Default value for span is now 11 for Siemens and 2 for GE scanners.
          • The span=0 case (i.e. span-3 for segment 0, span=1 for oblique ones, erroneously - by STIR used for the GE Advance) is no deprecated. GE uses span=2.
            + by STIR used for the GE Advance) is no deprecated. GE uses span=2.
            (Reading a "span=0" case is still supported)
          • @@ -133,7 +153,7 @@

            Changed functionality

            • We now always check (in ProjDataInfo*NoArcCorr) if number of tangential positions in the projection data exceeds the maximum number - of non arc-corrected bins set for the scanner. If it is, an error is raised. + of non arc-corrected bins set for the scanner. If it is, an error is raised. You might therefore have to adapt your interfile header.
            • Write STIR6.0 as Interfile key version to denote TOF changes. @@ -160,7 +180,7 @@

              Documentation changes

            -

            test changes

            +

            Test changes

            recon_test_pack changes

            • @@ -172,72 +192,71 @@

              recon_test_pack changes

            C++ tests

            -
              -
            • - additional tests for TOF, expansion of some existing tests for TOF -
            • -
            - -

            What's new for developers (aside from what should be obvious -from the above):

            - -

            Major bugs fixed

            -
              -
            • see above
            • -
            - -

            Backward incompatibities

            -
              -
            • - ListModeData now has a shared_ptr proj_data_info_sptr - protected member, and the scanner_sptr member has been removed.
              - Warning: If your derived class had its own proj_data_info_sptr, it should be removed. -
            • -
            • - virtual ListModeData::get_scanner_ptr() is replaced by ListModeData::get_scanner(). -
            • -
            • - ProjDataInfo*NoArcCorr::get_bin_for_det_pair is now private. - Use get_bin_for_det_pos_pair instead. -
            • -
            - -

            New functionality

            - -

            TOF related

            -
              -
            • Scanner now allows storing TOF information. This is currently not yet done for all - TOF-capable scanners though. Contributions welcome! -
            • -
            • - All projection-data related classes and their members now have a TOF bin index and related information. - At present, old-style accessors are in an awkward format such as -
              -      auto sino = proj_data.get_sinogram(ax_pos_num, segment_num, false, timing_pos);
              -    
              - These are deprecated since version 5.2 and should be replaced by -
              -      const SinogramIndices sinogram_idxs{ax_pos_num, segment_num, timing_pos};
              -      auto sino = proj_data.get_sinogram(sinogram_idxs);
              -    
              -
            • -
            • - List-mode data for TOF-capable scanners need to pass this through appropriately of course. -
            • -
            +
              +
            • + additional tests for TOF, expansion of some existing tests for TOF +
            • +
            + +

            What's new for developers (aside from what should be obvious + from the above):

            + +

            Backward incompatibities

            +
              +
            • + ListModeData now has a shared_ptr<const ProjDataInfo> proj_data_info_sptr + protected member, and the scanner_sptr member has been removed.
              + Warning: If your derived class had its own proj_data_info_sptr, it should be removed. +
            • +
            • + virtual ListModeData::get_scanner_ptr() is replaced by ListModeData::get_scanner(). +
            • +
            • + ProjDataInfo*NoArcCorr::get_bin_for_det_pair is now private. + Use get_bin_for_det_pos_pair instead. +
            • +
            + +

            New functionality

            + +

            TOF related

            +
              +
            • Scanner now allows storing TOF information. This is currently not yet done for all + TOF-capable scanners though. Contributions welcome! +
            • +
            • + All projection-data related classes and their members now have a TOF bin index and related information. + At present, old-style accessors are in an awkward format such as +
              +          auto sino = proj_data.get_sinogram(ax_pos_num, segment_num, false, timing_pos_num);
              +        
              + These are deprecated since version 5.2 and should be replaced by +
              +          const SinogramIndices sinogram_idxs{ax_pos_num, segment_num, timing_pos_num};
              +          auto sino = proj_data.get_sinogram(sinogram_idxs);
              +        
              +
            • +
            • + List-mode data for TOF-capable scanners need to pass the relevant information through appropriately of course. +
            • +
            -

            Non-TOF related

            -
              -
            • - projectors now have a clone() member, currently returning a bare pointer (like other STIR classes) -
            • -
            • Bin can now be output to stream as text
            • -
            • added RunTests::check_if_equal for Bin
            • -
            • - KeyParser has a new facility to add an alias to a keyword. This can be used to rename a keyword - for instance while remaining backwards compatible. By default, a warning will be written, but this can be disabled. -
            • -
            +

            Non-TOF related

            +
              +
            • + Projectors now have a clone() member, currently returning a bare pointer (like other STIR classes). +
            • +
            • + Bin can now be output to stream as text. +
            • +
            • + Added RunTests::check_if_equal for Bin. +
            • +
            • + KeyParser has a new facility to add an alias to a keyword. This can be used to rename a keyword + for instance while remaining backwards compatible. By default, a warning will be written, but this can be disabled. +
            • +
            From 21eb434785394974f7102a21d5d1fb2a532167b7 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sat, 6 Jan 2024 00:57:49 +0000 Subject: [PATCH 428/509] use "TOF timing resolution" keyword for hroot as well --- examples/samples/root_header.hroot | 6 ++++++ recon_test_pack/root_header.hroot | 6 +++--- src/listmode_buildblock/CListModeDataROOT.cxx | 6 ++++-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/examples/samples/root_header.hroot b/examples/samples/root_header.hroot index 6461b7d4ad..a3b37322fe 100644 --- a/examples/samples/root_header.hroot +++ b/examples/samples/root_header.hroot @@ -11,6 +11,12 @@ Default bin size (cm) := 0.208626 Maximum number of non-arc-corrected bins := 344 Default number of arc-corrected bins := 344 View offset (degrees) := 0 + +; TOF information (optional) +Maximum number of (unmashed) TOF time bins := 5 +Size of unmashed TOF time bins (ps) := 820.0 +TOF timing resolution (ps) := 400.0 + ; optional keywords to create "virtual" crystals to accomodate for gaps between blocks ; if you do not specify these, the STIR defaults (determined by the "originating system") ; will be used (which are zero for a User_defined_scanner) diff --git a/recon_test_pack/root_header.hroot b/recon_test_pack/root_header.hroot index 390167acee..afc654912e 100644 --- a/recon_test_pack/root_header.hroot +++ b/recon_test_pack/root_header.hroot @@ -11,9 +11,9 @@ Default bin size (cm) := 0.208626 Maximum number of non-arc-corrected bins := 501 Default number of arc-corrected bins := 501 View offset (degrees) := 0 -Number of TOF time bins := 5 -Size of timing bin (ps) := 820.00 -Timing resolution (ps) := 400.0 +Maximum number of (unmashed) TOF time bins := 5 +Size of unmashed TOF time bins (ps) := 820.0 +TOF timing resolution (ps) := 400.0 GATE scanner type := GATE_Cylindrical_PET GATE_Cylindrical_PET Parameters := diff --git a/src/listmode_buildblock/CListModeDataROOT.cxx b/src/listmode_buildblock/CListModeDataROOT.cxx index dec07a58fb..fa2279f266 100644 --- a/src/listmode_buildblock/CListModeDataROOT.cxx +++ b/src/listmode_buildblock/CListModeDataROOT.cxx @@ -33,7 +33,7 @@ START_NAMESPACE_STIR constexpr static char max_num_timing_bins_keyword[] = "Maximum number of (unmashed) TOF time bins"; constexpr static char size_timing_bin_keyword[] = "Size of unmashed TOF time bins (ps)"; -constexpr static char timing_resolution_keyword[] = "Timing resolution (ps)"; +constexpr static char timing_resolution_keyword[] = "TOF timing resolution (ps)"; CListModeDataROOT:: CListModeDataROOT(const std::string& hroot_filename) @@ -77,7 +77,9 @@ CListModeDataROOT(const std::string& hroot_filename) this->parser.add_alias_key(size_timing_bin_keyword, "Size of timing bin (ps)"); #endif this->parser.add_key(timing_resolution_keyword, &this->timing_resolution); - +#if STIR_VERSION < 070000 + this->parser.add_alias_key(timing_resolution_keyword, "timing resolution (ps)"); +#endif this->parser.add_key("TOF mashing factor", &this->tof_mash_factor); #if STIR_VERSION < 070000 this->parser.add_alias_key("TOF mashing factor", "%TOF mashing factor"); From ba0add651973e1528f066224ca3ff63213e12111 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sat, 6 Jan 2024 13:57:55 +0000 Subject: [PATCH 429/509] DetectionPosition(Pair) to stream updates - change order of text in DetectionPosition>>stream to fit with constructor to avoid confusion. This affects SWIG output in Python. - add DetectionPositionPair>>stream - --- src/include/stir/stream.h | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/src/include/stir/stream.h b/src/include/stir/stream.h index 45b2fb21e9..1f28ae0984 100644 --- a/src/include/stir/stream.h +++ b/src/include/stir/stream.h @@ -14,7 +14,7 @@ Copyright (C) 2000 PARAPET partners Copyright (C) 2000-2009 Hammersmith Imanet Ltd Copyright (C) 2013 Kris Thielemans - Copyright (C) 2023 University College London + Copyright (C) 2023, 2024 University College London This file is part of STIR. @@ -29,6 +29,7 @@ #include "stir/BasicCoordinate.h" #include "stir/Bin.h" #include "stir/DetectionPosition.h" +#include "stir/DetectionPositionPair.h" #include #include @@ -86,6 +87,7 @@ operator<<(std::ostream& str, const std::vector& v); /*! \brief Outputs a Bin to a stream. + \ingroup projdata Output is of the form \verbatim @@ -103,17 +105,34 @@ inline std::ostream& operator<<(std::ostream& out, const Bin& bin) /*! \brief Outputs a DetectionPosition to a stream. + \ingroup projdata Output is of the form \verbatim - [radial=.., axial=..., tangential=...] + [tangential=..., axial=..., radial=...] \endverbatim */ template inline std::ostream& operator<<(std::ostream& out, const DetectionPosition& det_pos) { - return out << "[radial=" << det_pos.radial_coord() << ", axial=" << det_pos.axial_coord() - << ", tangential=" << det_pos.tangential_coord() << "]"; + return out << "[tangential=" << det_pos.tangential_coord() << ", axial=" << det_pos.axial_coord() + << ", radial=" << det_pos.radial_coord() << "]"; +} + +/*! + \brief Outputs a DetectionPosition to a stream. + \ingroup projdata + + Output is of the form + \verbatim + [pos1=..., pos2=..., timing_pos=...] + \endverbatim +*/ +template +inline std::ostream& operator<<(std::ostream& out, const DetectionPositionPair& det_pos) +{ + return out << "[pos1=" << det_pos.pos1() << ", pos2=" << det_pos.pos2() + << ", timing_pos=" << det_pos.timing_pos() << "]"; } /*! From 46787da6e8ba6cd83f634b73e4554e295eb1db7b Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sat, 6 Jan 2024 14:26:18 +0000 Subject: [PATCH 430/509] removed unused variables --- src/recon_buildblock/BinNormalisationFromECAT8.cxx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/recon_buildblock/BinNormalisationFromECAT8.cxx b/src/recon_buildblock/BinNormalisationFromECAT8.cxx index 4c6ed5811f..0b8a542b07 100644 --- a/src/recon_buildblock/BinNormalisationFromECAT8.cxx +++ b/src/recon_buildblock/BinNormalisationFromECAT8.cxx @@ -570,8 +570,8 @@ get_uncalibrated_bin_efficiency(const Bin& bin) const { uncompressed_bin, detection_position_pair); - const DetectionPosition<>& pos1 = detection_position_pair.pos1(); - const DetectionPosition<>& pos2 = detection_position_pair.pos2(); + //const DetectionPosition<>& pos1 = detection_position_pair.pos1(); + //const DetectionPosition<>& pos2 = detection_position_pair.pos2(); float lor_efficiency= 0.; /* From fdd76cc62a4be541b8de5ca047ab14b281e31a63 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sat, 6 Jan 2024 14:34:44 +0000 Subject: [PATCH 431/509] [SWIG] fix DetectionPositionPair timing_pos and repr printing --- src/swig/stir_projdata.i | 12 +++--------- src/swig/test/python/test_buildblock.py | 13 ++++++++++++- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/swig/stir_projdata.i b/src/swig/stir_projdata.i index 9911d1e394..6a1e0eb433 100644 --- a/src/swig/stir_projdata.i +++ b/src/swig/stir_projdata.i @@ -41,23 +41,18 @@ %attributeref(stir::DetectionPosition, unsigned int, axial_coord); %attributeref(stir::DetectionPosition, unsigned int, radial_coord); %include "stir/DetectionPosition.h" -#ifdef STIR_TOF ADD_REPR(stir::DetectionPosition, %arg(*$self)) -#endif %template(DetectionPosition) stir::DetectionPosition; +%attributeref(stir::DetectionPositionPair, int, timing_pos); %attributeref(stir::DetectionPositionPair, stir::DetectionPosition, pos1); %attributeref(stir::DetectionPositionPair, stir::DetectionPosition, pos2); %include "stir/DetectionPositionPair.h" -#ifdef STIR_TOF - //ADD_REPR(stir::DetectionPositionPair, %arg(*$self)) -#endif +ADD_REPR(stir::DetectionPositionPair, %arg(*$self)) %template(DetectionPositionPair) stir::DetectionPositionPair; %attributeref(stir::SegmentIndices, int, segment_num); -#ifdef STIR_TOF %attributeref(stir::SegmentIndices, int, timing_pos_num); -#endif %attributeref(stir::ViewgramIndices, int, view_num); %attributeref(stir::SinogramIndices, int, axial_pos_num); %attributeref(stir::Bin, int, axial_pos_num); @@ -69,9 +64,8 @@ ADD_REPR(stir::DetectionPosition, %arg(*$self)) %include "stir/ViewgramIndices.h" %include "stir/SinogramIndices.h" %include "stir/Bin.h" -#ifdef STIR_TOF ADD_REPR(stir::Bin, %arg(*$self)) -#endif + %newobject stir::Scanner::get_scanner_from_name; %include "stir/Scanner.h" diff --git a/src/swig/test/python/test_buildblock.py b/src/swig/test/python/test_buildblock.py index 2d2472b7dc..1db0eb0eb0 100755 --- a/src/swig/test/python/test_buildblock.py +++ b/src/swig/test/python/test_buildblock.py @@ -185,7 +185,18 @@ def test_zoom_image(): assert abs(zoomed_image[ind]-1)<.001 zoomed_image=zoom_image(image, zoom, offset, offset, new_size, ZoomOptions(ZoomOptions.preserve_projections)) assert abs(zoomed_image[ind]-1./(zoom))<.001 - + +def test_DetectionPositionPair(): + d1=DetectionPosition(1,2,0) + d2=DetectionPosition(4,5,6) + dp=DetectionPositionPair(d1,d2,3) + assert d1==dp.pos1 + assert d2==dp.pos2 + assert dp.timing_pos == 3 + dp.pos1.tangential_coord = 7 + assert dp.pos1.tangential_coord == 7 + assert d1.tangential_coord == 1 + def test_Scanner(): scanner=Scanner.get_scanner_from_name("ECAT 962") assert scanner.get_num_rings()==32 From 1d26b908385f190c1b21d23935bf4f42e910e357 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sat, 6 Jan 2024 14:54:42 +0000 Subject: [PATCH 432/509] updated release notes [ci skip] --- documentation/release_6.0.htm | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/documentation/release_6.0.htm b/documentation/release_6.0.htm index 2138fc505c..4af892973f 100644 --- a/documentation/release_6.0.htm +++ b/documentation/release_6.0.htm @@ -118,6 +118,13 @@

            Python (and MATLAB)

            derived classes.
          +
        • +
        • + add DetectionPositionPair.__repr__ for printing and + change order of text in DetectionPosition.__repr__ to + fit with constructor to avoid confusion.
          + PR #1316 +
        From ed02068490adf30fcd938432ce848f8b9f529435 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Mon, 8 Jan 2024 14:46:22 +0000 Subject: [PATCH 433/509] correct name of listmode file in VQC example --- examples/GE-Signa-PETMR/process_VQC_data.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/GE-Signa-PETMR/process_VQC_data.sh b/examples/GE-Signa-PETMR/process_VQC_data.sh index 1eebcf3f64..f8f8568667 100755 --- a/examples/GE-Signa-PETMR/process_VQC_data.sh +++ b/examples/GE-Signa-PETMR/process_VQC_data.sh @@ -37,7 +37,7 @@ datadir=`cd "$datadir";pwd` mkdir output cd output -listmode="$datadir"/LST/LST_30501_PET_Scan_for_VQC_Verification/LIST0000uncompressed.BLF +listmode="$datadir"/LST/LST_30501_PET_Scan_for_VQC_Verification/LIST0000.BLF # make a frame definition file with 1 frame for all the data create_fdef_from_listmode.sh frames.fdef "$listmode" From 6041df329fcf907ffb23ccb3ee8ae9187e3c066c Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Mon, 8 Jan 2024 15:29:58 +0000 Subject: [PATCH 434/509] removed guessing code based on very old scanners When parsing Interfile headers for projection data and the originating system is not recognised, the previous version of STIR tried to guess the scanner based on the number of views or rings. This was using very old scanners though, and could lead to confusion. These guesses have now been removed. --- documentation/release_6.0.htm | 7 +++- src/IO/InterfileHeader.cxx | 71 ++++------------------------------- 2 files changed, 13 insertions(+), 65 deletions(-) diff --git a/documentation/release_6.0.htm b/documentation/release_6.0.htm index 4af892973f..dc73b85b77 100644 --- a/documentation/release_6.0.htm +++ b/documentation/release_6.0.htm @@ -45,7 +45,12 @@

        Summary for end users (also to be read by developers)

        Changes breaking backwards compatibility from a user-perspective

          -
        • +
        • + When parsing Interfile headers for projection data and the originating system + is not recognised, the previous version of STIR tried to guess the scanner based on the + number of views or rings. This was using very old scanners though, and could lead to + confusion. These guesses have now been removed. +

        Bug fixes

        diff --git a/src/IO/InterfileHeader.cxx b/src/IO/InterfileHeader.cxx index b5c5252970..bac5cceaea 100644 --- a/src/IO/InterfileHeader.cxx +++ b/src/IO/InterfileHeader.cxx @@ -2,7 +2,7 @@ Copyright (C) 2000 PARAPET partners Copyright (C) 2000 - 2009-04-30, Hammersmith Imanet Ltd Copyright (C) 2011-07-01 - 2012, Kris Thielemans - Copyright (C) 2013, 2016, 2018, 2020, 2023 University College London + Copyright (C) 2013, 2016, 2018, 2020, 2023, 2024 University College London Copyright 2017 ETH Zurich, Institute of Particle Physics and Astrophysics This file is part of STIR. @@ -39,14 +39,12 @@ #include "stir/ProjDataInfoGenericNoArcCorr.h" #include -#ifndef STIR_NO_NAMESPACES using std::pair; using std::sort; using std::cerr; using std::endl; using std::string; using std::vector; -#endif START_NAMESPACE_STIR const double @@ -1063,13 +1061,7 @@ bool InterfilePDFSHeader::post_processing() cerr << sorted_num_rings_per_segment[i] << " "; cerr << endl; cerr << "Total number of planes :" - << -#ifndef STIR_NO_NAMESPACES // stupid work-around for VC - std::accumulate -#else - accumulate -#endif - (num_rings_per_segment.begin(), num_rings_per_segment.end(), 0) + << std::accumulate(num_rings_per_segment.begin(), num_rings_per_segment.end(), 0) << endl; #endif @@ -1080,63 +1072,14 @@ bool InterfilePDFSHeader::post_processing() guessed_scanner_ptr->get_type() != Scanner::Unknown_scanner; if (!originating_system_was_recognised) { - // feable attempt to guess the system by checking the num_views etc - - char const * warning_msg = 0; - if (num_detectors_per_ring < 1) - { - num_detectors_per_ring = num_views*2; - warning_msg = "\nInterfile warning: I don't recognise 'originating system' value.\n" - "\tI guessed %s from 'num_views' (note: this guess is wrong for mashed data)\n" - " and 'number of rings'\n"; - } - else - { - warning_msg = "\nInterfile warning: I don't recognise 'originating system' value.\n" - "I guessed %s from 'number of detectors per ring' and 'number of rings'\n"; - } - - - switch (num_detectors_per_ring) - { - case 192*2: - guessed_scanner_ptr.reset(new Scanner( Scanner::E953)); - warning(boost::format(warning_msg) % "ECAT 953"); - break; - case 336*2: - guessed_scanner_ptr.reset(new Scanner( Scanner::Advance)); - warning(boost::format(warning_msg) % "Advance"); - break; - case 288*2: - if(num_rings == 104) - { //added by Dylan Togane - guessed_scanner_ptr.reset(new Scanner( Scanner::HRRT)); - warning(boost::format(warning_msg) % "HRRT"); - } - else if (num_rings == 48) - { - guessed_scanner_ptr.reset(new Scanner( Scanner::E966)); - warning(boost::format(warning_msg) % "ECAT 966"); - } - else if (num_rings == 32) - { - guessed_scanner_ptr.reset(new Scanner( Scanner::E962)); - warning(boost::format(warning_msg) % "ECAT 962"); - } - break; // Dylan Togane [dtogane@camhpet.on.ca] 30/07/2002 bug fix: added break - case 256*2: - guessed_scanner_ptr.reset(new Scanner( Scanner::E951)); - warning(boost::format(warning_msg) % "ECAT 951"); - break; - } - - if (guessed_scanner_ptr->get_type() == Scanner::Unknown_scanner) - warning(std::string("\nInterfile warning: I did not recognise the scanner neither from \n" - "'originating_system' or 'number of detectors per ring' and 'number of rings'.\n")); + warning("Interfile warning: I did not recognise the scanner from 'originating_system' (" + + get_exam_info().originating_system + ")"); } bool mismatch_between_header_and_guess = false; - + + // check if STIR info matches the one in the header, and fill in missing details + if (guessed_scanner_ptr->get_type() != Scanner::Unknown_scanner && guessed_scanner_ptr->get_type() != Scanner::User_defined_scanner) { From e05b0871340e5facbfff5535f5e6bd41ee0588b4 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Mon, 8 Jan 2024 15:35:06 +0000 Subject: [PATCH 435/509] use TOF info from guessed scanner if it's missing from the header follow same strategy as for other keywords --- src/IO/InterfileHeader.cxx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/IO/InterfileHeader.cxx b/src/IO/InterfileHeader.cxx index bac5cceaea..8d030530c1 100644 --- a/src/IO/InterfileHeader.cxx +++ b/src/IO/InterfileHeader.cxx @@ -1140,6 +1140,17 @@ bool InterfilePDFSHeader::post_processing() transaxial_distance_between_blocks_in_cm = guessed_scanner_ptr->get_transaxial_block_spacing()/10; // end of new variables for block geometry + if (guessed_scanner_ptr->is_tof_ready()) + { + if (max_num_timing_poss < 0) + max_num_timing_poss = guessed_scanner_ptr->get_max_num_timing_poss(); + if (size_of_timing_pos < 0) + size_of_timing_pos = guessed_scanner_ptr->get_size_of_timing_pos(); + if (timing_resolution < 0) + timing_resolution = guessed_scanner_ptr->get_timing_resolution(); + } + + // consistency check with values of the guessed_scanner_ptr we guessed above if (num_rings != guessed_scanner_ptr->get_num_rings()) From f69e66b1800fdbc936b87cf2f641bef8fbfbe2e2 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Mon, 8 Jan 2024 17:18:13 +0000 Subject: [PATCH 436/509] friendlier error message when using TOF-norm --- .../PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx index 3ddc7cc81e..f8401fba05 100644 --- a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx +++ b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx @@ -570,7 +570,8 @@ ensure_norm_is_set_up(bool for_original_data) const if (!this->norm_already_setup || this->latest_setup_norm_was_with_orig_data) { if (this->normalisation_sptr->set_up(proj_data_sptr->get_exam_info_sptr(), this->sens_proj_data_info_sptr) == Succeeded::no) - error("Set_up of norm with non-TOF data failed."); + error("Set_up of norm with non-TOF data failed.\n" + "If your norm is TOF, set \"use time-of-flight sensitivities\" to true."); } } this->norm_already_setup = true; From 79104709e063da1bd65ebc5170e79a3874f00fdd Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Tue, 9 Jan 2024 09:54:13 +0000 Subject: [PATCH 437/509] add constructor fpor SinogramIndices that takes a Bin --- src/include/stir/SinogramIndices.h | 4 ++++ src/include/stir/SinogramIndices.inl | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/include/stir/SinogramIndices.h b/src/include/stir/SinogramIndices.h index d7ac00aa0b..e4d2f87811 100644 --- a/src/include/stir/SinogramIndices.h +++ b/src/include/stir/SinogramIndices.h @@ -24,6 +24,7 @@ #define __stir_SinogramIndices_h__ #include "stir/SegmentIndices.h" +#include "stir/Bin.h" START_NAMESPACE_STIR @@ -41,6 +42,9 @@ class SinogramIndices : public SegmentIndices //! constructor specifying indices inline SinogramIndices( const int axial_pos_num,const int segment_num, const int timing_pos_num); + //! constructor from Bin + inline SinogramIndices(const Bin&); + //! get view number for const objects inline int axial_pos_num() const; diff --git a/src/include/stir/SinogramIndices.inl b/src/include/stir/SinogramIndices.inl index bcb31b41f1..4a0a390a9c 100644 --- a/src/include/stir/SinogramIndices.inl +++ b/src/include/stir/SinogramIndices.inl @@ -31,6 +31,10 @@ SinogramIndices::SinogramIndices( const int axial_pos_num,const int segment_num, : SegmentIndices(segment_num, timing_pos_num),_axial_pos(axial_pos_num) {} +SinogramIndices::SinogramIndices(const Bin& bin) + : SegmentIndices(bin),_axial_pos(bin.axial_pos_num()) + {} + int SinogramIndices::axial_pos_num() const { From 1e234ed42c07352ec1a365ee1b02e0a2150c26e2 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Tue, 9 Jan 2024 09:54:50 +0000 Subject: [PATCH 438/509] remove proj_data_info_sptr member (now in ListModeData) --- src/include/stir/listmode/CListModeDataGEHDF5.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/include/stir/listmode/CListModeDataGEHDF5.h b/src/include/stir/listmode/CListModeDataGEHDF5.h index 48219b387d..3275128ae0 100644 --- a/src/include/stir/listmode/CListModeDataGEHDF5.h +++ b/src/include/stir/listmode/CListModeDataGEHDF5.h @@ -72,7 +72,6 @@ class CListModeDataGEHDF5 : public CListModeData typedef CListRecordGEHDF5 CListRecordT; std::string listmode_filename; - shared_ptr proj_data_info_sptr; shared_ptr > current_lm_data_ptr; unsigned long first_time_stamp; unsigned long lm_duration_in_millisecs; From 062ae941a7b5126d1b140f648a624aba0d126747 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Tue, 9 Jan 2024 09:56:22 +0000 Subject: [PATCH 439/509] add TOF info to GE RDF9 event (untested) --- src/include/stir/listmode/CListRecordGEHDF5.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/include/stir/listmode/CListRecordGEHDF5.h b/src/include/stir/listmode/CListRecordGEHDF5.h index 3ae32bf4ee..ed2583b0f7 100644 --- a/src/include/stir/listmode/CListRecordGEHDF5.h +++ b/src/include/stir/listmode/CListRecordGEHDF5.h @@ -266,6 +266,7 @@ dynamic_cast(&e2) != 0 && det_pos.pos1().axial_coord() = event_data.loXtalAxialID; det_pos.pos2().tangential_coord() = this->get_uncompressed_proj_data_info_sptr()->get_scanner_sptr()->get_num_detectors_per_ring() - 1 - event_data.hiXtalTransAxID; det_pos.pos2().axial_coord() = event_data.hiXtalAxialID; + det_pos.timing_pos() = event_data.deltaTime; } //! This routine sets in a coincidence event from detector "indices" From 4ff14e905e3106194029f16576f6591790a4cace Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Tue, 9 Jan 2024 09:57:47 +0000 Subject: [PATCH 440/509] RFS: attempt to cope with TOF templates by replication --- src/buildblock/multiply_crystal_factors.cxx | 24 ++++++++++++++++---- src/data_buildblock/randoms_from_singles.cxx | 5 ++-- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/buildblock/multiply_crystal_factors.cxx b/src/buildblock/multiply_crystal_factors.cxx index be6a4eceec..af8183b911 100644 --- a/src/buildblock/multiply_crystal_factors.cxx +++ b/src/buildblock/multiply_crystal_factors.cxx @@ -33,9 +33,7 @@ void multiply_crystal_factors_help(ProjData& proj_data, const TProjDataInfo& proj_data_info, const Array<2,float>& efficiencies, const float global_factor) { - if (proj_data_info.get_num_tof_poss() != 1) - error("multiply_crystal_factors needs non-TOF input"); - + const auto non_tof_proj_data_info_sptr = proj_data_info.create_non_tof_clone(); Bin bin; for (bin.segment_num() = proj_data.get_min_segment_num(); @@ -48,7 +46,7 @@ void multiply_crystal_factors_help(ProjData& proj_data, ++bin.axial_pos_num()) { Sinogram sinogram = - proj_data_info.get_empty_sinogram(bin.axial_pos_num(),bin.segment_num()); + non_tof_proj_data_info_sptr->get_empty_sinogram(SinogramIndices(bin)); #ifdef STIR_OPENMP # if _OPENMP >= 200711 @@ -98,7 +96,23 @@ void multiply_crystal_factors_help(ProjData& proj_data, #endif } } - proj_data.set_sinogram(sinogram); + // now set sinogram, a bit complicated for TOF as we replicate + if (proj_data.get_num_tof_poss() == 1) + { + proj_data.set_sinogram(sinogram); + } + else + { + for (bin.timing_pos_num() = proj_data.get_min_tof_pos_num(); + bin.timing_pos_num() <= proj_data.get_max_tof_pos_num(); + ++ bin.timing_pos_num()) + { + // construct TOF sinogram with same values as the non-TOF sinogram, + // but appropriate meta-data. + const Sinogram tof_sinogram(sinogram, proj_data.get_proj_data_info_sptr(), SinogramIndices(bin)); + proj_data.set_sinogram(tof_sinogram); + } + } } } diff --git a/src/data_buildblock/randoms_from_singles.cxx b/src/data_buildblock/randoms_from_singles.cxx index 6d4c93a19e..54a52a0816 100644 --- a/src/data_buildblock/randoms_from_singles.cxx +++ b/src/data_buildblock/randoms_from_singles.cxx @@ -9,7 +9,7 @@ */ /* - Copyright (C) 2020, 2021, University Copyright London + Copyright (C) 2020, 2021, 2024, University Copyright London This file is part of STIR. SPDX-License-Identifier: Apache-2.0 @@ -87,8 +87,9 @@ void randoms_from_singles(ProjData& proj_data, const SinglesRates& singles, % isotope_halflife % decay_corr_factor % duration % (1/corr_factor), 2); + // Finally, if we have TOF data, we distribute randoms evenly over the TOF bins multiply_crystal_factors(proj_data, total_singles, - static_cast(coincidence_time_window*corr_factor)); + static_cast(coincidence_time_window * corr_factor / proj_data.get_num_tof_poss())); } } From 1cf79bbcc291e4ab7035494db3468c1464aad24c Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Tue, 9 Jan 2024 10:02:03 +0000 Subject: [PATCH 441/509] adding an optional TOF template (WIP) --- examples/GE-Signa-PETMR/TOF_template.hs | 55 +++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 examples/GE-Signa-PETMR/TOF_template.hs diff --git a/examples/GE-Signa-PETMR/TOF_template.hs b/examples/GE-Signa-PETMR/TOF_template.hs new file mode 100644 index 0000000000..ae99cbe403 --- /dev/null +++ b/examples/GE-Signa-PETMR/TOF_template.hs @@ -0,0 +1,55 @@ +!INTERFILE := +!imaging modality := PT +name of data file := template.s +originating system := GE Signa PET/MR +!version of keys := STIR4.0 +!GENERAL DATA := +!GENERAL IMAGE DATA := +!type of data := PET +imagedata byte order := LITTLEENDIAN +!PET STUDY (General) := +!PET data type := Emission +applied corrections := {None} +!number format := float +!number of bytes per pixel := 4 +number of dimensions := 5 +matrix axis label [5] := timing positions +!matrix size [5] := 27 +matrix axis label [4] := segment +!matrix size [4] := 45 +matrix axis label [3] := view +!matrix size [3] := 224 +matrix axis label [2] := axial coordinate +!matrix size [2] := { 1,5,9,13,17,21,25,29,33,37,41,45,49,53,57,61,65,69,73,77,81,85,89,85,81,77,73,69,65,61,57,53,49,45,41,37,33,29,25,21,17,13,9,5,1} +matrix axis label [1] := tangential coordinate +!matrix size [1] := 357 + +TOF mashing factor := 13 +minimum ring difference per segment := { -44,-43,-41,-39,-37,-35,-33,-31,-29,-27,-25,-23,-21,-19,-17,-15,-13,-11,-9,-7,-5,-3,-1,2,4,6,8,10,12,14,16,18,20,22,24,26,28,30,32,34,36,38,40,42,44} +maximum ring difference per segment := { -44,-42,-40,-38,-36,-34,-32,-30,-28,-26,-24,-22,-20,-18,-16,-14,-12,-10,-8,-6,-4,-2,1,3,5,7,9,11,13,15,17,19,21,23,25,27,29,31,33,35,37,39,41,43,44} +Scanner parameters:= +Scanner type := GE Signa PET/MR +Number of rings := 45 +Number of detectors per ring := 448 +Inner ring diameter (cm) := 62.36 +Average depth of interaction (cm) := 0.85 +Distance between rings (cm) := 0.556 +Default bin size (cm) := 0.201565 +View offset (degrees) := -5.23 +Maximum number of non-arc-corrected bins := 357 +Default number of arc-corrected bins := 331 +Energy resolution := 0.105 +Reference energy (in keV) := 511 +Number of blocks per bucket in transaxial direction := 4 +Number of blocks per bucket in axial direction := 5 +Number of crystals per block in axial direction := 9 +Number of crystals per block in transaxial direction := 4 +Number of detector layers := 1 +Number of crystals per singles unit in axial direction := 1 +Number of crystals per singles unit in transaxial direction := 1 +end scanner parameters:= +effective central bin size (cm) := 0.224608 +number of time frames := 1 +start vertical bed position (mm) := 0 +start horizontal bed position (mm) := 0 +!END OF INTERFILE := From 331eaaf6b96ee2dcfaaa3747b7fc96077f72e3f1 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Wed, 10 Jan 2024 12:03:06 +0000 Subject: [PATCH 442/509] explicitly force GE norm to be non-TOF this was already fine, but setting the TOF mashing to 0 explicitly makes it clearer --- src/recon_buildblock/BinNormalisationFromGEHDF5.cxx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/recon_buildblock/BinNormalisationFromGEHDF5.cxx b/src/recon_buildblock/BinNormalisationFromGEHDF5.cxx index 332d3ea034..cf5937b879 100644 --- a/src/recon_buildblock/BinNormalisationFromGEHDF5.cxx +++ b/src/recon_buildblock/BinNormalisationFromGEHDF5.cxx @@ -334,13 +334,14 @@ read_norm_data(const string& filename) // if(this->use_geometric_factors()) { - // Construct a proper ProjDataInfo to initialize geometry factors array and use it to know the boudns of the iteratios to load it. + // Construct a proper ProjDataInfo to initialize geometry factors array and use it to know the bounds of the iteratios to load it. shared_ptr projInfo = ProjDataInfo::construct_proj_data_info(scanner_ptr, /*span*/ 2, /* max_delta*/ scanner_ptr->get_num_rings()-1, /* num_views */ scanner_ptr->get_num_detectors_per_ring()/2, /* num_tangential_poss */ scanner_ptr->get_max_num_non_arccorrected_bins(), - /* arc_corrected */ false + /* arc_corrected */ false, + /* tof_mash_factor */ 0 ); geo_eff_factors_sptr.reset(new ProjDataInMemory(m_input_hdf5_sptr->get_exam_info_sptr(), projInfo, From bdf3853e43a06bab5d1263c319ad05177252a111 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Wed, 10 Jan 2024 12:05:47 +0000 Subject: [PATCH 443/509] correct GE Signa PET/MR unmashed TOF bin size checked info from RDF file --- src/buildblock/Scanner.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/buildblock/Scanner.cxx b/src/buildblock/Scanner.cxx index 4ee8cc8a39..e4158bc6ca 100644 --- a/src/buildblock/Scanner.cxx +++ b/src/buildblock/Scanner.cxx @@ -448,7 +448,7 @@ Scanner::Scanner(Type scanner_type) 0.105F, // energy resolution from Levin et al. TMI 2016 511.F, (short int)(351), - (float)(89.0F/13.0F), + (float)(13.02), (float)(390.0F) ); break; From cdc3bcd71bd898c309e6f701e28f3def0b439274 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Wed, 10 Jan 2024 12:09:00 +0000 Subject: [PATCH 444/509] rename Scanner::set_num_max_of_timing_poss to set_max_num_timing_poss also made sure we have to call set_up() again after changing TOF keywords --- src/include/stir/Scanner.h | 2 +- src/include/stir/Scanner.inl | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/include/stir/Scanner.h b/src/include/stir/Scanner.h index 067bf22ddf..3b73742f5b 100644 --- a/src/include/stir/Scanner.h +++ b/src/include/stir/Scanner.h @@ -457,7 +457,7 @@ class Scanner /*! \sa get_reference_energy() */ inline void set_reference_energy(const float new_num); //! Set the maximum number of TOF bins. - inline void set_num_max_of_timing_poss(int new_num); + inline void set_max_num_timing_poss(int new_num); //! Set the delta t which correspnds to the max number of TOF bins. inline void set_size_of_timing_poss(float new_num); //! Set timing resolution diff --git a/src/include/stir/Scanner.inl b/src/include/stir/Scanner.inl index d8c065da89..97aa66fc33 100644 --- a/src/include/stir/Scanner.inl +++ b/src/include/stir/Scanner.inl @@ -453,19 +453,22 @@ void Scanner::set_crystal_map_file_name(const std::string& new_crystal_map_file_ _already_setup = false; } -void Scanner::set_num_max_of_timing_poss(const int new_num) +void Scanner::set_max_num_timing_poss(const int new_num) { max_num_of_timing_poss = new_num; + _already_setup = false; } void Scanner::set_size_of_timing_poss(const float new_num) { size_timing_pos = new_num; + _already_setup = false; } void Scanner::set_timing_resolution(const float new_num_in_ps) { timing_resolution = new_num_in_ps; + _already_setup = false; } /******** Calculate singles bin index from detection position *********/ From 56da074615e0cf16120171755ab2e683758293da Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Wed, 10 Jan 2024 12:13:09 +0000 Subject: [PATCH 445/509] read TOF info from GE RDF9 files and pass to scanner --- src/IO/GEHDF5Wrapper.cxx | 90 ++++++++++++++++++++++++---------------- 1 file changed, 55 insertions(+), 35 deletions(-) diff --git a/src/IO/GEHDF5Wrapper.cxx b/src/IO/GEHDF5Wrapper.cxx index 3142a7cb62..27f6daa321 100644 --- a/src/IO/GEHDF5Wrapper.cxx +++ b/src/IO/GEHDF5Wrapper.cxx @@ -15,7 +15,7 @@ /* Copyright (C) 2017-2019, University of Leeds Copyright (C) 2018 University of Hull - Copyright (C) 2018-2020, University College London + Copyright (C) 2018-2021, 2024, University College London This file is part of STIR. SPDX-License-Identifier: Apache-2.0 @@ -89,34 +89,10 @@ bool GEHDF5Wrapper::check_GE_signature(H5::H5File& file) return false; } -// // Checks if input file is listfile. -// AB: todo do we want this func? helps test from filename -/* -bool -GEHDF5Wrapper::is_list_file(const std::string& filename) -{ - - H5::H5File file; - - if(!file.isHdf5(filename)) - error("File is not HDF5. Aborting"); - - file.openFile( filename, H5F_ACC_RDONLY ); - // All RDF files shoudl have this DataSet - H5::DataSet dataset = file.openDataSet("/HeaderData/RDFConfiguration/isListFile"); - unsigned int is_list; - dataset.read(&is_list, H5::PredType::STD_U32LE); - return is_list; - -} -*/ - - -// AB todo: only valid for RDF9 (until they tell us otherwise) bool GEHDF5Wrapper::is_list_file() const { - // have we already checked this? + // have we already checked this? (note: initially set to false in check_file()) if(is_list) return true; @@ -277,6 +253,21 @@ GEHDF5Wrapper::open(const std::string& filename) return Succeeded::yes; } +static float read_float(const H5::H5File& file, const std::string& dataset) +{ + float tmp = 0.F; + H5::DataSet ds = file.openDataSet(dataset.c_str()); + ds.read(&tmp, H5::PredType::NATIVE_FLOAT); + return tmp;} + +static std::int32_t read_int32(const H5::H5File& file, const std::string& dataset) +{ + std::int32_t tmp = 0; + H5::DataSet ds = file.openDataSet(dataset.c_str()); + ds.read(&tmp, H5::PredType::NATIVE_INT32); + return tmp; +} + shared_ptr GEHDF5Wrapper::get_scanner_from_HDF5() { std::string read_str_scanner; @@ -342,6 +333,13 @@ shared_ptr GEHDF5Wrapper::get_scanner_from_HDF5() str_radial_crystals_per_block.read(&num_transaxial_crystals_per_block, H5::PredType::NATIVE_UINT32); str_axial_crystals_per_block.read(&num_axial_crystals_per_block, H5::PredType::NATIVE_UINT32); + // TOF related + const float timingResolutionInPico = read_float(file, "/HeaderData/SystemGeometry/timingResolutionInPico"); + const int posCoincidenceWindow = read_int32(file, "/HeaderData/AcqParameters/EDCATParameters/posCoincidenceWindow"); + const int negCoincidenceWindow = read_int32(file, "/HeaderData/AcqParameters/EDCATParameters/negCoincidenceWindow"); + const float coincTimingPrecisionInPico = read_float(file, "/HeaderData/AcqParameters/EDCATParameters/coincTimingPrecision") * 1000; // in nanoSecs in file + const int num_tof_bins = posCoincidenceWindow + negCoincidenceWindow + 1; + int num_rings = num_axial_blocks_per_bucket*num_axial_crystals_per_block*axial_modules_per_system; int num_detectors_per_ring = num_transaxial_blocks_per_bucket*num_transaxial_crystals_per_block*radial_modules_per_system; float ring_spacing = detector_axial_size/num_rings; @@ -375,6 +373,31 @@ shared_ptr GEHDF5Wrapper::get_scanner_from_HDF5() scanner_sptr->set_inner_ring_radius((effective_ring_diameter/2) - def_DOI); scanner_sptr->set_average_depth_of_interaction(def_DOI); } + if (timingResolutionInPico > 0 // Signa files seem to have zero in this field + && (fabs(scanner_sptr->get_timing_resolution() - timingResolutionInPico) > .1F)) + { + warning("GEHDF5Wrapper: default STIR timing resolution is " + + std::to_string(scanner_sptr->get_timing_resolution()) + + ", while RDF says " + std::to_string(timingResolutionInPico) + + "\nWill adjust scanner info to fit with the RDF file"); + scanner_sptr->set_timing_resolution(timingResolutionInPico); + } + if (fabs(scanner_sptr->get_size_of_timing_pos() - coincTimingPrecisionInPico)> .1F) + { + warning("GEHDF5Wrapper: default STIR size of (unmashed) TOF bins is " + + std::to_string(scanner_sptr->get_size_of_timing_pos()) + + ", while RDF says " + std::to_string(coincTimingPrecisionInPico) + + "\nWill adjust scanner info to fit with the RDF file"); + scanner_sptr->set_size_of_timing_poss(coincTimingPrecisionInPico); + } + if (std::abs(scanner_sptr->get_max_num_timing_poss() - num_tof_bins) > 0) + { + warning("GEHDF5Wrapper: default STIR number of (unmashed) TOF bins is " + + std::to_string(scanner_sptr->get_max_num_timing_poss()) + + ", while RDF says " + std::to_string(num_tof_bins) + + "\nWill adjust scanner info to fit with the RDF file"); + scanner_sptr->set_max_num_timing_poss(num_tof_bins); + } if (scanner_sptr->get_default_bin_size() <= 0.F) { warning("GEHDF5Wrapper: default bin-size is not set. This will create trouble for FBP etc"); @@ -401,7 +424,8 @@ void GEHDF5Wrapper::initialise_proj_data_info_from_HDF5() /* max_delta*/ scanner_sptr->get_num_rings()-1, /* num_views */ scanner_sptr->get_num_detectors_per_ring()/2, /* num_tangential_poss */ scanner_sptr->get_max_num_non_arccorrected_bins(), - /* arc_corrected */ false + /* arc_corrected */ false, + this->is_list_file() ? 1 : 0 // TODO change when reading sinos as TOF ); this->proj_data_info_sptr-> set_bed_position_horizontal(this->read_dataset_int32("/HeaderData/AcqParameters/LandmarkParameters/absTableLongitude")/10.F); /* units in RDF are 0.1 mm */ @@ -705,14 +729,10 @@ float GEHDF5Wrapper::get_coincidence_time_window() const if(!is_list_file() && !is_sino_file()) error("The file provided is not list or sino data. Aborting"); - H5::DataSet ds_coincTimingPrecision = file.openDataSet("/HeaderData/AcqParameters/EDCATParameters/coincTimingPrecision"); - H5::DataSet ds_posCoincidenceWindow = file.openDataSet("/HeaderData/AcqParameters/EDCATParameters/posCoincidenceWindow"); - float coincTimingPrecision = 0; - int posCoincidenceWindow = 0; - ds_coincTimingPrecision.read(&coincTimingPrecision,H5::PredType::NATIVE_FLOAT); - ds_posCoincidenceWindow.read(&posCoincidenceWindow,H5::PredType::NATIVE_INT32); - - return (2*posCoincidenceWindow+1) *coincTimingPrecision*1e-9; + const int posCoincidenceWindow = read_int32(file, "/HeaderData/AcqParameters/EDCATParameters/posCoincidenceWindow"); + const int negCoincidenceWindow = read_int32(file, "/HeaderData/AcqParameters/EDCATParameters/negCoincidenceWindow"); + const float coincTimingPrecision = read_float(file, "/HeaderData/AcqParameters/EDCATParameters/coincTimingPrecision"); + return (posCoincidenceWindow + negCoincidenceWindow + 1) *coincTimingPrecision*1e-9; } float GEHDF5Wrapper::get_halflife() const From 6a32d45e405a5a9007f2863d1d01d862ceea8d5a Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Wed, 10 Jan 2024 12:29:42 +0000 Subject: [PATCH 446/509] remove GEHDF5Wrapper::get_coincidence_time_window() use ProjDataInfo member instead --- src/IO/GEHDF5Wrapper.cxx | 11 ----------- src/include/stir/IO/GEHDF5Wrapper.h | 3 --- src/utilities/construct_randoms_from_GEsingles.cxx | 6 ++---- 3 files changed, 2 insertions(+), 18 deletions(-) diff --git a/src/IO/GEHDF5Wrapper.cxx b/src/IO/GEHDF5Wrapper.cxx index 27f6daa321..f366a0c730 100644 --- a/src/IO/GEHDF5Wrapper.cxx +++ b/src/IO/GEHDF5Wrapper.cxx @@ -724,17 +724,6 @@ Succeeded GEHDF5Wrapper::initialise_efficiency_factors() return Succeeded::yes; } -float GEHDF5Wrapper::get_coincidence_time_window() const -{ - if(!is_list_file() && !is_sino_file()) - error("The file provided is not list or sino data. Aborting"); - - const int posCoincidenceWindow = read_int32(file, "/HeaderData/AcqParameters/EDCATParameters/posCoincidenceWindow"); - const int negCoincidenceWindow = read_int32(file, "/HeaderData/AcqParameters/EDCATParameters/negCoincidenceWindow"); - const float coincTimingPrecision = read_float(file, "/HeaderData/AcqParameters/EDCATParameters/coincTimingPrecision"); - return (posCoincidenceWindow + negCoincidenceWindow + 1) *coincTimingPrecision*1e-9; -} - float GEHDF5Wrapper::get_halflife() const { if(!is_list_file() && !is_sino_file()) diff --git a/src/include/stir/IO/GEHDF5Wrapper.h b/src/include/stir/IO/GEHDF5Wrapper.h index 6fb88093cb..332b94d36a 100644 --- a/src/include/stir/IO/GEHDF5Wrapper.h +++ b/src/include/stir/IO/GEHDF5Wrapper.h @@ -91,9 +91,6 @@ class GEHDF5Wrapper Succeeded initialise_efficiency_factors(); - //! reads coincidence time window from file (in secs) - float get_coincidence_time_window() const; - //! reads the isotope half-life from file (in secs) float get_halflife() const; diff --git a/src/utilities/construct_randoms_from_GEsingles.cxx b/src/utilities/construct_randoms_from_GEsingles.cxx index b799b2a0e5..3d1f695613 100755 --- a/src/utilities/construct_randoms_from_GEsingles.cxx +++ b/src/utilities/construct_randoms_from_GEsingles.cxx @@ -7,15 +7,13 @@ Dead-time is not taken into account. - \todo We currently assume F-18 for decay. - \author Palak Wadhwa \author Kris Thielemans */ /* Copyright (C) 2017- 2019, University of Leeds - Copyright (C) 2020, 2021, University College London + Copyright (C) 2020, 2021, 2024 University College London This file is part of STIR. SPDX-License-Identifier: Apache-2.0 @@ -86,7 +84,7 @@ int main(int argc, char **argv) output_file_name); GE::RDF_HDF5::SinglesRatesFromGEHDF5 singles(input_filename); - const float coincidence_time_window = input_file.get_coincidence_time_window(); + const float coincidence_time_window = input_file.get_proj_data_info_sptr()->get_coincidence_window_in_pico_sec() / 1e12F; const float isotope_halflife = input_file.get_halflife(); From f28b74bdaa1379a7242862cfade1111a2a1bf88b Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Wed, 10 Jan 2024 12:38:12 +0000 Subject: [PATCH 447/509] fix: remove static read_int32 use existing read_dataset_int32 --- src/IO/GEHDF5Wrapper.cxx | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/src/IO/GEHDF5Wrapper.cxx b/src/IO/GEHDF5Wrapper.cxx index f366a0c730..ff7a96f203 100644 --- a/src/IO/GEHDF5Wrapper.cxx +++ b/src/IO/GEHDF5Wrapper.cxx @@ -53,6 +53,15 @@ GEHDF5Wrapper::read_dataset_int32(const std::string& dataset_name) return tmp; } + +static float read_float(const H5::H5File& file, const std::string& dataset) +{ + float tmp = 0.F; + H5::DataSet ds = file.openDataSet(dataset.c_str()); + ds.read(&tmp, H5::PredType::NATIVE_FLOAT); + return tmp; +} + bool GEHDF5Wrapper::check_GE_signature(const std::string& filename) { try @@ -253,21 +262,6 @@ GEHDF5Wrapper::open(const std::string& filename) return Succeeded::yes; } -static float read_float(const H5::H5File& file, const std::string& dataset) -{ - float tmp = 0.F; - H5::DataSet ds = file.openDataSet(dataset.c_str()); - ds.read(&tmp, H5::PredType::NATIVE_FLOAT); - return tmp;} - -static std::int32_t read_int32(const H5::H5File& file, const std::string& dataset) -{ - std::int32_t tmp = 0; - H5::DataSet ds = file.openDataSet(dataset.c_str()); - ds.read(&tmp, H5::PredType::NATIVE_INT32); - return tmp; -} - shared_ptr GEHDF5Wrapper::get_scanner_from_HDF5() { std::string read_str_scanner; @@ -335,8 +329,8 @@ shared_ptr GEHDF5Wrapper::get_scanner_from_HDF5() // TOF related const float timingResolutionInPico = read_float(file, "/HeaderData/SystemGeometry/timingResolutionInPico"); - const int posCoincidenceWindow = read_int32(file, "/HeaderData/AcqParameters/EDCATParameters/posCoincidenceWindow"); - const int negCoincidenceWindow = read_int32(file, "/HeaderData/AcqParameters/EDCATParameters/negCoincidenceWindow"); + const int posCoincidenceWindow = read_dataset_int32("/HeaderData/AcqParameters/EDCATParameters/posCoincidenceWindow"); + const int negCoincidenceWindow = read_dataset_int32("/HeaderData/AcqParameters/EDCATParameters/negCoincidenceWindow"); const float coincTimingPrecisionInPico = read_float(file, "/HeaderData/AcqParameters/EDCATParameters/coincTimingPrecision") * 1000; // in nanoSecs in file const int num_tof_bins = posCoincidenceWindow + negCoincidenceWindow + 1; From 876463fb05de247a8feeaf5aeb1280b3bd004edc Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Wed, 10 Jan 2024 13:20:22 +0000 Subject: [PATCH 448/509] read radionuclide info from GE RDF9 --- src/IO/GEHDF5Wrapper.cxx | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/IO/GEHDF5Wrapper.cxx b/src/IO/GEHDF5Wrapper.cxx index ff7a96f203..77445d8107 100644 --- a/src/IO/GEHDF5Wrapper.cxx +++ b/src/IO/GEHDF5Wrapper.cxx @@ -25,6 +25,8 @@ #include "stir/IO/GEHDF5Wrapper.h" #include "stir/IndexRange3D.h" +#include "stir/Radionuclide.h" +#include "stir/RadionuclideDB.h" #include "stir/is_null_ptr.h" #include "stir/info.h" #include "stir/error.h" @@ -493,6 +495,22 @@ void GEHDF5Wrapper::initialise_exam_info() TimeFrameDefinitions tm(tf); exam_info_sptr->set_time_frame_definitions(tm); + + // radionuclide + { + auto rn_name_ds = file.openDataSet("/HeaderData/ExamData/radionuclideName"); + H5::StrType str_type(rn_name_ds); + std::string rn_name; + rn_name_ds.read(rn_name, str_type); + RadionuclideDB radionuclide_db; + Radionuclide radionuclide = radionuclide_db.get_radionuclide(exam_info_sptr->imaging_modality,rn_name); + + const float positron_fraction = read_float(file, "/HeaderData/ExamData/positronFraction"); + const float half_life = read_float(file, "/HeaderData/ExamData/halfLife"); + if (radionuclide.get_half_life(false) < 0) + radionuclide = Radionuclide(rn_name, 511.F, positron_fraction, half_life, exam_info_sptr->imaging_modality); + exam_info_sptr->set_radionuclide(radionuclide); + } } Succeeded GEHDF5Wrapper::initialise_listmode_data() From d517cdf59e3eaa6a61065a53759973c8742bc1cd Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Wed, 10 Jan 2024 14:01:00 +0000 Subject: [PATCH 449/509] remove GEHDF5Wrapper:get_halflife() use proj_data_info instead --- src/IO/GEHDF5Wrapper.cxx | 12 ------------ src/include/stir/IO/GEHDF5Wrapper.h | 3 --- src/utilities/construct_randoms_from_GEsingles.cxx | 5 ++--- 3 files changed, 2 insertions(+), 18 deletions(-) diff --git a/src/IO/GEHDF5Wrapper.cxx b/src/IO/GEHDF5Wrapper.cxx index 77445d8107..130ed98ec6 100644 --- a/src/IO/GEHDF5Wrapper.cxx +++ b/src/IO/GEHDF5Wrapper.cxx @@ -736,18 +736,6 @@ Succeeded GEHDF5Wrapper::initialise_efficiency_factors() return Succeeded::yes; } -float GEHDF5Wrapper::get_halflife() const -{ - if(!is_list_file() && !is_sino_file()) - error("The file provided is not list or sino data. Aborting"); - - H5::DataSet ds_halflife = file.openDataSet("/HeaderData/ExamData/halfLife"); - float halflife = 0; - ds_halflife.read(&halflife,H5::PredType::NATIVE_FLOAT); - return halflife; -} - - // Developed for listmode access Succeeded GEHDF5Wrapper::read_list_data( char* output, const std::streampos offset, const hsize_t size) const diff --git a/src/include/stir/IO/GEHDF5Wrapper.h b/src/include/stir/IO/GEHDF5Wrapper.h index 332b94d36a..2168152ce0 100644 --- a/src/include/stir/IO/GEHDF5Wrapper.h +++ b/src/include/stir/IO/GEHDF5Wrapper.h @@ -91,9 +91,6 @@ class GEHDF5Wrapper Succeeded initialise_efficiency_factors(); - //! reads the isotope half-life from file (in secs) - float get_halflife() const; - //! reads listmode event(s) /* \param[output] output: has to be pre-allocated and of the correct size \param[in] offset: start in listmode data (in number of bytes) diff --git a/src/utilities/construct_randoms_from_GEsingles.cxx b/src/utilities/construct_randoms_from_GEsingles.cxx index 3d1f695613..a97351a0c9 100755 --- a/src/utilities/construct_randoms_from_GEsingles.cxx +++ b/src/utilities/construct_randoms_from_GEsingles.cxx @@ -84,9 +84,8 @@ int main(int argc, char **argv) output_file_name); GE::RDF_HDF5::SinglesRatesFromGEHDF5 singles(input_filename); - const float coincidence_time_window = input_file.get_proj_data_info_sptr()->get_coincidence_window_in_pico_sec() / 1e12F; - - const float isotope_halflife = input_file.get_halflife(); + const float coincidence_time_window = proj_data_info_sptr->get_coincidence_window_in_pico_sec() / 1e12F; + const float isotope_halflife = exam_info_sptr->get_radionuclide().get_half_life(); randoms_from_singles(proj_data, singles, coincidence_time_window, isotope_halflife); return EXIT_SUCCESS; From 26f358f319c57c9012e2837187c14a619a5a41f2 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Wed, 10 Jan 2024 20:52:21 +0000 Subject: [PATCH 450/509] radionuclide parsing for Interfile Interfile headers now use use the following keywords: number of radionuclides := 1 radionuclide name[1] := ... radionuclide halflife (sec)[1] := ... radionuclide branching factor[1] := ... Previous versions of STIR used `isotope name`. This is still recognised if `radionuclide name[1]` is not present. Note that neither versions are confirming to the (very old) Interfile 4.0 proposal. --- documentation/release_6.0.htm | 17 ++++++++++++++ src/IO/InterfileHeader.cxx | 34 ++++++++++++++++++++------- src/IO/interfile.cxx | 31 +++++++++++++++++++----- src/include/stir/IO/InterfileHeader.h | 7 +++++- 4 files changed, 74 insertions(+), 15 deletions(-) diff --git a/documentation/release_6.0.htm b/documentation/release_6.0.htm index dc73b85b77..5221c56458 100644 --- a/documentation/release_6.0.htm +++ b/documentation/release_6.0.htm @@ -104,6 +104,23 @@

        General

      +
    • +
        Interfile headers now use use the following keywords: +
        +            number of radionuclides := 1
        +            radionuclide name[1] := ...
        +            radionuclide halflife (sec)[1] := ...
        +            radionuclide branching factor[1] := ...
        +          
        + Previous versions of STIR used isotope name. This is still recognised + if radionuclide name[1] is not present. Note that + neither versions are confirming to the (very old) Interfile 4.0 proposal. + +
      • + Radionuclide information is read from Interfile and GE HDF5 headers. + If the radionuclide name is recognised to the STIR database, its values for half-life etc + are used, as opposed to what was recorded in the file (if anything). +

      Python (and MATLAB)

      diff --git a/src/IO/InterfileHeader.cxx b/src/IO/InterfileHeader.cxx index 8d030530c1..2e64f6aab8 100644 --- a/src/IO/InterfileHeader.cxx +++ b/src/IO/InterfileHeader.cxx @@ -167,8 +167,11 @@ InterfileHeader::InterfileHeader() data_offset = 0UL; calibration_factor=-1; - - + radionuclide_name.resize(1); + radionuclide_half_life.resize(1); + radionuclide_half_life[1] = -1.F; + radionuclide_branching_ratio.resize(1); + radionuclide_branching_ratio[0] = -1.F; add_key("name of data file", &data_file_name); add_key("originating system", &exam_info_sptr->originating_system); @@ -176,7 +179,12 @@ InterfileHeader::InterfileHeader() ignore_key("GENERAL IMAGE DATA"); add_key("calibration factor", &calibration_factor); - add_key("isotope name", &isotope_name); + // deprecated + add_key("isotope name", &isotope_name); + ignore_key("number of radionuclides"); // just always use 1. TODO should check really + add_vectorised_key("radionuclide name", &radionuclide_name); + add_vectorised_key("radionuclide halflife (sec)", &radionuclide_half_life); + add_vectorised_key("radionuclide branching factor", &radionuclide_branching_ratio); add_key("study date", &study_date_time.date); add_key("study_time", &study_date_time.time); add_key("type of data", @@ -268,12 +276,22 @@ bool InterfileHeader::post_processing() {} } -// if(this->calibration_factor>0) - this->exam_info_sptr->set_calibration_factor(calibration_factor); - - // here I need to cal the DB and set the Radionuclide member + this->exam_info_sptr->set_calibration_factor(calibration_factor); + + const bool is_spect = this->exam_info_sptr->imaging_modality.get_modality() == ImagingModality::NM; + + // radionuclide + { RadionuclideDB radionuclide_db; - this->exam_info_sptr->set_radionuclide(radionuclide_db.get_radionuclide(exam_info_sptr->imaging_modality,isotope_name)); + const std::string rn_name = !this->radionuclide_name.empty()? + this->radionuclide_name[0] : this->isotope_name; + auto radionuclide = radionuclide_db.get_radionuclide(exam_info_sptr->imaging_modality, rn_name); + if (radionuclide.get_half_life(false) < 0) + radionuclide = Radionuclide(rn_name, + is_spect ? -1.F : 511.F, // TODO handle energy for SPECT + radionuclide_branching_ratio[0], radionuclide_half_life[0], this->exam_info_sptr->imaging_modality); + this->exam_info_sptr->set_radionuclide(radionuclide); + } if (patient_orientation_index<0 || patient_rotation_index<0) return true; diff --git a/src/IO/interfile.cxx b/src/IO/interfile.cxx index 0f7aaad1fd..42a79c6dd9 100644 --- a/src/IO/interfile.cxx +++ b/src/IO/interfile.cxx @@ -1,7 +1,7 @@ /* Copyright (C) 2000 PARAPET partners Copyright (C) 2000- 2011, Hammersmith Imanet Ltd - Copyright (C) 2013, 2018, 2023 University College London + Copyright (C) 2013, 2018, 2023, 2024 University College London Copyright 2017 ETH Zurich, Institute of Particle Physics and Astrophysics This file is part of STIR. @@ -503,6 +503,26 @@ static void write_interfile_modality(std::ostream& output_header, const ExamInf output_header << "!imaging modality := " << exam_info.imaging_modality.get_name() << '\n'; } +static void write_interfile_radionuclide_info(std::ostream& output_header, const ExamInfo& exam_info) +{ + const auto radionuclide = exam_info.get_radionuclide(); + //const bool is_spect = exam_info.imaging_modality.get_modality() == ImagingModality::NM; + + // TODO we only support one + output_header << "number of radionuclides := 1\n"; + if (!radionuclide.get_name().empty() && radionuclide.get_name()!="Unknown") + output_header << "radionuclide name[1] := " << radionuclide.get_name() << '\n'; + if (radionuclide.get_half_life(false) >= 0) + { + output_header << "radionuclide halflife (sec)[1] := " << radionuclide.get_half_life() << '\n'; + } + if (radionuclide.get_branching_ratio(false) >= 0) + { + output_header << "radionuclide branching factor[1] := " + << radionuclide.get_branching_ratio() << '\n'; + } +} + static void interfile_create_filenames(const std::string& filename, std::string& data_name, std::string& header_name) { data_name=filename; @@ -594,10 +614,8 @@ write_basic_interfile_image_header(const string& header_file_name, if (exam_info.get_calibration_factor()>0.F) output_header << "calibration factor := " < image_relative_start_times; std::vector image_durations; int bytes_per_pixel; - + + //! \deprecated std::string isotope_name; + std::vector radionuclide_name; + std::vector radionuclide_half_life; + std::vector radionuclide_branching_ratio; + float calibration_factor; private: From f5187826858e1adb37507f098856f272598788cd Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Thu, 11 Jan 2024 11:41:53 +0000 Subject: [PATCH 451/509] TOF BUG FIX: ProjData::get_segment...(SegmentIndices) ignored TOF --- src/include/stir/ProjData.inl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/include/stir/ProjData.inl b/src/include/stir/ProjData.inl index 59cc165c9b..71bc4156a6 100644 --- a/src/include/stir/ProjData.inl +++ b/src/include/stir/ProjData.inl @@ -27,13 +27,13 @@ START_NAMESPACE_STIR SegmentBySinogram ProjData::get_segment_by_sinogram(const SegmentIndices& si) const { - return this->get_segment_by_sinogram(si.segment_num()); + return this->get_segment_by_sinogram(si.segment_num(), si.timing_pos_num()); } SegmentByView ProjData::get_segment_by_view(const SegmentIndices& si) const { - return this->get_segment_by_view(si.segment_num()); + return this->get_segment_by_view(si.segment_num(), si.timing_pos_num()); } Viewgram From 91caf65204c3ce8cdc22c9b4888d34cc63f15e98 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Thu, 11 Jan 2024 11:43:49 +0000 Subject: [PATCH 452/509] add more members using *Indices to Segment classes --- src/include/stir/Segment.h | 7 +++++++ src/include/stir/Segment.inl | 16 +++++++++++++++- src/include/stir/SegmentBySinogram.h | 18 ++++++++++-------- src/include/stir/SegmentByView.h | 18 ++++++++++-------- 4 files changed, 42 insertions(+), 17 deletions(-) diff --git a/src/include/stir/Segment.h b/src/include/stir/Segment.h index 72e5678ad0..fd6ffe260e 100644 --- a/src/include/stir/Segment.h +++ b/src/include/stir/Segment.h @@ -23,6 +23,8 @@ #include "stir/ProjDataInfo.h" #include "stir/SegmentIndices.h" +#include "stir/SinogramIndices.h" +#include "stir/ViewgramIndices.h" #include "stir/shared_ptr.h" START_NAMESPACE_STIR @@ -83,6 +85,11 @@ class Segment //! return a new viewgram, with data set as in the segment virtual Viewgram get_viewgram(int view_num) const = 0; + //! return a new sinogram, with data set as in the segment + inline Sinogram get_sinogram(const SinogramIndices& s) const; + //! return a new viewgram, with data set as in the segment + inline Viewgram get_viewgram(const ViewgramIndices&) const; + //! set data in segment according to sinogram \c s virtual void set_sinogram(const Sinogram& s) = 0; //! set sinogram at a different axial_pos_num diff --git a/src/include/stir/Segment.inl b/src/include/stir/Segment.inl index 6623fe6229..bc1f3958e9 100644 --- a/src/include/stir/Segment.inl +++ b/src/include/stir/Segment.inl @@ -3,7 +3,7 @@ /* Copyright (C) 2000 PARAPET partners Copyright (C) 2000- 2007, IRSL - Copyright (C) 2023, University College London + Copyright (C) 2023, 2024 University College London This file is part of STIR. SPDX-License-Identifier: Apache-2.0 AND License-ref-PARAPET-license @@ -54,4 +54,18 @@ Segment::get_proj_data_info_sptr() const return proj_data_info_sptr; } +template +Sinogram +Segment::get_sinogram(const SinogramIndices& s) const +{ + return this->get_sinogram(s.axial_pos_num()); +} + +template +Viewgram +Segment::get_viewgram(const ViewgramIndices& v) const +{ + return this->get_viewgram(v.view_num()); +} + END_NAMESPACE_STIR diff --git a/src/include/stir/SegmentBySinogram.h b/src/include/stir/SegmentBySinogram.h index a3a72143b2..c8e2d7948c 100644 --- a/src/include/stir/SegmentBySinogram.h +++ b/src/include/stir/SegmentBySinogram.h @@ -103,22 +103,24 @@ class SegmentBySinogram : public Segment, public Array<3,elemT> inline int get_min_tangential_pos_num() const; //! Get maximum tangential position number inline int get_max_tangential_pos_num() const; + using Segment::get_sinogram; + using Segment::get_viewgram; //! Get sinogram - inline Sinogram get_sinogram(int axial_pos_num) const; + inline Sinogram get_sinogram(int axial_pos_num) const override; //! Get viewgram - Viewgram get_viewgram(int view_num) const; + Viewgram get_viewgram(int view_num) const override; //! Set viewgram - void set_viewgram(const Viewgram&); + void set_viewgram(const Viewgram&) override; //! Set sinogram - inline void set_sinogram(Sinogram const &s, int axial_pos_num); - inline void set_sinogram(const Sinogram& s); + inline void set_sinogram(Sinogram const &s, int axial_pos_num) override; + inline void set_sinogram(const Sinogram& s) override; //! Overloading Array::grow - void grow(const IndexRange<3>& range); + void grow(const IndexRange<3>& range) override; //! Overloading Array::resize - void resize(const IndexRange<3>& range); + void resize(const IndexRange<3>& range) override; - virtual bool operator ==(const Segment&) const; + virtual bool operator ==(const Segment&) const override; }; END_NAMESPACE_STIR diff --git a/src/include/stir/SegmentByView.h b/src/include/stir/SegmentByView.h index f4381c5ec1..4773bd8110 100644 --- a/src/include/stir/SegmentByView.h +++ b/src/include/stir/SegmentByView.h @@ -106,23 +106,25 @@ template class SegmentByView : public Segment, public Ar //! Get maximum tangetial position number inline int get_max_tangential_pos_num() const; + using Segment::get_sinogram; + using Segment::get_viewgram; //! Get sinogram - Sinogram get_sinogram(int axial_pos_num) const; + Sinogram get_sinogram(int axial_pos_num) const override; //! Get viewgram - inline Viewgram get_viewgram(int view_num) const; + inline Viewgram get_viewgram(int view_num) const override; //! Set sinogram - inline void set_sinogram(const Sinogram &s); + inline void set_sinogram(const Sinogram &s) override; //! Set sinogram - void set_sinogram(Sinogram const &s, int axial_pos_num); + void set_sinogram(Sinogram const &s, int axial_pos_num) override; //! Set viewgram - inline void set_viewgram(const Viewgram &v); + inline void set_viewgram(const Viewgram &v) override; //! Overloading Array::grow - void grow(const IndexRange<3>& range); + void grow(const IndexRange<3>& range) override; //! Overloading Array::resize - void resize(const IndexRange<3>& range); + void resize(const IndexRange<3>& range) override; - virtual bool operator ==(const Segment&) const; + virtual bool operator ==(const Segment&) const override; }; END_NAMESPACE_STIR From b618cf6fbffc59d69813db3a5e44da302fb6d52e Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Thu, 11 Jan 2024 11:45:59 +0000 Subject: [PATCH 453/509] add TOF dimension to example ProjDataVisualisation --- .../BackendTools/STIRInterface.py | 24 +++++++---------- .../UIGroupboxProjdataDimensions.py | 17 ++++++++++-- .../ProjDataVisualisation.py | 27 +++++++++++++------ 3 files changed, 44 insertions(+), 24 deletions(-) diff --git a/examples/python/projdata_visualisation/BackendTools/STIRInterface.py b/examples/python/projdata_visualisation/BackendTools/STIRInterface.py index d3e220bb90..46b98ef3bd 100644 --- a/examples/python/projdata_visualisation/BackendTools/STIRInterface.py +++ b/examples/python/projdata_visualisation/BackendTools/STIRInterface.py @@ -1,4 +1,4 @@ -# Copyright 2022 University College London +# Copyright 2022, 2024 University College London # Author Robert Twyman @@ -23,7 +23,7 @@ class ProjDataDims(Enum): AXIAL_POS = auto() VIEW_NUMBER = auto() TANGENTIAL_POS = auto() - + TIMING_POS = auto() class ProjDataVisualisationBackend: """Class used as STIR interface to the projection data for ProjDataVisualisation.""" @@ -84,13 +84,14 @@ def print_segment_data_configuration(self) -> None: f"\tNumber of axial positions:\t\t\t{self.segment_data.get_num_axial_poss()}\n" ) - def refresh_segment_data(self, segment_number=0) -> stir.FloatSegmentByView: + def refresh_segment_data(self, segment_number=0, timing_pos=0) -> stir.FloatSegmentByView: """Loads a segment data, from the projection data, into memory allowing for faster access.""" if self.projdata is None: self.load_projdata() if self.projdata is not None: - self.segment_data = self.projdata.get_segment_by_view(segment_number) + seg_idx = stir.SegmentIndices(segment_number, timing_pos) + self.segment_data = self.projdata.get_segment_by_view(seg_idx) return self.segment_data @staticmethod @@ -120,21 +121,16 @@ def get_limits(self, dimension: ProjDataDims, segment_number: int) -> tuple: elif dimension == ProjDataDims.TANGENTIAL_POS: return self.projdata.get_min_tangential_pos_num(), \ self.projdata.get_max_tangential_pos_num() + elif dimension == ProjDataDims.TIMING_POS: + return self.projdata.get_min_tof_pos_num(), \ + self.projdata.get_max_tof_pos_num() else: raise ValueError("Unknown sinogram dimension: " + str(dimension)) def get_num_indices(self, dimension: ProjDataDims): """Returns the number of indices in the given dimension.""" - if dimension == ProjDataDims.SEGMENT_NUM: - return self.projdata.get_num_segments() - elif dimension == ProjDataDims.AXIAL_POS: - return self.projdata.get_num_axial_poss(self.get_current_segment_num()) - elif dimension == ProjDataDims.VIEW_NUMBER: - return self.projdata.get_num_views() - elif dimension == ProjDataDims.TANGENTIAL_POS: - return self.projdata.get_num_tangential_poss() - else: - raise ValueError("Unknown sinogram dimension: " + str(dimension)) + l = self.get_limits(dimension) + return l[1] - l[0] + 1 def get_current_segment_num(self) -> int: """Returns the segment number of the current segment data.""" diff --git a/examples/python/projdata_visualisation/BackendTools/UIGroupboxProjdataDimensions.py b/examples/python/projdata_visualisation/BackendTools/UIGroupboxProjdataDimensions.py index 57f68b339d..c0ecefd9a0 100644 --- a/examples/python/projdata_visualisation/BackendTools/UIGroupboxProjdataDimensions.py +++ b/examples/python/projdata_visualisation/BackendTools/UIGroupboxProjdataDimensions.py @@ -1,4 +1,4 @@ -# Copyright 2022 University College London +# Copyright 2022, 2024 University College London # Author Robert Twyman @@ -42,6 +42,10 @@ def __init__(self, stir_interface: ProjDataVisualisationBackend) -> QGroupBox: ProjDataDims.TANGENTIAL_POS: { 'label': 'Tangential position', 'connect_method': self.tangential_pos_refresh + }, + ProjDataDims.TIMING_POS: { + 'label': 'TOF bin', + 'connect_method': self.timing_pos_refresh } } @@ -54,6 +58,7 @@ def __init__(self, stir_interface: ProjDataVisualisationBackend) -> QGroupBox: self.UI_slider_spinboxes[ProjDataDims.AXIAL_POS].add_item_to_layout(layout, row=2) self.UI_slider_spinboxes[ProjDataDims.VIEW_NUMBER].add_item_to_layout(layout, row=4) self.UI_slider_spinboxes[ProjDataDims.TANGENTIAL_POS].add_item_to_layout(layout, row=6) + self.UI_slider_spinboxes[ProjDataDims.TIMING_POS].add_item_to_layout(layout, row=8) layout.setRowStretch(5, 1) self.groupbox.setLayout(layout) @@ -83,7 +88,8 @@ def segment_number_refresh(self): """ This function is called when the user changes the segment number value. Because of the way the STIR segment data is handled, the segment_data needs to change first.""" new_segment_num = self.UI_slider_spinboxes[ProjDataDims.SEGMENT_NUM].value() - self.stir_interface.refresh_segment_data(new_segment_num) + new_timing_pos = self.UI_slider_spinboxes[ProjDataDims.TIMING_POS].value() + self.stir_interface.refresh_segment_data(new_segment_num, new_timing_pos) self.UI_controller_UI_change_trigger() def axial_pos_refresh(self): @@ -98,6 +104,13 @@ def tangential_pos_refresh(self): """This function is called when the user changes the tangential position value.""" self.UI_controller_UI_change_trigger() + def timing_pos_refresh(self): + """This function is called when the user changes the TOF bin value.""" + new_segment_num = self.UI_slider_spinboxes[ProjDataDims.SEGMENT_NUM].value() + new_timing_pos = self.UI_slider_spinboxes[ProjDataDims.TIMING_POS].value() + self.stir_interface.refresh_segment_data(new_segment_num, new_timing_pos) + self.UI_controller_UI_change_trigger() + def refresh_sliders_and_spinboxes_ranges(self) -> None: """Update the sliders and spinboxes ranges based upon the stir_interface projdata.""" if self.stir_interface.projdata is None: diff --git a/examples/python/projdata_visualisation/ProjDataVisualisation.py b/examples/python/projdata_visualisation/ProjDataVisualisation.py index aea07e88b0..b1eaff90c6 100644 --- a/examples/python/projdata_visualisation/ProjDataVisualisation.py +++ b/examples/python/projdata_visualisation/ProjDataVisualisation.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# Copyright 2022 University College London +# Copyright 2022, 2024 University College London # Author Robert Twyman @@ -26,6 +26,7 @@ from BackendTools.STIRInterface import ProjDataVisualisationBackend, ProjDataDims from BackendTools.UIGroupboxProjdataDimensions import UIGroupboxProjDataDimensions +import stir class ProjDataVisualisationWidgetGallery(QDialog): def __init__(self, parent=None): @@ -181,16 +182,20 @@ def update_display_image(self): image = self.get_sinogram_numpy_array() ax.title.set_text( f"Sinogram - Segment: {self.UI_groupbox_projdata_dimensions.value(ProjDataDims.SEGMENT_NUM)}, " - f"Axial Position: {self.UI_groupbox_projdata_dimensions.value(ProjDataDims.AXIAL_POS)}") + f"Axial Position: {self.UI_groupbox_projdata_dimensions.value(ProjDataDims.AXIAL_POS)}, " + f"TOF bin: {self.UI_groupbox_projdata_dimensions.value(ProjDataDims.TIMING_POS)}") ax.yaxis.set_label_text("Views/projection angle") ax.xaxis.set_label_text("Tangential positions") + ax.xaxis.set_label_text("TOF bins") elif self.viewgram_radio_button.isChecked(): image = self.get_viewgram_numpy_array() ax.title.set_text( f"Sinogram - Segment: {self.UI_groupbox_projdata_dimensions.value(ProjDataDims.SEGMENT_NUM)}," - f"View Number: {self.UI_groupbox_projdata_dimensions.value(ProjDataDims.VIEW_NUMBER)}") + f"View Number: {self.UI_groupbox_projdata_dimensions.value(ProjDataDims.VIEW_NUMBER)}, " + f"TOF bin: {self.UI_groupbox_projdata_dimensions.value(ProjDataDims.TIMING_POS)}") ax.yaxis.set_label_text("Axial positions") ax.xaxis.set_label_text("Tangential positions") + ax.xaxis.set_label_text("TOF bins") else: msg = f"Error: No radio button is checked... How did you get here?\n" raise Exception(msg) @@ -201,6 +206,14 @@ def update_display_image(self): ) self.display_image_matplotlib_canvas.draw() + def get_bin(self) -> stir.Bin: + view_num = self.UI_groupbox_projdata_dimensions.value(ProjDataDims.VIEW_NUMBER) + axial_pos = self.UI_groupbox_projdata_dimensions.value(ProjDataDims.AXIAL_POS) + segment_num = self.UI_groupbox_projdata_dimensions.value(ProjDataDims.SEGMENT_NUM) + tangential_pos = self.UI_groupbox_projdata_dimensions.value(ProjDataDims.TANGENTIAL_POS) + timing_pos = self.UI_groupbox_projdata_dimensions.value(ProjDataDims.TIMING_POS) + return stir.Bin(segment_num, view_num, axial_pos, tangential_pos, timing_pos) + def get_sinogram_numpy_array(self): """ This function returns the sinogram numpy array based on the current UI configuration parameters for segment @@ -209,16 +222,14 @@ def get_sinogram_numpy_array(self): if self.stir_interface.projdata is None: return None - axial_pos = self.UI_groupbox_projdata_dimensions.value(ProjDataDims.AXIAL_POS) return self.stir_interface.as_numpy( - self.stir_interface.segment_data.get_sinogram(axial_pos)) + self.stir_interface.segment_data.get_sinogram(self.get_bin().axial_pos_num)) def get_viewgram_numpy_array(self): if self.stir_interface.projdata is None: return None - view_num = self.UI_groupbox_projdata_dimensions.value(ProjDataDims.VIEW_NUMBER) - return self.stir_interface.as_numpy(self.stir_interface.segment_data.get_viewgram(view_num)) + return self.stir_interface.as_numpy(self.stir_interface.segment_data.get_viewgram(self.get_bin().view_num)) def browse_file_system_for_projdata(self): initial = self.projdata_filename_box.text() @@ -256,7 +267,7 @@ def set_projdata(self, projdata): def OpenProjDataVisualisation(projdata=None): """ - Function to open the ProjDataVisualisation GUI window. Will not exit pyton on window close. + Function to open the ProjDataVisualisation GUI window. Will not exit python on window close. projdata: Proj data to be visualised. Can be either a stir.ProjData object, a file path (str) or None. If None, an empty GUI will be opened. """ app = QApplication([]) From c0c4c5b7aec9069f683feba22e5ce1cda43e7ca4 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Thu, 11 Jan 2024 17:20:48 +0000 Subject: [PATCH 454/509] fix bug in get_limits --- .../projdata_visualisation/BackendTools/STIRInterface.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/python/projdata_visualisation/BackendTools/STIRInterface.py b/examples/python/projdata_visualisation/BackendTools/STIRInterface.py index 46b98ef3bd..811cbe278f 100644 --- a/examples/python/projdata_visualisation/BackendTools/STIRInterface.py +++ b/examples/python/projdata_visualisation/BackendTools/STIRInterface.py @@ -127,10 +127,10 @@ def get_limits(self, dimension: ProjDataDims, segment_number: int) -> tuple: else: raise ValueError("Unknown sinogram dimension: " + str(dimension)) - def get_num_indices(self, dimension: ProjDataDims): + def get_num_indices(self, dimension: ProjDataDims) -> int: """Returns the number of indices in the given dimension.""" - l = self.get_limits(dimension) - return l[1] - l[0] + 1 + limits = self.get_limits(dimension, self.get_current_segment_num()) + return limits[1] - limits[0] + 1 def get_current_segment_num(self) -> int: """Returns the segment number of the current segment data.""" From d796b405265bed999880d6b1e264cf1b6298bba7 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Fri, 12 Jan 2024 09:53:52 +0000 Subject: [PATCH 455/509] GEHDF5 listmode: guarantee record-size in internal processing Previous code was leading to a stack corruption in debug mode, although I don't understand why. In any case, this is more correct. --- src/include/stir/listmode/CListRecordGEHDF5.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/include/stir/listmode/CListRecordGEHDF5.h b/src/include/stir/listmode/CListRecordGEHDF5.h index ed2583b0f7..e71d0ca209 100644 --- a/src/include/stir/listmode/CListRecordGEHDF5.h +++ b/src/include/stir/listmode/CListRecordGEHDF5.h @@ -266,7 +266,7 @@ dynamic_cast(&e2) != 0 && det_pos.pos1().axial_coord() = event_data.loXtalAxialID; det_pos.pos2().tangential_coord() = this->get_uncompressed_proj_data_info_sptr()->get_scanner_sptr()->get_num_detectors_per_ring() - 1 - event_data.hiXtalTransAxID; det_pos.pos2().axial_coord() = event_data.hiXtalAxialID; - det_pos.timing_pos() = event_data.deltaTime; + det_pos.timing_pos() = event_data.get_tof_bin(); } //! This routine sets in a coincidence event from detector "indices" @@ -285,9 +285,9 @@ dynamic_cast(&e2) != 0 && union { detail::CListAnyRecordDataGEHDF5 rec; - boost::uint16_t raw; + boost::uint16_t raw[4]; }; - std::copy(data_ptr, data_ptr+2, &raw); + std::copy(data_ptr, data_ptr+2, &raw[0]); switch(rec.eventLength) { case detail::LENGTH_6_EVT: return std::size_t(6); From 7d9a77453f1647aaef1bb9a58ec2e831b5eca9aa Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Fri, 12 Jan 2024 11:12:16 +0000 Subject: [PATCH 456/509] swap sign of TOF bin for GE HDF5 Images are now fine. --- src/include/stir/listmode/CListRecordGEHDF5.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/include/stir/listmode/CListRecordGEHDF5.h b/src/include/stir/listmode/CListRecordGEHDF5.h index e71d0ca209..49e2cc12e5 100644 --- a/src/include/stir/listmode/CListRecordGEHDF5.h +++ b/src/include/stir/listmode/CListRecordGEHDF5.h @@ -108,7 +108,7 @@ namespace RDF_HDF5 { inline int get_tof_bin() const { - return static_cast(deltaTime); + return static_cast(-deltaTime); } #if STIRIsNativeByteOrderBigEndian // Do byteswapping first before using this bit field. From 5d77bc401c539224c72ecc3fcc80ee8a353722ab Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Fri, 12 Jan 2024 10:42:31 +0000 Subject: [PATCH 457/509] loop over TOF bins fixes #1323 --- src/utilities/compare_projdata.cxx | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/utilities/compare_projdata.cxx b/src/utilities/compare_projdata.cxx index 0cf14024d0..35bd01afcb 100644 --- a/src/utilities/compare_projdata.cxx +++ b/src/utilities/compare_projdata.cxx @@ -1,7 +1,7 @@ /* Copyright (C) 2000 PARAPET partners Copyright (C) 2000-2006 Hammersmith Imanet Ltd - Copyright (C) 2013 University College London + Copyright (C) 2013, 2024 University College London This file is part of STIR. SPDX-License-Identifier: Apache-2.0 AND License-ref-PARAPET-license @@ -29,17 +29,16 @@ the data. #include "stir/ProjData.h" #include "stir/SegmentByView.h" +#include "stir/SegmentIndices.h" #include "stir/shared_ptr.h" #include #include -#ifndef STIR_NO_NAMESPACES using std::cerr; using std::cout; using std::endl; using std::max; -#endif @@ -145,13 +144,17 @@ int main(int argc, char *argv[]) } float max_pos_error=0.F, max_neg_error=0.F, amplitude=0.F; - for (int segment_num = -max_segment; segment_num <= max_segment ; segment_num++) - { - SegmentByView input1=first_operand->get_segment_by_view(segment_num); - const SegmentByView input2=second_operand->get_segment_by_view(segment_num); + for (int timing_pos_num=first_operand->get_min_tof_pos_num(); + timing_pos_num<=first_operand->get_max_tof_pos_num(); + ++timing_pos_num) + for (int segment_num = -max_segment; segment_num <= max_segment ; segment_num++) + { + SegmentIndices s_idx(segment_num, timing_pos_num); + auto input1=first_operand->get_segment_by_view(s_idx); + const auto input2=second_operand->get_segment_by_view(s_idx); - update_comparison(input1,input2,max_pos_error,max_neg_error, amplitude); - } + update_comparison(input1,input2,max_pos_error,max_neg_error, amplitude); + } const float max_abs_error=max(max_pos_error, -max_neg_error); bool same=(max_abs_error/amplitude<=tolerance)?true:false; From 42a594b643ca3fc473f562ec284eb18aa9dcdde2 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Fri, 12 Jan 2024 11:56:09 +0000 Subject: [PATCH 458/509] correct hard-wired numbers for Signa in test --- src/test/test_time_of_flight.cxx | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/test/test_time_of_flight.cxx b/src/test/test_time_of_flight.cxx index 26f48b9c95..1a0e04e97a 100644 --- a/src/test/test_time_of_flight.cxx +++ b/src/test/test_time_of_flight.cxx @@ -164,14 +164,15 @@ void TOF_Tests::test_tof_proj_data_info_kernel() { const int correct_tof_mashing_factor = 39; - const int num_timing_positions = 9; - float correct_width_of_tof_bin = test_scanner_sptr->get_size_of_timing_pos() * + const int num_timing_positions = test_scanner_sptr->get_max_num_timing_poss() / correct_tof_mashing_factor; + const float correct_width_of_tof_bin = test_scanner_sptr->get_size_of_timing_pos() * test_proj_data_info_sptr->get_tof_mash_factor() * 0.299792458f/2; - float correct_timing_locations[num_timing_positions] = {-360.201f/2 + correct_width_of_tof_bin/2, -280.156f/2 + correct_width_of_tof_bin/2, - -200.111f/2 + correct_width_of_tof_bin/2, -120.067f/2 + correct_width_of_tof_bin/2, - 0.0f, 40.022f/2 + correct_width_of_tof_bin/2, - 120.067f/2 + correct_width_of_tof_bin/2, 200.111f/2 + correct_width_of_tof_bin/2, - 280.156f/2+ correct_width_of_tof_bin/2}; + const float correct_timing_locations[num_timing_positions] = + {-4*correct_width_of_tof_bin, -3*correct_width_of_tof_bin, + -2*correct_width_of_tof_bin, -1*correct_width_of_tof_bin, + 0*correct_width_of_tof_bin, 1*correct_width_of_tof_bin, + 2*correct_width_of_tof_bin, 3*correct_width_of_tof_bin, + 4*correct_width_of_tof_bin}; check_if_equal(correct_tof_mashing_factor, test_proj_data_info_sptr->get_tof_mash_factor(), "Different TOF mashing factor."); @@ -179,10 +180,10 @@ TOF_Tests::test_tof_proj_data_info_kernel() check_if_equal(num_timing_positions, test_proj_data_info_sptr->get_num_tof_poss(), "Different number of timing positions."); - for (int timing_num = test_proj_data_info_sptr->get_min_tof_pos_num(), counter = 0; - timing_num <= test_proj_data_info_sptr->get_max_tof_pos_num(); ++ timing_num, counter++) + for (int timing_pos_num = test_proj_data_info_sptr->get_min_tof_pos_num(), counter = 0; + timing_pos_num <= test_proj_data_info_sptr->get_max_tof_pos_num(); ++ timing_pos_num, counter++) { - Bin bin(0, 0, 0, 0, timing_num, 1.f); + Bin bin(0, 0, 0, 0, timing_pos_num, 1.f); check_if_equal(static_cast(correct_width_of_tof_bin), static_cast(test_proj_data_info_sptr->get_sampling_in_k(bin)), "Error in get_sampling_in_k()"); @@ -343,11 +344,11 @@ TOF_Tests::test_tof_kernel_application(bool print_to_file) export_lor(proj_matrix_row, lor2.p1(), lor2.p2(), 500000000); - for (int timing_num = test_proj_data_info_sptr->get_min_tof_pos_num(); - timing_num <= test_proj_data_info_sptr->get_max_tof_pos_num(); ++ timing_num) + for (int timing_pos_num = test_proj_data_info_sptr->get_min_tof_pos_num(); + timing_pos_num <= test_proj_data_info_sptr->get_max_tof_pos_num(); ++ timing_pos_num) { ProjMatrixElemsForOneBin new_proj_matrix_row; - Bin bin(seg_num, view_num, axial_num, tang_num, timing_num, 1.f); + Bin bin(seg_num, view_num, axial_num, tang_num, timing_pos_num, 1.f); t.reset(); t.start(); test_proj_matrix_sptr->get_proj_matrix_elems_for_one_bin(new_proj_matrix_row, @@ -357,7 +358,7 @@ TOF_Tests::test_tof_kernel_application(bool print_to_file) if (print_to_file) export_lor(new_proj_matrix_row, - lor2.p1(), lor2.p2(), timing_num, + lor2.p1(), lor2.p2(), timing_pos_num, proj_matrix_row); From e30371034c6ed2cb269f2a1a8ab0db14542e0238 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Fri, 12 Jan 2024 11:10:24 +0000 Subject: [PATCH 459/509] add --list-bin option to list_lm_events --- documentation/release_6.0.htm | 5 +++ src/listmode_utilities/list_lm_events.cxx | 53 ++++++++++++++--------- 2 files changed, 37 insertions(+), 21 deletions(-) diff --git a/documentation/release_6.0.htm b/documentation/release_6.0.htm index 5221c56458..c44345a0bb 100644 --- a/documentation/release_6.0.htm +++ b/documentation/release_6.0.htm @@ -121,6 +121,11 @@

      General

      If the radionuclide name is recognised to the STIR database, its values for half-life etc are used, as opposed to what was recorded in the file (if anything).
    • +
    • + list_lm_events now has an additional option --event-bin which lists the bin + assigned for the event (according to the "native" projection data, i.e. without any mashing).
      + In addition, the --event-LOR option now also works for SPECT (it was disabled by accident). +

    Python (and MATLAB)

    diff --git a/src/listmode_utilities/list_lm_events.cxx b/src/listmode_utilities/list_lm_events.cxx index 65802b44ac..b1ad3a7562 100644 --- a/src/listmode_utilities/list_lm_events.cxx +++ b/src/listmode_utilities/list_lm_events.cxx @@ -29,7 +29,7 @@ #include "stir/listmode/CListEventCylindricalScannerWithDiscreteDetectors.h" #include "stir/Succeeded.h" #include "stir/IO/read_from_file.h" - +#include "stir/stream.h" #include "stir/Scanner.h" #include #include @@ -55,6 +55,7 @@ int main(int argc, char *argv[]) bool list_time=true; bool list_coincidence=false; bool list_event_LOR=false; + bool list_event_bin=false; bool list_gating=true; bool list_unknown=false; unsigned long num_events_to_list = 0; @@ -80,6 +81,10 @@ int main(int argc, char *argv[]) { list_event_LOR = atoi(argv[1])!=0; } + else if (strcmp(argv[0], "--event-bin")==0) + { + list_event_bin = atoi(argv[1])!=0; + } else if (strcmp(argv[0], "--unknown")==0) { list_unknown = atoi(argv[1])!=0; @@ -99,11 +104,15 @@ int main(int argc, char *argv[]) << "--time 0|1 : list time events or not (default: 1)\n" << "--gating 0|1 : list gating events or not (default: 1)\n" << "--coincidence 0|1 0|1): list coincidence event info or not (default: 0)\n" - << "--event-LOR 0|1 : ((identical to --SPECT-event) list LOR end-points if coincidence/gamma event or not (default: 0)\n" + << "--event-LOR 0|1 : (identical to --SPECT-event) list LOR end-points if coincidence/gamma event, or not (default: 0)\n" + << "--event-bin 0|1 : bin coordinates if coincidence/gamma event, or not (default: 0)\n" << "--unknown 0|1 : list if event of unknown type encountered or not (default: 0)\n" << "--num-events-to-list : limit number of events written to stdout\n" + << "\n--event-bin uses the \"native\" projection data info associated to the\n" + << " list-mode file, i.e. without any mashing in views/TOF etc).\n" << "\nNote that for some PET scanners, coincidences are listed with crystal info.\n" - << "For others, you should list LOR coordinates (as well) as the 'coincidence' option will only list prompt/delayed info.\n"; + << " For others, you should list LOR coordinates or bins (as well) as the 'coincidence' option\n" + << " will only list prompt/delayed info.\n"; return EXIT_FAILURE; } @@ -111,6 +120,7 @@ int main(int argc, char *argv[]) cout << "LORs will be listed as 2 points (z1,y1,x1)-(z2,y2,x2).\n"; shared_ptr lm_data_ptr(read_from_file(argv[0])); + auto proj_data_info_sptr = lm_data_ptr->get_proj_data_info_sptr(); cout << "Scanner: " << lm_data_ptr->get_scanner().get_name() << endl; @@ -180,26 +190,27 @@ int main(int argc, char *argv[]) } } if (list_event_LOR) - { - if (auto event_ptr = - dynamic_cast(&record.event())) // cast not necessary, but looks same as above - if (event_ptr!=0) - { - LORAs2Points lor; - lor=event_ptr->get_LOR(); - cout << " LOR " - << "(" << lor.p1().z() - << "," << lor.p1().y() - << "," << lor.p1().x() - << ")-" - << "(" << lor.p2().z() - << "," << lor.p2().y() - << "," << lor.p2().x() - << ")"; - listed = true; - } + { + const auto lor = record.event().get_LOR(); + cout << " LOR " + << "(" << lor.p1().z() + << "," << lor.p1().y() + << "," << lor.p1().x() + << ")-" + << "(" << lor.p2().z() + << "," << lor.p2().y() + << "," << lor.p2().x() + << ")"; + listed = true; } } + if (list_event_bin) + { + Bin bin; + record.event().get_bin(bin, *proj_data_info_sptr); + cout << " bin " << bin; + listed = true; + } if (!recognised && list_unknown) { cout << "Unknown type"; From 54bd997ca8cab37fcec11bf4a4d61aba0793f95f Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Fri, 12 Jan 2024 12:13:01 +0000 Subject: [PATCH 460/509] fix test on half_life causing errors writing data Default (i.e. unset) half-life is 0, so the test thought the half-life was set, then causing an error thrown. --- src/IO/interfile.cxx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/IO/interfile.cxx b/src/IO/interfile.cxx index 42a79c6dd9..4233af724a 100644 --- a/src/IO/interfile.cxx +++ b/src/IO/interfile.cxx @@ -512,11 +512,11 @@ static void write_interfile_radionuclide_info(std::ostream& output_header, const output_header << "number of radionuclides := 1\n"; if (!radionuclide.get_name().empty() && radionuclide.get_name()!="Unknown") output_header << "radionuclide name[1] := " << radionuclide.get_name() << '\n'; - if (radionuclide.get_half_life(false) >= 0) + if (radionuclide.get_half_life(false) > 0) { output_header << "radionuclide halflife (sec)[1] := " << radionuclide.get_half_life() << '\n'; } - if (radionuclide.get_branching_ratio(false) >= 0) + if (radionuclide.get_branching_ratio(false) > 0) { output_header << "radionuclide branching factor[1] := " << radionuclide.get_branching_ratio() << '\n'; From 4653741fe3fe2693d35d999e50ab84c3fdfdccaa Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Fri, 12 Jan 2024 12:14:12 +0000 Subject: [PATCH 461/509] don't delete test output files if the test fails. --- src/test/test_stir_math.cxx | 46 +++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/src/test/test_stir_math.cxx b/src/test/test_stir_math.cxx index b3cf7f8d2f..f45d3b26d1 100644 --- a/src/test/test_stir_math.cxx +++ b/src/test/test_stir_math.cxx @@ -194,18 +194,21 @@ stir_mathTests::run_tests() check_if_equal( calc_data, *out_data_ptr,"test with power and scalar multiplication with --including-first and --accumulate"); } - remove("STIRtmp1.ahv"); - remove("STIRtmp1.hv"); - remove("STIRtmp1.v"); - remove("STIRtmp2.ahv"); - remove("STIRtmp2.hv"); - remove("STIRtmp2.v"); - remove("STIRtmp3.ahv"); - remove("STIRtmp3.hv"); - remove("STIRtmp3.v"); - remove("STIRtmpout.ahv"); - remove("STIRtmpout.hv"); - remove("STIRtmpout.v"); + if (this->is_everything_ok()) + { + remove("STIRtmp1.ahv"); + remove("STIRtmp1.hv"); + remove("STIRtmp1.v"); + remove("STIRtmp2.ahv"); + remove("STIRtmp2.hv"); + remove("STIRtmp2.v"); + remove("STIRtmp3.ahv"); + remove("STIRtmp3.hv"); + remove("STIRtmp3.v"); + remove("STIRtmpout.ahv"); + remove("STIRtmpout.hv"); + remove("STIRtmpout.v"); + } } // projdata @@ -325,14 +328,17 @@ stir_mathTests::run_tests() "test with power and scalar multiplication with --including-first and --accumulate"); } - remove("STIRtmp1.hs"); - remove("STIRtmp1.s"); - remove("STIRtmp2.hs"); - remove("STIRtmp2.s"); - remove("STIRtmp3.hs"); - remove("STIRtmp3.s"); - remove("STIRtmpout.hs"); - remove("STIRtmpout.s"); + if (this->is_everything_ok()) + { + remove("STIRtmp1.hs"); + remove("STIRtmp1.s"); + remove("STIRtmp2.hs"); + remove("STIRtmp2.s"); + remove("STIRtmp3.hs"); + remove("STIRtmp3.s"); + remove("STIRtmpout.hs"); + remove("STIRtmpout.s"); + } } } From ee2bc9a72902b9f2074abe4b6e0b1567f3aa455a Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Fri, 12 Jan 2024 12:33:03 +0000 Subject: [PATCH 462/509] make clang happy (and generalise) --- src/test/test_time_of_flight.cxx | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/test/test_time_of_flight.cxx b/src/test/test_time_of_flight.cxx index 1a0e04e97a..2ac19bde93 100644 --- a/src/test/test_time_of_flight.cxx +++ b/src/test/test_time_of_flight.cxx @@ -167,12 +167,11 @@ TOF_Tests::test_tof_proj_data_info_kernel() const int num_timing_positions = test_scanner_sptr->get_max_num_timing_poss() / correct_tof_mashing_factor; const float correct_width_of_tof_bin = test_scanner_sptr->get_size_of_timing_pos() * test_proj_data_info_sptr->get_tof_mash_factor() * 0.299792458f/2; - const float correct_timing_locations[num_timing_positions] = - {-4*correct_width_of_tof_bin, -3*correct_width_of_tof_bin, - -2*correct_width_of_tof_bin, -1*correct_width_of_tof_bin, - 0*correct_width_of_tof_bin, 1*correct_width_of_tof_bin, - 2*correct_width_of_tof_bin, 3*correct_width_of_tof_bin, - 4*correct_width_of_tof_bin}; + std::vector correct_timing_locations(num_timing_positions); + for (int i=0; iget_tof_mash_factor(), "Different TOF mashing factor."); From faa781cdd84b9d10eab68977c510e9f41e06e01f Mon Sep 17 00:00:00 2001 From: robbietuk Date: Fri, 12 Jan 2024 09:26:39 -0800 Subject: [PATCH 463/509] ProjData Visualisation window improvement and segment num bug fix (#1321) - Fixes a bug with the segment_num parameter not being used in the method, instead it was using an internal call to self.get_current_segment_num(). This limited the method to the current state of the data, rather than the abstract. - Expand MLP canvas to full width - Fix bug when a projdata file cannot be loaded (e.g. missing binary). Previously, the projection data would be updated and fail when loading the segment_data into memory. However, the stir_interface.projdata was updated. This lead to errors with slider and reading new data. The PR uses temporary variables when projdata and segment are loaded. - Remove refresh_segment_data as this was leading to issues with the above and recursive calls to load_projdata - Add formatting to print_projdata_configuration and print_segment_data_configuration --- .../BackendTools/STIRInterface.py | 85 +++++++++---------- .../UIGroupboxProjdataDimensions.py | 68 ++++++++------- .../ProjDataVisualisation.py | 55 ++++++------ 3 files changed, 105 insertions(+), 103 deletions(-) diff --git a/examples/python/projdata_visualisation/BackendTools/STIRInterface.py b/examples/python/projdata_visualisation/BackendTools/STIRInterface.py index 811cbe278f..ebb14c5f4c 100644 --- a/examples/python/projdata_visualisation/BackendTools/STIRInterface.py +++ b/examples/python/projdata_visualisation/BackendTools/STIRInterface.py @@ -8,14 +8,13 @@ # # See STIR/LICENSE.txt for details -import time +from enum import Enum, auto import numpy + import stir import stirextra -from enum import Enum, auto - class ProjDataDims(Enum): """Enum for the dimensions of a sinogram.""" @@ -35,64 +34,62 @@ def __init__(self, *args, **kwargs) -> None: self.segment_data = None - def load_projdata(self, filename=None) -> bool: - """Loads STIR projection data from a file.""" - if filename is not None and filename != "": - self.projdata_filename = filename - - if self.projdata_filename != "": - print("ProjDataVisualisationBackend.load_data: Loading data from file: " + self.projdata_filename) - try: - self.projdata = stir.ProjData.read_from_file(self.projdata_filename) - self.segment_data = self.refresh_segment_data() - print("ProjDataVisualisationBackend.load_data: Data loaded.") - self.print_projdata_configuration() - except RuntimeError: - print("ProjDataVisualisationBackend.load_data: Error loading data from file: " + self.projdata_filename) - return False - return True - return False + def load_projdata_from_file(self, filename: str | None = None) -> bool: + """ + Loads STIR projection data from a file and updates the segment data in memory. + :param filename: The filename to load the projection data from. + :return: True if the data was loaded successfully, False otherwise. + """ + if filename is None or filename == "": + return False + + self.projdata_filename = filename + + print("ProjDataVisualisationBackend.load_data: Loading data from file: " + self.projdata_filename) + try: + new_projdata = stir.ProjData.read_from_file(self.projdata_filename) + new_segment_data = new_projdata.get_segment_by_view(stir.SegmentIndices(0, 0)) + except RuntimeError: + print("ProjDataVisualisationBackend.load_data: Error loading data from file: " + self.projdata_filename) + return False + + self.projdata = new_projdata + self.segment_data = new_segment_data + print("ProjDataVisualisationBackend.load_data: Data loaded.") + self.print_projdata_configuration() + return True def set_projdata(self, projdata: stir.ProjData) -> None: """Sets the projection data stream.""" self.projdata = projdata self.projdata_filename = "ProjData filename set externally, no filename" - self.segment_data = self.refresh_segment_data() + self.segment_data = self.projdata.get_segment_by_view(stir.SegmentIndices(0, 0)) self.print_projdata_configuration() def print_projdata_configuration(self) -> None: """Prints the configuration of the projection data.""" print( f"\nProjection data configuration for:\n" - f"\t'{self.projdata_filename}'\n" - f"\tNumber of views:\t\t\t\t\t{self.projdata.get_num_views()}\n" - f"\tNumber of tangential positions:\t\t{self.projdata.get_num_tangential_poss()}\n" - f"\tNumber of segments:\t\t\t\t\t{self.projdata.get_num_segments()}\n" - f"\tNumber of axial positions:\t\t\t{self.projdata.get_num_axial_poss(0)}\n" - f"\tNumber of tof positions:\t\t\t{self.projdata.get_num_tof_poss()}\n" - f"\tNumber of non-tof sinograms:\t\t{self.projdata.get_num_non_tof_sinograms()}\n\n" + f"'{self.projdata_filename}'\n" + f"Number of views: {self.projdata.get_num_views():>10}\n" + f"Number of tangential positions: {self.projdata.get_num_tangential_poss():>10}\n" + f"Number of segments: {self.projdata.get_num_segments():>10}\n" + f"Number of axial positions: {self.projdata.get_num_axial_poss(0):>10}\n" + f"Number of tof positions: {self.projdata.get_num_tof_poss():>10}\n" + f"Number of non-tof sinograms: {self.projdata.get_num_non_tof_sinograms():>10}\n\n" ) def print_segment_data_configuration(self) -> None: """Prints the configuration of the segment data.""" print( f"\nSegment data configuration for:\n" - f"\t'{self.projdata_filename}':\n" - f"\tSegment Number: {self.get_current_segment_num()}\n" - f"\tNumber of views:\t\t\t\t\t{self.segment_data.get_num_views()}\n" - f"\tNumber of tangential positions:\t\t{self.segment_data.get_num_tangential_poss()}\n" - f"\tNumber of axial positions:\t\t\t{self.segment_data.get_num_axial_poss()}\n" + f"'{self.projdata_filename}':\n" + f"Segment Number: {self.get_current_segment_num()}\n" + f"Number of views: {self.segment_data.get_num_views():>10}\n" + f"Number of tangential positions: {self.segment_data.get_num_tangential_poss():>10}\n" + f"Number of axial positions: {self.segment_data.get_num_axial_poss():>10}\n" ) - def refresh_segment_data(self, segment_number=0, timing_pos=0) -> stir.FloatSegmentByView: - """Loads a segment data, from the projection data, into memory allowing for faster access.""" - if self.projdata is None: - self.load_projdata() - - if self.projdata is not None: - seg_idx = stir.SegmentIndices(segment_number, timing_pos) - self.segment_data = self.projdata.get_segment_by_view(seg_idx) - return self.segment_data @staticmethod def as_numpy(data: stir.ProjData) -> numpy.array: @@ -113,8 +110,8 @@ def get_limits(self, dimension: ProjDataDims, segment_number: int) -> tuple: return self.projdata.get_min_segment_num(), \ self.projdata.get_max_segment_num() elif dimension == ProjDataDims.AXIAL_POS: - return self.projdata.get_min_axial_pos_num(self.get_current_segment_num()), \ - self.projdata.get_max_axial_pos_num(self.get_current_segment_num()) + return self.projdata.get_min_axial_pos_num(segment_number), \ + self.projdata.get_max_axial_pos_num(segment_number) elif dimension == ProjDataDims.VIEW_NUMBER: return self.projdata.get_min_view_num(), \ self.projdata.get_max_view_num() diff --git a/examples/python/projdata_visualisation/BackendTools/UIGroupboxProjdataDimensions.py b/examples/python/projdata_visualisation/BackendTools/UIGroupboxProjdataDimensions.py index c0ecefd9a0..bc22ba512e 100644 --- a/examples/python/projdata_visualisation/BackendTools/UIGroupboxProjdataDimensions.py +++ b/examples/python/projdata_visualisation/BackendTools/UIGroupboxProjdataDimensions.py @@ -8,10 +8,11 @@ # # See STIR/LICENSE.txt for details +from BackendTools.STIRInterface import ProjDataDims, ProjDataVisualisationBackend from PyQt5.QtCore import Qt from PyQt5.QtWidgets import QGroupBox, QGridLayout, QLabel, QSpinBox, QSlider -from BackendTools.STIRInterface import ProjDataDims, ProjDataVisualisationBackend +import stir class UIGroupboxProjDataDimensions: @@ -87,9 +88,8 @@ def UI_controller_UI_change_trigger(self): def segment_number_refresh(self): """ This function is called when the user changes the segment number value. Because of the way the STIR segment data is handled, the segment_data needs to change first.""" - new_segment_num = self.UI_slider_spinboxes[ProjDataDims.SEGMENT_NUM].value() - new_timing_pos = self.UI_slider_spinboxes[ProjDataDims.TIMING_POS].value() - self.stir_interface.refresh_segment_data(new_segment_num, new_timing_pos) + self.stir_interface.segment_data = self.stir_interface.projdata.get_segment_by_view( + self.get_segment_indices_from_UI()) self.UI_controller_UI_change_trigger() def axial_pos_refresh(self): @@ -106,48 +106,49 @@ def tangential_pos_refresh(self): def timing_pos_refresh(self): """This function is called when the user changes the TOF bin value.""" - new_segment_num = self.UI_slider_spinboxes[ProjDataDims.SEGMENT_NUM].value() - new_timing_pos = self.UI_slider_spinboxes[ProjDataDims.TIMING_POS].value() - self.stir_interface.refresh_segment_data(new_segment_num, new_timing_pos) + self.stir_interface.segment_data = self.stir_interface.projdata.get_segment_by_view( + self.get_segment_indices_from_UI()) self.UI_controller_UI_change_trigger() + def get_segment_indices_from_UI(self) -> stir.SegmentIndices: + """Returns the segment indices from the UI slider and spinboxes.""" + return stir.SegmentIndices(self.UI_slider_spinboxes[ProjDataDims.SEGMENT_NUM].value(), + self.UI_slider_spinboxes[ProjDataDims.TIMING_POS].value()) + def refresh_sliders_and_spinboxes_ranges(self) -> None: """Update the sliders and spinboxes ranges based upon the stir_interface projdata.""" if self.stir_interface.projdata is None: return # Update all slider and spinbox ranges, should start with segment number for dimension in ProjDataDims: - current_segment_num = self.stir_interface.get_current_segment_num() - limits = self.stir_interface.get_limits(dimension, current_segment_num) + limits = self.stir_interface.get_limits(dimension, self.stir_interface.get_current_segment_num()) self.UI_slider_spinboxes[dimension].update_limits(limits=limits) - def update_enable_disable(self, sinogram_radio_button_state: bool): - # Segment slider and scroll box handling + def configure_enable_disable_sliders(self, is_sinogram_mode: bool): + """Configure the sliders and spinboxes based upon the current mode and the projdata limits in each dimension.""" + + self.disable(ProjDataDims.TANGENTIAL_POS) + segment_limits = self.stir_interface.get_limits(ProjDataDims.SEGMENT_NUM, self.stir_interface.get_current_segment_num()) - if self.stir_interface.projdata is not None: - if segment_limits[0] == 0 and segment_limits[1] == 0: - self.disable(ProjDataDims.SEGMENT_NUM) - else: - self.enable(ProjDataDims.SEGMENT_NUM) + self.update_dimension_state(ProjDataDims.SEGMENT_NUM, segment_limits) - # Check if sinogram or viewgram is selected and disable the appropriate sliders and spinboxes - if sinogram_radio_button_state: - # Disable the tangential position slider and spinbox + if is_sinogram_mode: + # No view number in sinogram mode self.disable(ProjDataDims.VIEW_NUMBER) - axial_pos_limits = self.stir_interface.get_limits(ProjDataDims.AXIAL_POS, - self.stir_interface.get_current_segment_num()) - if (axial_pos_limits[0] - axial_pos_limits[1]) != 0: - self.enable(ProjDataDims.AXIAL_POS) - else: - self.disable(ProjDataDims.AXIAL_POS) - - elif not sinogram_radio_button_state: + axial_limits = self.stir_interface.get_limits(ProjDataDims.AXIAL_POS, + self.stir_interface.get_current_segment_num()) + self.update_dimension_state(ProjDataDims.AXIAL_POS, axial_limits) + else: + # No axial position in viewgram (not sinogram) mode self.disable(ProjDataDims.AXIAL_POS) - self.enable(ProjDataDims.VIEW_NUMBER) + view_limits = self.stir_interface.get_limits(ProjDataDims.VIEW_NUMBER, + self.stir_interface.get_current_segment_num()) + self.update_dimension_state(ProjDataDims.VIEW_NUMBER, view_limits) - if True: # Until I work out what to do with tangential position - self.disable(ProjDataDims.TANGENTIAL_POS) + tof_limits = self.stir_interface.get_limits(ProjDataDims.TIMING_POS, + self.stir_interface.get_current_segment_num()) + self.update_dimension_state(ProjDataDims.TIMING_POS, tof_limits) def __construct_slider_spinboxes(self, slider_spinbox_configurations: dict) -> dict: """ @@ -174,6 +175,13 @@ def __construct_slider_spinboxes(self, slider_spinbox_configurations: dict) -> d ) return UI_slider_spinboxes + def update_dimension_state(self, dimension, limits): + """Updates the state of the slider and spinbox for the given dimension. If limits are equal, disable.""" + if limits[1] == limits[0]: + self.disable(dimension) + else: + self.enable(dimension) + def enable(self, dimension: ProjDataDims) -> None: """Enables the slider and spinbox for the given dimension.""" self.UI_slider_spinboxes[dimension].enable() diff --git a/examples/python/projdata_visualisation/ProjDataVisualisation.py b/examples/python/projdata_visualisation/ProjDataVisualisation.py index b1eaff90c6..983d50e62e 100644 --- a/examples/python/projdata_visualisation/ProjDataVisualisation.py +++ b/examples/python/projdata_visualisation/ProjDataVisualisation.py @@ -64,25 +64,26 @@ def __init__(self, parent=None): push_button_load_projdata = QPushButton("Load") push_button_load_projdata.clicked.connect(self.load_projdata) self.load_projdata_status = QLabel(f"") - - # Sinogram and viewgram radio buttons - gramTypeLabel = QLabel(f"Type of 2D data:") + # Configure Layout + FilenameControlGroupBoxLayout = QGridLayout() + FilenameControlGroupBoxLayout.addWidget(self.projdata_filename_box, 0, 0, 1, 2) + FilenameControlGroupBoxLayout.addWidget(push_button_browse_projdata, 1, 0, 1, 1) + FilenameControlGroupBoxLayout.addWidget(push_button_load_projdata, 1, 1, 1, 1) + FilenameControlGroupBoxLayout.addWidget(self.load_projdata_status, 2, 0, 1 , 2) + self.FilenameControlGroupBox.setLayout(FilenameControlGroupBoxLayout) + + # Sinogram and viewgram radio buttons in a groupbox self.sinogram_radio_button = QRadioButton("Sinogram") self.viewgram_radio_button = QRadioButton("Viewgram") self.sinogram_radio_button.setChecked(True) # Default to sinogram self.sinogram_radio_button.toggled.connect(self.refresh_UI_configuration) self.viewgram_radio_button.toggled.connect(self.refresh_UI_configuration) - # Configure Layout - layout = QGridLayout() - layout.addWidget(self.projdata_filename_box, 0, 0, 1, 2) - layout.addWidget(push_button_browse_projdata, 1, 0, 1, 1) - layout.addWidget(push_button_load_projdata, 1, 1, 1, 1) - layout.addWidget(self.load_projdata_status, 2, 0, 1, 2) - layout.addWidget(gramTypeLabel, 3, 0, 1, 2) - layout.addWidget(self.sinogram_radio_button, 4, 0, 1, 1) - layout.addWidget(self.viewgram_radio_button, 4, 1, 1, 1) - self.FilenameControlGroupBox.setLayout(layout) + ModeSelectionGroupBox = QGroupBox("Data Visualization Mode") + ModeSelectionGroupBoxLayout = QVBoxLayout() + ModeSelectionGroupBoxLayout.addWidget(self.sinogram_radio_button) + ModeSelectionGroupBoxLayout.addWidget(self.viewgram_radio_button) + ModeSelectionGroupBox.setLayout(ModeSelectionGroupBoxLayout) # ############################################# @@ -120,18 +121,13 @@ def __init__(self, parent=None): # ### Configure Main Layout ### # ############################# topLayout = QHBoxLayout() - # topLayout.addStretch(1) mainLayout = QGridLayout() mainLayout.addLayout(topLayout, 0, 0, 1, 5) mainLayout.addWidget(self.FilenameControlGroupBox, 2, 0) - mainLayout.addWidget(self.ProjDataVisualisationGroupBox, 1, 1) - # mainLayout.addWidget(self.bottomLeftTabWidget, 2, 0) - mainLayout.addWidget(self.UI_groupbox_projdata_dimensions.groupbox, 2, 1) - # mainLayout.setRowStretch(1, 1) - # mainLayout.setRowStretch(2, 1) - # mainLayout.setColumnStretch(0, 1) - # mainLayout.setColumnStretch(1, 1) + mainLayout.addWidget(ModeSelectionGroupBox, 3, 0) + mainLayout.addWidget(self.ProjDataVisualisationGroupBox, 1, 0, 1, 2) + mainLayout.addWidget(self.UI_groupbox_projdata_dimensions.groupbox, 2, 1, 2, 1) self.setLayout(mainLayout) self.change_UI_style('Fusion') @@ -141,7 +137,7 @@ def __init__(self, parent=None): def configure_backend(self): ### Backend ### self.stir_interface = ProjDataVisualisationBackend(sys.argv) - self.stir_interface.refresh_segment_data(0) + self.stir_interface.load_projdata_from_file() def change_UI_style(self, styleName): QApplication.setStyle(QStyleFactory.create(styleName)) @@ -163,7 +159,7 @@ def refresh_UI_configuration(self): It calls the updateDisplayImage function to update the display image. """ self.UI_groupbox_projdata_dimensions.refresh_sliders_and_spinboxes_ranges() - self.UI_groupbox_projdata_dimensions.update_enable_disable(self.sinogram_radio_button.isChecked()) + self.UI_groupbox_projdata_dimensions.configure_enable_disable_sliders(self.sinogram_radio_button.isChecked()) self.update_display_image() def update_display_image(self): @@ -240,7 +236,7 @@ def browse_file_system_for_projdata(self): else: self.projdata_filename_box.setText(initial) - def load_projdata(self, filename=None): + def load_projdata(self, filename=None) -> None: """ This function loads the projdata file and updates the UI. """ @@ -250,14 +246,15 @@ def load_projdata(self, filename=None): if filename is not None and filename != "": self.projdata_filename_box.setText(filename) - data_load_successful = self.stir_interface.load_projdata(self.projdata_filename_box.text()) + data_load_successful = self.stir_interface.load_projdata_from_file(self.projdata_filename_box.text()) - if data_load_successful: - self.load_projdata_status.setText("STATUS: ProjData loaded successfully from file.") - else: + if not data_load_successful: self.load_projdata_status.setText("STATUS: Failed to load ProjData from file.") - + return + + self.load_projdata_status.setText("STATUS: ProjData loaded successfully from file.") self.refresh_UI_configuration() + def set_projdata(self, projdata): self.stir_interface.set_projdata(projdata) From e3fa72f09b909ee7501e18afc4dae049cf2a4b6a Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Fri, 12 Jan 2024 21:05:14 +0000 Subject: [PATCH 464/509] fix Interfile parsing of isotope_name --- src/IO/InterfileHeader.cxx | 4 ++-- src/IO/InterfileHeaderSiemens.cxx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/IO/InterfileHeader.cxx b/src/IO/InterfileHeader.cxx index 2e64f6aab8..c13375b389 100644 --- a/src/IO/InterfileHeader.cxx +++ b/src/IO/InterfileHeader.cxx @@ -179,7 +179,7 @@ InterfileHeader::InterfileHeader() ignore_key("GENERAL IMAGE DATA"); add_key("calibration factor", &calibration_factor); - // deprecated + // deprecated, but used by Siemens add_key("isotope name", &isotope_name); ignore_key("number of radionuclides"); // just always use 1. TODO should check really add_vectorised_key("radionuclide name", &radionuclide_name); @@ -283,7 +283,7 @@ bool InterfileHeader::post_processing() // radionuclide { RadionuclideDB radionuclide_db; - const std::string rn_name = !this->radionuclide_name.empty()? + const std::string rn_name = !this->radionuclide_name[0].empty()? this->radionuclide_name[0] : this->isotope_name; auto radionuclide = radionuclide_db.get_radionuclide(exam_info_sptr->imaging_modality, rn_name); if (radionuclide.get_half_life(false) < 0) diff --git a/src/IO/InterfileHeaderSiemens.cxx b/src/IO/InterfileHeaderSiemens.cxx index 696be0d08a..0cc02718ff 100644 --- a/src/IO/InterfileHeaderSiemens.cxx +++ b/src/IO/InterfileHeaderSiemens.cxx @@ -158,7 +158,7 @@ void InterfileHeaderSiemens::set_type_of_data() } else { - warning("Interfile parsing of Siemens listmode: unexpected 'type of data:=" + type_of_data + "' (expected PET). Continuing"); + warning("Interfile parsing of Siemens header: unexpected 'type of data:=" + type_of_data + "' (expected PET). Continuing"); } } From b33c838c02ff5a56762344107b88468983f1cb21 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Fri, 12 Jan 2024 22:43:46 +0000 Subject: [PATCH 465/509] fix Interfile parsing of radionuclide if unknown - default for half_life was set out of the allocated vector - make sure that returned radionuclide is "unknown" --- src/IO/InterfileHeader.cxx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/IO/InterfileHeader.cxx b/src/IO/InterfileHeader.cxx index c13375b389..0e7e9e1c8e 100644 --- a/src/IO/InterfileHeader.cxx +++ b/src/IO/InterfileHeader.cxx @@ -169,7 +169,7 @@ InterfileHeader::InterfileHeader() radionuclide_name.resize(1); radionuclide_half_life.resize(1); - radionuclide_half_life[1] = -1.F; + radionuclide_half_life[0] = -1.F; radionuclide_branching_ratio.resize(1); radionuclide_branching_ratio[0] = -1.F; @@ -287,7 +287,7 @@ bool InterfileHeader::post_processing() this->radionuclide_name[0] : this->isotope_name; auto radionuclide = radionuclide_db.get_radionuclide(exam_info_sptr->imaging_modality, rn_name); if (radionuclide.get_half_life(false) < 0) - radionuclide = Radionuclide(rn_name, + radionuclide = Radionuclide(rn_name.empty() ? "Unknown" : rn_name, is_spect ? -1.F : 511.F, // TODO handle energy for SPECT radionuclide_branching_ratio[0], radionuclide_half_life[0], this->exam_info_sptr->imaging_modality); this->exam_info_sptr->set_radionuclide(radionuclide); From 850bb18c72643e11123b2f8e36450a609ac3950e Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sat, 13 Jan 2024 08:11:49 +0000 Subject: [PATCH 466/509] read %TOF mashing factor in Siemens list files --- src/IO/InterfileHeaderSiemens.cxx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/IO/InterfileHeaderSiemens.cxx b/src/IO/InterfileHeaderSiemens.cxx index 0cc02718ff..673d22bdfc 100644 --- a/src/IO/InterfileHeaderSiemens.cxx +++ b/src/IO/InterfileHeaderSiemens.cxx @@ -544,7 +544,6 @@ InterfileListmodeHeaderSiemens::InterfileListmodeHeaderSiemens() ignore_key("gantry crystal radius (cm)"); ignore_key("bin size (cm)"); ignore_key("septa state"); - ignore_key("%tof mashing factor"); ignore_key("%preset type"); ignore_key("%preset value"); ignore_key("%preset unit"); From ff566a7159202a416c09fbea9b210cb6b82fe7b1 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sun, 14 Jan 2024 01:59:58 +0000 Subject: [PATCH 467/509] move/rename/test get_coincidence_window* from ProjDataInfo to Scanner The name and location is hopefully more logical. Throw error if it is unknown. To enable this, set max_num_timing_poss=1 for non-TOF scanners (it was 0 for most). Add coincidence-window tests to Scanner:check_consistency Moved the "TOF_time <-> mm" functions to TOF_conversions.h Fixes #1330 --- src/buildblock/ProjDataInfo.cxx | 1 + src/buildblock/Scanner.cxx | 118 ++++++++++++++----- src/data_buildblock/randoms_from_singles.cxx | 13 +- src/include/stir/ProjDataInfo.h | 14 +-- src/include/stir/ProjDataInfo.inl | 25 ---- src/include/stir/Scanner.h | 27 ++++- src/include/stir/TOF_conversions.h | 33 ++++++ src/include/stir/common.h | 4 +- src/recon_buildblock/ProjMatrixByBin.cxx | 3 +- src/test/test_time_of_flight.cxx | 2 +- 10 files changed, 158 insertions(+), 82 deletions(-) create mode 100644 src/include/stir/TOF_conversions.h diff --git a/src/buildblock/ProjDataInfo.cxx b/src/buildblock/ProjDataInfo.cxx index 1189d6ce25..78712ea2aa 100644 --- a/src/buildblock/ProjDataInfo.cxx +++ b/src/buildblock/ProjDataInfo.cxx @@ -41,6 +41,7 @@ #include "stir/IndexRange2D.h" #include "stir/IndexRange3D.h" #include "stir/Bin.h" +#include "stir/TOF_conversions.h" // include for ask and ask_num #include "stir/utilities.h" #include "stir/warning.h" diff --git a/src/buildblock/Scanner.cxx b/src/buildblock/Scanner.cxx index e4158bc6ca..2a4b02c9b8 100644 --- a/src/buildblock/Scanner.cxx +++ b/src/buildblock/Scanner.cxx @@ -5,7 +5,7 @@ Copyright (C) 2010-2013, King's College London Copyright (C) 2016, University of Hull Copyright 2017 ETH Zurich, Institute of Particle Physics and Astrophysics - Copyright (C) 2013-2016,2019-2021, 2923 University College London + Copyright (C) 2013-2016,2019-2021, 2023, 2024 University College London Copyright (C) 2017-2018, University of Leeds This file is part of STIR. @@ -32,6 +32,7 @@ */ #include "stir/Scanner.h" +#include "stir/TOF_conversions.h" #include "stir/utilities.h" #include "stir/Succeeded.h" #include "stir/interfile_keyword_functions.h" @@ -126,7 +127,7 @@ Scanner::Scanner(Type scanner_type) 510.0F, 7.0F, 13.5F, 3.129F, 0.0F, 2, 4, 4, 8, 4, 8 * 4, 1, 0.37F, 511.F, - 0, 0.F, 0.F); + 1, 0.F, 0.F); // 16 BUCKETS per ring in TWO rings - i.e. 32 buckets in total break; @@ -138,7 +139,7 @@ Scanner::Scanner(Type scanner_type) 510.0F, 7.0F, 6.75F, 3.12932F, 0.0F, 1, 4, 8, 8, 8, 8 * 4, 1, 0.0F, 511.F, - 0, 0.F, 0.F); + 1, 0.F, 0.F); break; case E953: @@ -148,7 +149,7 @@ Scanner::Scanner(Type scanner_type) 382.5F, 7.0F, 6.75F, 3.12932F, static_cast(15.*_PI/180), 1, 4, 8, 8, 8, 8 * 4, 1, 0.0F, 511.F, - 0, 0.F, 0.F); + 1, 0.F, 0.F); break; case E921: @@ -158,7 +159,7 @@ Scanner::Scanner(Type scanner_type) 412.5F, 7.0F, 6.75F, 3.375F, static_cast(15.*_PI/180), 1, 4, 8, 8, 8, 8 * 4, 1, 0.0F, 511.F, - 0, 0.F, 0.F); + 1, 0.F, 0.F); break; case E925: @@ -168,7 +169,7 @@ Scanner::Scanner(Type scanner_type) 412.5F, 7.0F, 6.75F, 3.375F, static_cast(15.*_PI/180), 3, 4, 8, 8, 8, 8 * 4, 1, 0.0F, 511.F, - 0, 0.F, 0.F); + 1, 0.F, 0.F); break; @@ -179,7 +180,7 @@ Scanner::Scanner(Type scanner_type) 412.0F, 7.0F, 6.25F, 1.650F, static_cast(13.*_PI/180), 1, 8, 8, 7, 8, 7 * 8, 1, 0.0F, 511.F, - 0, 0.F, 0.F); + 1, 0.F, 0.F); break; case E962: @@ -189,7 +190,7 @@ Scanner::Scanner(Type scanner_type) 412.0F, 7.0F, 4.85F, 2.25F, 0.0F, 4, 3, 8, 8, 8, 8 * 3, 1, 0.0F, 511.F, - 0, 0.F, 0.F); + 1, 0.F, 0.F); break; case E966: @@ -199,7 +200,7 @@ Scanner::Scanner(Type scanner_type) 412.0F, 7.0F, 4.850F, 2.250F, 0.0, 6, 2, 8, 8, 2 * 8, 8 * 2, 1, 0.0F, 511.F, - 0, 0.F, 0.F); + 1, 0.F, 0.F); break; case E1080: @@ -209,7 +210,7 @@ Scanner::Scanner(Type scanner_type) 412.0F, 7.0F, 4.0F, 2.000F, 0.0F, 1, 2, 13+1, 13+1, 0,0, 1, 0.0F, 511.F, - 0, 0.F, 0.F); + 1, 0.F, 0.F); // Transaxial blocks have 13 physical crystals and a gap at the // 14th crystal where the counts are zero. // There are 39 rings with 13 axial crystals per block. @@ -224,7 +225,7 @@ Scanner::Scanner(Type scanner_type) 328.0F, 7.0F, 4.0625F, 2.08626F, 0.0F, 2, 1, 8, 9, 16, 9, 1, 0.145F, 511.F, - 0, 0.F, 0.F); // TODO bucket/singles info incorrect? 224 buckets in total, but not sure how distributed + 1, 0.F, 0.F); // TODO bucket/singles info incorrect? 224 buckets in total, but not sure how distributed break; case test_scanner: @@ -292,7 +293,7 @@ Scanner::Scanner(Type scanner_type) 380.0F - 7.0F, 7.0F, 6.75F, 3.1088F, 0.0F, 1, 4, 8, 8, 8, 32, 1, 0.0F, 511.F, - 0, 0.F, 0.F); + 1, 0.F, 0.F); // Default 7.0mm average interaction depth. // This 7mm taken off the inner ring radius so that the effective radius remains 380mm @@ -305,7 +306,7 @@ Scanner::Scanner(Type scanner_type) 115 / 2.F, 7.0F, 6.25F, 1.65F, 0.0F, 1, 16, 8, 7, 8, 0, 1, 0.0F, 511.F, - 0, 0.F, 0.F); // HR block, 4 buckets per ring + 1, 0.F, 0.F); // HR block, 4 buckets per ring // Default 7.0mm average interaction depth. // 8 x 0 crystals per singles unit because not known @@ -319,7 +320,7 @@ Scanner::Scanner(Type scanner_type) /*MeanInnerRadius*/ 75.5/2.F, /*AverageDoI*/ 10.F, /*Ring Spacing*/ 3.F, /*BinSize*/ 0.1F, /*IntrinsicTilt*/ 0.F, 1, 1, 1, 1, 0, 0, 1, 0.0F, 511.F, - 0, 0.F, 0.F); + 1, 0.F, 0.F); break; case nanoPET: @@ -330,7 +331,7 @@ Scanner::Scanner(Type scanner_type) 12 * 39, 174.F, 5.0F, 1.17F, 1.17F, /* Actual size is 1.12 and 0.05 is the thickness of the optical reflector */ 0.0F, /* not sure for this */ 0,0,0,0,0,0, 1, 0.0F, 511.F, - 0, 0.F, 0.F); + 1, 0.F, 0.F); break; case HYPERimage: @@ -340,7 +341,7 @@ Scanner::Scanner(Type scanner_type) 490, 103.97F, 3.0F, 1.4F, 1.4F, /* Actual size is 1.3667 and assume 0.0333 is the thickness of the optical reflector */ 0.F, 0,0,0,0,0,0,1, 0.0F, 511.F, - 0, 0.F, 0.F); + 1, 0.F, 0.F); break; @@ -354,7 +355,7 @@ Scanner::Scanner(Type scanner_type) 471.875F - 8.4F, 8.4F, 8.5F, 1.970177F, 0.0F, //TODO view offset shouldn't be zero 3, 2, 6, 6, 1, 1, 1, 0.0F, 511.F, - 0, 0.F, 0.F); + 1, 0.F, 0.F); break; case DiscoveryLS: @@ -364,7 +365,7 @@ Scanner::Scanner(Type scanner_type) 471.875F - 8.4F, 8.4F, 8.5F, 1.970177F, 0.0F, //TODO view offset shouldn't be zero 3, 2, 6, 6, 1, 1, 1, 0.0F, 511.F, - 0, 0.F, 0.F); + 1, 0.F, 0.F); break; case DiscoveryST: @@ -377,7 +378,7 @@ Scanner::Scanner(Type scanner_type) static_cast(-4.54224*_PI/180),//sign? 4, 2, 6, 6, 1, 1, 1, 0.0F, 511.F, - 0, 0.F, 0.F);// TODO not sure about sign of view_offset + 1, 0.F, 0.F);// TODO not sure about sign of view_offset break; case DiscoverySTE: @@ -389,9 +390,7 @@ Scanner::Scanner(Type scanner_type) 4, 2, 6, 8, 1, 1, 1, // TODO not sure about sign of view_offset 0.22F, // energy resolution 511.F, - (short int)(0.F), - (float)(0.F), - (float)(0.F)); + 1, 0.F, 0.F); break; case DiscoveryRX: @@ -409,7 +408,7 @@ Scanner::Scanner(Type scanner_type) 2, 6, 9, 1, 1, 1, 0.0F, 511.F, - 1, 1.F, 1.F);// TODO not sure about sign of view_offset + 1, 0.F, 0.F);// TODO not sure about sign of view_offset break; case Discovery600: @@ -428,7 +427,7 @@ Scanner::Scanner(Type scanner_type) 2, 6, 8, 1, 1, 1, 0.0F, 511.F, - 1, 1.F, 1.F); + 1, 0.F, 0.F); break; case PETMR_Signa: @@ -556,7 +555,7 @@ Scanner::Scanner(Type scanner_type) 780.0F, 7.0F, 5.1875F, 2.F, 0.0F, 0, 0, 0, 0, 0,0, 1, 0.0F, 511.F, - 0, 0.F, 0.F); + 1, 0.F, 0.F); // Default 7.0mm average interaction depth. // crystals per singles unit etc unknown break; @@ -568,7 +567,7 @@ Scanner::Scanner(Type scanner_type) 234.765F, 7.0F, 2.4375F, 1.21875F, 0.0F, 0, 0, 0, 0, 0, 0, 2, 0.0F, 511.F, - 0, 0.F, 0.F); // added by Dylan Togane + 1, 0.F, 0.F); // added by Dylan Togane // warning: used 7.0mm average interaction depth. // crystals per singles unit etc unknown break; @@ -605,7 +604,7 @@ Scanner::Scanner(Type scanner_type) 29, 0 /* all detectors in a ring? */, 1, 0.0F, 511.F, - 0, 0.F, 0.F); + 1, 0.F, 0.F); break; #if 0 @@ -630,7 +629,7 @@ Scanner::Scanner(Type scanner_type) 0, 0 /* Not sure about these, but shouldn't be important */, 1, 0.0F, 511.F, - 0, 0.F, 0.F); + 1, 0.F, 0.F); break; case HiDAC: // all of these don't make any sense for the HiDAC @@ -639,7 +638,7 @@ Scanner::Scanner(Type scanner_type) 0.F, 0.F, 0.F, 0.F, 0.F, 0, 0, 0, 0, 0, 0, 0, 0.0F, 511.F, - 0, 0.F, 0.F); + 1, 0.F, 0.F); break; @@ -663,7 +662,7 @@ Scanner::Scanner(Type scanner_type) 1, //num_detector_layers_v -1, //energy_resolution_v -1, //reference_energy_v - (short int)0, 0.F, 0.F, // non-TOF + (short int)1, 0.F, 0.F, // non-TOF "", //scanner_geometry_v 2.2, //axial_crystal_spacing_v 2.2, //transaxial_crystal_spacing_v @@ -780,7 +779,7 @@ case UPENN_6rings: 0.F, 0.F, 0.F, 0.F, 0.F, 0, 0, 0, 0, 0, 0, 0, 0.0F, 511.F, - 0, 0.F, 0.F); + 1, 0.F, 0.F); break; @@ -791,7 +790,7 @@ case UPENN_6rings: 0.F, 0.F, 0.F, 0.F, 0.F, 0, 0, 0, 0, 0, 0, 0, 0.0F, 511.F, - 0, 0.F, 0.F); + 1, 0.F, 0.F); break; @@ -1291,6 +1290,43 @@ check_consistency() const return Succeeded::no; } + if (this->is_tof_ready()) + { + const auto w = this->get_coincidence_window_width_in_ps(); + const auto r = this->get_timing_resolution(); + const auto w_mm = this->get_coincidence_window_width_in_mm(); + const auto FOV_D = this->get_max_FOV_radius()*2; + + if ((w < 10.F) || (w > 10000.F)) + { + warning("Scanner: coincidence window width is expected to be around 4500ps but is " + + std::to_string(w)); + return Succeeded::no; + } + if (this->get_max_num_timing_poss() > 1) + { + // TOF-capable + if (w < r) + { + warning("Scanner: coincidence window width in ps is expected to be smaller than the timing resolution for a TOF scanner,\n" + "but they are " + + std::to_string(w) + " and " + std::to_string(r) + " resp."); + return Succeeded::no; + } + if ((w_mm > 2*FOV_D) || (w_mm < FOV_D/2)) + { + warning("Scanner: coincidence window width in mm is expected to be of the order of the FOV diameter for a TOF scanner,\n" + "but they are " + + std::to_string(w_mm) + " and " + std::to_string(FOV_D) + " resp."); + return Succeeded::no; + } + } + } + else + { + if (this->get_max_num_timing_poss() <= 0) + warning("Scanner: maximum number of timing positions is not yet known. We can only handle non-TOF.", 2); + } return Succeeded::yes; } @@ -1689,6 +1725,24 @@ std::list Scanner::get_names_of_predefined_scanners() return ret; } +float Scanner::get_coincidence_window_width_in_ps() const +{ + const auto w = this->get_size_of_timing_pos(); + if (this->is_tof_ready()) + return this->get_max_num_timing_poss() * w; + // presumably non-TOF + if (w>0) + return w; + error("Scanner coincidence window currently unknown. Sorry"); + return 0.F; // to avoid compiler warning +} + +float +Scanner::get_coincidence_window_width_in_mm() const +{ + return tof_delta_time_to_mm(get_coincidence_window_width_in_ps()); +} + static list string_list(const string& s) { diff --git a/src/data_buildblock/randoms_from_singles.cxx b/src/data_buildblock/randoms_from_singles.cxx index 54a52a0816..16a1727cd2 100644 --- a/src/data_buildblock/randoms_from_singles.cxx +++ b/src/data_buildblock/randoms_from_singles.cxx @@ -32,13 +32,16 @@ START_NAMESPACE_STIR void randoms_from_singles(ProjData& proj_data, const SinglesRates& singles, const float coincidence_time_window, float isotope_halflife) { - if (isotope_halflife == -1.F) + const auto& scanner = *proj_data.get_proj_data_info_sptr()->get_scanner_ptr(); + if (coincidence_time_window <= 0.F) + { + coincidence_time_window =scanner.get_coincidence_window_width_in_ps() / 1e12F; + } + if (isotope_halflife <= 0.F) isotope_halflife = proj_data.get_exam_info().get_radionuclide().get_half_life(); - const int num_rings = - proj_data.get_proj_data_info_sptr()->get_scanner_ptr()->get_num_rings(); - const int num_detectors_per_ring = - proj_data.get_proj_data_info_sptr()->get_scanner_ptr()->get_num_detectors_per_ring(); + const int num_rings = scanner.get_num_rings(); + const int num_detectors_per_ring = scanner.get_num_detectors_per_ring(); const TimeFrameDefinitions frame_defs = proj_data.get_exam_info_sptr()->get_time_frame_definitions(); diff --git a/src/include/stir/ProjDataInfo.h b/src/include/stir/ProjDataInfo.h index d7d232b7fa..74e4bfbf87 100644 --- a/src/include/stir/ProjDataInfo.h +++ b/src/include/stir/ProjDataInfo.h @@ -111,13 +111,6 @@ class ProjDataInfo const int num_views, const int num_tangential_poss, const bool arc_corrected = true, const int tof_mash_factor = 0); - //! \name Conversion functions between TOF delta_time and mm - //@{ - inline static double - mm_to_tof_delta_time(const float dist); - inline static float - tof_delta_time_to_mm(const double delta_time); - //@} /************ constructors ***********/ // TODO should probably be protected @@ -251,12 +244,7 @@ class ProjDataInfo inline int get_min_tof_pos_num() const; //! Get the index of the last timgin position. inline int get_max_tof_pos_num() const; - //! Get the coincide window in pico seconds - //! \warning Proposed convension: If the scanner is not TOF ready then - //! the coincidence windowis in the TOF bin size. - inline float get_coincidence_window_in_pico_sec() const; - //! Get the total width of the coincide window in mm - inline float get_coincidence_window_width() const; + //! Get the total number of sinograms /*! Note that this will count TOF sinograms as well. \see get_num_non_tof_sinograms() diff --git a/src/include/stir/ProjDataInfo.inl b/src/include/stir/ProjDataInfo.inl index b382796b02..d714e1764e 100644 --- a/src/include/stir/ProjDataInfo.inl +++ b/src/include/stir/ProjDataInfo.inl @@ -28,17 +28,6 @@ #include "boost/format.hpp" #include "stir/warning.h" START_NAMESPACE_STIR -double -ProjDataInfo::mm_to_tof_delta_time(const float dist) -{ - return dist / _c_light_div2; -} - -float -ProjDataInfo::tof_delta_time_to_mm(const double delta_time) -{ - return static_cast(delta_time * _c_light_div2); -} shared_ptr ProjDataInfo:: @@ -167,20 +156,6 @@ ProjDataInfo::get_max_tof_pos_num() const return max_tof_pos_num; } -float -ProjDataInfo::get_coincidence_window_in_pico_sec() const -{ - return scanner_ptr->is_tof_ready()? (scanner_ptr->get_max_num_timing_poss() * - scanner_ptr->get_size_of_timing_pos()) - :(scanner_ptr->get_size_of_timing_pos()); -} - -float -ProjDataInfo::get_coincidence_window_width() const -{ - return tof_delta_time_to_mm(get_coincidence_window_in_pico_sec()); -} - bool ProjDataInfo::is_tof_data() const { diff --git a/src/include/stir/Scanner.h b/src/include/stir/Scanner.h index 3b73742f5b..d5f0ab2bc6 100644 --- a/src/include/stir/Scanner.h +++ b/src/include/stir/Scanner.h @@ -319,12 +319,33 @@ class Scanner inline int get_num_transaxial_singles_units() const; /* inline int get_num_layers_singles_units() const; */ inline int get_num_singles_units() const; - //! Get the maximum number of TOF bins. + //! Get the maximum number of TOF bins. + /*! \return will be 0 or negative if not known */ inline int get_max_num_timing_poss() const; - //! Get the delta t which correspnds to the max number of TOF bins in picosecs. + //! Get the size for one (unmashed) TOF bin in picoseconds + /*! + \return will be 0 or negative if not known + \todo change name to \c get_size_of_timing_pos_in_ps + */ inline float get_size_of_timing_pos() const; - //! Get the timing resolution of the scanner. + //! Get the timing resolution of the scanner in picoseconds + /*! + \return will be 0 or negative if not known + \todo change name to \c get_size_of_timing_pos_in_ps + */ inline float get_timing_resolution() const; + //! Get the full width of the coincidence window in picoseconds + /*! + This is often written as \f$2\tau\f$ and is usually around 4000ps. + It is determined from \c get_max_num_timing_poss() and \c get_size_of_timing_pos(). + + \warning This is currently not known yet for many non-TOF scanners. The function will + then throw an error. + */ + float get_coincidence_window_width_in_ps() const; + //! Get the full width of the coincidence window in millimeter + /*! Calls get_coincidence_window_width_in_ps() */ + float get_coincidence_window_width_in_mm() const; //! \name number of "fake" crystals per block, inserted by the scanner /*! Some scanners (including many Siemens scanners) insert virtual crystals in the sinogram data. diff --git a/src/include/stir/TOF_conversions.h b/src/include/stir/TOF_conversions.h new file mode 100644 index 0000000000..ac6e35d985 --- /dev/null +++ b/src/include/stir/TOF_conversions.h @@ -0,0 +1,33 @@ +// +// +/* + Copyright (C) 2017, University College London + This file is part of STIR. + + SPDX-License-Identifier: Apache-2.0 + See STIR/LICENSE.txt for details +*/ +/*! + \file + \ingroup buildblock + \brief Implementations of inline functions for TOF time to mm + + \author Nikos Efthimiou + \author Kris Thielemans +*/ + +#include "stir/common.h" +START_NAMESPACE_STIR + +inline double mm_to_tof_delta_time(const float dist) +{ + return dist / speed_of_light_in_mm_per_ps_div2; +} + +inline float tof_delta_time_to_mm(const double delta_time) +{ + return static_cast(delta_time * speed_of_light_in_mm_per_ps_div2); +} + +END_NAMESPACE_STIR + diff --git a/src/include/stir/common.h b/src/include/stir/common.h index 9ada91aa84..bb3c8a41ce 100644 --- a/src/include/stir/common.h +++ b/src/include/stir/common.h @@ -246,9 +246,9 @@ START_NAMESPACE_STIR #endif //! Define the speed of light in mm / ps -const double _c_light = 0.299792458; +constexpr double speed_of_light_in_mm_per_ps = 0.299792458; //! This ratio is used often. -const double _c_light_div2 = _c_light * 0.5; +constexpr double speed_of_light_in_mm_per_ps_div2 = speed_of_light_in_mm_per_ps * 0.5; //! returns the square of a number, templated. /*! \ingroup buildblock */ diff --git a/src/recon_buildblock/ProjMatrixByBin.cxx b/src/recon_buildblock/ProjMatrixByBin.cxx index e402e92928..46a430b417 100644 --- a/src/recon_buildblock/ProjMatrixByBin.cxx +++ b/src/recon_buildblock/ProjMatrixByBin.cxx @@ -27,6 +27,7 @@ #include "stir/recon_buildblock/ProjMatrixByBin.h" #include "stir/recon_buildblock/ProjMatrixElemsForOneBin.h" +#include "stir/TOF_conversions.h" // define a local preprocessor symbol to keep code relatively clean #ifdef STIR_NO_MUTABLE @@ -75,7 +76,7 @@ enable_tof(const shared_ptr& _proj_data_info_sptr, const boo if (v) { tof_enabled = true; - gauss_sigma_in_mm = ProjDataInfo::tof_delta_time_to_mm(proj_data_info_sptr->get_scanner_ptr()->get_timing_resolution()) / 2.355f; + gauss_sigma_in_mm = tof_delta_time_to_mm(proj_data_info_sptr->get_scanner_ptr()->get_timing_resolution()) / 2.355f; r_sqrt2_gauss_sigma = 1.0f/ (gauss_sigma_in_mm * static_cast(sqrt(2.0))); } } diff --git a/src/test/test_time_of_flight.cxx b/src/test/test_time_of_flight.cxx index 2ac19bde93..6b580f06c6 100644 --- a/src/test/test_time_of_flight.cxx +++ b/src/test/test_time_of_flight.cxx @@ -195,7 +195,7 @@ TOF_Tests::test_tof_proj_data_info_kernel() + test_proj_data_info_sptr->get_sampling_in_k(Bin(0,0,0,0,0,1.f)); set_tolerance(static_cast(0.005)); - check_if_equal(static_cast(total_width), static_cast(test_proj_data_info_sptr->get_coincidence_window_width()), + check_if_equal(static_cast(total_width), static_cast(test_proj_data_info_sptr->get_scanner_ptr()->get_coincidence_window_width_in_mm()), "Coincidence widths don't match."); From 485843be70a66271f4f4ea39729f955479159c10 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sat, 13 Jan 2024 10:30:03 +0000 Subject: [PATCH 468/509] make coincidence_time_window arg of randoms_from_singles default we now have it in Scanner --- src/data_buildblock/randoms_from_singles.cxx | 6 ++---- src/include/stir/data/randoms_from_singles.h | 14 ++++++++++++-- src/utilities/construct_randoms_from_GEsingles.cxx | 4 +--- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/data_buildblock/randoms_from_singles.cxx b/src/data_buildblock/randoms_from_singles.cxx index 16a1727cd2..334ccbdd68 100644 --- a/src/data_buildblock/randoms_from_singles.cxx +++ b/src/data_buildblock/randoms_from_singles.cxx @@ -30,13 +30,11 @@ START_NAMESPACE_STIR void randoms_from_singles(ProjData& proj_data, const SinglesRates& singles, - const float coincidence_time_window, float isotope_halflife) + float coincidence_time_window, float isotope_halflife) { const auto& scanner = *proj_data.get_proj_data_info_sptr()->get_scanner_ptr(); if (coincidence_time_window <= 0.F) - { - coincidence_time_window =scanner.get_coincidence_window_width_in_ps() / 1e12F; - } + coincidence_time_window = scanner.get_coincidence_window_width_in_ps() / 1e12F; if (isotope_halflife <= 0.F) isotope_halflife = proj_data.get_exam_info().get_radionuclide().get_half_life(); diff --git a/src/include/stir/data/randoms_from_singles.h b/src/include/stir/data/randoms_from_singles.h index dacf3fc1a1..43a4b7370a 100644 --- a/src/include/stir/data/randoms_from_singles.h +++ b/src/include/stir/data/randoms_from_singles.h @@ -27,7 +27,16 @@ class SinglesRates; /*! \ingroup singles_buildblock - \brief Estimate randoms from singles + \brief Estimate randoms from singles (RFS) + + \param[in,out] proj_data + Projection data to store output. It needs to be properly initialised with sizes etc. + If \a coincidence_time_window or + \a radionuclide_halflife are invalid, they will be determined from the \a proj_data. + \param[in] singles + Input value for RFS + \param[in] coincidence_time_window Scanner coincidence window (in secs). Deprecated. + \param[in] radionuclide_halflife half-life. Deprecated. This uses the formula \f$ R_{ij}= \tau S_i S_j \f$ (with \f$\tau\f$ the \c coincidence_time_window) for finding the randoms-rate in terms of the @@ -59,6 +68,7 @@ class SinglesRates; \todo Dead-time is currently completely ignored. */ -void randoms_from_singles(ProjData& proj_data, const SinglesRates& singles, const float coincidence_time_window, float isotope_halflife=-1.F); +void randoms_from_singles(ProjData& proj_data, const SinglesRates& singles, + float coincidence_time_window=-1.F, float radionuclide_halflife=-1.F); END_NAMESPACE_STIR diff --git a/src/utilities/construct_randoms_from_GEsingles.cxx b/src/utilities/construct_randoms_from_GEsingles.cxx index a97351a0c9..78240ddae8 100755 --- a/src/utilities/construct_randoms_from_GEsingles.cxx +++ b/src/utilities/construct_randoms_from_GEsingles.cxx @@ -84,9 +84,7 @@ int main(int argc, char **argv) output_file_name); GE::RDF_HDF5::SinglesRatesFromGEHDF5 singles(input_filename); - const float coincidence_time_window = proj_data_info_sptr->get_coincidence_window_in_pico_sec() / 1e12F; - const float isotope_halflife = exam_info_sptr->get_radionuclide().get_half_life(); - randoms_from_singles(proj_data, singles, coincidence_time_window, isotope_halflife); + randoms_from_singles(proj_data, singles); return EXIT_SUCCESS; } From 9b40626a1011b2190d6c2c1a969ab3b92b7ed75d Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sun, 14 Jan 2024 02:32:49 +0000 Subject: [PATCH 469/509] fix RFS for TOF but change get_all_det_pos_pairs_for_bin - get_all_det_pos_pairs_for_bin now defaults to ignore TOF. This is probably what is expected by most people. - fix multiply_crystal_factors for TOF (in the previous version, it was unexpectedly running over all unmashed TOF bins, giving the wrong scale factor, and taking a long time --- .../ProjDataInfoCylindricalNoArcCorr.cxx | 38 ++++++++++--------- src/buildblock/multiply_crystal_factors.cxx | 12 +++--- .../stir/ProjDataInfoCylindricalNoArcCorr.h | 13 +++++-- src/test/test_proj_data_info.cxx | 2 +- 4 files changed, 38 insertions(+), 27 deletions(-) diff --git a/src/buildblock/ProjDataInfoCylindricalNoArcCorr.cxx b/src/buildblock/ProjDataInfoCylindricalNoArcCorr.cxx index db0f5573cf..f53678d611 100644 --- a/src/buildblock/ProjDataInfoCylindricalNoArcCorr.cxx +++ b/src/buildblock/ProjDataInfoCylindricalNoArcCorr.cxx @@ -336,31 +336,42 @@ initialise_det1det2_to_uncompressed_view_tangpos() const unsigned int ProjDataInfoCylindricalNoArcCorr:: -get_num_det_pos_pairs_for_bin(const Bin& bin) const +get_num_det_pos_pairs_for_bin(const Bin& bin, bool ignore_non_spatial_dimensions) const { return get_num_ring_pairs_for_segment_axial_pos_num(bin.segment_num(), bin.axial_pos_num())* get_view_mashing_factor()* - std::max(1,get_tof_mash_factor()); + (ignore_non_spatial_dimensions ? 1 : std::max(1,get_tof_mash_factor())); } void ProjDataInfoCylindricalNoArcCorr:: get_all_det_pos_pairs_for_bin(vector >& dps, - const Bin& bin) const + const Bin& bin, + bool ignore_non_spatial_dimensions) const { this->initialise_uncompressed_view_tangpos_to_det1det2_if_not_done_yet(); - dps.resize(get_num_det_pos_pairs_for_bin(bin)); + dps.resize(get_num_det_pos_pairs_for_bin(bin, ignore_non_spatial_dimensions)); const ProjDataInfoCylindrical::RingNumPairs& ring_pairs = get_all_ring_pairs_for_segment_axial_pos_num(bin.segment_num(), bin.axial_pos_num()); // not sure how to handle mashing with non-zero view offset... assert(get_min_view_num()==0); - // not sure how to handle even tof mashing - assert(!is_tof_data() || (get_tof_mash_factor() % 2 == 1)); + + int min_timing_pos_num = 0; + int max_timing_pos_num = 0; + if (!ignore_non_spatial_dimensions) + { + // not sure how to handle even tof mashing + assert(!is_tof_data() || (get_tof_mash_factor() % 2 == 1)); // TODOTOF + // we will need to add all (unmashed) timing_pos for the current bin + min_timing_pos_num = bin.timing_pos_num()*get_tof_mash_factor() - (get_tof_mash_factor() / 2); + max_timing_pos_num = bin.timing_pos_num()*get_tof_mash_factor() + (get_tof_mash_factor() / 2); + } + unsigned int current_dp_num=0; for (int uncompressed_view_num=bin.view_num()*get_view_mashing_factor(); uncompressed_view_num<(bin.view_num()+1)*get_view_mashing_factor(); @@ -374,8 +385,8 @@ get_all_det_pos_pairs_for_bin(vector >& dps, rings_iter != ring_pairs.end(); ++rings_iter) { - for (int uncompressed_timing_pos_num = bin.timing_pos_num()*get_tof_mash_factor() - (get_tof_mash_factor() / 2); - uncompressed_timing_pos_num <= bin.timing_pos_num()*get_tof_mash_factor() + (get_tof_mash_factor() / 2); + for (int uncompressed_timing_pos_num = min_timing_pos_num; + uncompressed_timing_pos_num <= max_timing_pos_num; ++uncompressed_timing_pos_num) { assert(current_dp_num < get_num_det_pos_pairs_for_bin(bin)); @@ -383,16 +394,7 @@ get_all_det_pos_pairs_for_bin(vector >& dps, dps[current_dp_num].pos1().axial_coord() = rings_iter->first; dps[current_dp_num].pos2().tangential_coord() = det2_num; dps[current_dp_num].pos2().axial_coord() = rings_iter->second; - // need to keep dp.timing_pos positive - if (uncompressed_timing_pos_num > 0) - { - dps[current_dp_num].timing_pos() = static_cast(uncompressed_timing_pos_num); - } - else - { - std::swap(dps[current_dp_num].pos1(), dps[current_dp_num].pos2()); - dps[current_dp_num].timing_pos() = static_cast(-uncompressed_timing_pos_num); - } + dps[current_dp_num].timing_pos() = uncompressed_timing_pos_num; ++current_dp_num; } } diff --git a/src/buildblock/multiply_crystal_factors.cxx b/src/buildblock/multiply_crystal_factors.cxx index af8183b911..2911d44887 100644 --- a/src/buildblock/multiply_crystal_factors.cxx +++ b/src/buildblock/multiply_crystal_factors.cxx @@ -24,6 +24,7 @@ #include "stir/Bin.h" #include "stir/Sinogram.h" #include "stir/error.h" +#include START_NAMESPACE_STIR @@ -33,8 +34,9 @@ void multiply_crystal_factors_help(ProjData& proj_data, const TProjDataInfo& proj_data_info, const Array<2,float>& efficiencies, const float global_factor) { - const auto non_tof_proj_data_info_sptr = proj_data_info.create_non_tof_clone(); - Bin bin; + const auto non_tof_proj_data_info_sptr = + std::dynamic_pointer_cast(proj_data_info.create_non_tof_clone()); + Bin bin; for (bin.segment_num() = proj_data.get_min_segment_num(); bin.segment_num() <= proj_data.get_max_segment_num(); @@ -59,8 +61,8 @@ void multiply_crystal_factors_help(ProjData& proj_data, view_num <= proj_data.get_max_view_num(); ++ view_num) { - for (int tangential_pos_num = proj_data_info.get_min_tangential_pos_num(); - tangential_pos_num <= proj_data_info.get_max_tangential_pos_num(); + for (int tangential_pos_num = proj_data.get_min_tangential_pos_num(); + tangential_pos_num <= proj_data.get_max_tangential_pos_num(); ++tangential_pos_num) { // Construct bin with appropriate values @@ -70,7 +72,7 @@ void multiply_crystal_factors_help(ProjData& proj_data, parallel_bin.tangential_pos_num() = tangential_pos_num; std::vector > det_pos_pairs; - proj_data_info.get_all_det_pos_pairs_for_bin(det_pos_pairs, parallel_bin); + non_tof_proj_data_info_sptr->get_all_det_pos_pairs_for_bin(det_pos_pairs, parallel_bin); // using the default argument to ignore TOF here float result = 0.F; for (unsigned int i=0; i >&, - const Bin&) const; + const Bin&, + bool ignore_non_spatial_dimensions = true) const; private: // old function, now private. Use get_bin_for_det_pos_pair instead. diff --git a/src/test/test_proj_data_info.cxx b/src/test/test_proj_data_info.cxx index 1c7801cc23..c583adcba9 100644 --- a/src/test/test_proj_data_info.cxx +++ b/src/test/test_proj_data_info.cxx @@ -1408,7 +1408,7 @@ ProjDataInfoCylindricalNoArcCorrTests::test_proj_data_info(ProjDataInfoCylindric bin.view_num() = view_num; bin.tangential_pos_num() = tangential_pos_num; std::vector> det_pos_pairs; - proj_data_info.get_all_det_pos_pairs_for_bin(det_pos_pairs, bin); + proj_data_info.get_all_det_pos_pairs_for_bin(det_pos_pairs, bin, false); // include TOF Bin new_bin; // set value for comparison with bin new_bin.set_bin_value(0); From 37717ce74700aa2c287d1a0650a70f7fb4722f94 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sun, 14 Jan 2024 02:53:48 +0000 Subject: [PATCH 470/509] change convention of multiply_crystal_factors for TOF, and add doc --- src/buildblock/multiply_crystal_factors.cxx | 8 ++++++-- src/data_buildblock/randoms_from_singles.cxx | 5 ++--- src/include/stir/multiply_crystal_factors.h | 9 ++++++++- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/buildblock/multiply_crystal_factors.cxx b/src/buildblock/multiply_crystal_factors.cxx index 2911d44887..a9e3415a8d 100644 --- a/src/buildblock/multiply_crystal_factors.cxx +++ b/src/buildblock/multiply_crystal_factors.cxx @@ -9,7 +9,7 @@ */ /* - Copyright (C) 2021, 2022 University Copyright London + Copyright (C) 2021, 2022, 2024 University Copyright London This file is part of STIR. SPDX-License-Identifier: Apache-2.0 @@ -32,8 +32,12 @@ START_NAMESPACE_STIR template void multiply_crystal_factors_help(ProjData& proj_data, const TProjDataInfo& proj_data_info, - const Array<2,float>& efficiencies, const float global_factor) + const Array<2,float>& efficiencies, float global_factor) { + // we will duplicate TOF sinograms, so need to divide with their number such that + // total remains preserved + global_factor /= proj_data.get_num_tof_poss(); + const auto non_tof_proj_data_info_sptr = std::dynamic_pointer_cast(proj_data_info.create_non_tof_clone()); Bin bin; diff --git a/src/data_buildblock/randoms_from_singles.cxx b/src/data_buildblock/randoms_from_singles.cxx index 334ccbdd68..1f47d44803 100644 --- a/src/data_buildblock/randoms_from_singles.cxx +++ b/src/data_buildblock/randoms_from_singles.cxx @@ -84,13 +84,12 @@ void randoms_from_singles(ProjData& proj_data, const SinglesRates& singles, info(boost::format("Isotope half-life: %1%\n" "RFS: decay correction factor: %2%,\n" "time frame duration: %3%.\n" - "total correction factor from (singles_totals)^2 to randoms_totals: %4%.\n") + "total correction factor from 2tau*(singles_totals)^2 to randoms_totals: %4%.\n") % isotope_halflife % decay_corr_factor % duration % (1/corr_factor), 2); - // Finally, if we have TOF data, we distribute randoms evenly over the TOF bins multiply_crystal_factors(proj_data, total_singles, - static_cast(coincidence_time_window * corr_factor / proj_data.get_num_tof_poss())); + static_cast(coincidence_time_window * corr_factor)); } } diff --git a/src/include/stir/multiply_crystal_factors.h b/src/include/stir/multiply_crystal_factors.h index df5a4654e7..e51b532706 100644 --- a/src/include/stir/multiply_crystal_factors.h +++ b/src/include/stir/multiply_crystal_factors.h @@ -29,7 +29,7 @@ template class Array; \brief Construct proj-data as a multiple of crystal efficiencies (or singles) - \param[in,out] proj_data projection data to write output (potentially to read first). This needs + \param[in,out] proj_data projection data to write output. This needs to be an existing object as geometry will be obtained from it. \param[in] efficiencies array of factors, one per crystal \param[in] global_factor global additional factor to use @@ -38,6 +38,13 @@ template class Array; the sum of \c efficiencies[c1]*efficiencies[c2] (with \c c1,c2 the crystals in the bin). This is useful for normalisation, but also for randoms from singles. + + \warning If TOF data is used, each TOF bin will be set to 1/num_tof_bins the non-TOF value. + This is appropriate for RFS, but would be confusing when using for normalisation. + + \warning, the name is a bit misleading. This function does currently not multiply + the existing data with the efficiencies, but overwrites it. + */ void multiply_crystal_factors(ProjData& proj_data, const Array<2,float>& efficiencies, const float global_factor); From 39cdc689302b82f67362c11e6ff46036b40e59b8 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sun, 14 Jan 2024 08:57:16 +0000 Subject: [PATCH 471/509] correct statement in assert() --- src/buildblock/ProjDataInfoCylindricalNoArcCorr.cxx | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/buildblock/ProjDataInfoCylindricalNoArcCorr.cxx b/src/buildblock/ProjDataInfoCylindricalNoArcCorr.cxx index f53678d611..e2b0a605b1 100644 --- a/src/buildblock/ProjDataInfoCylindricalNoArcCorr.cxx +++ b/src/buildblock/ProjDataInfoCylindricalNoArcCorr.cxx @@ -381,15 +381,13 @@ get_all_det_pos_pairs_for_bin(vector >& dps, uncompressed_view_tangpos_to_det1det2[uncompressed_view_num][bin.tangential_pos_num()].det1_num; const int det2_num = uncompressed_view_tangpos_to_det1det2[uncompressed_view_num][bin.tangential_pos_num()].det2_num; - for (ProjDataInfoCylindrical::RingNumPairs::const_iterator rings_iter = ring_pairs.begin(); - rings_iter != ring_pairs.end(); - ++rings_iter) + for (auto rings_iter = ring_pairs.begin(); rings_iter != ring_pairs.end(); ++rings_iter) { for (int uncompressed_timing_pos_num = min_timing_pos_num; uncompressed_timing_pos_num <= max_timing_pos_num; ++uncompressed_timing_pos_num) { - assert(current_dp_num < get_num_det_pos_pairs_for_bin(bin)); + assert(current_dp_num < get_num_det_pos_pairs_for_bin(bin, ignore_non_spatial_dimensions)); dps[current_dp_num].pos1().tangential_coord() = det1_num; dps[current_dp_num].pos1().axial_coord() = rings_iter->first; dps[current_dp_num].pos2().tangential_coord() = det2_num; @@ -399,7 +397,7 @@ get_all_det_pos_pairs_for_bin(vector >& dps, } } } - assert(current_dp_num == get_num_det_pos_pairs_for_bin(bin)); + assert(current_dp_num == get_num_det_pos_pairs_for_bin(bin, ignore_non_spatial_dimensions)); } Succeeded From 2c776e35ec2a806cc126aece4c1178b3593363aa Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sun, 14 Jan 2024 09:05:49 +0000 Subject: [PATCH 472/509] updated release notes [ci skip] --- documentation/release_6.0.htm | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/documentation/release_6.0.htm b/documentation/release_6.0.htm index c44345a0bb..fe52b78660 100644 --- a/documentation/release_6.0.htm +++ b/documentation/release_6.0.htm @@ -292,6 +292,23 @@

    Non-TOF related

+

Changed functionality

+ +

TOF related

+
    +
  • + ProjDataInfoCylindricalNoArcCorr::get_all_det_pos_pairs_for_bin is in most places intended to return + the physical locations. However, a `DetectionPositionPair` also contains (unmashed) TOF bin information. + This will be further complicated once energy windows are supported. The + method therefore has an extra boolean argument ignore_non_spatial_dimensions, which defaults to + true. +
  • +
  • + multiply_crystal_factors is essentially a non-TOF calculation. When given TOF projection data, + it will "spread" the non-TOF result equally over all TOF bins. This is also appropriate for + randoms_from_singles. +
  • +
From aeea2f55b569e29e925b97fdf4acbdbd075aadd7 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sun, 14 Jan 2024 22:03:51 +0000 Subject: [PATCH 473/509] removed CListEvent::delta_time also get_delta_time() as not needed. Kept (i.e. moved) delta_time in CListEventROOT --- .../listmode/CListEventScannerWithDiscreteDetectors.inl | 2 +- src/include/stir/listmode/CListRecord.h | 5 ----- src/include/stir/listmode/CListRecordGEHDF5.h | 5 ----- src/include/stir/listmode/CListRecordROOT.h | 6 +++++- 4 files changed, 6 insertions(+), 12 deletions(-) diff --git a/src/include/stir/listmode/CListEventScannerWithDiscreteDetectors.inl b/src/include/stir/listmode/CListEventScannerWithDiscreteDetectors.inl index 10dc47c31a..ee29616bdb 100644 --- a/src/include/stir/listmode/CListEventScannerWithDiscreteDetectors.inl +++ b/src/include/stir/listmode/CListEventScannerWithDiscreteDetectors.inl @@ -60,7 +60,7 @@ CListEventScannerWithDiscreteDetectors:: get_LOR() const { LORAs2Points lor; - const bool swap = true;// this->get_delta_time() < 0.F; + const bool swap = true; // provide somewhat shorter names for the 2 coordinates, taking swap into account CartesianCoordinate3D& coord_1 = swap ? lor.p2() : lor.p1(); CartesianCoordinate3D& coord_2 = swap ? lor.p1() : lor.p2(); diff --git a/src/include/stir/listmode/CListRecord.h b/src/include/stir/listmode/CListRecord.h index 7df46194cc..c728bde6d5 100644 --- a/src/include/stir/listmode/CListRecord.h +++ b/src/include/stir/listmode/CListRecord.h @@ -57,11 +57,6 @@ class CListEvent : public ListEvent Succeeded set_prompt(const bool prompt = true); - double get_delta_time() const { return delta_time; } -protected: - //! The detection time difference, between the two photons. - double delta_time; - }; /*-coincidence event*/ //! Class for records in a PET list mode file diff --git a/src/include/stir/listmode/CListRecordGEHDF5.h b/src/include/stir/listmode/CListRecordGEHDF5.h index 49e2cc12e5..f698a7dd3c 100644 --- a/src/include/stir/listmode/CListRecordGEHDF5.h +++ b/src/include/stir/listmode/CListRecordGEHDF5.h @@ -313,11 +313,6 @@ dynamic_cast(&e2) != 0 && //ByteOrder::swap_order(this->raw[0]); } - if (this->is_event()) - { - // set TOF info in ps - this->delta_time = this->event_data.get_tof_bin() *this-> get_scanner_ptr()->get_size_of_timing_pos(); - } return Succeeded::yes; } diff --git a/src/include/stir/listmode/CListRecordROOT.h b/src/include/stir/listmode/CListRecordROOT.h index e4f2ca0663..53992d1bff 100644 --- a/src/include/stir/listmode/CListRecordROOT.h +++ b/src/include/stir/listmode/CListRecordROOT.h @@ -51,7 +51,9 @@ class CListEventROOT : public CListEventCylindricalScannerWithDiscreteDetectors inline bool is_prompt() const { return true; } -private: + double get_delta_time() const { return delta_time; } + + private: //! First ring, in order to detector tangestial index int ring1; //! Second ring, in order to detector tangestial index @@ -60,6 +62,8 @@ class CListEventROOT : public CListEventCylindricalScannerWithDiscreteDetectors int det1; //! Second detector, in order to detector tangestial index int det2; + //! The detection time difference, between the two photons. + double delta_time; #ifdef STIR_ROOT_ROTATION_AS_V4 //! This is the number of detector we have to rotate in order to //! align GATE and STIR. From 86b3f9168486c589fb4742763ca8af7a5267913f Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sun, 14 Jan 2024 22:03:56 +0000 Subject: [PATCH 474/509] fix list_lm_events - adjust for CListEvent:get_delta_time being removed - fix printing of bin - only print TOF info for TOF-ready scanner --- src/listmode_utilities/list_lm_events.cxx | 26 ++++++++++++++--------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/listmode_utilities/list_lm_events.cxx b/src/listmode_utilities/list_lm_events.cxx index b1ad3a7562..93fc8118f9 100644 --- a/src/listmode_utilities/list_lm_events.cxx +++ b/src/listmode_utilities/list_lm_events.cxx @@ -121,8 +121,10 @@ int main(int argc, char *argv[]) shared_ptr lm_data_ptr(read_from_file(argv[0])); auto proj_data_info_sptr = lm_data_ptr->get_proj_data_info_sptr(); + auto scanner = lm_data_ptr->get_scanner(); + const bool list_TOF_info = scanner.is_tof_ready(); - cout << "Scanner: " << lm_data_ptr->get_scanner().get_name() << endl; + cout << "Scanner: " << scanner.get_name() << endl; unsigned long num_listed_events = 0; { @@ -164,8 +166,12 @@ int main(int argc, char *argv[]) if (record.is_event()) { recognised=true; + Bin bin; + record.event().get_bin(bin, *proj_data_info_sptr); + if (list_coincidence) { + if (auto event_ptr = dynamic_cast(&record.event())) { @@ -184,8 +190,8 @@ int main(int argc, char *argv[]) << ",r:" << det_pos.pos2().axial_coord() << ",l:" << det_pos.pos2().radial_coord() << ")\t"; - cout << " TOF-bin: " << det_pos.timing_pos() - << " delta time: " << event_ptr->get_delta_time(); + if (list_TOF_info) + cout << " TOF-bin: " << det_pos.timing_pos(); listed = true; } } @@ -201,16 +207,16 @@ int main(int argc, char *argv[]) << "," << lor.p2().y() << "," << lor.p2().x() << ")"; + if (list_TOF_info) + cout << " k: " << proj_data_info_sptr->get_k(bin); + listed = true; + } + if (list_event_bin) + { + cout << " bin " << bin; listed = true; } } - if (list_event_bin) - { - Bin bin; - record.event().get_bin(bin, *proj_data_info_sptr); - cout << " bin " << bin; - listed = true; - } if (!recognised && list_unknown) { cout << "Unknown type"; From 434837c8e97a80b6402d6684bceaa849f44b4d8a Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Wed, 17 Jan 2024 07:34:11 +0000 Subject: [PATCH 475/509] [SWIG] add PinholeSPECTUB matrix (#1336) --- documentation/release_6.0.htm | 3 +++ src/swig/stir.i | 1 + src/swig/stir_projectors.i | 19 ++++++++++++++++--- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/documentation/release_6.0.htm b/documentation/release_6.0.htm index fe52b78660..b9e7ed1817 100644 --- a/documentation/release_6.0.htm +++ b/documentation/release_6.0.htm @@ -130,6 +130,9 @@

General

Python (and MATLAB)

    +
  • exposed ProjMatrixByBinPinholeSPECTUB
    + PR #1366 +
  • PR #1288
    • diff --git a/src/swig/stir.i b/src/swig/stir.i index 224b918d7f..9ac439079e 100644 --- a/src/swig/stir.i +++ b/src/swig/stir.i @@ -136,6 +136,7 @@ #include "stir/recon_buildblock/ProjMatrixByBinUsingRayTracing.h" #include "stir/recon_buildblock/ProjMatrixByBinSPECTUB.h" +#include "stir/recon_buildblock/ProjMatrixByBinPinholeSPECTUB.h" #include "stir/recon_buildblock/QuadraticPrior.h" #include "stir/recon_buildblock/PLSPrior.h" #include "stir/recon_buildblock/RelativeDifferencePrior.h" diff --git a/src/swig/stir_projectors.i b/src/swig/stir_projectors.i index 4ba2b4ae18..aa7c6885ab 100644 --- a/src/swig/stir_projectors.i +++ b/src/swig/stir_projectors.i @@ -45,6 +45,13 @@ >); %shared_ptr(stir::ProjMatrixByBinSPECTUB); +%shared_ptr(stir::RegisteredParsingObject< + stir::ProjMatrixByBinPinholeSPECTUB, + stir::ProjMatrixByBin, + stir::ProjMatrixByBin + >); +%shared_ptr(stir::ProjMatrixByBinPinholeSPECTUB); + %include "stir/recon_buildblock/ForwardProjectorByBin.h" %include "stir/recon_buildblock/BackProjectorByBin.h" %include "stir/recon_buildblock/ProjMatrixByBin.h" @@ -60,7 +67,15 @@ stir::ProjMatrixByBin >; -%include "stir/recon_buildblock/ProjMatrixByBinSPECTUB.h" +%include "stir/recon_buildblock/ProjMatrixByBinPinholeSPECTUB.h" + +%template (internalRPProjMatrixByBinPinholeSPECTUB) stir::RegisteredParsingObject< + stir::ProjMatrixByBinPinholeSPECTUB, + stir::ProjMatrixByBin, + stir::ProjMatrixByBin + >; + +%include "stir/recon_buildblock/ProjMatrixByBinPinholeSPECTUB.h" %template (internalRPForwardProjectorByBinUsingProjMatrixByBin) stir::RegisteredParsingObject; %include "stir/recon_buildblock/BackProjectorByBinUsingProjMatrixByBin.h" -%include "stir/recon_buildblock/ProjMatrixByBinSPECTUB.h" - %shared_ptr(stir::ProjectorByBinPair); // explicitly ignore constructor, because SWIG tries to instantiate the abstract class otherwise %ignore stir::ProjectorByBinPair::ProjectorByBinPair(); From d56b24601c3b72c1dfa056ab21605ef686b14bd2 Mon Sep 17 00:00:00 2001 From: danieldeidda Date: Mon, 22 Jan 2024 10:02:27 +0000 Subject: [PATCH 476/509] adding Ge68 to radionuclide database --- src/config/radionuclide_info.json | 12 +++++++++++- src/config/radionuclide_names.json | 1 + 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/config/radionuclide_info.json b/src/config/radionuclide_info.json index cf209a00b2..91b0262737 100644 --- a/src/config/radionuclide_info.json +++ b/src/config/radionuclide_info.json @@ -61,6 +61,16 @@ } ] }, + { + "name": "^68^Germanium", + "decays": [ + { "modality": "PET", + "keV": 511, + "branching_ratio": 1, + "half_life": 23410080 + } + ] + }, { "name": "^99m^Technetium", "decays": [ @@ -91,7 +101,7 @@ "half_life": 281776.32 } ] - + }, { "name": "^177^Lutetium", diff --git a/src/config/radionuclide_names.json b/src/config/radionuclide_names.json index e445f7fd90..c279da15b0 100644 --- a/src/config/radionuclide_names.json +++ b/src/config/radionuclide_names.json @@ -5,6 +5,7 @@ [ "^15^Oxygen", "15O", "O-15" ], [ "^64^Copper", "64Cu", "Cu-64" ], [ "^68^Gallium", "68Ga", "Ga-68" ], + [ "^68^Germanium", "68Ge", "Ge-68" ], [ "^99m^Technetium", "99mTc", "Tc-99m" ], [ "^131^Iodine", "131I", "I-131" ], [ "^67^Gallium", "67Ga", "Ga-67" ], From 8ea366b80590f0df90664e6c230b8d845ce9d9cf Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Mon, 22 Jan 2024 21:29:00 +0000 Subject: [PATCH 477/509] add already_set_up to GeneralisedObjectiveFunction --- documentation/release_6.0.htm | 5 +++++ src/buildblock/ProjDataInfoGenericNoArcCorr.cxx | 14 ++------------ ...arModelForMeanAndGatedProjDataWithMotion.cxx | 9 +++++++-- .../GeneralisedObjectiveFunction.h | 10 +++++++++- ...arModelForMeanAndGatedProjDataWithMotion.txx | 17 ++++++++++++----- .../GeneralisedObjectiveFunction.cxx | 16 ++++++++++++++++ ...issonLogLikelihoodWithLinearModelForMean.cxx | 10 +++++++++- ...oodWithLinearModelForMeanAndListModeData.cxx | 9 +++++++-- ...orMeanAndListModeDataWithProjMatrixByBin.cxx | 8 +++++--- ...elihoodWithLinearModelForMeanAndProjData.cxx | 15 +++++++++++++-- .../test_blocks_on_cylindrical_projectors.cxx | 2 +- 11 files changed, 86 insertions(+), 29 deletions(-) diff --git a/documentation/release_6.0.htm b/documentation/release_6.0.htm index b9e7ed1817..cc8be1cfa7 100644 --- a/documentation/release_6.0.htm +++ b/documentation/release_6.0.htm @@ -252,6 +252,11 @@

      Backward incompatibities

      ProjDataInfo*NoArcCorr::get_bin_for_det_pair is now private. Use get_bin_for_det_pos_pair instead.
    • +
    • + The GeneralisedObjectiveFunction hierarchy now has a already_set_up + member variable that needs to be set to false by set_* + functions and checked by callers. +

    New functionality

    diff --git a/src/buildblock/ProjDataInfoGenericNoArcCorr.cxx b/src/buildblock/ProjDataInfoGenericNoArcCorr.cxx index e900897d24..ee21c30280 100644 --- a/src/buildblock/ProjDataInfoGenericNoArcCorr.cxx +++ b/src/buildblock/ProjDataInfoGenericNoArcCorr.cxx @@ -74,18 +74,8 @@ ProjDataInfoGenericNoArcCorr(const shared_ptr scanner_sptr, this->initialise_det1det2_to_uncompressed_view_tangpos(); #endif - CartesianCoordinate3D< float> b1,b2; - Bin bin; - bin.segment_num() = 0; - bin.axial_pos_num() = 0; - bin.view_num() = 0; - bin.tangential_pos_num() = 0; -// setting shift_z to 0 before it is actually estimated. Otherwise the next function will use it - this->z_shift.z()=0; - find_cartesian_coordinates_of_detection(b1,b2,bin); - float shift=b2.z(); - - this->z_shift.z()=shift; + // find shift between "new" centre-of-scanner and "old" centre-of-first-ring coordinate system + this->z_shift.z()=this->get_scanner_ptr()->get_coordinate_for_det_pos(DetectionPosition<>(0,0,0)).z(); this->z_shift.y()=0; this->z_shift.x()=0; } diff --git a/src/experimental/motion/PoissonLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion.cxx b/src/experimental/motion/PoissonLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion.cxx index 6d2b4de604..4453c06ea8 100644 --- a/src/experimental/motion/PoissonLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion.cxx +++ b/src/experimental/motion/PoissonLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion.cxx @@ -285,6 +285,7 @@ void PoissonLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion:: set_proj_data_sptr(const shared_ptr& arg) { + this->already_set_up = false; this->_gated_proj_data_sptr = arg; } @@ -293,8 +294,8 @@ void PoissonLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion:: set_max_segment_num_to_process(const int arg) { + this->already_set_up = this->already_set_up && (this->max_timing_pos_num_to_process == arg); this->max_segment_num_to_process = arg; - } template @@ -302,6 +303,7 @@ void PoissonLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion:: set_zero_seg0_end_planes(const bool arg) { + this->already_set_up = this->already_set_up && (this->zero_seg0_end_planes == arg); this->zero_seg0_end_planes = arg; } @@ -310,7 +312,7 @@ void PoissonLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion:: set_additive_proj_data_sptr(const shared_ptr& arg) { - + this->already_set_up = false; this->_gated_additive_proj_data_sptr = arg; } @@ -319,6 +321,7 @@ void PoissonLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion:: set_projector_pair_sptr(const shared_ptr& arg) { + this->already_set_up = false; this->projector_pair_ptr = arg; } @@ -328,6 +331,7 @@ void PoissonLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion:: set_frame_num(const int arg) { + this->already_set_up = this->already_set_up && (this->frame_num == arg); this->frame_num = arg; } @@ -336,6 +340,7 @@ void PoissonLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion:: set_frame_definitions(const TimeFrameDefinitions& arg) { + this->already_set_up = this->already_set_up && (this->frame_defs == arg); this->frame_defs = arg; } diff --git a/src/include/stir/recon_buildblock/GeneralisedObjectiveFunction.h b/src/include/stir/recon_buildblock/GeneralisedObjectiveFunction.h index 021089257a..d479dd8940 100644 --- a/src/include/stir/recon_buildblock/GeneralisedObjectiveFunction.h +++ b/src/include/stir/recon_buildblock/GeneralisedObjectiveFunction.h @@ -86,12 +86,19 @@ class GeneralisedObjectiveFunction: { public: - //GeneralisedObjectiveFunction(); + GeneralisedObjectiveFunction() + : already_set_up(false) + {} + virtual ~GeneralisedObjectiveFunction(); //! Creates a suitable target as determined by the parameters + /*! + \warning This should not check \c already_set_up (unfortunately), + as it is currently called in Reconstruction::reconstruct() before calling set_up(). + */ virtual TargetT * construct_target_ptr() const = 0; @@ -331,6 +338,7 @@ class GeneralisedObjectiveFunction: protected: int num_subsets; + bool already_set_up; shared_ptr > prior_sptr; diff --git a/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion.txx b/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion.txx index 2983842152..42a4a86e37 100644 --- a/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion.txx +++ b/src/include/stir/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion.txx @@ -175,7 +175,7 @@ template TargetT * PoissonLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion:: construct_target_ptr() const -{ +{ return this->target_parameter_parser.create(this->get_input_data()); } @@ -280,15 +280,16 @@ get_projector_pair_sptr() const template int PoissonLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion:: -set_num_subsets(const int num_subsets) +set_num_subsets(const int new_num_subsets) { + this->already_set_up = this->already_set_up && (this->num_subsets == new_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) + if(this->_single_gate_obj_funcs[gate_num].set_num_subsets(new_num_subsets) != new_num_subsets) error("set_num_subsets didn't work"); } - this->num_subsets=num_subsets; + this->num_subsets=new_num_subsets; return this->num_subsets; } @@ -296,13 +297,17 @@ template void PoissonLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion:: set_time_gate_definitions(const TimeGateDefinitions & time_gate_definitions) -{ this->_time_gate_definitions=time_gate_definitions; } +{ + this->already_set_up = false; + this->_time_gate_definitions=time_gate_definitions; +} template void PoissonLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion:: set_input_data(const shared_ptr & arg) { + this->already_set_up = false; this->_gated_proj_data_sptr = dynamic_pointer_cast(arg); } @@ -319,6 +324,7 @@ void PoissonLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion:: set_additive_proj_data_sptr(const shared_ptr &arg) { + this->already_set_up = false; this->_additive_gated_proj_data_sptr = dynamic_pointer_cast(arg); } @@ -327,6 +333,7 @@ void PoissonLogLikelihoodWithLinearModelForMeanAndGatedProjDataWithMotion:: set_normalisation_sptr(const shared_ptr& arg) { + this->already_set_up = false; // this->normalisation_sptr = arg; error("Not implemeted yet"); } diff --git a/src/recon_buildblock/GeneralisedObjectiveFunction.cxx b/src/recon_buildblock/GeneralisedObjectiveFunction.cxx index ee25939088..05867223cb 100644 --- a/src/recon_buildblock/GeneralisedObjectiveFunction.cxx +++ b/src/recon_buildblock/GeneralisedObjectiveFunction.cxx @@ -142,6 +142,8 @@ compute_sub_gradient(TargetT& gradient, const TargetT ¤t_estimate, const int subset_num) { + if (!this->already_set_up) + error("Need to call set_up() for objective function first"); assert(gradient.get_index_range() == current_estimate.get_index_range()); if (subset_num<0 || subset_num>=this->get_num_subsets()) @@ -194,6 +196,8 @@ GeneralisedObjectiveFunction:: compute_objective_function_without_penalty(const TargetT& current_estimate, const int subset_num) { + if (!this->already_set_up) + error("Need to call set_up() for objective function first"); if (subset_num<0 || subset_num>=this->get_num_subsets()) error("compute_objective_function_without_penalty subset_num out-of-range error"); @@ -231,6 +235,8 @@ add_multiplication_with_approximate_sub_Hessian_without_penalty(TargetT& output, const TargetT& input, const int subset_num) const { + if (!this->already_set_up) + error("Need to call set_up() for objective function first"); if (subset_num<0 || subset_num>=this->get_num_subsets()) error("add_multiplication_with_approximate_sub_Hessian_without_penalty subset_num out-of-range error"); @@ -259,6 +265,8 @@ add_multiplication_with_approximate_sub_Hessian(TargetT& output, const TargetT& input, const int subset_num) const { + if (!this->already_set_up) + error("Need to call set_up() for objective function first"); if (this->add_multiplication_with_approximate_sub_Hessian_without_penalty(output, input, subset_num) == Succeeded::no) return Succeeded::no; @@ -406,6 +414,8 @@ accumulate_sub_Hessian_times_input_without_penalty(TargetT& output, const TargetT& input, const int subset_num) const { + if (!this->already_set_up) + error("Need to call set_up() for objective function first"); if (subset_num<0 || subset_num>=this->get_num_subsets()) error("accumulate_sub_Hessian_times_input_without_penalty subset_num out-of-range error"); @@ -487,6 +497,12 @@ bool GeneralisedObjectiveFunction:: subsets_are_approximately_balanced(std::string& warning_message) const { +#if 0 + // TODO cannot do this yet, as this function is called in + // `PoissonLogLikelihoodWithLinearModelForMean::compute_sensitivities` during set_up() + if (!this->already_set_up) + error("Need to call set_up() for objective function first"); +#endif return this->actual_subsets_are_approximately_balanced(warning_message); } diff --git a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMean.cxx b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMean.cxx index 6af6f741d5..031a346ac6 100644 --- a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMean.cxx +++ b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMean.cxx @@ -95,6 +95,7 @@ void PoissonLogLikelihoodWithLinearModelForMean:: set_sensitivity_filename(const std::string& filename) { + this->already_set_up = false; this->sensitivity_filename = filename; } @@ -103,6 +104,7 @@ void PoissonLogLikelihoodWithLinearModelForMean:: set_subsensitivity_filenames(const std::string& filenames) { + this->already_set_up = false; this->subsensitivity_filenames = filenames; try { @@ -171,6 +173,7 @@ void PoissonLogLikelihoodWithLinearModelForMean:: set_use_subset_sensitivities(const bool arg) { + this->already_set_up = this->already_set_up && (this->use_subset_sensitivities == arg); this->use_subset_sensitivities = arg; } @@ -179,6 +182,7 @@ void PoissonLogLikelihoodWithLinearModelForMean:: set_subset_sensitivity_sptr(const shared_ptr& arg, const int subset_num) { + this->already_set_up = false; this->subsensitivity_sptrs[subset_num] = arg; } @@ -348,7 +352,7 @@ set_up(shared_ptr const& target_sptr) return Succeeded::no; } } - + this->already_set_up = true; return Succeeded::yes; } @@ -359,6 +363,8 @@ compute_sub_gradient_without_penalty(TargetT& gradient, const TargetT ¤t_estimate, const int subset_num) { + if (!this->already_set_up) + error("Need to call set_up() for objective function first"); this->actual_compute_subset_gradient_without_penalty(gradient, current_estimate, subset_num, false); } @@ -369,6 +375,8 @@ compute_sub_gradient_without_penalty_plus_sensitivity(TargetT& gradient, const TargetT ¤t_estimate, const int subset_num) { + if (!this->already_set_up) + error("Need to call set_up() for objective function first"); actual_compute_subset_gradient_without_penalty(gradient, current_estimate, subset_num, true); } diff --git a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeData.cxx b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeData.cxx index bf95a274ef..6af8e55f97 100644 --- a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeData.cxx +++ b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeData.cxx @@ -100,10 +100,10 @@ PoissonLogLikelihoodWithLinearModelForMeanAndListModeData::post_process if (this->list_mode_filename.length() == 0 && !this->skip_lm_input_file) { warning("You need to specify an input file\n"); return true; } if (!this->skip_lm_input_file) - { + { this->list_mode_data_sptr= read_from_file(this->list_mode_filename); - } + } if (this->additive_projection_data_filename != "0") { @@ -123,6 +123,7 @@ PoissonLogLikelihoodWithLinearModelForMeanAndListModeData::post_process } target_parameter_parser.check_values(); + this->already_set_up = false; return false; } @@ -131,6 +132,7 @@ void PoissonLogLikelihoodWithLinearModelForMeanAndListModeData:: set_input_data(const shared_ptr & arg) { + this->already_set_up = false; try { this->list_mode_data_sptr = dynamic_pointer_cast(arg); @@ -146,6 +148,7 @@ void PoissonLogLikelihoodWithLinearModelForMeanAndListModeData:: set_max_segment_num_to_process(const int arg) { + this->already_set_up = this->already_set_up && (this->max_segment_num_to_process == arg); this->max_segment_num_to_process = arg; } @@ -258,6 +261,7 @@ void PoissonLogLikelihoodWithLinearModelForMeanAndListModeData:: set_additive_proj_data_sptr(const shared_ptr &arg) { + this->already_set_up = false; try { this->additive_proj_data_sptr = dynamic_pointer_cast(arg); @@ -281,6 +285,7 @@ void PoissonLogLikelihoodWithLinearModelForMeanAndListModeData:: set_normalisation_sptr(const shared_ptr& arg) { + this->already_set_up = false; this->normalisation_sptr = arg; } diff --git a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx index 94bd7c288e..b5467eeaa4 100644 --- a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx +++ b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin.cxx @@ -116,6 +116,7 @@ int PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin:: set_num_subsets(const int new_num_subsets) { + this->already_set_up = this->already_set_up && (this->num_subsets == new_num_subsets); this->num_subsets = new_num_subsets; return this->num_subsets; } @@ -125,7 +126,8 @@ void PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin:: set_proj_matrix(const shared_ptr& arg) { - this->PM_sptr = arg; + this->already_set_up = false; + this->PM_sptr = arg; } #if 0 @@ -134,6 +136,7 @@ void PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin:: set_proj_data_info(const ProjData& arg) { + this->already_set_up = false; // this will be broken now. Why did we need this? this->proj_data_info_sptr = arg.get_proj_data_info_sptr()->create_shared_clone(); if(this->skip_lm_input_file) @@ -724,8 +727,7 @@ TargetT * PoissonLogLikelihoodWithLinearModelForMeanAndListModeDataWithProjMatrixByBin:: construct_target_ptr() const { - - return + return this->target_parameter_parser.create(this->get_input_data()); } diff --git a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx index f8401fba05..ac686cf449 100644 --- a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx +++ b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx @@ -391,6 +391,7 @@ int PoissonLogLikelihoodWithLinearModelForMeanAndProjData:: set_num_subsets(const int new_num_subsets) { + this->already_set_up = this->already_set_up && (this->num_subsets == new_num_subsets); this->num_subsets = std::max(new_num_subsets,1); return this->num_subsets; @@ -401,6 +402,7 @@ void PoissonLogLikelihoodWithLinearModelForMeanAndProjData:: set_proj_data_sptr(const shared_ptr& arg) { + this->already_set_up = false; this->proj_data_sptr = arg; } @@ -409,6 +411,7 @@ void PoissonLogLikelihoodWithLinearModelForMeanAndProjData:: set_max_segment_num_to_process(const int arg) { + this->already_set_up = this->already_set_up && (this->max_segment_num_to_process == arg); this->max_segment_num_to_process = arg; } @@ -417,6 +420,7 @@ void PoissonLogLikelihoodWithLinearModelForMeanAndProjData:: set_max_timing_pos_num_to_process(const int arg) { + this->already_set_up = this->already_set_up && (this->max_timing_pos_num_to_process == arg); this->max_timing_pos_num_to_process = arg; } @@ -425,6 +429,7 @@ void PoissonLogLikelihoodWithLinearModelForMeanAndProjData:: set_zero_seg0_end_planes(const bool arg) { + this->already_set_up = this->already_set_up && (this->zero_seg0_end_planes == arg); this->zero_seg0_end_planes = arg; } @@ -433,7 +438,8 @@ void PoissonLogLikelihoodWithLinearModelForMeanAndProjData:: set_additive_proj_data_sptr(const shared_ptr &arg) { - this->additive_proj_data_sptr = dynamic_pointer_cast(arg); + this->already_set_up = false; + this->additive_proj_data_sptr = dynamic_pointer_cast(arg); } template @@ -441,6 +447,7 @@ void PoissonLogLikelihoodWithLinearModelForMeanAndProjData:: set_projector_pair_sptr(const shared_ptr& arg) { + this->already_set_up = false; this->projector_pair_ptr = arg; } @@ -449,6 +456,7 @@ void PoissonLogLikelihoodWithLinearModelForMeanAndProjData:: set_frame_num(const int arg) { + this->already_set_up = this->already_set_up && (this->frame_num == arg); this->frame_num = arg; } @@ -457,6 +465,7 @@ void PoissonLogLikelihoodWithLinearModelForMeanAndProjData:: set_frame_definitions(const TimeFrameDefinitions& arg) { + this->already_set_up = this->already_set_up && (this->frame_defs == arg); this->frame_defs = arg; } @@ -465,6 +474,7 @@ void PoissonLogLikelihoodWithLinearModelForMeanAndProjData:: set_normalisation_sptr(const shared_ptr& arg) { + this->already_set_up = false; this->normalisation_sptr = arg; } @@ -473,7 +483,8 @@ void PoissonLogLikelihoodWithLinearModelForMeanAndProjData:: set_input_data(const shared_ptr & arg) { - this->proj_data_sptr = dynamic_pointer_cast(arg); + this->already_set_up = false; + this->proj_data_sptr = dynamic_pointer_cast(arg); } template diff --git a/src/recon_test/test_blocks_on_cylindrical_projectors.cxx b/src/recon_test/test_blocks_on_cylindrical_projectors.cxx index d325ff685f..c1d60b6693 100644 --- a/src/recon_test/test_blocks_on_cylindrical_projectors.cxx +++ b/src/recon_test/test_blocks_on_cylindrical_projectors.cxx @@ -217,7 +217,7 @@ BlocksTests::run_symmetry_test(ForwardProjectorByBin& forw_projector1, ForwardPr scannerBlocks_sptr->set_up(); auto proj_data_info_blocks_sptr = std::make_shared(); - proj_data_info_blocks_sptr = set_direct_projdata_info(scannerBlocks_sptr); + proj_data_info_blocks_sptr = set_direct_projdata_info(scannerBlocks_sptr, 2); // now forward-project images // info(boost::format("Test blocks on Cylindrical: Forward projector used: %1%") % forw_projector1.parameter_info()); From 9dada627b35678c35268b95dce67d91b8f6e54f4 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Mon, 22 Jan 2024 23:25:14 +0000 Subject: [PATCH 478/509] nicer error when sensitivity test fails --- recon_test_pack/run_tests.sh | 45 +++++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/recon_test_pack/run_tests.sh b/recon_test_pack/run_tests.sh index 9aa41f4dff..fe7aaf7b66 100755 --- a/recon_test_pack/run_tests.sh +++ b/recon_test_pack/run_tests.sh @@ -4,7 +4,7 @@ # Copyright (C) 2000 - 2001 PARAPET partners # Copyright (C) 2001 - 2009-10-11, Hammersmith Imanet Ltd # Copyright (C) 2011, Kris Thielemans -# Copyright (C) 2013 - 2014, University College London +# Copyright (C) 2013 - 2014, 2024, University College London # This file is part of STIR. # # SPDX-License-Identifier: Apache-2.0 AND License-ref-PARAPET-license @@ -19,7 +19,7 @@ if [ -n "$TRAVIS" -o -n "$GITHUB_WORKSPACE" ]; then set -e fi -echo This script should work with STIR version 5.2. If you have +echo This script should work with STIR version 6.0. If you have echo a later version, you might have to update your test pack. echo Please check the web site. echo @@ -96,16 +96,20 @@ echo --------- TESTS THAT USE INTERPOLATING BACKPROJECTOR -------- echo echo ------------- Running OSMAPOSL for sensitivity ------------- echo Running ${INSTALL_DIR}OSMAPOSL for sensitivity -${MPIRUN} ${INSTALL_DIR}OSMAPOSL OSMAPOSL_test_for_sensitivity.par 1> OSMAPOSL_test_for_sensitivity.log 2> OSMAPOSL_test_for_sensitivity_stderr.log - -echo '---- Comparing output of sensitivity (should be identical up to tolerance)' -echo Running ${INSTALL_DIR}compare_image -if ${INSTALL_DIR}compare_image RPTsens_seg4.hv my_RPTsens_seg4.hv; +if ${MPIRUN} ${INSTALL_DIR}OSMAPOSL OSMAPOSL_test_for_sensitivity.par 1> OSMAPOSL_test_for_sensitivity.log 2> OSMAPOSL_test_for_sensitivity_stderr.log then -echo ---- This test seems to be ok !; + echo '---- Comparing output of sensitivity (should be identical up to tolerance)' + echo Running ${INSTALL_DIR}compare_image + if ${INSTALL_DIR}compare_image RPTsens_seg4.hv my_RPTsens_seg4.hv; + then + echo ---- This test seems to be ok !; + else + echo There were problems here!; + ThereWereErrors=1; + fi else -echo There were problems here!; -ThereWereErrors=1; + echo There were problems here!; + ThereWereErrors=1; fi echo @@ -143,18 +147,23 @@ ${INSTALL_DIR}generate_image generate_uniform_image.par ${INSTALL_DIR}postfilter my_uniform_image_circular.hv my_uniform_image.hv postfilter_truncate_circular_FOV.par echo ------------- Running OSMAPOSL for sensitivity ------------- echo Running ${INSTALL_DIR}OSMAPOSL for sensitivity -${MPIRUN} ${INSTALL_DIR}OSMAPOSL OSMAPOSL_test_PM_for_sensitivity.par 1> sensitivity_PM.log 2> sensitivity_PM_stderr.log - -echo '---- Comparing output of sensitivity (should be identical up to tolerance)' -echo Running ${INSTALL_DIR}compare_image -if ${INSTALL_DIR}compare_image RPTsens_seg3_PM.hv my_RPTsens_seg3_PM.hv; +if ${MPIRUN} ${INSTALL_DIR}OSMAPOSL OSMAPOSL_test_PM_for_sensitivity.par 1> sensitivity_PM.log 2> sensitivity_PM_stderr.log then -echo ---- This test seems to be ok !; + echo '---- Comparing output of sensitivity (should be identical up to tolerance)' + echo Running ${INSTALL_DIR}compare_image + if ${INSTALL_DIR}compare_image RPTsens_seg3_PM.hv my_RPTsens_seg3_PM.hv; + then + echo ---- This test seems to be ok !; + else + echo There were problems here!; + ThereWereErrors=1; + fi else -echo There were problems here!; -ThereWereErrors=1; + echo There were problems here!; + ThereWereErrors=1; fi + echo echo -------- Running OSMAPOSL with the MRP prior -------- echo Running ${INSTALL_DIR}OSMAPOSL From 0d6b884815bd951f31318cce87c48f37a0a1f31c Mon Sep 17 00:00:00 2001 From: Markus Jehl Date: Thu, 25 Jan 2024 14:53:27 +0000 Subject: [PATCH 479/509] Added check for view mashing for generic proj data and fixed the blocks on cylindrical projection teests. Muted excessive logging in forward projector by default (was undone in TOF merge). --- .../ProjDataInfoGenericNoArcCorr.cxx | 4 +++- .../ForwardProjectorByBin.cxx | 4 ++-- .../test_blocks_on_cylindrical_projectors.cxx | 23 +++++++++---------- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/buildblock/ProjDataInfoGenericNoArcCorr.cxx b/src/buildblock/ProjDataInfoGenericNoArcCorr.cxx index ee21c30280..a79eb80ad7 100644 --- a/src/buildblock/ProjDataInfoGenericNoArcCorr.cxx +++ b/src/buildblock/ProjDataInfoGenericNoArcCorr.cxx @@ -66,7 +66,9 @@ ProjDataInfoGenericNoArcCorr(const shared_ptr scanner_sptr, error("ProjDataInfoGenericNoArcCorr: first argument (scanner_ptr) is zero"); if (num_tangential_poss > scanner_sptr->get_max_num_non_arccorrected_bins()) error("ProjDataInfoGenericNoArcCorr: number of tangential positions exceeds the maximum number of non arc-corrected bins set for the scanner."); - + if (scanner_sptr->get_max_num_views() != num_views) + error("ProjDataInfoGenericNoArcCorr: view mashing is not supported"); + uncompressed_view_tangpos_to_det1det2_initialised = false; det1det2_to_uncompressed_view_tangpos_initialised = false; #ifdef STIR_OPENMP_SAFE_BUT_SLOW diff --git a/src/recon_buildblock/ForwardProjectorByBin.cxx b/src/recon_buildblock/ForwardProjectorByBin.cxx index 1195487fe1..0cf35673ba 100644 --- a/src/recon_buildblock/ForwardProjectorByBin.cxx +++ b/src/recon_buildblock/ForwardProjectorByBin.cxx @@ -216,9 +216,9 @@ ForwardProjectorByBin::forward_project(ProjData& proj_data, { const ViewSegmentNumbers vs=vs_nums_to_process[i]; if (proj_data.get_proj_data_info_sptr()->is_tof_data()) - info(boost::format("Processing view %1% of segment %2% of TOF bin %3%") % vs.view_num() % vs.segment_num() % k); + info(boost::format("Processing view %1% of segment %2% of TOF bin %3%") % vs.view_num() % vs.segment_num() % k, 3); else - info(boost::format("Processing view %1% of segment %2%") % vs.view_num() % vs.segment_num()); + info(boost::format("Processing view %1% of segment %2%") % vs.view_num() % vs.segment_num(), 3); RelatedViewgrams viewgrams = proj_data.get_empty_related_viewgrams(vs, symmetries_sptr, false, k); forward_project(viewgrams); diff --git a/src/recon_test/test_blocks_on_cylindrical_projectors.cxx b/src/recon_test/test_blocks_on_cylindrical_projectors.cxx index c1d60b6693..0577ce494b 100644 --- a/src/recon_test/test_blocks_on_cylindrical_projectors.cxx +++ b/src/recon_test/test_blocks_on_cylindrical_projectors.cxx @@ -69,11 +69,9 @@ class BlocksTests : public RunTests private: template - shared_ptr set_blocks_projdata_info(shared_ptr scanner_sptr, int view_fraction = 1, - int bin_fraction = 1); + shared_ptr set_blocks_projdata_info(shared_ptr scanner_sptr, int bin_fraction = 1); template - shared_ptr set_direct_projdata_info(shared_ptr scanner_sptr, int view_fraction = 4, - int bin_fraction = 4); + shared_ptr set_direct_projdata_info(shared_ptr scanner_sptr, int bin_fraction = 4); void run_symmetry_test(ForwardProjectorByBin& forw_projector1, ForwardProjectorByBin& forw_projector2); void run_plane_symmetry_test(ForwardProjectorByBin& forw_projector1, ForwardProjectorByBin& forw_projector2); @@ -87,7 +85,7 @@ class BlocksTests : public RunTests /*! The following is a function to allow a projdata_info BlocksOnCylindrical to be created from the scanner. */ template shared_ptr -BlocksTests::set_blocks_projdata_info(shared_ptr scanner_sptr, int view_fraction, int bin_fraction) +BlocksTests::set_blocks_projdata_info(shared_ptr scanner_sptr, int bin_fraction) { auto segments = scanner_sptr->get_num_rings() - 1; VectorWithOffset num_axial_pos_per_segment(-segments, segments); @@ -102,7 +100,7 @@ BlocksTests::set_blocks_projdata_info(shared_ptr scanner_sptr, int view auto proj_data_info_blocks_sptr = std::make_shared( scanner_sptr, num_axial_pos_per_segment, min_ring_diff_v, max_ring_diff_v, - scanner_sptr->get_max_num_views() / view_fraction, scanner_sptr->get_max_num_non_arccorrected_bins() / bin_fraction); + scanner_sptr->get_max_num_views(), scanner_sptr->get_max_num_non_arccorrected_bins() / bin_fraction); return proj_data_info_blocks_sptr; } @@ -110,7 +108,7 @@ BlocksTests::set_blocks_projdata_info(shared_ptr scanner_sptr, int view /*! Function to generate projection data containing only segment 0. Also allows reducing no. of views and tangential bins. */ template shared_ptr -BlocksTests::set_direct_projdata_info(shared_ptr scanner_sptr, int view_fraction, int bin_fraction) +BlocksTests::set_direct_projdata_info(shared_ptr scanner_sptr, int bin_fraction) { // VectorWithOffset num_axial_pos_per_segment(0, 0); // VectorWithOffset min_ring_diff_v(0, 0); @@ -130,7 +128,7 @@ BlocksTests::set_direct_projdata_info(shared_ptr scanner_sptr, int view auto proj_data_info_blocks_sptr = std::make_shared( scanner_sptr, num_axial_pos_per_segment, min_ring_diff_v, max_ring_diff_v, - scanner_sptr->get_max_num_views() / view_fraction, scanner_sptr->get_max_num_non_arccorrected_bins() / bin_fraction); + scanner_sptr->get_max_num_views(), scanner_sptr->get_max_num_non_arccorrected_bins() / bin_fraction); return proj_data_info_blocks_sptr; } @@ -328,7 +326,7 @@ BlocksTests::run_plane_symmetry_test(ForwardProjectorByBin& forw_projector1, For scannerBlocks_sptr->set_up(); auto proj_data_info_blocks_sptr = std::make_shared(); - proj_data_info_blocks_sptr = set_direct_projdata_info(scannerBlocks_sptr, 1); + proj_data_info_blocks_sptr = set_direct_projdata_info(scannerBlocks_sptr); // now forward-project image @@ -486,7 +484,7 @@ BlocksTests::run_axial_projection_test(ForwardProjectorByBin& forw_projector, Ba auto proj_data_info_blocks_sptr = std::make_shared(); proj_data_info_blocks_sptr - = set_blocks_projdata_info(scannerBlocks_sptr, 4); // now forward-project image + = set_blocks_projdata_info(scannerBlocks_sptr); // now forward-project image shared_ptr> image_sptr(image.clone()); shared_ptr> bck_proj_image_sptr(image.clone()); @@ -582,7 +580,7 @@ BlocksTests::run_map_orientation_test(ForwardProjectorByBin& forw_projector1, Fo scannerBlocks_sptr->set_up(); auto proj_data_info_blocks_sptr = std::make_shared(); - proj_data_info_blocks_sptr = set_direct_projdata_info(scannerBlocks_sptr, 1, 4); + proj_data_info_blocks_sptr = set_direct_projdata_info(scannerBlocks_sptr, 4); Bin bin, bin1, bin2, binR1; CartesianCoordinate3D b1, b2, rb1, rb2; DetectionPosition<> det_pos, det_pos_ord; @@ -618,7 +616,7 @@ BlocksTests::run_map_orientation_test(ForwardProjectorByBin& forw_projector1, Fo scannerBlocks_reord_sptr->set_up(); auto proj_data_info_blocks_reord_sptr = std::make_shared(); - proj_data_info_blocks_reord_sptr = set_direct_projdata_info(scannerBlocks_reord_sptr, 1, 4); + proj_data_info_blocks_reord_sptr = set_direct_projdata_info(scannerBlocks_reord_sptr, 4); // now forward-project images // info(boost::format("Test blocks on Cylindrical: Forward projector used: %1%") % forw_projector1.parameter_info()); @@ -906,6 +904,7 @@ BlocksTests::run_tests() run_projection_test(forw_projector1, forw_projector1_parallelproj); print_time("projection cpu vs. gpu comparison test took: "); #endif + timer.stop(); } END_NAMESPACE_STIR From 0039d69e6b92d4f4ea3633d085985421de74b0dc Mon Sep 17 00:00:00 2001 From: Markus Jehl Date: Fri, 26 Jan 2024 13:10:30 +0000 Subject: [PATCH 480/509] Downsampled scanner axially for one BlocksOnCylindrical projection test and added its debug build to the github workflow. --- .github/workflows/build-test.yml | 2 +- src/recon_test/test_blocks_on_cylindrical_projectors.cxx | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 1be05e812f..c7b200449a 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -314,7 +314,7 @@ jobs: cd ${GITHUB_WORKSPACE}/build # don't run all of them in Debug mode as it takes too long if test ${BUILD_TYPE} = Debug; then - EXCLUDE="-E test_data_processor_projectors|test_export_array|test_ArcCorrection|test_blocks_on_cylindrical_projectors" + EXCLUDE="-E test_data_processor_projectors|test_export_array|test_ArcCorrection" fi ctest --output-on-failure -C ${BUILD_TYPE} ${EXCLUDE} diff --git a/src/recon_test/test_blocks_on_cylindrical_projectors.cxx b/src/recon_test/test_blocks_on_cylindrical_projectors.cxx index 0577ce494b..163935c92a 100644 --- a/src/recon_test/test_blocks_on_cylindrical_projectors.cxx +++ b/src/recon_test/test_blocks_on_cylindrical_projectors.cxx @@ -480,6 +480,9 @@ BlocksTests::run_axial_projection_test(ForwardProjectorByBin& forw_projector, Ba auto scannerBlocks_sptr = std::make_shared(Scanner::SAFIRDualRingPrototype); scannerBlocks_sptr->set_scanner_geometry("BlocksOnCylindrical"); + scannerBlocks_sptr->set_num_axial_crystals_per_block(scannerBlocks_sptr->get_num_axial_crystals_per_block() / 4); + scannerBlocks_sptr->set_axial_block_spacing(scannerBlocks_sptr->get_axial_block_spacing() * 4); + scannerBlocks_sptr->set_num_rings(scannerBlocks_sptr->get_num_rings() / 4); scannerBlocks_sptr->set_up(); auto proj_data_info_blocks_sptr = std::make_shared(); From 83b80369ba0e5faa1027a23820f6961e43b9c7d2 Mon Sep 17 00:00:00 2001 From: robbietuk Date: Fri, 26 Jan 2024 12:59:42 -0800 Subject: [PATCH 481/509] Remove duplicate SplitEmptyFunction: false from clang-format --- .clang-format | 2 -- 1 file changed, 2 deletions(-) diff --git a/.clang-format b/.clang-format index d2e399865d..0d026b17ac 100644 --- a/.clang-format +++ b/.clang-format @@ -25,8 +25,6 @@ BraceWrapping: SplitEmptyFunction: false SplitEmptyRecord: true SplitEmptyNamespace: true - # non-standard GNU - SplitEmptyFunction: false ColumnLimit: 130 IndentPPDirectives: AfterHash PointerAlignment: Left From 3f9cfc628dfc78aeac95f21ce58c5f867bf2ed3e Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sat, 27 Jan 2024 15:15:03 +0000 Subject: [PATCH 482/509] require CMake 3.14 clean-up CMake files accordingly --- CMakeLists.txt | 13 +----- documentation/release_6.0.htm | 4 +- src/CMakeLists.txt | 21 ++------- src/swig/CMakeLists.txt | 88 ++++++++++------------------------- 4 files changed, 34 insertions(+), 92 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index fc7b94e512..c21efbf785 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,11 +5,7 @@ # See STIR/LICENSE.txt for details # cmake file for building STIR. See the STIR User's Guide and http://www.cmake.org. -if (CMAKE_HOST_WIN32) - cmake_minimum_required(VERSION 3.12.0) -else() - cmake_minimum_required(VERSION 3.1.0) -endif() +cmake_minimum_required(VERSION 3.14.0) # enable ccache https://ccache.samba.org/ find_program(CCACHE_PROGRAM ccache) @@ -249,12 +245,7 @@ set (STIR_INCLUDE_DIRS "include") # the sources and tell the user to explicitly use STIR_REGISTRIES. # This will be set to either a list of source files, or a list of object files. # See STIRConfig.cmake.in on how we cope with this -if (CMAKE_VERSION VERSION_LESS 3.12) - message(WARNING "Your CMake version is older than 3.12. The STIR_REGISTRIES - will be exported as source files only. Please upgrade CMake!") -else() - install(TARGETS stir_registries EXPORT STIRTargets DESTINATION lib) -endif() +install(TARGETS stir_registries EXPORT STIRTargets DESTINATION lib) # install the registry sources for older CMake versions install(FILES ${STIR_REGISTRIES} DESTINATION ${STIR_DATA_DIR}/src) diff --git a/documentation/release_6.0.htm b/documentation/release_6.0.htm index cc8be1cfa7..41d478dcbd 100644 --- a/documentation/release_6.0.htm +++ b/documentation/release_6.0.htm @@ -199,7 +199,9 @@

    Changed functionality

Build system

- No major changes. +
    +
  • CMake version 3.14 is now required.
  • +

Known problems

See our issue tracker.

diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 69748da973..f4c462799e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -153,14 +153,7 @@ if(STIR_OPENMP) find_package(OpenMP REQUIRED) add_definitions(${OpenMP_CXX_FLAGS}) - if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.9.0") - set (OpenMP_EXE_LINKER_FLAGS OpenMP::OpenMP_CXX) - else() - message(WARNING "Your CMake version is old. OpenMP linking flags might be incorrect.") - # need to explicitly set this. Definitely for gcc, hopefully also for other systems. - # See https://gitlab.kitware.com/cmake/cmake/issues/15392 - set (OpenMP_EXE_LINKER_FLAGS ${OpenMP_CXX_FLAGS}) - endif() + set (OpenMP_EXE_LINKER_FLAGS OpenMP::OpenMP_CXX) endif() #### Flags for compatibility between different systems @@ -179,15 +172,9 @@ check_function_exists(getopt HAVE_SYSTEM_GETOPT) # we have to do this ourselves. # Finally, we need to cope with the case where a user passes -std=cxx11 by hand to CMAKE_CXX_FLAGS. set(CMAKE_REQUIRED_DEFINITIONS ${CMAKE_CXX_FLAGS}) -if (CMAKE_VERSION VERSION_LESS 3.8) - if (CMAKE_CXX_STANDARD) - set(CMAKE_REQUIRED_DEFINITIONS -std=cxx${CMAKE_CXX_STANDARD} ${CMAKE_REQUIRED_DEFINITIONS}) - endif() -else() - # set policy 0067 to let try_compile honor CMAKE_CXX_STANDARD etc - # Note: need to set this before including CheckCXXSymbolExists - cmake_policy(SET CMP0067 NEW) -endif() +# set policy 0067 to let try_compile honor CMAKE_CXX_STANDARD etc +# Note: need to set this before including CheckCXXSymbolExists +cmake_policy(SET CMP0067 NEW) #set(CMAKE_REQUIRED_INCLUDES ${Boost_INCLUDE_DIRS}) #set(CMAKE_REQUIRED_QUIET FALSE) # Finally, do the checks! diff --git a/src/swig/CMakeLists.txt b/src/swig/CMakeLists.txt index 4ffe12d1d5..4110241d6b 100644 --- a/src/swig/CMakeLists.txt +++ b/src/swig/CMakeLists.txt @@ -11,14 +11,10 @@ set(dir swig) # UseSWIG chooses its own modulename as target (LEGACY) -if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.13.0") - cmake_policy(SET CMP0078 OLD) -endif() +cmake_policy(SET CMP0078 OLD) # UseSWIG honors SWIG_MODULE_NAME via -module flag (but we're not using it currently) -if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.14.0") - cmake_policy(SET CMP0086 NEW) -endif() +cmake_policy(SET CMP0086 NEW) if(BUILD_SWIG_PYTHON OR BUILD_SWIG_OCTAVE OR BUILD_SWIG_MATLAB) @@ -76,21 +72,13 @@ if(BUILD_SWIG_PYTHON OR BUILD_SWIG_OCTAVE OR BUILD_SWIG_MATLAB) # To get round this, some include files in STIR have "#ifdef SWIG" statements. # We need to compile the wrapper with -DSWIG. # However, none of the other STIR files (including the registries) should be compiled with -DSWIG - # We will do this using the SWIG_GENERATED_COMPILE_DEFINITIONS property, if supported by CMake - if (CMAKE_VERSION VERSION_LESS 3.12) - message(WARNING "Our work-around for a SWIG problem needs CMake 3.12 or more recent. We recommend to upgrade CMake to 3.12 or later.") - function(SWIG_WORKAROUND module) - SET_TARGET_PROPERTIES(${module} PROPERTIES COMPILE_FLAGS -DSWIG) - # example attempt to only add it to the source only on older CMake, but KT cannot get it to work - # set_property(SOURCE CMakeFiles/_stir.dir/stirPYTHON_wrap.cxx PROPERTY COMPILE_DEFINITIONS SWIG) - endfunction() - else() - # KT thought this would work, but it doesn't. So instead, we use a similar line for each TARGET - # set_property(SOURCE stir.i PROPERTY SWIG_GENERATED_COMPILE_DEFINITIONS SWIG) - function(SWIG_WORKAROUND module) - set_property(TARGET ${module} PROPERTY SWIG_GENERATED_COMPILE_DEFINITIONS SWIG) - endfunction() - endif() + # We will do this using the SWIG_GENERATED_COMPILE_DEFINITIONS property + + # KT thought this would work, but it doesn't. So instead, we use a similar line for each TARGET + # set_property(SOURCE stir.i PROPERTY SWIG_GENERATED_COMPILE_DEFINITIONS SWIG) + function(SWIG_WORKAROUND module) + set_property(TARGET ${module} PROPERTY SWIG_GENERATED_COMPILE_DEFINITIONS SWIG) + endfunction() #endif() endif() @@ -118,43 +106,25 @@ set(swig_stir_dependencies ) if(BUILD_SWIG_PYTHON) - if (${CMAKE_VERSION} VERSION_LESS "3.14") # from 3.14 findPython has Numpy support - if (Python_EXECUTABLE) - set (PYTHON_EXECUTABLE ${Python_EXECUTABLE}) - endif() - # find Python interpreter. Needed for tests, and best to enforce consistency anyway. - find_package(PythonInterp QUIET) - find_package(PythonLibs REQUIRED) - find_package(Numpy REQUIRED) - # TODO would be better to use target_include_directories - INCLUDE_DIRECTORIES(${PYTHON_INCLUDE_PATH}) - INCLUDE_DIRECTORIES(${NUMPY_INCLUDE_DIRS}) - set(STIR_Python_dependency ${PYTHON_LIBRARIES}) - else() - if (PYTHON_EXECUTABLE) - message(WARNING "Usage of PYTHON_EXECUTABLE is deprecated. Use Python_EXECUTABLE instead.") - set (Python_EXECUTABLE ${PYTHON_EXECUTABLE}) - endif() - find_package(Python REQUIRED COMPONENTS Interpreter Development NumPy) - message(STATUS "We will use Python::Module Python::NumPy targets. FYI, this is (roughly) what it corresponds to:\n" + if (PYTHON_EXECUTABLE) + message(WARNING "Usage of PYTHON_EXECUTABLE is deprecated. Use Python_EXECUTABLE instead.") + set (Python_EXECUTABLE ${PYTHON_EXECUTABLE}) + endif() + find_package(Python REQUIRED COMPONENTS Interpreter Development NumPy) + message(STATUS "We will use Python::Module Python::NumPy targets. FYI, this is (roughly) what it corresponds to:\n" ".... Python_LIBRARIES: ${Python_LIBRARIES}\n" ".... Python_INCLUDE_DIRS: ${Python_INCLUDE_DIRS}\n" ".... Python_NumPy_INCLUDE_DIRS: ${Python_NumPy_INCLUDE_DIRS}") - set(STIR_Python_dependency Python::Module Python::NumPy) - endif() + set(STIR_Python_dependency Python::Module Python::NumPy) # TODO probably better to call the module stirpy or something # TODO -builtin option only appropriate for python # while the next statement sets it for all modules called stir SET(SWIG_MODULE_stir_EXTRA_FLAGS -builtin ${SWIG_DOXY_OPTIONS}) set(SWIG_MODULE_stir_EXTRA_DEPS ${swig_stir_dependencies}) - if (CMAKE_VERSION VERSION_LESS "3.8") - SWIG_ADD_MODULE(stir python stir.i $) - else() - SWIG_ADD_LIBRARY(stir LANGUAGE python TYPE MODULE SOURCES stir.i $) - if("${CMAKE_CXX_COMPILER_ID}" MATCHES "MSVC") - set_property(TARGET ${SWIG_MODULE_stir_REAL_NAME} PROPERTY SWIG_GENERATED_COMPILE_OPTIONS /bigobj) - endif() + SWIG_ADD_LIBRARY(stir LANGUAGE python TYPE MODULE SOURCES stir.i $) + if("${CMAKE_CXX_COMPILER_ID}" MATCHES "MSVC") + set_property(TARGET ${SWIG_MODULE_stir_REAL_NAME} PROPERTY SWIG_GENERATED_COMPILE_OPTIONS /bigobj) endif() SWIG_WORKAROUND(${SWIG_MODULE_stir_REAL_NAME}) SWIG_LINK_LIBRARIES(stir PUBLIC ${STIR_LIBRARIES} ${STIR_Python_dependency}) @@ -198,13 +168,9 @@ if (BUILD_SWIG_OCTAVE) SET(SWIG_MODULE_stiroct_EXTRA_FLAGS -module stiroct ${SWIG_DOXY_OPTIONS}) set(SWIG_MODULE_stiroct_EXTRA_DEPS ${swig_stir_dependencies}) - if (CMAKE_VERSION VERSION_LESS "3.8") - SWIG_ADD_MODULE(stiroct octave stir.i $) - else() - SWIG_ADD_LIBRARY(stiroct LANGUAGE octave TYPE MODULE SOURCES stir.i $) - if("${CMAKE_CXX_COMPILER_ID}" MATCHES "MSVC") - set_property(TARGET ${SWIG_MODULE_stiroct_REAL_NAME} PROPERTY SWIG_GENERATED_COMPILE_OPTIONS /bigobj) - endif() + SWIG_ADD_LIBRARY(stiroct LANGUAGE octave TYPE MODULE SOURCES stir.i $) + if("${CMAKE_CXX_COMPILER_ID}" MATCHES "MSVC") + set_property(TARGET ${SWIG_MODULE_stiroct_REAL_NAME} PROPERTY SWIG_GENERATED_COMPILE_OPTIONS /bigobj) endif() SET_TARGET_PROPERTIES(${SWIG_MODULE_stiroct_REAL_NAME} PROPERTIES SUFFIX ${OCTAVE_SUFFIX} PREFIX "${OCTAVE_PREFIX}") SWIG_WORKAROUND(${SWIG_MODULE_stiroct_REAL_NAME}) @@ -227,13 +193,9 @@ if (BUILD_SWIG_MATLAB) SET(SWIG_MODULE_stirMATLAB_EXTRA_FLAGS -module ${module_name} ${SWIG_DOXY_OPTIONS}) set(SWIG_MODULE_stirMATLAB_EXTRA_DEPS ${swig_stir_dependencies}) # TODO depending on SWIG version add "-mexname stirMATLAB_wrap" above - if (CMAKE_VERSION VERSION_LESS "3.8") - SWIG_ADD_MODULE(stirMATLAB matlab stir.i $) - else() - SWIG_ADD_LIBRARY(stirMATLAB LANGUAGE matlab TYPE MODULE SOURCES stir.i $) - if("${CMAKE_CXX_COMPILER_ID}" MATCHES "MSVC") - set_property(TARGET ${SWIG_MODULE_stirMATLAB_REAL_NAME} PROPERTY SWIG_GENERATED_COMPILE_OPTIONS /bigobj) - endif() + SWIG_ADD_LIBRARY(stirMATLAB LANGUAGE matlab TYPE MODULE SOURCES stir.i $) + if("${CMAKE_CXX_COMPILER_ID}" MATCHES "MSVC") + set_property(TARGET ${SWIG_MODULE_stirMATLAB_REAL_NAME} PROPERTY SWIG_GENERATED_COMPILE_OPTIONS /bigobj) endif() if (WIN32) set (Matlab_CXXLINKER_FLAGS "/EXPORT:mexFunction") From 4be06de32ffc099fdadf4c7e716306cc9fae0911 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sat, 27 Jan 2024 15:17:49 +0000 Subject: [PATCH 483/509] require C++-14 --- CMakeLists.txt | 4 +++- documentation/release_6.0.htm | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c21efbf785..7ca8bddfeb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,7 +31,9 @@ endif() set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/src/cmake;${CMAKE_MODULE_PATH}") include(src/cmake/SetC++Version.cmake) -UseCXX(11) +# minimum C++ version required (you can still ask for a more recent one +# by setting CMAKE_CXX_STANDARD) +UseCXX(14) # set default build-type to Release if(NOT CMAKE_BUILD_TYPE) diff --git a/documentation/release_6.0.htm b/documentation/release_6.0.htm index 41d478dcbd..fc8cebd726 100644 --- a/documentation/release_6.0.htm +++ b/documentation/release_6.0.htm @@ -201,6 +201,11 @@

Changed functionality

Build system

  • CMake version 3.14 is now required.
  • +
  • + C++-14 is now required.
    + In fact, it is possible that C++-11 still works. If you really need it, + you can try to modify the main CMakeLists.txt accordingly. +

Known problems

From 4825b0db1caa399909e84aec2309a04612d5d52e Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sat, 27 Jan 2024 20:32:01 +0000 Subject: [PATCH 484/509] work around swig 4.2 problem See https://github.com/swig/swig/issues/2768 --- src/swig/stir_dataprocessors.i | 56 +++++++++++++++++----------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/src/swig/stir_dataprocessors.i b/src/swig/stir_dataprocessors.i index b22cfcbde1..cf9868753b 100644 --- a/src/swig/stir_dataprocessors.i +++ b/src/swig/stir_dataprocessors.i @@ -3,34 +3,34 @@ %shared_ptr(stir::DataProcessor >) %shared_ptr(stir::RegisteredParsingObject< stir::ChainedDataProcessor >, - stir::DataProcessor >, - stir::DataProcessor > >) + stir::DataProcessor >, + stir::DataProcessor > >) %shared_ptr(stir::ChainedDataProcessor >) %shared_ptr(stir::RegisteredParsingObject, - stir::DataProcessor >, - stir::DataProcessor > >) + stir::DataProcessor >, + stir::DataProcessor > >) %shared_ptr(stir::SeparableCartesianMetzImageFilter) %shared_ptr(stir::RegisteredParsingObject, - stir::DataProcessor >, - stir::DataProcessor > >) + stir::DataProcessor >, + stir::DataProcessor > >) %shared_ptr(stir::SeparableGaussianImageFilter) %shared_ptr(stir::RegisteredParsingObject, - stir::DataProcessor >, - stir::DataProcessor > >) + stir::DataProcessor >, + stir::DataProcessor > >) %shared_ptr(stir::SeparableConvolutionImageFilter) %shared_ptr(stir::RegisteredParsingObject, - stir::DataProcessor >, - stir::DataProcessor > >) + stir::DataProcessor >, + stir::DataProcessor > >) %shared_ptr(stir::TruncateToCylindricalFOVImageProcessor) -%shared_ptr(stir::RegisteredParsingObject >, - stir::DataProcessor >, - stir::DataProcessor > >) -%shared_ptr(stir::HUToMuImageProcessor >) +%shared_ptr(stir::RegisteredParsingObject >, + stir::DataProcessor >, + stir::DataProcessor > >) +%shared_ptr(stir::HUToMuImageProcessor >) #undef elemT #endif @@ -46,38 +46,38 @@ %template(DataProcessor3DFloat) stir::DataProcessor >; %template(RPChainedDataProcessor3DFloat) stir::RegisteredParsingObject< stir::ChainedDataProcessor >, - stir::DataProcessor >, - stir::DataProcessor > >; + stir::DataProcessor >, + stir::DataProcessor > >; %template(ChainedDataProcessor3DFloat) stir::ChainedDataProcessor >; %template(RPSeparableCartesianMetzImageFilter3DFloat) stir::RegisteredParsingObject< stir::SeparableCartesianMetzImageFilter, - stir::DataProcessor >, - stir::DataProcessor > >; + stir::DataProcessor >, + stir::DataProcessor > >; %template(SeparableCartesianMetzImageFilter3DFloat) stir::SeparableCartesianMetzImageFilter; %template(RPSeparableGaussianImageFilter3DFloat) stir::RegisteredParsingObject< stir::SeparableGaussianImageFilter, - stir::DataProcessor >, -stir::DataProcessor > >; + stir::DataProcessor >, +stir::DataProcessor > >; %template(SeparableGaussianImageFilter3DFloat) stir::SeparableGaussianImageFilter; %template(RPSeparableConvolutionImageFilter3DFloat) stir::RegisteredParsingObject< stir::SeparableConvolutionImageFilter, - stir::DataProcessor >, -stir::DataProcessor > >; + stir::DataProcessor >, +stir::DataProcessor > >; %template(SeparableConvolutionImageFilter3DFloat) stir::SeparableConvolutionImageFilter; %template(RPTruncateToCylindricalFOVImageProcessor3DFloat) stir::RegisteredParsingObject< stir::TruncateToCylindricalFOVImageProcessor, - stir::DataProcessor >, -stir::DataProcessor > >; + stir::DataProcessor >, +stir::DataProcessor > >; %template(TruncateToCylindricalFOVImageProcessor3DFloat) stir::TruncateToCylindricalFOVImageProcessor; %template(RPHUToMuImageProcessor3DFloat) stir::RegisteredParsingObject< - stir::HUToMuImageProcessor >, - stir::DataProcessor >, - stir::DataProcessor > >; + stir::HUToMuImageProcessor >, + stir::DataProcessor >, + stir::DataProcessor > >; -%template(HUToMuImageProcessor3DFloat) stir::HUToMuImageProcessor >; +%template(HUToMuImageProcessor3DFloat) stir::HUToMuImageProcessor >; #undef elemT From a5582cd80aad71c910660f705509d11f870b6fcc Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sun, 28 Jan 2024 07:54:19 +0000 Subject: [PATCH 485/509] removed VOLPET and obsolete GE_IO code --- CMakeLists.txt | 5 - documentation/STIR-UsersGuide.tex | 7 - documentation/release_6.0.htm | 4 + src/CMakeLists.txt | 9 - src/buildblock/CMakeLists.txt | 1 - src/buildblock/DiscretisedDensity.cxx | 45 ---- src/buildblock/ProjData.cxx | 69 +----- src/buildblock/ProjDataGEAdvance.cxx | 323 -------------------------- src/cmake/STIRConfig.h.in | 2 - src/include/stir/ProjDataGEAdvance.h | 104 --------- 10 files changed, 7 insertions(+), 562 deletions(-) delete mode 100644 src/buildblock/ProjDataGEAdvance.cxx delete mode 100644 src/include/stir/ProjDataGEAdvance.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 7ca8bddfeb..a1e0486e33 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -92,7 +92,6 @@ find_package( Boost 1.36.0 REQUIRED ) option(DISABLE_LLN_MATRIX "disable use of LLN library" OFF) option(DISABLE_ITK "disable use of ITK library" OFF) option(DISABLE_AVW "disable use of AVW library" OFF) -option(DISABLE_RDF "disable use of GE RDF library" OFF) option(DISABLE_HDF5 "disable use of HDF5 libraries" OFF) option(DISABLE_STIR_LOCAL "disable use of LOCAL extensions to STIR" OFF) option(DISABLE_CERN_ROOT "disable use of Cern ROOT libraries" OFF) @@ -131,10 +130,6 @@ if(NOT DISABLE_AVW) find_package(AVW) endif() -if(NOT DISABLE_RDF) - find_package(RDF) -endif() - if(NOT DISABLE_HDF5) find_package(HDF5 COMPONENTS CXX) endif() diff --git a/documentation/STIR-UsersGuide.tex b/documentation/STIR-UsersGuide.tex index 0d14db36be..64fc5dbf05 100644 --- a/documentation/STIR-UsersGuide.tex +++ b/documentation/STIR-UsersGuide.tex @@ -718,13 +718,6 @@ \subsubsection{Siemens interfile-like} proposed Interfile standard but with some variations. We read this since STIR 4.0, but do not write it. -\subsubsection{VOLPET sinograms} - -The GE VOLPET sinogram format for the GE Advance was supported for reading, but -only for data files with a single data set, maximum ring -difference 11 and contain 281 bins * 256 segments * 336 views. -This has not been tested in a long time so will be removed in a future version. - \subsubsection{RDF9 data} See section \ref{sec:RDFsupport} for enabling support for data from GE scanners that use the GE RDF format. We currently support uncompressed listmode, diff --git a/documentation/release_6.0.htm b/documentation/release_6.0.htm index fc8cebd726..3f4940486e 100644 --- a/documentation/release_6.0.htm +++ b/documentation/release_6.0.htm @@ -51,6 +51,10 @@

Changes breaking backwards compatibility from a user-perspective

number of views or rings. This was using very old scanners though, and could lead to confusion. These guesses have now been removed. +
  • + support for the GE VOLPET format (an old format used by the GE Advance and Discover LS + sinograms when using "break-pointing") has been removed. +
  • Bug fixes

    diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f4c462799e..4db7d6b76c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -57,15 +57,6 @@ else() message(STATUS "AVW IO library not used.") endif() -if (RDF_FOUND) - set(HAVE_RDF ON) - message(STATUS "GE RDF support enabled.") - include_directories(${RDF_INCLUDE_DIRS}) - -else() - message(STATUS "RDF support disabled.") -endif() - if ((NOT DISABLE_HDF5) AND HDF5_FOUND) set(HAVE_HDF5 ON) message(STATUS "HDF5 support enabled.") diff --git a/src/buildblock/CMakeLists.txt b/src/buildblock/CMakeLists.txt index 1d90c88c6a..2307dd71ed 100644 --- a/src/buildblock/CMakeLists.txt +++ b/src/buildblock/CMakeLists.txt @@ -19,7 +19,6 @@ set(${dir_LIB_SOURCES} ProjDataInfoSubsetByView.cxx ArcCorrection.cxx ProjDataFromStream.cxx - ProjDataGEAdvance.cxx ProjDataInMemory.cxx ProjDataInterfile.cxx Scanner.cxx diff --git a/src/buildblock/DiscretisedDensity.cxx b/src/buildblock/DiscretisedDensity.cxx index d50ad342c4..5dcb29303f 100644 --- a/src/buildblock/DiscretisedDensity.cxx +++ b/src/buildblock/DiscretisedDensity.cxx @@ -35,9 +35,6 @@ #endif #include "stir/VoxelsOnCartesianGrid.h" #include "stir/is_null_ptr.h" -#ifdef STIR_USE_GE_IO -#include "stir_experimental/IO/GE/niff.h" -#endif #endif #include @@ -139,48 +136,6 @@ DiscretisedDensity:: } #endif // HAVE_LLN_MATRIX -#ifdef STIR_USE_GE_IO - // NIFF file format (a simple almost raw format from GE) - { - using namespace GE_IO; - if (Niff::isReadableNiffFile(filename)) - { - Niff::Niff input_niff(filename, std::ios::in | std::ios::out); - if (input_niff.get_num_dimensions() != 3) - { - warning("DiscretisedDensity::read_from_file:\n" - "File %s is a NIFF file but has %d dimensions (should be 3)", - filename.c_str(), input_niff.get_num_dimensions()); - return 0; - } - CartesianCoordinate3D voxel_size; - { - std::vector pixel_spacing = input_niff.get_pixel_spacing(); - std::copy(pixel_spacing.begin(), pixel_spacing.end(), voxel_size.begin()); - } - Array<3, float> data; - { - input_niff.fill_array(data); - // change sizes to standard STIR convention - for (int z=data.get_min_index(); z<= data.get_max_index(); ++z) - { - data[z].set_min_index(-(data[z].get_length()/2)); - for (int y=data[z].get_min_index(); y<= data[z].get_max_index(); ++y) - { - data[z][y].set_min_index(-(data[z][y].get_length()/2)); - } - } - } - CartesianCoordinate3D origin; - origin.fill(0); - - return - new VoxelsOnCartesianGrid(data, - origin, - voxel_size); - } - } -#endif #ifdef HAVE_LLN_MATRIX { diff --git a/src/buildblock/ProjData.cxx b/src/buildblock/ProjData.cxx index c8582bab03..515b72f9cc 100644 --- a/src/buildblock/ProjData.cxx +++ b/src/buildblock/ProjData.cxx @@ -32,7 +32,6 @@ #include "stir/Viewgram.h" #include "stir/DataSymmetriesForViewSegmentNumbers.h" -#include "stir/utilities.h" // TODO remove (temporary for test GEAdvance) // for read_from_file #include "stir/IO/FileSignature.h" #include "stir/IO/interfile.h" @@ -43,18 +42,6 @@ #include "stir/Viewgram.h" -#ifndef STIR_USE_GE_IO -#include "stir/ProjDataGEAdvance.h" -#else -#include "stir_experimental/IO/GE/ProjDataVOLPET.h" -#ifdef HAVE_RDF -#include "stir_experimental/IO/GE/stir_RDF.h" -#include "stir_experimental/IO/GE/ProjDataRDF.h" -#endif -#endif // STIR_USE_GE_IO -#ifdef HAVE_IE -#include "stir_experimental/IO/GE/ProjDataIE.h" -#endif #ifdef HAVE_HDF5 #include "stir/ProjDataGEHDF5.h" #include "stir/IO/GEHDF5Wrapper.h" @@ -90,9 +77,9 @@ START_NAMESPACE_STIR Currently supported:
      -
    • GE VOLPET data (via class ProjDataVOLPET)
    • Interfile (using read_interfile_PDFS()) -
    • ECAT 7 3D sinograms and attenuation files +
    • ECAT 7 3D sinograms and attenuation files + >li> GE RDF9 (in HDF5)
    Developer's note: ideally the return value would be an stir::unique_ptr. @@ -118,44 +105,7 @@ read_from_file(const string& filename, error("ProjData::read_from_file: error opening file %s", actual_filename.c_str()); const FileSignature file_signature(actual_filename); - const char * signature = file_signature.get_signature(); - - // GE Advance - if (strncmp(signature, "2D3D", 4) == 0) - { -// if (ask("Read with old code (Y) or new (N)?",false)) -#ifndef STIR_USE_GE_IO - { -#ifndef NDEBUG - info("ProjData::read_from_file trying to read " + filename + " as GE Advance file", 3); -#endif - return shared_ptr( new ProjDataGEAdvance(input) ); - } - //else -#else // use VOLPET - { -#ifndef NDEBUG - info("ProjData::read_from_file trying to read " + filename + " as GE VOLPET file", 3); -#endif - delete input;// TODO no longer use pointer after getting rid of ProjDataGEAdvance - return shared_ptr( new GE_IO::ProjDataVOLPET(filename, openmode) ); - } -#endif // STIR_USE_GE_IO to differentiate between Advance and VOLPET code - } - - delete input;// TODO no longer use pointer after getting rid of ProjDataGEAdvance - -#ifdef HAVE_IE - // GE IE file format - if (GE_IO::is_IE_signature(signature)) - { -#ifndef NDEBUG - info("ProjData::read_from_file trying to read " + filename + " as GE IE file", 3); -#endif - return shared_ptr( new GE_IO::ProjDataIE(filename) ); - } -#endif // HAVE_IE - + const char * signature = file_signature.get_signature(); #ifdef HAVE_LLN_MATRIX // ECAT 7 @@ -191,19 +141,6 @@ read_from_file(const string& filename, if (!is_null_ptr(ptr)) return ptr; } - - -#if defined(STIR_USE_GE_IO) && defined(HAVE_RDF) - if (GE_IO::is_RDF_file(actual_filename)) - { -#ifndef NDEBUG - info("ProjData::read_from_file trying to read " + filename + " as RDF", 3); -#endif - shared_ptr ptr(new GE_IO::ProjDataRDF(filename)); - if (!is_null_ptr(ptr)) - return ptr; - } -#endif // RDF #ifdef HAVE_HDF5 if (GE::RDF_HDF5::GEHDF5Wrapper::check_GE_signature(actual_filename)) diff --git a/src/buildblock/ProjDataGEAdvance.cxx b/src/buildblock/ProjDataGEAdvance.cxx deleted file mode 100644 index 30a119bf84..0000000000 --- a/src/buildblock/ProjDataGEAdvance.cxx +++ /dev/null @@ -1,323 +0,0 @@ -/*! - - \file - \ingroup projdata - - \brief Implementations for class stir::ProjDataGEAdvance - - \author Damiano Belluzzo - \author Kris Thielemans - \author PARAPET project -*/ -/* - Copyright (C) 2000 PARAPET partners - Copyright (C) 2000 - 2009-06-22, Hammersmith Imanet Ltd - Copyright (C) 2011, Kris Thielemans - This file is part of STIR. - - SPDX-License-Identifier: Apache-2.0 AND License-ref-PARAPET-license - - See STIR/LICENSE.txt for details -*/ - - - -#include "stir/ProjDataGEAdvance.h" -#include "stir/Succeeded.h" -#include "stir/Viewgram.h" -#include "stir/Sinogram.h" -#include "stir/Scanner.h" -#include "stir/Succeeded.h" -#include "stir/IO/read_data.h" -#include "stir/warning.h" -#include "stir/error.h" - -#include -#include -#include -#include - -#ifndef STIR_NO_NAMESPACES -using std::vector; -using std::ios; -using std::accumulate; -using std::find; -using std::iostream; -using std::streamoff; -#endif - -START_NAMESPACE_STIR - -ProjDataGEAdvance::ProjDataGEAdvance(iostream* s) - : - sino_stream(s), - offset(0), - on_disk_data_type(NumericType::SHORT), - on_disk_byte_order(ByteOrder::big_endian) -{ - // TODO find from file - const int max_delta = 11; - shared_ptr scanner_sptr(new Scanner(Scanner::Advance)); - proj_data_info_sptr.reset( - ProjDataInfo::ProjDataInfoGE(scanner_sptr, max_delta, - scanner_sptr->get_max_num_views(), - scanner_sptr->get_default_num_arccorrected_bins())); - - - const int min_view = proj_data_info_sptr->get_min_view_num(); - const int max_view = proj_data_info_sptr->get_max_view_num(); - view_scaling_factor.grow(min_view, max_view); - - const int offset_begin = 256; - sino_stream->seekg(offset, ios::beg); // overall offset - sino_stream->seekg(offset_begin, ios::cur); // "breakfile" offset - - // reads the views_scaling_factors - { - float scale = float(1); - // KT 18/10/99 added on_disk_byte_order - if (read_data(*sino_stream, view_scaling_factor, - NumericType::FLOAT, - scale, - on_disk_byte_order) == Succeeded::no - || scale != 1) - error("ProjDataGEAdvance: error reading scale factors from header\n"); - - // for ( int k=min_view ; k <= max_view ; k++) - // { - // cout << "view_scaling_factor[" << k << "] = " - // << view_scaling_factor[k] << endl; - // } - } - - //============================================================= - // parameters as in the unsorted case (help to access the file) - //============================================================= - //------------------------------------------------------------- - // WARNING: this construction works only with odd max_delta - //------------------------------------------------------------- - - int num_segments_orig = proj_data_info_sptr->get_max_segment_num()+1; - //(int) get_max_average_ring_difference(); - - num_rings_orig.resize(num_segments_orig); - segment_sequence_orig.resize(num_segments_orig); - - - const int num_rings = proj_data_info_sptr->get_scanner_ptr()->get_num_rings(); - num_rings_orig[0]=(2*num_rings-1); - - { - for (unsigned int i=1; i<= num_rings_orig.size()/2; i++) - { - num_rings_orig[2*i-1]=(2*num_rings-1) - 4*i; - num_rings_orig[2*i]=(2*num_rings-1) - 4*i; - // cout << "num_rings_orig[" << 2*i-1 << "] = " << num_rings_orig[2*i-1] << endl; - // cout << "num_rings_orig[" << 2*i << "] = " << num_rings_orig[2*i] << endl; - } - } - - - - // segment sequence in the unsorted data format - - segment_sequence_orig[0]=0; - - { - for (unsigned int i=1; i<= segment_sequence_orig.size()/2; i++) - { - segment_sequence_orig[2*i-1] = static_cast(i); - segment_sequence_orig[2*i] = -static_cast(i); - // cout << "segment_sequence_orig[" << 2*i-1 << "] = " << segment_sequence_orig[2*i-1] << endl; - // cout << "segment_sequence_orig[" << 2*i << "] = " << segment_sequence_orig[2*i] << endl; - } - } - -} - -Viewgram -ProjDataGEAdvance:: -get_viewgram(const int view_num, const int segment_num, - const bool make_num_tangential_poss_odd, const int timing_pos) const - { - // -------------------------------------------------------- - // -------------------------------------------------------- - // Advance GE format reader with sort of the segments - // -------------------------------------------------------- - // -------------------------------------------------------- - // - // segment_num 0 +1 -1 +2 -2 +3 -3 ....... - // ave_ring_diff 0 +2 -2 +3 -3 +4 -4 ....... - // max_ring_diff +1 +2 -2 +3 -3 +4 -4 ....... - // min_ring_diff -1 +2 -2 +3 -3 +4 -4 ....... - - - streamoff odd_ring_segment_offset; - streamoff num_rings_offset; - streamoff segment_offset; - streamoff jump_size; - streamoff jump_ring; - - - //---------------------------------------------------------------- - // data with delta ring = +1 and -1 are contained in segment 0: - // for this reason there is not a segment +1 and a segment -1 - //---------------------------------------------------------------- - - // segment number in the unsorted data format - // (no problem for segments +1 and -1) - - const int sign_segment_num = (segment_num == 0) ? 0 : ( (segment_num > 0) ? 1 : -1 ); - const int segment_num_orig = (int) ((segment_num+sign_segment_num) / 2); - - // cout << "segment_num_orig " << segment_num_orig << endl; - - // segment index in the unsorted data format - - const int index_orig = - static_cast(find(segment_sequence_orig.begin(), - segment_sequence_orig.end(), segment_num_orig) - - segment_sequence_orig.begin()); - - // cout << "index_orig " << index_orig << endl; - - - //--------------------------------------------------------------- - // OFFSETS - //--------------------------------------------------------------- - - const streamoff offset_begin = 256; - - // offset in rings for the odd sorted segments (value 0 or 1) - - odd_ring_segment_offset = abs((segment_num+sign_segment_num) % 2); - - // cout << "odd_ring_segment_offset " << odd_ring_segment_offset << endl; - - // offset in rings in the unsorted data format - - num_rings_offset = - accumulate(num_rings_orig.begin(), - num_rings_orig.begin() + index_orig, 0) + - odd_ring_segment_offset; - - // cout << "num_rings_offset " << num_rings_offset << endl; - - // offset to the first data in the first view_num of the segment chosen - - segment_offset = - num_rings_offset * get_num_tangential_poss() * on_disk_data_type.size_in_bytes(); - - // cout << "segment_offset " << segment_offset << endl; - - // size of the jump to the first record of the following view_num - if (segment_num == 0) - { - jump_size = - ( - accumulate(num_rings_orig.begin(), num_rings_orig.end(), 0) - - num_rings_orig[index_orig] + - + 2 * odd_ring_segment_offset - ) * get_num_tangential_poss() * on_disk_data_type.size_in_bytes(); - } - else - { - jump_size = - ( - accumulate(num_rings_orig.begin(), num_rings_orig.end(), 0) - - num_rings_orig[index_orig] - - 1 // offset to balance the jump_ring in the cycle - + 2 * odd_ring_segment_offset - ) * get_num_tangential_poss() * on_disk_data_type.size_in_bytes(); - } - // cout << "jump_size " << jump_size << endl; - - // size of the jump between two rings of the same view_num and of the same - // segment - if (segment_num == 0) - jump_ring = 0; - else - jump_ring = get_num_tangential_poss() * on_disk_data_type.size_in_bytes(); - - streamoff ring_offset = get_num_tangential_poss() * on_disk_data_type.size_in_bytes(); - - // jumps the initial offset - - sino_stream->seekg(offset, ios::beg); // overall offset - sino_stream->seekg(offset_begin, ios::cur); // "breakfile" offset - sino_stream->seekg(get_num_views() * sizeof(float), ios::cur); // skip view scaling factors - - // jumps at the beginning of the chosen segment - - sino_stream->seekg(segment_offset, ios::cur); - - // jump to the right Viewgram - - streamoff jump_ini = (view_num - get_min_view_num())* - (ring_offset*get_num_axial_poss(segment_num) - + jump_ring*get_num_axial_poss(segment_num) - + jump_size); - - // cout << "ring_offset " << ring_offset << endl; - // cout << "jump_ring " << jump_ring << endl; - // cout << "jump_size " << jump_size << endl; - // cout << " jump_ini " << jump_ini << endl; - - sino_stream->seekg(jump_ini, ios::cur); - - Viewgram data = get_empty_viewgram(view_num, segment_num,false); - - for (int ring =get_min_axial_pos_num(segment_num) ; ring <= get_max_axial_pos_num(segment_num); ring++) - { - { - float scale = float(1); - if (read_data(*sino_stream, data[ring], - on_disk_data_type, - scale, - on_disk_byte_order) == Succeeded::no - || scale != 1) - error("ProjDataGEAdvance: error reading data\n"); - } - sino_stream->seekg(jump_ring, ios::cur); - } - - // scales the Viewgram - data *= view_scaling_factor[view_num]; - - if (make_num_tangential_poss_odd && get_num_tangential_poss()%2==0) - { - const int new_max_tangential_pos = get_max_tangential_pos_num() + 1; - data.grow( - IndexRange2D(get_min_axial_pos_num(segment_num), - get_max_axial_pos_num(segment_num), - get_min_tangential_pos_num(), - new_max_tangential_pos)); - } - - return data; -} - - -Succeeded ProjDataGEAdvance::set_viewgram(const Viewgram& v) -{ - // TODO - // but this is difficult: how to adjust the scale factors when writing only 1 viewgram ? - warning("ProjDataGEAdvance::set_viewgram not implemented yet\n"); - return Succeeded::no; -} - -Sinogram ProjDataGEAdvance::get_sinogram(const int ax_pos_num, const int segment_num, - const bool make_num_tangential_poss_odd, const int timing_pos) const -{ - // TODO - error("ProjDataGEAdvance::get_sinogram not implemented yet\n"); - return get_empty_sinogram(ax_pos_num, segment_num);} - -Succeeded ProjDataGEAdvance::set_sinogram(const Sinogram& s) -{ - // TODO - warning("ProjDataGEAdvance::set_sinogram not implemented yet\n"); - return Succeeded::no; -} - -END_NAMESPACE_STIR diff --git a/src/cmake/STIRConfig.h.in b/src/cmake/STIRConfig.h.in index 4641f174d4..db343ce30d 100644 --- a/src/cmake/STIRConfig.h.in +++ b/src/cmake/STIRConfig.h.in @@ -63,8 +63,6 @@ namespace stir { #cmakedefine HAVE_AVW -#cmakedefine HAVE_RDF - #cmakedefine HAVE_HDF5 #cmakedefine HAVE_ITK diff --git a/src/include/stir/ProjDataGEAdvance.h b/src/include/stir/ProjDataGEAdvance.h deleted file mode 100644 index 2a6bec7251..0000000000 --- a/src/include/stir/ProjDataGEAdvance.h +++ /dev/null @@ -1,104 +0,0 @@ -// -// -/*! - - \file - \ingroup projdata - - \brief Declaration of class stir::ProjDataGEAdvance - - \author Damiano Belluzzo - \author Kris Thielemans - \author PARAPET project - - -*/ -/* - Copyright (C) 2000 PARAPET partners - Copyright (C) 2000- 2009, Hammersmith Imanet Ltd - This file is part of STIR. - - SPDX-License-Identifier: Apache-2.0 AND License-ref-PARAPET-license - - See STIR/LICENSE.txt for details -*/ -#ifndef __ProjDataGEAdvance_H__ -#define __ProjDataGEAdvance_H__ - -#include "stir/ProjData.h" -#include "stir/NumericType.h" -#include "stir/ByteOrder.h" -#include "stir/Array.h" -#include "stir/deprecated.h" - -#include -#include - -START_NAMESPACE_STIR - - -/*! - \ingroup projdata - \brief A class which reads projection data from a GE Advance - sinogram file. - - \warning support is still very basic. It assumes max_ring_diff == 11 and arc-corrected data. - The sinogram has to be extracted from the database using the "official" GE BREAKPOINT strategy. - (the file should contain 281 bins x 265 segments x 336 views.) - - No writing yet. - - \deprecated -*/ -class STIR_DEPRECATED ProjDataGEAdvance : public ProjData -{ -public: - - static ProjDataGEAdvance* ask_parameters(const bool on_disk = true); - - - ProjDataGEAdvance (std::iostream* s); - - //! Get & set viewgram - Viewgram get_viewgram(const int view_num, const int segment_num, - const bool make_num_tangential_poss_odd=false, const int timing_pos = 0) const; - Succeeded set_viewgram(const Viewgram& v); - - //! Get & set sinogram - Sinogram get_sinogram(const int ax_pos_num, const int sergment_num, - const bool make_num_tangential_poss_odd=false, const int timing_pos = 0) const; - Succeeded set_sinogram(const Sinogram& s); - -// float get_bin_value(const Bin& this_bin) const -// { -// // Do nothing -// } -private: - //the file with the data - //This has to be a reference (or pointer) to a stream, - //because assignment on streams is not defined; - // TODO make shared_ptr - std::iostream* sino_stream; - //offset of the whole 3d sinogram in the stream - std::streamoff offset; - - - NumericType on_disk_data_type; - - ByteOrder on_disk_byte_order; - - // view_scaling_factor is only used when reading data from file. Data are stored in - // memory as float, with the scale factor multiplied out - - Array<1,float> view_scaling_factor; - - std::vector num_rings_orig; - std::vector segment_sequence_orig; - - -}; - -END_NAMESPACE_STIR - - -#endif From 3b4f0d7d9a10505dd1766d4d7fdc6e0fe14f1689 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sun, 28 Jan 2024 08:02:39 +0000 Subject: [PATCH 486/509] remove AVW support --- CMakeLists.txt | 5 - documentation/STIR-UsersGuide.tex | 34 ----- documentation/STIR-general-overview.tex | 5 - documentation/release_6.0.htm | 5 +- src/CMakeLists.txt | 8 -- src/Doxyfile.in | 2 +- src/IO/CMakeLists.txt | 10 -- src/IO/stir_AVW.cxx | 160 ------------------------ src/cmake/FindAVW.cmake | 26 ---- src/cmake/STIRConfig.cmake.in | 13 -- src/cmake/STIRConfig.h.in | 2 - src/experimental/utilities/AVWROI.cxx | 137 -------------------- src/include/stir/IO/stir_AVW.h | 48 ------- src/utilities/CMakeLists.txt | 4 - src/utilities/conv_AVW.cxx | 154 ----------------------- 15 files changed, 5 insertions(+), 608 deletions(-) delete mode 100644 src/IO/stir_AVW.cxx delete mode 100644 src/cmake/FindAVW.cmake delete mode 100644 src/experimental/utilities/AVWROI.cxx delete mode 100644 src/include/stir/IO/stir_AVW.h delete mode 100644 src/utilities/conv_AVW.cxx diff --git a/CMakeLists.txt b/CMakeLists.txt index a1e0486e33..1ba8a58015 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -91,7 +91,6 @@ find_package( Boost 1.36.0 REQUIRED ) # Listed here such that we know if we should compile extra utilities option(DISABLE_LLN_MATRIX "disable use of LLN library" OFF) option(DISABLE_ITK "disable use of ITK library" OFF) -option(DISABLE_AVW "disable use of AVW library" OFF) option(DISABLE_HDF5 "disable use of HDF5 libraries" OFF) option(DISABLE_STIR_LOCAL "disable use of LOCAL extensions to STIR" OFF) option(DISABLE_CERN_ROOT "disable use of Cern ROOT libraries" OFF) @@ -126,10 +125,6 @@ if(NOT DISABLE_CERN_ROOT) endif() endif() -if(NOT DISABLE_AVW) - find_package(AVW) -endif() - if(NOT DISABLE_HDF5) find_package(HDF5 COMPONENTS CXX) endif() diff --git a/documentation/STIR-UsersGuide.tex b/documentation/STIR-UsersGuide.tex index 64fc5dbf05..9d0f029a77 100644 --- a/documentation/STIR-UsersGuide.tex +++ b/documentation/STIR-UsersGuide.tex @@ -262,12 +262,6 @@ \subsubsection{ enables NRRD, MetaIO and Nifti IO, and DICOM reading. Use your package manager, including conda or pip, or check the \url{https://itk.org/download/}{installation instructions for ITK}. -\subsubsection{ -Enabling AVW support\label{sec:installAVW}} -AVW was the library used by the commercial program \url{https://analyzedirect.com/}{Analyze}. -\textit{AVW support has not been tested since about 2010 and we have no idea of STIR still builds with it.} AVW support will be dropped in STIR 6.0. -See \ref{sec:convAVW} for usage. - \subsection{ Building} Since version 2.2, \textit{STIR} contains files to build STIR using the platform-independent @@ -377,8 +371,6 @@ \subsubsection{ \begin{itemize} \item LLN files for ECAT support via \texttt{LLN\_INCLUDE\_DIRS} and \texttt{LLN\_LIBRARIES}. See section \ref{sec:ECAT67support}. -\item AVW\texttrademark{} [deprecated] (a commercial library, probably no longer available) -via \texttt{AVW\_ROOT\_DIR}. See section \ref{sec:installAVW}. \item ITK\_DIR, use IO from ITK, see section \ref{sec:installITK}. \item CERN ROOT files used by GEANT and GATE via \texttt{ROOT\_DIR}. If this is not set, we will try to get it from the \texttt{ROOTSYS} environment (or CMake) variable. @@ -741,11 +733,6 @@ \subsubsection{ECAT6 and ECAT7 data} \textbf{Warning} ECAT7 support is no longer tested. -\subsubsection{Image IO using the AVW library} -\textit{AVW support has not been tested since about 2010.} - -See the \texttt{conv\_AVW} utility (section \ref{sec:convAVW}). - \subsubsection{Image IO using the ITK library \label{sec:ITKIO}} If you compiled \textit{STIR} such that it could find the \textit{ITK} library, you will be read all image formats supported by \textit{ITK}, see \url{http://www.itk.org/Wiki/ITK/File_Formats}{the ITK Wiki} @@ -3399,27 +3386,6 @@ \subsubsubsection{ecat\_swap\_corners} Allows copying header info between ECAT7 files. Check doxygen, or run without parameters for usage info. -{ \subsubsubsection{conv\_AVW} -} -\label{sec:convAVW} -\textit{AVW support has not been tested since about 2010.} - -If you have the \textit{AVW}\texttrademark library -\footnote{See \url{http://www.mayo.edu/bir/Software/AVW/AVW1.html} -{www.mayo.edu/bir/Software/AVW/AVW1.html}. -} installed on your system, the build process should have built \texttt{utilities/conv\_AVW}, -see also \ref{sec:UsingCMake}. -This utility allows to use the \textit{AVW} library to read an image, and then write it out -using \textit{STIR} as Interfile.\\ -\textbf{Warning:} the \textit{AVW} library seems to do flip some images depending -on the file format. For instance, it reads \textit{ECAT7} files using a z-flip compared -to \textit{STIR}. - -It normally should be run as follows: -\cmdline{conv\_AVW [ --flip\_z ] imagefile} - -It will require access to a run-time license for \textit{AVW}. - { \subsubsubsection{conv\_GATE\_projdata\_to\_interfile} } This program converts GATE raw sinogram output (.ima) into STIR interfile format. This utility diff --git a/documentation/STIR-general-overview.tex b/documentation/STIR-general-overview.tex index 28d8b8626b..7a6a4ed5da 100644 --- a/documentation/STIR-general-overview.tex +++ b/documentation/STIR-general-overview.tex @@ -410,11 +410,6 @@ \subsection{ \item Siemens ``interfile-like'' data \item GE RDF9 (if you have the HDF5 libraries) \item -The commercial AnalyzeAVW library from the BIR, Mayo University, can be used -to read images via a conversion utility. This will be dropped in version 6.0. -\item -GE Advance VOLPET sinogram format, but for reading only. This will be dropped in version 6.0. -\item ECAT 7 matrix format for reading only might work but is no longer supported. However, this file format needs the ECAT Matrix library (developed previously by M. Sibomana and C. Michel at Louvain la Neuve, Belgium). diff --git a/documentation/release_6.0.htm b/documentation/release_6.0.htm index 3f4940486e..d6ab29b479 100644 --- a/documentation/release_6.0.htm +++ b/documentation/release_6.0.htm @@ -52,9 +52,12 @@

    Changes breaking backwards compatibility from a user-perspective

    confusion. These guesses have now been removed.
  • - support for the GE VOLPET format (an old format used by the GE Advance and Discover LS + (deprecated) support for the GE VOLPET format (an old format used by the GE Advance and Discover LS sinograms when using "break-pointing") has been removed.
  • +
  • + (deprecated) support for the AVW format via the (very old) AnalyzeAVW commercial library has been removed. +
  • Bug fixes

    diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4db7d6b76c..3be9e35097 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -49,14 +49,6 @@ else() message(STATUS "UPENN libs support disabled.") endif() -if (AVW_FOUND) - set(HAVE_AVW ON) - message(STATUS "AVW library IO added.") - include_directories(${AVW_INCLUDE_DIRS}) -else() - message(STATUS "AVW IO library not used.") -endif() - if ((NOT DISABLE_HDF5) AND HDF5_FOUND) set(HAVE_HDF5 ON) message(STATUS "HDF5 support enabled.") diff --git a/src/Doxyfile.in b/src/Doxyfile.in index 9325caa4ec..b355114170 100644 --- a/src/Doxyfile.in +++ b/src/Doxyfile.in @@ -1968,7 +1968,7 @@ PREDEFINED = __GNUC__=4 \ __GNUC_MINOR__=6 \ DOXYGEN_SKIP \ HAVE_LLN_MATRIX \ - HAVE_AVW \ + HAVE_HDF5 \ USE_PMRT \ HAVE_CERN_ROOT \ "USING_NAMESPACE_STIR=using namespace stir;" \ diff --git a/src/IO/CMakeLists.txt b/src/IO/CMakeLists.txt index c225214236..ef8e53e6da 100644 --- a/src/IO/CMakeLists.txt +++ b/src/IO/CMakeLists.txt @@ -74,12 +74,6 @@ if (HAVE_HDF5) ) endif() -if(AVW_FOUND) - list(APPEND ${dir_LIB_SOURCES} - stir_AVW.cxx - ) -endif() - include(stir_lib_target) if (LLN_FOUND) @@ -99,10 +93,6 @@ if (HAVE_HDF5) target_link_libraries(IO PUBLIC ${HDF5_CXX_LIBRARIES}) endif() -if (AVW_FOUND) - target_link_libraries(IO PUBLIC ${AVW_LIBRARIES}) -endif() - if (HAVE_ITK) target_link_libraries(IO PUBLIC ITKCommon ${ITK_LIBRARIES}) endif() diff --git a/src/IO/stir_AVW.cxx b/src/IO/stir_AVW.cxx deleted file mode 100644 index 32c5785fac..0000000000 --- a/src/IO/stir_AVW.cxx +++ /dev/null @@ -1,160 +0,0 @@ -// -// -/* - Copyright (C) 2001- 2008, Hammersmith Imanet Ltd - This file is part of STIR. - - SPDX-License-Identifier: Apache-2.0 - - See STIR/LICENSE.txt for details -*/ - -/*! -\file -\ingroup IO -\brief routines to convert AVW data structures to STIR -\author Kris Thielemans - -*/ - -#ifdef HAVE_AVW - -#include "stir/IO/stir_AVW.h" -#include "stir/IndexRange3D.h" -#include "stir/VoxelsOnCartesianGrid.h" -#include "stir/CartesianCoordinate3D.h" -#include "stir/warning.h" - -START_NAMESPACE_STIR - -namespace AVW -{ - -template -static -void -AVW_Volume_to_VoxelsOnCartesianGrid_help(VoxelsOnCartesianGrid& image, - elemT const* avw_data, - const bool flip_z) -{ - // std::copy(avw_data, avw_data+avw_volume->VoxelsPerVolume, image->begin_all()); - - // AVW data seems to be y-flipped - for (int z=image.get_min_z(); z<=image.get_max_z(); ++z) - { - const int out_z = - !flip_z ? z : image.get_max_z() - z + image.get_min_z(); - for (int y=image.get_max_y(); y>=image.get_min_y(); --y) - { - for (int x=image.get_min_x(); x<=image.get_max_x(); ++x) - image[out_z][y][x] = static_cast(*avw_data++); - //std::copy(avw_data, avw_data + image.get_x_size(), image[z][y].begin()); - //avw_data += image.get_x_size(); - } - } -} - - -VoxelsOnCartesianGrid * -AVW_Volume_to_VoxelsOnCartesianGrid(AVW_Volume const* const avw_volume, - const bool flip_z) -{ - // find sizes et al - - const int size_x = avw_volume->Width; - const int size_y = avw_volume->Height; - const int size_z = avw_volume->Depth; - IndexRange3D range(0, size_z-1, - -(size_y/2), -(size_y/2)+size_y-1, - -(size_x/2), -(size_x/2)+size_x-1); - - CartesianCoordinate3D voxel_size; - voxel_size.x() = - static_cast(AVW_GetNumericInfo("VoxelWidth", avw_volume->Info)); - if (voxel_size.x()==0) - { - warning("AVW_Volume_to_VoxelsOnCartesianGrid: VoxelWidth not found or 0"); - } - - voxel_size.y() = - static_cast(AVW_GetNumericInfo("VoxelHeight", avw_volume->Info)); - if (voxel_size.y()==0) - { - warning("AVW_Volume_to_VoxelsOnCartesianGrid: VoxelHeight not found or 0"); - } - - voxel_size.z() = - static_cast(AVW_GetNumericInfo("VoxelDepth", avw_volume->Info)); - if (voxel_size.z()==0) - { - warning("AVW_Volume_to_VoxelsOnCartesianGrid: VoxelDepth not found or 0"); - } - - // construct VoxelsOnCartesianGrid - VoxelsOnCartesianGrid * volume_ptr = - new VoxelsOnCartesianGrid(range, - CartesianCoordinate3D(0,0,0), - voxel_size); - - // fill in data - switch(avw_volume->DataType) - { - case AVW_SIGNED_CHAR: - { - AVW_Volume_to_VoxelsOnCartesianGrid_help(*volume_ptr, - reinterpret_cast(avw_volume->Mem), flip_z); - break; - } - case AVW_UNSIGNED_CHAR: - { - AVW_Volume_to_VoxelsOnCartesianGrid_help(*volume_ptr, - reinterpret_cast(avw_volume->Mem), flip_z); - break; - } - case AVW_UNSIGNED_SHORT: - { - AVW_Volume_to_VoxelsOnCartesianGrid_help(*volume_ptr, - reinterpret_cast(avw_volume->Mem), flip_z); - break; - } - case AVW_SIGNED_SHORT: - { - AVW_Volume_to_VoxelsOnCartesianGrid_help(*volume_ptr, - reinterpret_cast(avw_volume->Mem), flip_z); - break; - } - case AVW_UNSIGNED_INT: - { - AVW_Volume_to_VoxelsOnCartesianGrid_help(*volume_ptr, - reinterpret_cast(avw_volume->Mem), flip_z); - break; - } - case AVW_SIGNED_INT: - { - AVW_Volume_to_VoxelsOnCartesianGrid_help(*volume_ptr, - reinterpret_cast(avw_volume->Mem), flip_z); - break; - } - case AVW_FLOAT: - { - AVW_Volume_to_VoxelsOnCartesianGrid_help(*volume_ptr, - reinterpret_cast(avw_volume->Mem), flip_z); - break; - } - default: - { - warning("AVW_Volume_to_VoxelsOnCartesianGrid: unsupported data type %d\n", - avw_volume->DataType); - delete volume_ptr; - return 0; - } - } - - return volume_ptr; -} - -} // end namespace AVW - -END_NAMESPACE_STIR - -#endif diff --git a/src/cmake/FindAVW.cmake b/src/cmake/FindAVW.cmake deleted file mode 100644 index 582c1e9c64..0000000000 --- a/src/cmake/FindAVW.cmake +++ /dev/null @@ -1,26 +0,0 @@ -# expects files in $AVW/include and $AVW/$TARGET/lib - - set(AVW_ROOT_DIR CACHE PATH "root of AVW, normally found from the AVW environment variable") - if (NOT AVW_ROOT_DIR AND NOT $ENV{AVW} STREQUAL "") - set(AVW_ROOT_DIR $ENV{AVW_ROOT_DIR}) - endif(NOT AVW_ROOT_DIR AND NOT $ENV{AVW} STREQUAL "") - -#TODO necessary? - IF( AVW_ROOT_DIR ) - file(TO_CMAKE_PATH ${AVW_ROOT_DIR} AVW_ROOT_DIR) - ENDIF( AVW_ROOT_DIR ) - - find_path(AVW_INCLUDE_DIRS NAME AVW.h PATHS ${AVW_ROOT_DIR}/include - DOC "location of AVW include files") -# message(STATUS "AVW_INCLUDE_DIRS ${AVW_INCLUDE_DIRS}") - - find_library(AVW_LIBRARIES NAME AVW - HINTS ${AVW_ROOT_DIR}/$ENV{TARGET}/lib/ - DOC "location of AVW library") - -# handle the QUIETLY and REQUIRED arguments and set AVW_FOUND to TRUE if -# all listed variables are TRUE -INCLUDE(FindPackageHandleStandardArgs) -FIND_PACKAGE_HANDLE_STANDARD_ARGS(AVW "AVW matrix library not found. If you do have it, set the missing variables" AVW_LIBRARIES AVW_INCLUDE_DIRS) - -MARK_AS_ADVANCED(AVW_LIBRARIES AVW_INCLUDE_DIRS ) \ No newline at end of file diff --git a/src/cmake/STIRConfig.cmake.in b/src/cmake/STIRConfig.cmake.in index c38fb758c9..e67f5b85e3 100644 --- a/src/cmake/STIRConfig.cmake.in +++ b/src/cmake/STIRConfig.cmake.in @@ -115,19 +115,6 @@ if (@CERN_ROOT_FOUND@) set(STIR_BUILT_WITH_CERN_ROOT TRUE) endif() -if (@AVW_FOUND@) - set(AVW_ROOT_DIR @AVW_ROOT_DIR@) - find_package(AVW ${STIR_FIND_TYPE}) - if(NOT AVW_FOUND) - SET(STIR_FOUND OFF) - endif() - message(STATUS "AVW support in STIR enabled.") - # need to add this as registries rely on it - # would be better to add it to STIR_INCLUDE_DIRS (TODO) - include_directories(${AVW_INCLUDE_DIRS}) - set(STIR_BUILT_WITH_AVW TRUE) -endif() - # Following lines are currently not necessary but would need to be enabled # if nlohmann_json stops being a header-only library. # See buildblock/CMakeLists.xt diff --git a/src/cmake/STIRConfig.h.in b/src/cmake/STIRConfig.h.in index db343ce30d..515f021e5b 100644 --- a/src/cmake/STIRConfig.h.in +++ b/src/cmake/STIRConfig.h.in @@ -61,8 +61,6 @@ namespace stir { #cmakedefine HAVE_UPENN -#cmakedefine HAVE_AVW - #cmakedefine HAVE_HDF5 #cmakedefine HAVE_ITK diff --git a/src/experimental/utilities/AVWROI.cxx b/src/experimental/utilities/AVWROI.cxx deleted file mode 100644 index 06bab4337d..0000000000 --- a/src/experimental/utilities/AVWROI.cxx +++ /dev/null @@ -1,137 +0,0 @@ -x// -// -/* - Copyright (C) 2001- 2008, Hammersmith Imanet Ltd - This file is part of STIR. - - SPDX-License-Identifier: Apache-2.0 - - See STIR/LICENSE.txt for details -*/ -/*! -\file -\ingroup utilities -\brief convert AVW ROI files to images. Images are read using the AVW library. -\author Kris Thielemans - -*/ -#include "stir/IO/stir_AVW.h" -#include "AVW_ObjectMap.h" -#include "AVW_ImageFile.h" - -#include "stir/VoxelsOnCartesianGrid.h" -#include "stir/IO/OutputFileFormat.h" -#include "stir/utilities.h" -#include "stir/is_null_ptr.h" -#include "stir/Succeeded.h" -#include "stir/shared_ptr.h" -#include "stir/warning.h" -#include - -USING_NAMESPACE_STIR - -void print_usage_and_exit( const char * const program_name) -{ - { - std::cerr << "Usage : " << program_name << " [ --flip_z ] Analyzeobjectfile.obj\n"; - - exit(EXIT_FAILURE); - } -} - -int -main(int argc, char **argv) -{ - const char * const program_name = argv[0]; - // skip program name - --argc; - ++argv; - - bool flip_z = false; - - // first process command line options - while (argc>0 && argv[0][0]=='-') - { - if (strcmp(argv[0], "--flip_z")==0) - { - flip_z = true; - argc-=1; argv+=1; - } - else - { - std::cerr << "Unknown option '" << argv[0] <<"'\n"; - print_usage_and_exit(program_name); - } - } - - if (argc != 1) - print_usage_and_exit(program_name); - - char *objectfile = argv[0]; - stir::shared_ptr > > - output_file_format_sptr = - stir::OutputFileFormat >::default_sptr(); - { - // open non-existent file first - // this is necessary to get AVW_LoadObjectMap to work - AVW_ImageFile *avw_file= AVW_OpenImageFile("xxx non-existent I hope","r"); - } - - AVW_ObjectMap *object_map = AVW_LoadObjectMap(objectfile); - if(!object_map) { AVW_Error("AVW_LoadObjectMap"); exit(EXIT_FAILURE); } - - std::cerr << "Number of objects: " << object_map->NumberOfObjects << '\n'; - { - AVW_Volume *volume = NULL; - for (int object_num=0; object_numNumberOfObjects; ++object_num) - { - const char * const object_name = object_map->Object[object_num]->Name; - std::cerr << "Object " << object_num << ": " << object_name << '\n'; - - if (ask("Write this one?",true)) - { - volume = AVW_GetObject(object_map, object_num, volume); - if(!volume) - { - AVW_Error("AVW_GetObject"); - stir::warning("Error in object. Skipping...");//, AVW_ErrorMessage); - continue; - } - - shared_ptr > stir_volume_sptr = - stir::AVW::AVW_Volume_to_VoxelsOnCartesianGrid(volume, flip_z); - if (stir::is_null_ptr(stir_volume_sptr)) - { - stir::warning("Error converting object to STIR format. Skipping...", object_num); - continue; - } - char *header_filename = new char[strlen(objectfile) + strlen(object_name) + 10]; - { - strcpy(header_filename, objectfile); - // append object_name, but after getting rid of the extension in objectfile - replace_extension(header_filename, "_"); - strcat(header_filename, object_name); - } - //warning("Setting voxel size to 962 defaults\n"); - //stir_volume_sptr->set_voxel_size(Coordinate3D(2.425F,2.25F,2.25F)); - if (output_file_format_sptr->write_to_file(header_filename, *stir_volume_sptr) - == stir::Succeeded::no) - { - stir::warning("Error writing %s", header_filename); - } - else - { - std::cout << "Wrote " << header_filename << '\n'; - } - - delete[] header_filename; - } - } - AVW_DestroyVolume(volume); - } - AVW_DestroyObjectMap(object_map); - - return(EXIT_SUCCESS); -} - - diff --git a/src/include/stir/IO/stir_AVW.h b/src/include/stir/IO/stir_AVW.h deleted file mode 100644 index eb446f3e81..0000000000 --- a/src/include/stir/IO/stir_AVW.h +++ /dev/null @@ -1,48 +0,0 @@ -// -// -#ifndef __stir_IO_stir_AVW__H__ -#define __stir_IO_stir_AVW__H__ -/* - Copyright (C) 2001- 2007, Hammersmith Imanet Ltd - This file is part of STIR. - - SPDX-License-Identifier: Apache-2.0 - - See STIR/LICENSE.txt for details -*/ - -/*! -\file -\ingroup IO -\brief routines to convert AVW data structures to STIR -\author Kris Thielemans - -*/ - - -#ifdef HAVE_AVW - -#include "AVW.h" -#include "stir/common.h" - -START_NAMESPACE_STIR - -template class VoxelsOnCartesianGrid; - -namespace AVW -{ - - //! A routine that converts AVW volume to ::stir::VoxelsOnCartesianGrid - /*! Will return a null pointer if the convertion failed for some reason. - */ - VoxelsOnCartesianGrid * - AVW_Volume_to_VoxelsOnCartesianGrid(AVW_Volume const* const avw_volume, - const bool flip_z = false); - -} // end namespace AVW - -END_NAMESPACE_STIR - -#endif // HAVE_AVW - -#endif //__stir_IO_stir_AVW__H__ diff --git a/src/utilities/CMakeLists.txt b/src/utilities/CMakeLists.txt index 3ab181099e..5e5d3a8c0b 100644 --- a/src/utilities/CMakeLists.txt +++ b/src/utilities/CMakeLists.txt @@ -80,10 +80,6 @@ if (HAVE_ITK) list(APPEND ${dir_EXE_SOURCES} SPECT_dicom_to_interfile.cxx) endif() -if (AVW_FOUND) - list(APPEND ${dir_EXE_SOURCES} conv_AVW.cxx) -endif() - if (HAVE_HDF5) list(APPEND ${dir_EXE_SOURCES} construct_randoms_from_GEsingles.cxx diff --git a/src/utilities/conv_AVW.cxx b/src/utilities/conv_AVW.cxx deleted file mode 100644 index 4ecf60cbf1..0000000000 --- a/src/utilities/conv_AVW.cxx +++ /dev/null @@ -1,154 +0,0 @@ -// -// -/* - Copyright (C) 2001- 2007, Hammersmith Imanet Ltd - This file is part of STIR. - - SPDX-License-Identifier: Apache-2.0 - - See STIR/LICENSE.txt for details -*/ -/*! -\file -\ingroup utilities -\brief convert image files to different file format. Images are read using the AVW library. -\author Kris Thielemans - -*/ - -#include "stir/IO/stir_AVW.h" -#include "AVW_ImageFile.h" - -#include "stir/VoxelsOnCartesianGrid.h" -#include "stir/IO/OutputFileFormat.h" -#include "stir/utilities.h" -#include "stir/is_null_ptr.h" -#include "stir/Succeeded.h" -#include "stir/shared_ptr.h" -#include "stir/warning.h" -#include -#include - -void print_usage_and_exit( const char * const program_name) -{ - { - std::cerr << "Usage : " << program_name << " [ --flip_z ] imagefile\n"; - - AVW_List* list = AVW_ListFormats(AVW_SUPPORT_READ); - std::cerr << "Supported file formats for reading by AVW:\n "; - for (int i=0; iNumberOfEntries; ++i) - { - std::cerr << list->Entry[i] << '\n'; - } - exit(EXIT_FAILURE); - } -} - -int -main(int argc, char **argv) -{ - const char * const program_name = argv[0]; - // skip program name - --argc; - ++argv; - - bool flip_z = false; - - // first process command line options - while (argc>0 && argv[0][0]=='-') - { - if (strcmp(argv[0], "--flip_z")==0) - { - flip_z = true; - argc-=1; argv+=1; - } - else - { - std::cerr << "Unknown option '" << argv[0] <<"'\n"; - print_usage_and_exit(program_name); - } - } - - if (argc != 1) - print_usage_and_exit(program_name); - - char *imagefile = argv[0]; - stir::shared_ptr > > - output_file_format_sptr = - stir::OutputFileFormat >::default_sptr(); - { - // open non-existent file first - // this is necessary to get AVW_LoadObjectMap to work - AVW_ImageFile *avw_file= AVW_OpenImageFile("xxx non-existent I hope","r"); - } - - std::cout << "Reading ImageMap " << imagefile << '\n'; - AVW_ImageFile*avw_file = AVW_OpenImageFile(imagefile,"r"); - if(!avw_file) - { - AVW_Error("AVW_OpenImageFile"); - std::cout << std::flush; - exit(EXIT_FAILURE); - } - - std::cout << "Number of volumes: " << avw_file->NumVols << '\n'; - unsigned int min_volume_num = 0; - unsigned int max_volume_num = avw_file->NumVols-1; - if (!stir::ask("Attempt all data-sets (Y) or single data-set (N)", true)) - { - min_volume_num = max_volume_num = - stir::ask_num("Volume number ? ", 1U, avw_file->NumVols, 1U) - - 1; // subtract 1 as AVW numbering starts from 0 - } - { - AVW_Volume *volume = NULL; - for (unsigned int volume_num=min_volume_num; volume_num<=max_volume_num; ++volume_num) - { - { - volume = AVW_ReadVolume(avw_file, volume_num, volume); - if(!volume) - { - AVW_Error("AVW_ReadVolume"); - stir::warning("Error in volume %d. Skipping...", volume_num+1);//, AVW_ErrorMessage); - continue; - } - stir::shared_ptr > stir_volume_sptr = - stir::AVW::AVW_Volume_to_VoxelsOnCartesianGrid(volume, flip_z); - if (stir::is_null_ptr(stir_volume_sptr)) - { - stir::warning("Error converting volume to STIR format. Skipping...", volume_num); - continue; - } - char *header_filename = new char[strlen(imagefile) + 100]; - { - strcpy(header_filename, imagefile); - // keep extension, just in case we would have conflicts otherwise - // but replace the . with a _ - char * dot_ptr = strchr(stir::find_filename(header_filename),'.'); - if (dot_ptr != NULL) - *dot_ptr = '_'; - // now add stuff to say which frame, gate, bed, data this was - sprintf(header_filename+strlen(header_filename), "_f%ug%dd%db%d", - volume_num+1, 1, 0, 0); - } - if (output_file_format_sptr->write_to_file(header_filename, *stir_volume_sptr) - == stir::Succeeded::no) - { - stir::warning("Error writing %s", header_filename); - } - else - { - std::cout << "Wrote " << header_filename << '\n'; - } - delete[] header_filename; - } - } - AVW_DestroyVolume(volume); - } - AVW_CloseImageFile(avw_file); - - - return(EXIT_SUCCESS); -} - - From 3e5b6f7f518d6bb932a2602ca4d7e7c7fd5d5dbe Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sun, 28 Jan 2024 08:23:45 +0000 Subject: [PATCH 487/509] removed deprecated BinNormalisation::apply/undo members --- documentation/release_6.0.htm | 4 ++++ .../stir/recon_buildblock/BinNormalisation.h | 15 --------------- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/documentation/release_6.0.htm b/documentation/release_6.0.htm index d6ab29b479..e1c47058e6 100644 --- a/documentation/release_6.0.htm +++ b/documentation/release_6.0.htm @@ -271,6 +271,10 @@

    Backward incompatibities

    member variable that needs to be set to false by set_* functions and checked by callers. +
  • + (deprecated) BinNormalisation::undo and apply members that + take explicit time arguments have been removed. +
  • New functionality

    diff --git a/src/include/stir/recon_buildblock/BinNormalisation.h b/src/include/stir/recon_buildblock/BinNormalisation.h index b3fe253ee0..3400a2e4d3 100644 --- a/src/include/stir/recon_buildblock/BinNormalisation.h +++ b/src/include/stir/recon_buildblock/BinNormalisation.h @@ -126,21 +126,6 @@ class BinNormalisation : public RegisteredObject */ void undo(ProjData&, shared_ptr = shared_ptr()) const; - - //! old interface. do not use - STIR_DEPRECATED void undo(ProjData& p,const double /*start_time*/, const double /*end_time*/, - shared_ptr sym = shared_ptr()) const - { - this->undo(p, sym); - } - - - //! old interface. do not use - STIR_DEPRECATED void apply(ProjData& p,const double /*start_time*/, const double /*end_time*/, - shared_ptr sym = shared_ptr()) const - { - this->apply(p, sym); - } void set_exam_info_sptr(const shared_ptr _exam_info_sptr); From 168af0b92d70d4821968b8ac26ea2e4e7bd85ac7 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sun, 28 Jan 2024 08:35:24 +0000 Subject: [PATCH 488/509] removed code deprecated functions for exptending/interpolating projdata They were already disabled due to check on STIR_VERSION. extend_sinogram_in_views, extend_segment_in_views,interpolate_axial_position --- documentation/release_6.0.htm | 13 +- src/buildblock/CMakeLists.txt | 1 - src/buildblock/extend_projdata.cxx | 136 ------------------ src/buildblock/interpolate_axial_position.cxx | 129 ----------------- src/include/stir/extend_projdata.h | 9 -- src/include/stir/interpolate_axial_position.h | 50 ------- 6 files changed, 11 insertions(+), 327 deletions(-) delete mode 100644 src/buildblock/interpolate_axial_position.cxx delete mode 100644 src/include/stir/interpolate_axial_position.h diff --git a/documentation/release_6.0.htm b/documentation/release_6.0.htm index e1c47058e6..144cb985d0 100644 --- a/documentation/release_6.0.htm +++ b/documentation/release_6.0.htm @@ -272,8 +272,17 @@

    Backward incompatibities

    functions and checked by callers.
  • - (deprecated) BinNormalisation::undo and apply members that - take explicit time arguments have been removed. + (deprecated) members/functions have been removed +
      +
    • + BinNormalisation::undo and apply members that + take explicit time arguments +
    • +
    • + extend_sinogram_in_views, extend_segment_in_views + and interpolate_axial_position +
    • +
  • diff --git a/src/buildblock/CMakeLists.txt b/src/buildblock/CMakeLists.txt index 2307dd71ed..add1c45ebc 100644 --- a/src/buildblock/CMakeLists.txt +++ b/src/buildblock/CMakeLists.txt @@ -30,7 +30,6 @@ set(${dir_LIB_SOURCES} Sinogram.cxx RelatedViewgrams.cxx scale_sinograms.cxx - interpolate_axial_position.cxx interpolate_projdata.cxx extend_projdata.cxx multiply_crystal_factors.cxx diff --git a/src/buildblock/extend_projdata.cxx b/src/buildblock/extend_projdata.cxx index 774a62e965..1b8b0cb25f 100644 --- a/src/buildblock/extend_projdata.cxx +++ b/src/buildblock/extend_projdata.cxx @@ -30,126 +30,6 @@ START_NAMESPACE_STIR -#if STIR_VERSION < 060000 -namespace detail -{ - /* This function takes symmetries in the sinogram space into account - to find data in the negative segment if necessary. - However, it needs testing if it would work for non-direct sinograms. - */ - inline static - Array<2,float> - extend_sinogram_in_views(const Array<2,float>& sino_positive_segment, - const Array<2,float>& sino_negative_segment, - const ProjDataInfo& proj_data_info, - const int min_view_extension, const int max_view_extension) - { - //* Check if projdata are from 0 to pi-phi - bool min_is_extended=false; - bool max_is_extended=false; - BasicCoordinate<2,int> min_in, max_in; - if (!sino_positive_segment.get_regular_range(min_in, max_in)) - { - warning("input segment 0 should have a regular range"); - } - - const int org_min_view_num=min_in[1]; - const int org_max_view_num=max_in[1]; - - const float min_phi = proj_data_info.get_phi(Bin(0,0,0,0)); - const float max_phi = proj_data_info.get_phi(Bin(0,max_in[1],0,0)); - - const float sampling_phi = - proj_data_info.get_phi(Bin(0,1,0,0)) - min_phi; - const int num_views_for_180 = round(_PI/sampling_phi); - - if (fabs(min_phi)< .01) - { - min_in[1]-=min_view_extension; - min_is_extended=true; - } - if (fabs(max_phi-(_PI-sampling_phi))<.01) - { - max_in[1]+=max_view_extension; - max_is_extended=true; - } - - - IndexRange<2> extended_range(min_in, max_in); - Array<2,float> input_extended_view(extended_range); - - if (!min_is_extended) - warning("Minimum view of the original projdata is not 0"); - if (!max_is_extended) - warning("Maximum view of the original projdata is not 180-sampling_phi"); - - for (int view_num=min_in[1]; view_num<=max_in[1]; ++view_num) - { - bool use_extension=false; - int symmetric_view_num=0; - if (view_numorg_max_view_num && max_is_extended==true) - { - use_extension=true; - symmetric_view_num = view_num - num_views_for_180; - } - - if (!use_extension) - input_extended_view[view_num]= - sino_positive_segment[view_num]; - else - { - const int symmetric_min = std::max(min_in[2], -max_in[2]); - const int symmetric_max = std::min(-min_in[2], max_in[2]); - for (int tang_num=symmetric_min; tang_num<=symmetric_max; ++tang_num) - input_extended_view[view_num][tang_num]= - sino_negative_segment[symmetric_view_num][-tang_num]; - // now do extrapolation where we don't have data - for (int tang_num=min_in[2]; tang_num -extend_segment_in_views(const SegmentBySinogram& sino, - const int min_view_extension, const int max_view_extension) -{ - if (sino.get_segment_num()!=0) - error("extend_segment with single segment works only for segment 0"); - - BasicCoordinate<3,int> min, max; - - min[1]=sino.get_min_axial_pos_num(); - max[1]=sino.get_max_axial_pos_num(); - min[2]=sino.get_min_view_num(); - max[2]=sino.get_max_view_num(); - min[3]=sino.get_min_tangential_pos_num(); - max[3]=sino.get_max_tangential_pos_num(); - const IndexRange<3> out_range(min,max); - Array<3,float> out(out_range); - for (int ax_pos_num=min[1]; ax_pos_num <=max[1] ; ++ax_pos_num) - { - out[ax_pos_num] = - detail:: - extend_sinogram_in_views(sino[ax_pos_num],sino[ax_pos_num], - *(sino.get_proj_data_info_sptr()), - min_view_extension, max_view_extension); - } - return out; -} -#endif - /* This function takes symmetries into account to extend segments in any direction. However, it needs testing if it works for non-direct sinograms. */ @@ -253,20 +133,4 @@ extend_segment(const SegmentBySinogram& segment, const int view_extension return out; } -#if STIR_VERSION < 060000 -Array<2,float> -extend_sinogram_in_views(const Sinogram& sino, - const int min_view_extension, const int max_view_extension) -{ - if (sino.get_segment_num()!=0) - error("extend_segment with single segment works only for segment 0"); - - return - detail:: - extend_sinogram_in_views(sino, sino, - *(sino.get_proj_data_info_sptr()), - min_view_extension, max_view_extension); -} -#endif - END_NAMESPACE_STIR diff --git a/src/buildblock/interpolate_axial_position.cxx b/src/buildblock/interpolate_axial_position.cxx deleted file mode 100644 index 2fbe668f32..0000000000 --- a/src/buildblock/interpolate_axial_position.cxx +++ /dev/null @@ -1,129 +0,0 @@ -// -// -/* - Copyright (C) 2022 National Physical Laboratory - Copyright (C) 2022 University College London - This file is part of STIR. - - SPDX-License-Identifier: Apache-2.0 - - See STIR/LICENSE.txt for details -*/ -/*! - \file - \ingroup projdata - \brief Perform B-Splines Interpolation of axial position. At present, it uses nearest neighbour interpolation in segment 0 if projdata_in only - - \author Daniel Deidda - \author Kris Thielemans - -*/ -#include "stir/ProjData.h" -//#include "stir/display.h" -#include "stir/ProjDataInfo.h" -#include "stir/ProjDataInfoCylindricalNoArcCorr.h" -#include "stir/IndexRange.h" -#include "stir/BasicCoordinate.h" -#include "stir/Sinogram.h" -#include "stir/SegmentBySinogram.h" -#include "stir/Succeeded.h" -#include "stir/numerics/BSplines.h" -#include "stir/numerics/BSplinesRegularGrid.h" -#include "stir/interpolate_axial_position.h" -#include "stir/extend_projdata.h" -#include "stir/numerics/sampling_functions.h" -#include -#ifdef STIR_OPENMP -#include -#endif -#include "stir/num_threads.h" -#include "stir/error.h" - -START_NAMESPACE_STIR - -#if STIR_VERSION < 060000 -Succeeded -interpolate_axial_position(ProjData& proj_data_out, - const ProjData& proj_data_in) -{ - const ProjDataInfo & proj_data_in_info = - *proj_data_in.get_proj_data_info_sptr(); - const ProjDataInfo & proj_data_out_info = - *proj_data_out.get_proj_data_info_sptr(); - - if (typeid(proj_data_in_info) != typeid(proj_data_out_info)) - { - error("interpolate_axial_position needs both projection data to be of the same type\n" - "(e.g. both arc-corrected or both not arc-corrected)"); - } - if (fabs(proj_data_in_info.get_scanner_ptr()->get_inner_ring_radius() - - proj_data_out_info.get_scanner_ptr()->get_inner_ring_radius()) > 1) - { - error("interpolate_axial_position needs both projection to be of a scanner with the same ring radius"); - } -// create maps for the m coordinate depending on segment and axial_position - VectorWithOffset > m_in(proj_data_in.get_min_segment_num(),proj_data_in.get_max_segment_num()); - VectorWithOffset > m_out(proj_data_out.get_min_segment_num(),proj_data_out.get_max_segment_num()); - - for (int segment=proj_data_in.get_min_segment_num();segment<=proj_data_in.get_max_segment_num();segment++) - { - m_in.at(segment).resize(proj_data_in.get_min_axial_pos_num(segment),proj_data_in.get_max_axial_pos_num(segment)); - - for (int axial_pos=proj_data_in.get_min_axial_pos_num(segment); axial_pos<=proj_data_in.get_max_axial_pos_num(segment);axial_pos++) - { - Bin bin(segment,0,axial_pos,0); - m_in.at(segment).at(axial_pos)=proj_data_in_info.get_m(bin); - } - } - - for (int segment=proj_data_out.get_min_segment_num();segment<=proj_data_out.get_max_segment_num();segment++) - { - m_out.at(segment).resize(proj_data_out.get_min_axial_pos_num(segment),proj_data_out.get_max_axial_pos_num(segment)); - - for (int axial_pos=proj_data_out.get_min_axial_pos_num(segment); axial_pos<=proj_data_out.get_max_axial_pos_num(segment);axial_pos++) - { - Bin bin(segment,0,axial_pos,0); - m_out.at(segment).at(axial_pos)=proj_data_out_info.get_m(bin); - } - } - -//now calculate the difference between the upsampled m (m_out) and the direct m (m_in) to be used for the nearest-neigbour interpolation - for (int segment=proj_data_out.get_min_segment_num();segment<=proj_data_out.get_max_segment_num();segment++) - for (int axial_pos=proj_data_out.get_min_axial_pos_num(segment); axial_pos<=proj_data_out.get_max_axial_pos_num(segment);axial_pos++) - { - VectorWithOffset diff(proj_data_in.get_min_axial_pos_num(0),proj_data_in.get_max_axial_pos_num(0)); - int axial_pos_in=0; - - if (proj_data_in_info==proj_data_out_info) - axial_pos_in=axial_pos; - else{ - for (auto it=m_in.at(0).begin();it!=m_in.at(0).end();it++) - { - diff.at(it-m_in.at(0).begin())=abs(m_out.at(segment).at(axial_pos) - *it); - } - auto result=std::min_element(diff.begin(),diff.end()); - axial_pos_in=std::distance(diff.begin(), result); - } - - Sinogram sino= proj_data_out.get_empty_sinogram(axial_pos,segment); - const auto sino_in=proj_data_in.get_sinogram(axial_pos_in,0); -#ifdef STIR_OPENMP -# if _OPENMP <201107 - #pragma omp parallel for -# else - #pragma omp parallel for collapse(2) schedule(dynamic) -# endif -#endif - for (int view=proj_data_out.get_min_view_num(); view<=proj_data_out.get_max_view_num();view++) - for (int tan=proj_data_out.get_min_tangential_pos_num(); tan<=proj_data_out.get_max_tangential_pos_num();tan++) - { - sino[view][tan]=sino_in[view][tan]; - } - if (proj_data_out.set_sinogram(sino) == Succeeded::no) - return Succeeded::no; - } - return Succeeded::yes; -} -#endif - -END_NAMESPACE_STIR diff --git a/src/include/stir/extend_projdata.h b/src/include/stir/extend_projdata.h index 17035e0d4b..c505978268 100644 --- a/src/include/stir/extend_projdata.h +++ b/src/include/stir/extend_projdata.h @@ -30,15 +30,6 @@ tangential position, the values are extrapolated by nearest neighbour known valu This is probably only useful before calling interpolation routines, or for FORE. */ -#if STIR_VERSION < 060000 - STIR_DEPRECATED Array<3,float> - extend_segment_in_views(const SegmentBySinogram& sino, - const int min_view_extension, const int max_view_extension); - - STIR_DEPRECATED Array<2,float> - extend_sinogram_in_views(const Sinogram& sino, - const int min_view_extension, const int max_view_extension); -#endif /*! \brief Generic function to extend a segment in any or all directions. Axially and diff --git a/src/include/stir/interpolate_axial_position.h b/src/include/stir/interpolate_axial_position.h deleted file mode 100644 index 2d21a4260f..0000000000 --- a/src/include/stir/interpolate_axial_position.h +++ /dev/null @@ -1,50 +0,0 @@ -// -// -/* - Copyright (C) 2022 National Physical Laboratory - Copyright (C) 2022 University College London - This file is part of STIR. - - SPDX-License-Identifier: Apache-2.0 - - See STIR/LICENSE.txt for details -*/ -/* - \ingroup projdata - \file Declaration of stir::interpolate_axial_position - - \author Daniel Deidda - \author Kris Thielemans - -*/ - -START_NAMESPACE_STIR - -class ProjData; -template class BasicCoordinate; -template class Sinogram; -template class SegmentBySinogram; - - -//! \brief Perform linear Interpolation -/*! - \ingroup projdata - \param[out] proj_data_out Its projection_data_info is used to - determine output characteristics. Data will be 'put' in here using - ProjData::set_sinogram(). - \param[in] proj_data_in input data - This function interpolates from a axially downsampled projdata to a full scanner. - This mostly is useful in the scatter sinogram expansion. -*/ -//@{ -#if STIR_VERSION < 060000 -STIR_DEPRECATED Succeeded -interpolate_axial_position(ProjData& proj_data_out, - const ProjData& proj_data_in); -//@} -#endif - -END_NAMESPACE_STIR - - - From ff35dc97185b433341fbfabb8efbd8a02f42a335 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sun, 28 Jan 2024 09:47:01 +0000 Subject: [PATCH 489/509] removed obsolete include --- src/buildblock/interpolate_projdata.cxx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/buildblock/interpolate_projdata.cxx b/src/buildblock/interpolate_projdata.cxx index 12984e051c..f4a9e768fd 100644 --- a/src/buildblock/interpolate_projdata.cxx +++ b/src/buildblock/interpolate_projdata.cxx @@ -31,7 +31,6 @@ #include "stir/numerics/BSplines.h" #include "stir/numerics/BSplinesRegularGrid.h" #include "stir/interpolate_projdata.h" -#include "stir/interpolate_axial_position.h" #include "stir/extend_projdata.h" #include "stir/numerics/sampling_functions.h" #include "stir/error.h" From 57eb72d0d509f39783e19bf2f46bee1fce5bf88a Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sun, 28 Jan 2024 19:39:58 +0000 Subject: [PATCH 490/509] remove STIR_NO_NAMESPACES macro and work-arounds --- src/IO/InterfileHeader.cxx | 5 +-- src/IO/InterfileHeaderSiemens.cxx | 2 -- src/IO/InterfilePDFSHeaderSPECT.cxx | 2 -- src/IO/stir_ecat6.cxx | 2 -- src/IO/stir_ecat7.cxx | 14 +-------- src/Shape_buildblock/Shape3D.cxx | 2 -- src/analytic/FBP3DRP/FBP3DRP.cxx | 2 -- .../FBP3DRP/FBP3DRPReconstruction.cxx | 2 -- .../ArrayFilter3DUsingConvolution.cxx | 2 -- src/buildblock/DiscretisedDensity.cxx | 10 ------ src/buildblock/DynamicDiscretisedDensity.cxx | 2 -- src/buildblock/GatedDiscretisedDensity.cxx | 2 -- src/buildblock/IndexRange.cxx | 31 ------------------- src/buildblock/MedianArrayFilter3D.cxx | 2 -- src/buildblock/NumericType.cxx | 2 -- src/buildblock/ParsingObject.cxx | 2 -- src/buildblock/ProjDataFromStream.cxx | 2 -- src/buildblock/ProjDataGEHDF5.cxx | 8 ----- src/buildblock/ProjDataInfo.cxx | 2 -- ...ojDataInfoBlocksOnCylindricalNoArcCorr.cxx | 2 -- src/buildblock/ProjDataInfoCylindrical.cxx | 2 -- .../ProjDataInfoCylindricalArcCorr.cxx | 2 -- src/buildblock/ProjDataInfoGeneric.cxx | 2 -- .../ProjDataInfoGenericNoArcCorr.cxx | 2 -- src/buildblock/ProjDataInterfile.cxx | 2 -- src/buildblock/SSRB.cxx | 2 -- src/buildblock/Scanner.cxx | 2 -- .../SeparableConvolutionImageFilter.cxx | 27 ++++------------ .../SeparableGaussianArrayFilter.cxx | 2 -- src/buildblock/SeparableMetzArrayFilter.cxx | 2 -- src/buildblock/TimeFrameDefinitions.cxx | 2 -- src/buildblock/TimeGateDefinitions.cxx | 2 -- src/buildblock/VoxelsOnCartesianGrid.cxx | 2 -- src/buildblock/centre_of_gravity.cxx | 2 -- src/buildblock/recon_array_functions.cxx | 2 -- src/buildblock/utilities.cxx | 6 ---- src/data_buildblock/SinglesRatesFromECAT7.cxx | 2 -- .../SinglesRatesFromSglFile.cxx | 2 -- src/eval_buildblock/ROIValues.cxx | 2 -- .../buildblock/DAVArrayFilter3D.cxx | 2 -- .../buildblock/DAVImageFilter3D.cxx | 2 -- ...ModifiedInverseAveragingImageFilterAll.cxx | 2 -- .../ModifiedInverseAverigingArrayFilter.cxx | 2 -- .../ModifiedInverseAverigingImageFilter.cxx | 2 -- .../NonseparableSpatiallyVaryingFilters.cxx | 2 -- .../NonseparableSpatiallyVaryingFilters3D.cxx | 2 -- .../SeparableLowPassArrayFilter.cxx | 2 -- .../buildblock/local_helping_functions.cxx | 2 -- ...iply_plane_scale_factorsImageProcessor.cxx | 2 -- .../listmode/CListModeDataLMF.cxx | 2 -- .../listmode_utilities/get_singles_info.cxx | 2 -- .../lm_to_projdata_with_MC.cxx | 2 -- src/experimental/motion/Polaris_MT_File.cxx | 2 -- .../motion/RigidObject3DTransformation.cxx | 2 -- .../motion_utilities/add_planes_to_image.cxx | 2 -- .../find_motion_corrected_norm_factors.cxx | 5 +-- .../remove_corrupted_sinograms.cxx | 2 -- .../rigid_object_transform_image.cxx | 2 -- .../BinNormalisationSinogramRescaling.cxx | 2 -- .../recon_buildblock/ProjMatrixByDensel.cxx | 6 ---- .../ProjMatrixByDenselUsingRayTracing.cxx | 2 -- ...test_ProjMatrixByBinUsingInterpolation.cxx | 2 -- .../test/test_LmToProjdataWithMC.cxx | 2 -- src/experimental/test/test_Quaternion.cxx | 2 -- .../test/test_proj_data_info_LOR.cxx | 2 -- src/experimental/utilities/CoG.cxx | 2 -- .../utilities/add_time_frame_info.cxx | 2 -- .../utilities/apply_plane_rescale_factors.cxx | 2 -- .../utilities/change_mhead_file_type.cxx | 2 -- .../compute_plane_rescale_factors.cxx | 2 -- .../utilities/create_normfactors.cxx | 2 -- .../utilities/create_normfactors3D.cxx | 2 -- .../utilities/fillwithotherprojdata.cxx | 2 -- .../find_sinogram_rescaling_factors.cxx | 2 -- src/experimental/utilities/fit_cylinder.cxx | 2 -- .../utilities/interpolate_blocks.cxx | 2 -- src/experimental/utilities/inverse_SSRB.cxx | 2 -- .../utilities/inverse_proj_data.cxx | 2 -- .../line_profiles_through_projdata.cxx | 2 -- .../utilities/list_TAC_ROI_values.cxx | 2 -- src/experimental/utilities/make_cylinder.cxx | 2 -- .../utilities/make_grid_image.cxx | 2 -- .../utilities/normalizedbckproj.cxx | 2 -- .../utilities/precompute_denominator_SPS.cxx | 2 -- .../utilities/prepare_projdata.cxx | 2 -- .../utilities/remove_sinograms.cxx | 2 -- .../utilities/set_blocks_to_value.cxx | 2 -- .../utilities/shift_projdata_along_axis.cxx | 2 -- .../utilities/show_ecat7_header.cxx | 2 -- .../utilities/threshold_norm_data.cxx | 2 -- .../utilities/zero_projdata_from_norm.cxx | 2 -- src/include/stir/Array.inl | 12 ------- src/include/stir/Array1d.h | 8 ----- src/include/stir/BasicCoordinate.inl | 4 --- src/include/stir/ByteOrder.h | 4 --- .../DiscretisedDensityOnCartesianGrid.inl | 6 ---- src/include/stir/IO/ecat6_types.h | 10 ------ src/include/stir/IO/stir_ecat7.h | 10 ------ src/include/stir/IO/stir_ecat_common.h | 17 +--------- src/include/stir/IndexRange.inl | 5 --- src/include/stir/NumericVectorWithOffset.inl | 16 ---------- src/include/stir/ProjDataFromStream.inl | 5 --- src/include/stir/config/gcc.h | 4 --- src/include/stir/evaluation/ROIValues.h | 5 --- .../BinNormalisationFromECAT8.h | 2 -- .../BinNormalisationFromGEHDF5.h | 2 -- .../stir/recon_buildblock/RelatedBins.h | 5 --- .../stir/recon_buildblock/RelatedDensels.h | 5 --- src/include/stir/stir_math.h | 3 -- .../recon_buildblock/ProjMatrixByDensel.h | 4 --- .../KOSMAPOSL/KOSMAPOSLReconstruction.cxx | 2 -- .../OSMAPOSL/OSMAPOSLReconstruction.cxx | 2 -- src/iterative/OSSPS/OSSPSReconstruction.cxx | 2 -- src/listmode_buildblock/CListModeDataECAT.cxx | 2 -- .../CListModeDataSAFIR.cxx | 2 -- src/listmode_buildblock/LmToProjData.cxx | 2 -- .../LmToProjDataBootstrap.cxx | 2 -- src/listmode_utilities/list_lm_events.cxx | 2 -- src/listmode_utilities/lm_fansums.cxx | 2 -- src/listmode_utilities/lm_to_projdata.cxx | 2 -- src/listmode_utilities/print_sgl_values.cxx | 2 -- src/listmode_utilities/rebin_sgl_file.cxx | 2 -- src/listmode_utilities/scan_sgl_file.cxx | 2 -- ...BackProjectorByBinUsingProjMatrixByBin.cxx | 5 +-- .../BinNormalisationFromECAT7.cxx | 2 -- .../BinNormalisationFromECAT8.cxx | 2 -- .../BinNormalisationFromGEHDF5.cxx | 2 -- .../BinNormalisationSPECT.cxx | 2 -- ...wardProjectorByBinUsingProjMatrixByBin.cxx | 7 +---- ...rdProjectorByBinUsingRayTracing_Siddon.cxx | 2 -- src/recon_buildblock/FourierRebinning.cxx | 2 -- .../IterativeReconstruction.cxx | 2 -- ...ihoodWithLinearModelForMeanAndProjData.cxx | 2 -- .../ProjMatrixByBinUsingRayTracing.cxx | 2 -- .../ProjMatrixElemsForOneBin.cxx | 9 +----- .../ProjMatrixElemsForOneDensel.cxx | 7 +---- .../RayTraceVoxelsOnCartesianGrid.cxx | 2 -- src/recon_test/bcktest.cxx | 2 -- ...ataSymmetriesForBins_PET_CartesianGrid.cxx | 2 -- src/test/IO/test_IO_DiscretisedDensity.cxx | 2 -- .../IO/test_IO_DynamicDiscretisedDensity.cxx | 2 -- .../test_IO_ParametricDiscretisedDensity.cxx | 2 -- .../test_ParametricDiscretisedDensity.cxx | 2 -- src/test/numerics/test_BSplines.cxx | 2 -- .../numerics/test_BSplinesRegularGrid.cxx | 2 -- .../numerics/test_BSplinesRegularGrid1D.cxx | 3 +- src/test/numerics/test_IR_filters.cxx | 2 -- src/test/test_Array.cxx | 2 -- src/test/test_ByteOrder.cxx | 2 -- src/test/test_DynamicDiscretisedDensity.cxx | 2 -- src/test/test_ImagingModality.cxx | 2 -- src/test/test_OutputFileFormat.cxx | 2 -- src/test/test_VectorWithOffset.cxx | 2 -- src/test/test_coordinates.cxx | 10 ------ src/test/test_filename_functions.cxx | 2 -- src/test/test_find_fwhm_in_image.cxx | 2 -- src/test/test_interpolate.cxx | 2 -- src/test/test_linear_regression.cxx | 2 -- src/test/test_multiple_proj_data.cxx | 2 -- src/test/test_proj_data_info.cxx | 2 -- src/test/test_stir_math.cxx | 2 -- src/test/test_warp_image.cxx | 2 -- src/utilities/SSRB.cxx | 2 -- .../calculate_attenuation_coefficients.cxx | 2 -- src/utilities/compare_image.cxx | 2 -- .../construct_randoms_from_GEsingles.cxx | 2 -- .../construct_randoms_from_singles.cxx | 2 -- src/utilities/conv_gipl_to_interfile.cxx | 2 -- src/utilities/conv_interfile_to_gipl.cxx | 2 -- src/utilities/correct_projdata.cxx | 2 -- src/utilities/create_projdata_template.cxx | 2 -- src/utilities/display_projdata.cxx | 2 -- src/utilities/do_linear_regression.cxx | 2 -- src/utilities/ecat/conv_to_ecat6.cxx | 2 -- src/utilities/ecat/conv_to_ecat7.cxx | 2 -- src/utilities/ecat/convecat6_if.cxx | 2 -- src/utilities/ecat/copy_ecat7_header.cxx | 2 -- src/utilities/ecat/ecat_swap_corners.cxx | 2 -- src/utilities/ecat/ifheaders_for_ecat7.cxx | 2 -- .../ecat/print_ecat_singles_values.cxx | 2 -- ..._triple_energy_window_scatter_sinogram.cxx | 2 -- src/utilities/find_fwhm_in_image.cxx | 2 -- src/utilities/find_maxima_in_image.cxx | 2 -- ...ents_in_image_quality_phantom_nema_nu4.cxx | 2 -- ...um_projection_of_viewgram_and_sinogram.cxx | 2 -- src/utilities/get_time_frame_info.cxx | 2 -- src/utilities/list_ROI_values.cxx | 2 -- src/utilities/manip_image.cxx | 2 -- src/utilities/manip_projdata.cxx | 2 -- src/utilities/postfilter.cxx | 2 -- src/utilities/rebin_projdata.cxx | 2 -- ...rate_true_from_random_scatter_for_necr.cxx | 2 -- src/utilities/shift_image.cxx | 2 -- src/utilities/shift_image_origin.cxx | 2 -- src/utilities/stir_math.cxx | 2 -- .../warp_and_accumulate_gated_images.cxx | 2 -- src/utilities/write_proj_matrix_by_bin.cxx | 2 -- src/utilities/zoom_image.cxx | 2 -- 198 files changed, 15 insertions(+), 593 deletions(-) diff --git a/src/IO/InterfileHeader.cxx b/src/IO/InterfileHeader.cxx index 0e7e9e1c8e..a43af08990 100644 --- a/src/IO/InterfileHeader.cxx +++ b/src/IO/InterfileHeader.cxx @@ -1032,10 +1032,7 @@ bool InterfilePDFSHeader::post_processing() { is_arccorrected = false; for ( -#ifndef STIR_NO_NAMESPACES - std:: -#endif - vector::const_iterator iter = applied_corrections.begin(); + auto iter = applied_corrections.begin(); iter != applied_corrections.end(); ++iter) { diff --git a/src/IO/InterfileHeaderSiemens.cxx b/src/IO/InterfileHeaderSiemens.cxx index 673d22bdfc..28dd03cc24 100644 --- a/src/IO/InterfileHeaderSiemens.cxx +++ b/src/IO/InterfileHeaderSiemens.cxx @@ -30,14 +30,12 @@ #include "stir/warning.h" #include "stir/error.h" -#ifndef STIR_NO_NAMESPACES using std::pair; using std::sort; using std::cerr; using std::endl; using std::string; using std::vector; -#endif START_NAMESPACE_STIR diff --git a/src/IO/InterfilePDFSHeaderSPECT.cxx b/src/IO/InterfilePDFSHeaderSPECT.cxx index c72e718403..17109f4015 100644 --- a/src/IO/InterfilePDFSHeaderSPECT.cxx +++ b/src/IO/InterfilePDFSHeaderSPECT.cxx @@ -23,12 +23,10 @@ #include #include "stir/warning.h" -#ifndef STIR_NO_NAMESPACES using std::pair; using std::sort; using std::cerr; using std::endl; -#endif START_NAMESPACE_STIR diff --git a/src/IO/stir_ecat6.cxx b/src/IO/stir_ecat6.cxx index b251178ba2..be50f1aab5 100644 --- a/src/IO/stir_ecat6.cxx +++ b/src/IO/stir_ecat6.cxx @@ -52,13 +52,11 @@ #include #include -#ifndef STIR_NO_NAMESPACES using std::cout; using std::endl; using std::fstream; using std::ios; using std::min; -#endif START_NAMESPACE_STIR START_NAMESPACE_ECAT diff --git a/src/IO/stir_ecat7.cxx b/src/IO/stir_ecat7.cxx index 409171f62b..74328e66bd 100644 --- a/src/IO/stir_ecat7.cxx +++ b/src/IO/stir_ecat7.cxx @@ -67,7 +67,6 @@ #include #include -#ifndef STIR_NO_NAMESPACES using std::size_t; using std::string; using std::ios; @@ -77,7 +76,6 @@ using std::cerr; using std::endl; using std::cout; using std::copy; -#endif START_NAMESPACE_STIR START_NAMESPACE_ECAT @@ -275,11 +273,7 @@ static long offset_in_ECAT_file (MatrixFile *mptr, int frame, int plane, int gat switch (mptr->mhptr->file_type) { -#ifndef STIR_NO_NAMESPACES case ::Sinogram: -#else - case CTISinogram: -#endif { // KT 14/05/2002 added error check. @@ -1993,13 +1987,7 @@ update_ECAT7_subheader(MatrixFile *mptr, Scan_subheader& shead, const MatDir& matdir) { const int ERROR=-1; - if (mptr->mhptr->file_type != -#ifndef STIR_NO_NAMESPACES - ::Sinogram -#else - CTISinogram -#endif - ) + if (mptr->mhptr->file_type != ::Sinogram) return Succeeded::no; return mat_write_scan_subheader(mptr->fptr, mptr->mhptr, matdir.strtblk, diff --git a/src/Shape_buildblock/Shape3D.cxx b/src/Shape_buildblock/Shape3D.cxx index 7fc34a2a15..f596ef5eb4 100644 --- a/src/Shape_buildblock/Shape3D.cxx +++ b/src/Shape_buildblock/Shape3D.cxx @@ -23,10 +23,8 @@ #include "stir/VoxelsOnCartesianGrid.h" #include "stir/info.h" -#ifndef STIR_NO_NAMESPACES using std::cerr; using std::endl; -#endif // Check the sampled elements of the voxel diff --git a/src/analytic/FBP3DRP/FBP3DRP.cxx b/src/analytic/FBP3DRP/FBP3DRP.cxx index 5c17ad2423..b3bd0007f0 100644 --- a/src/analytic/FBP3DRP/FBP3DRP.cxx +++ b/src/analytic/FBP3DRP/FBP3DRP.cxx @@ -26,11 +26,9 @@ #define Main master_main #endif -#ifndef STIR_NO_NAMESPACE using std::endl; using std::cerr; -#endif USING_NAMESPACE_STIR int Main(int argc, char **argv) diff --git a/src/analytic/FBP3DRP/FBP3DRPReconstruction.cxx b/src/analytic/FBP3DRP/FBP3DRPReconstruction.cxx index 2164c4cb64..2d6b4a332d 100644 --- a/src/analytic/FBP3DRP/FBP3DRPReconstruction.cxx +++ b/src/analytic/FBP3DRP/FBP3DRPReconstruction.cxx @@ -105,12 +105,10 @@ #include using std::min; using std::max; -#ifndef STIR_NO_NAMESPACE using std::cerr; using std::endl; using std::ofstream; using std::ios; -#endif START_NAMESPACE_STIR diff --git a/src/buildblock/ArrayFilter3DUsingConvolution.cxx b/src/buildblock/ArrayFilter3DUsingConvolution.cxx index 0c6665bf4f..7a5a007592 100644 --- a/src/buildblock/ArrayFilter3DUsingConvolution.cxx +++ b/src/buildblock/ArrayFilter3DUsingConvolution.cxx @@ -31,12 +31,10 @@ using std::max; using std::min; -#ifndef STIR_NO_NAMESPACES using std::iostream; using std::fstream; using std::cerr; using std::endl; -#endif START_NAMESPACE_STIR diff --git a/src/buildblock/DiscretisedDensity.cxx b/src/buildblock/DiscretisedDensity.cxx index 5dcb29303f..d908bef100 100644 --- a/src/buildblock/DiscretisedDensity.cxx +++ b/src/buildblock/DiscretisedDensity.cxx @@ -42,25 +42,15 @@ #include "stir/warning.h" #include "stir/error.h" -#ifndef STIR_NO_NAMESPACES using std::fstream; using std::string; -#endif START_NAMESPACE_STIR -#if 0 -// sadly, gcc 2.95.* does not support local namespaces as used below -// This is slightly funny as it does work in ProjData.cxx. -// Maybe because here it's in a template? -# if __GNUC__ == 2 #ifdef HAVE_LLN_MATRIX USING_NAMESPACE_ECAT USING_NAMESPACE_ECAT7 USING_NAMESPACE_ECAT6 -#endif -#endif - #endif /*! diff --git a/src/buildblock/DynamicDiscretisedDensity.cxx b/src/buildblock/DynamicDiscretisedDensity.cxx index d25210daf9..93f4b28399 100644 --- a/src/buildblock/DynamicDiscretisedDensity.cxx +++ b/src/buildblock/DynamicDiscretisedDensity.cxx @@ -32,9 +32,7 @@ #include "stir/warning.h" #include "stir/error.h" -#ifndef STIR_NO_NAMESPACES using std::string; -#endif START_NAMESPACE_STIR diff --git a/src/buildblock/GatedDiscretisedDensity.cxx b/src/buildblock/GatedDiscretisedDensity.cxx index e8ac559a98..f875cd7dbf 100644 --- a/src/buildblock/GatedDiscretisedDensity.cxx +++ b/src/buildblock/GatedDiscretisedDensity.cxx @@ -26,12 +26,10 @@ #include "stir/IO/interfile.h" #include "stir/IO/OutputFileFormat.h" -#ifndef STIR_NO_NAMESPACES using std::fstream; using std::cerr; using std::istringstream; using std::string; -#endif START_NAMESPACE_STIR diff --git a/src/buildblock/IndexRange.cxx b/src/buildblock/IndexRange.cxx index da47d7aea1..aa9bac8f57 100644 --- a/src/buildblock/IndexRange.cxx +++ b/src/buildblock/IndexRange.cxx @@ -36,14 +36,8 @@ IndexRange::get_regular_range( // check if empty range if (base_type::begin() == base_type::end()) { -#ifndef STIR_NO_NAMESPACES std::fill(min.begin(), min.end(), 0); std::fill(max.begin(), max.end(),-1); -#else - // gcc 2.8.1 needs ::fill, otherwise it gets confused with VectorWithOffset::fill - ::fill(min.begin(), min.end(), 0); - ::fill(max.begin(), max.end(),-1); -#endif return true; } @@ -71,13 +65,8 @@ IndexRange::get_regular_range( is_regular_range = regular_false; return false; } -#ifndef STIR_NO_NAMESPACES if (!std::equal(lower_dim_min.begin(), lower_dim_min.end(), lower_dim_min_try.begin()) || !std::equal(lower_dim_max.begin(), lower_dim_max.end(), lower_dim_max_try.begin())) -#else - if (!equal(lower_dim_min.begin(), lower_dim_min.end(), lower_dim_min_try.begin()) || - !equal(lower_dim_max.begin(), lower_dim_max.end(), lower_dim_max_try.begin())) -#endif { is_regular_range = regular_false; return false; @@ -118,13 +107,8 @@ IndexRange::get_regular_range( // check if empty range if (base_type::begin() == base_type::end()) { -#ifndef STIR_NO_NAMESPACES std::fill(min.begin(), min.end(), 0); std::fill(max.begin(), max.end(),-1); -#else - fill(min,base_type::begin(), min.end(), 0); - fill(max,base_type::begin(), max.end(),-1); -#endif return true; } @@ -152,13 +136,8 @@ IndexRange::get_regular_range( //is_regular_range = regular_false; return false; } -#ifndef STIR_NO_NAMESPACES if (!std::equal(lower_dim_min.begin(), lower_dim_min.end(), lower_dim_min_try.begin()) || !std::equal(lower_dim_max.begin(), lower_dim_max.end(), lower_dim_max_try.begin())) -#else - if (!equal(lower_dim_min.begin(), lower_dim_min.end(), lower_dim_min_try.begin()) || - !equal(lower_dim_max.begin(), lower_dim_max.end(), lower_dim_max_try.begin())) -#endif { //is_regular_range = regular_false; return false; @@ -188,13 +167,8 @@ IndexRange::get_regular_range( // check if empty range if (base_type::begin() == base_type::end()) { -#ifndef STIR_NO_NAMESPACES std::fill(min.begin(), min.end(), 0); std::fill(max.begin(), max.end(),-1); -#else - fill(min.begin(), min.end(), 0); - fill(max.begin(), max.end(),-1); -#endif return true; } @@ -222,13 +196,8 @@ IndexRange::get_regular_range( is_regular_range = regular_false; return false; } -#ifndef STIR_NO_NAMESPACES if (!std::equal(lower_dim_min.begin(), lower_dim_min.end(), lower_dim_min_try.begin()) || !std::equal(lower_dim_max.begin(), lower_dim_max.end(), lower_dim_max_try.begin())) -#else - if (!equal(lower_dim_min.begin(), lower_dim_min.end(), lower_dim_min_try.begin()) || - !equal(lower_dim_max.begin(), lower_dim_max.end(), lower_dim_max_try.begin())) -#endif { is_regular_range = regular_false; return false; diff --git a/src/buildblock/MedianArrayFilter3D.cxx b/src/buildblock/MedianArrayFilter3D.cxx index 19adc1e9c7..ada27d77c0 100644 --- a/src/buildblock/MedianArrayFilter3D.cxx +++ b/src/buildblock/MedianArrayFilter3D.cxx @@ -29,9 +29,7 @@ #include -#ifndef STIR_NO_NAMESPACES using std::nth_element; -#endif START_NAMESPACE_STIR diff --git a/src/buildblock/NumericType.cxx b/src/buildblock/NumericType.cxx index a5fa618051..c71b092514 100644 --- a/src/buildblock/NumericType.cxx +++ b/src/buildblock/NumericType.cxx @@ -24,10 +24,8 @@ #include "stir/NumericType.h" #include "stir/NumericInfo.h" -#ifndef STIR_NO_NAMESPACES using std::size_t; using std::string; -#endif START_NAMESPACE_STIR diff --git a/src/buildblock/ParsingObject.cxx b/src/buildblock/ParsingObject.cxx index c3163651c2..f505d91d8a 100644 --- a/src/buildblock/ParsingObject.cxx +++ b/src/buildblock/ParsingObject.cxx @@ -23,9 +23,7 @@ #include "stir/warning.h" #include "stir/error.h" -#ifndef STIR_NO_NAMESPACE using std::ifstream; -#endif START_NAMESPACE_STIR diff --git a/src/buildblock/ProjDataFromStream.cxx b/src/buildblock/ProjDataFromStream.cxx index 289ab9405f..c2ea406ba5 100644 --- a/src/buildblock/ProjDataFromStream.cxx +++ b/src/buildblock/ProjDataFromStream.cxx @@ -43,7 +43,6 @@ #include "stir/warning.h" #include "stir/error.h" -#ifndef STIR_NO_NAMESPACES using std::find; using std::ios; using std::iostream; @@ -53,7 +52,6 @@ using std::cout; using std::cerr; using std::endl; using std::vector; -#endif #ifdef _MSC_VER // work-around for compiler bug: VC messes up std namespace diff --git a/src/buildblock/ProjDataGEHDF5.cxx b/src/buildblock/ProjDataGEHDF5.cxx index 08f81a0ca2..dba0a58e25 100644 --- a/src/buildblock/ProjDataGEHDF5.cxx +++ b/src/buildblock/ProjDataGEHDF5.cxx @@ -31,11 +31,9 @@ #include "stir/error.h" #include "stir/CPUTimer.h" #include "stir/HighResWallClockTimer.h" -#ifndef STIR_NO_NAMESPACES using std::ofstream; using std::fstream; using std::ios; -#endif START_NAMESPACE_STIR @@ -205,14 +203,8 @@ Succeeded ProjDataGEHDF5::set_sinogram(const Sinogram& s) unsigned int ProjDataGEHDF5::find_segment_index_in_sequence(const int segment_num) const { -#ifndef STIR_NO_NAMESPACES - std::vector::const_iterator iter = std::find(segment_sequence.begin(), segment_sequence.end(), segment_num); -#else - vector::const_iterator iter = - find(segment_sequence.begin(), segment_sequence.end(), segment_num); -#endif // TODO do some proper error handling here assert(iter != segment_sequence.end()); return static_cast(iter - segment_sequence.begin()); diff --git a/src/buildblock/ProjDataInfo.cxx b/src/buildblock/ProjDataInfo.cxx index 78712ea2aa..4bdf41232b 100644 --- a/src/buildblock/ProjDataInfo.cxx +++ b/src/buildblock/ProjDataInfo.cxx @@ -60,14 +60,12 @@ #include "boost/foreach.hpp" #include "boost/format.hpp" -#ifndef STIR_NO_NAMESPACES using std::string; using std::vector; using std::cerr; using std::cout; using std::endl; using std::equal; -#endif START_NAMESPACE_STIR diff --git a/src/buildblock/ProjDataInfoBlocksOnCylindricalNoArcCorr.cxx b/src/buildblock/ProjDataInfoBlocksOnCylindricalNoArcCorr.cxx index 9964d8bc7d..de57f5a513 100644 --- a/src/buildblock/ProjDataInfoBlocksOnCylindricalNoArcCorr.cxx +++ b/src/buildblock/ProjDataInfoBlocksOnCylindricalNoArcCorr.cxx @@ -39,10 +39,8 @@ #include #endif -#ifndef STIR_NO_NAMESPACES using std::endl; using std::ends; -#endif START_NAMESPACE_STIR ProjDataInfoBlocksOnCylindricalNoArcCorr:: diff --git a/src/buildblock/ProjDataInfoCylindrical.cxx b/src/buildblock/ProjDataInfoCylindrical.cxx index fc5ed1d7e3..1ef98f3478 100644 --- a/src/buildblock/ProjDataInfoCylindrical.cxx +++ b/src/buildblock/ProjDataInfoCylindrical.cxx @@ -43,7 +43,6 @@ #include "stir/warning.h" #include "stir/error.h" -#ifndef STIR_NO_NAMESPACES using std::min_element; using std::max_element; using std::min; @@ -53,7 +52,6 @@ using std::endl; using std::string; using std::pair; using std::vector; -#endif START_NAMESPACE_STIR diff --git a/src/buildblock/ProjDataInfoCylindricalArcCorr.cxx b/src/buildblock/ProjDataInfoCylindricalArcCorr.cxx index 68a8cef70e..7cdbf24f77 100644 --- a/src/buildblock/ProjDataInfoCylindricalArcCorr.cxx +++ b/src/buildblock/ProjDataInfoCylindricalArcCorr.cxx @@ -37,11 +37,9 @@ #include #endif -#ifndef STIR_NO_NAMESPACES using std::endl; using std::ends; using std::string; -#endif START_NAMESPACE_STIR diff --git a/src/buildblock/ProjDataInfoGeneric.cxx b/src/buildblock/ProjDataInfoGeneric.cxx index c03adc7d81..5752a8800f 100755 --- a/src/buildblock/ProjDataInfoGeneric.cxx +++ b/src/buildblock/ProjDataInfoGeneric.cxx @@ -40,14 +40,12 @@ #include "stir/error.h" #include -#ifndef STIR_NO_NAMESPACES using std::min_element; using std::max_element; using std::min; using std::max; using std::swap; using std::endl; -#endif START_NAMESPACE_STIR diff --git a/src/buildblock/ProjDataInfoGenericNoArcCorr.cxx b/src/buildblock/ProjDataInfoGenericNoArcCorr.cxx index a79eb80ad7..c6754c142e 100644 --- a/src/buildblock/ProjDataInfoGenericNoArcCorr.cxx +++ b/src/buildblock/ProjDataInfoGenericNoArcCorr.cxx @@ -41,10 +41,8 @@ #include -#ifndef STIR_NO_NAMESPACES using std::endl; using std::ends; -#endif START_NAMESPACE_STIR ProjDataInfoGenericNoArcCorr:: diff --git a/src/buildblock/ProjDataInterfile.cxx b/src/buildblock/ProjDataInterfile.cxx index 74042f32b3..c3a3cbc5b1 100644 --- a/src/buildblock/ProjDataInterfile.cxx +++ b/src/buildblock/ProjDataInterfile.cxx @@ -25,13 +25,11 @@ #include #include -#ifndef STIR_NO_NAMESPACES using std::iostream; using std::fstream; using std::vector; using std::string; using std::ios; -#endif START_NAMESPACE_STIR diff --git a/src/buildblock/SSRB.cxx b/src/buildblock/SSRB.cxx index ea2d31b95c..2d6ca49050 100644 --- a/src/buildblock/SSRB.cxx +++ b/src/buildblock/SSRB.cxx @@ -30,12 +30,10 @@ #include "stir/warning.h" #include "stir/error.h" -#ifndef STIR_NO_NAMESPACES using std::fstream; using std::min; using std::max; using std::string; -#endif START_NAMESPACE_STIR diff --git a/src/buildblock/Scanner.cxx b/src/buildblock/Scanner.cxx index 2a4b02c9b8..997dea64e7 100644 --- a/src/buildblock/Scanner.cxx +++ b/src/buildblock/Scanner.cxx @@ -49,14 +49,12 @@ #include "stir/warning.h" #include "stir/error.h" -#ifndef STIR_NO_NAMESPACES using std::cerr; using std::cout; using std::endl; using std::cin; using std::string; using std::list; -#endif START_NAMESPACE_STIR diff --git a/src/buildblock/SeparableConvolutionImageFilter.cxx b/src/buildblock/SeparableConvolutionImageFilter.cxx index ed19b1bf3e..951ab40b05 100644 --- a/src/buildblock/SeparableConvolutionImageFilter.cxx +++ b/src/buildblock/SeparableConvolutionImageFilter.cxx @@ -60,13 +60,8 @@ post_processing() // copy filter_coefficients_for_parsing to filter_coefficients // todo drop any 0s at the start or end - typename VectorWithOffset< VectorWithOffset >::iterator - coefficients_iter = filter_coefficients.begin(); -#ifndef STIR_NO_NAMESPACES - std:: -#endif - vector< vector >::const_iterator - parsing_iter = filter_coefficients_for_parsing.begin(); + auto coefficients_iter = filter_coefficients.begin(); + auto parsing_iter = filter_coefficients_for_parsing.begin(); for (; parsing_iter != filter_coefficients_for_parsing.end(); ++parsing_iter, ++coefficients_iter) @@ -82,13 +77,8 @@ post_processing() *coefficients_iter = VectorWithOffset(min_index, min_index + size - 1); // can't use std::copy because of cast. sigh. - typename VectorWithOffset::iterator - coefficients_elem_iter = coefficients_iter->begin(); -#ifndef STIR_NO_NAMESPACES - std:: -#endif - vector::const_iterator - parsing_elem_iter = parsing_iter->begin(); + auto coefficients_elem_iter = coefficients_iter->begin(); + auto parsing_elem_iter = parsing_iter->begin(); for (; parsing_elem_iter != parsing_iter->end(); ++parsing_elem_iter, ++coefficients_elem_iter) @@ -121,13 +111,8 @@ SeparableConvolutionImageFilter( // copy filter_coefficients to filter_coefficients_for_parsing such // that get_parameters() works properly - typename VectorWithOffset< VectorWithOffset >::const_iterator - coefficients_iter = filter_coefficients.begin(); -#ifndef STIR_NO_NAMESPACES - std:: -#endif - vector< vector >::iterator - parsing_iter = filter_coefficients_for_parsing.begin(); + auto coefficients_iter = filter_coefficients.begin(); + auto parsing_iter = filter_coefficients_for_parsing.begin(); for (; parsing_iter != filter_coefficients_for_parsing.end(); ++parsing_iter, ++coefficients_iter) diff --git a/src/buildblock/SeparableGaussianArrayFilter.cxx b/src/buildblock/SeparableGaussianArrayFilter.cxx index b596126d54..4c8c013278 100644 --- a/src/buildblock/SeparableGaussianArrayFilter.cxx +++ b/src/buildblock/SeparableGaussianArrayFilter.cxx @@ -38,13 +38,11 @@ #include -#ifndef STIR_NO_NAMESPACES using std::ios; using std::fstream; using std::iostream; using std::cerr; using std::endl; -#endif START_NAMESPACE_STIR diff --git a/src/buildblock/SeparableMetzArrayFilter.cxx b/src/buildblock/SeparableMetzArrayFilter.cxx index d425c07a71..afbc3f8f22 100644 --- a/src/buildblock/SeparableMetzArrayFilter.cxx +++ b/src/buildblock/SeparableMetzArrayFilter.cxx @@ -32,10 +32,8 @@ #include #include -#ifndef STIR_NO_NAMESPACES using std::cerr; using std::endl; -#endif START_NAMESPACE_STIR diff --git a/src/buildblock/TimeFrameDefinitions.cxx b/src/buildblock/TimeFrameDefinitions.cxx index a4f669eddf..625780a100 100644 --- a/src/buildblock/TimeFrameDefinitions.cxx +++ b/src/buildblock/TimeFrameDefinitions.cxx @@ -36,7 +36,6 @@ #include "stir/error.h" #include "stir/info.h" -#ifndef STIR_NO_NAMESPACES using std::string; using std::pair; using std::vector; @@ -44,7 +43,6 @@ using std::make_pair; using std::cerr; using std::endl; using std::ifstream; -#endif START_NAMESPACE_STIR diff --git a/src/buildblock/TimeGateDefinitions.cxx b/src/buildblock/TimeGateDefinitions.cxx index fd0fa77c75..a77cbdf51a 100644 --- a/src/buildblock/TimeGateDefinitions.cxx +++ b/src/buildblock/TimeGateDefinitions.cxx @@ -22,7 +22,6 @@ #include #include -#ifndef STIR_NO_NAMESPACES using std::make_pair; using std::cerr; using std::endl; @@ -30,7 +29,6 @@ using std::ifstream; using std::string; using std::pair; using std::vector; -#endif START_NAMESPACE_STIR diff --git a/src/buildblock/VoxelsOnCartesianGrid.cxx b/src/buildblock/VoxelsOnCartesianGrid.cxx index 8a888fd421..a4ee7315da 100644 --- a/src/buildblock/VoxelsOnCartesianGrid.cxx +++ b/src/buildblock/VoxelsOnCartesianGrid.cxx @@ -44,10 +44,8 @@ #include "stir/warning.h" #include "stir/error.h" -#ifndef STIR_NO_NAMESPACES using std::ifstream; using std::max; -#endif #include START_NAMESPACE_STIR diff --git a/src/buildblock/centre_of_gravity.cxx b/src/buildblock/centre_of_gravity.cxx index bc8eea9383..52056c0a88 100644 --- a/src/buildblock/centre_of_gravity.cxx +++ b/src/buildblock/centre_of_gravity.cxx @@ -25,10 +25,8 @@ #include "stir/error.h" #include -#ifndef STIR_NO_NAMESPACES using std::min; using std::max; -#endif START_NAMESPACE_STIR diff --git a/src/buildblock/recon_array_functions.cxx b/src/buildblock/recon_array_functions.cxx index 35d26e8832..b5e98712af 100644 --- a/src/buildblock/recon_array_functions.cxx +++ b/src/buildblock/recon_array_functions.cxx @@ -34,12 +34,10 @@ #include -#ifndef STIR_NO_NAMESPACES using std::cout; using std::cerr; using std::endl; using std::max; -#endif START_NAMESPACE_STIR diff --git a/src/buildblock/utilities.cxx b/src/buildblock/utilities.cxx index ddae1efef0..dfdd3c396c 100644 --- a/src/buildblock/utilities.cxx +++ b/src/buildblock/utilities.cxx @@ -23,7 +23,6 @@ #include #include -#ifndef STIR_NO_NAMESPACES using std::ifstream; using std::ofstream; using std::iostream; @@ -35,7 +34,6 @@ using std::cerr; using std::endl; using std::ios; using std::string; -#endif START_NAMESPACE_STIR @@ -461,11 +459,7 @@ void * read_stream_in_memory(istream& input, streamsize& file_size) while( to_read != 0) { const streamsize this_read_size = -#ifndef STIR_NO_NAMESPACES std::min(to_read, chunk_size); -#else - min(to_read, chunk_size); -#endif input.read(current_location, this_read_size); if (!input) { error("Error after reading from stream"); } diff --git a/src/data_buildblock/SinglesRatesFromECAT7.cxx b/src/data_buildblock/SinglesRatesFromECAT7.cxx index ff88c1cd3d..937f5f7c67 100644 --- a/src/data_buildblock/SinglesRatesFromECAT7.cxx +++ b/src/data_buildblock/SinglesRatesFromECAT7.cxx @@ -29,10 +29,8 @@ #include "stir/warning.h" #include "stir/error.h" -#ifndef STIR_NO_NAMESPACES using std::ofstream; using std::streampos; -#endif START_NAMESPACE_STIR START_NAMESPACE_ECAT diff --git a/src/data_buildblock/SinglesRatesFromSglFile.cxx b/src/data_buildblock/SinglesRatesFromSglFile.cxx index dd7232fc86..c8f4d19c60 100644 --- a/src/data_buildblock/SinglesRatesFromSglFile.cxx +++ b/src/data_buildblock/SinglesRatesFromSglFile.cxx @@ -30,11 +30,9 @@ #include #include -#ifndef STIR_NO_NAMESPACES using std::ofstream; using std::streampos; using std::ios; -#endif diff --git a/src/eval_buildblock/ROIValues.cxx b/src/eval_buildblock/ROIValues.cxx index 7cb8129d4f..6e0a00737e 100644 --- a/src/eval_buildblock/ROIValues.cxx +++ b/src/eval_buildblock/ROIValues.cxx @@ -30,10 +30,8 @@ #endif -#ifndef STIR_NO_NAMESPACES using std::endl; using std::ends; -#endif START_NAMESPACE_STIR diff --git a/src/experimental/buildblock/DAVArrayFilter3D.cxx b/src/experimental/buildblock/DAVArrayFilter3D.cxx index 59c4dd7fa3..a8f91c2832 100644 --- a/src/experimental/buildblock/DAVArrayFilter3D.cxx +++ b/src/experimental/buildblock/DAVArrayFilter3D.cxx @@ -24,7 +24,6 @@ #include #include -#ifndef STIR_NO_NAMESPACES using std::ifstream; using std::ofstream; using std::fstream; @@ -33,7 +32,6 @@ using std::cerr; using std::endl; using std::sort; using std::min_element; -#endif START_NAMESPACE_STIR diff --git a/src/experimental/buildblock/DAVImageFilter3D.cxx b/src/experimental/buildblock/DAVImageFilter3D.cxx index 0b2af597f4..53c2452bad 100644 --- a/src/experimental/buildblock/DAVImageFilter3D.cxx +++ b/src/experimental/buildblock/DAVImageFilter3D.cxx @@ -24,13 +24,11 @@ #include #include -#ifndef STIR_NO_NAMESPACES using std::ifstream; using std::ofstream; using std::fstream; using std::cerr; using std::endl; -#endif START_NAMESPACE_STIR diff --git a/src/experimental/buildblock/ModifiedInverseAveragingImageFilterAll.cxx b/src/experimental/buildblock/ModifiedInverseAveragingImageFilterAll.cxx index f8ede8cae1..7b5c6eb0e6 100644 --- a/src/experimental/buildblock/ModifiedInverseAveragingImageFilterAll.cxx +++ b/src/experimental/buildblock/ModifiedInverseAveragingImageFilterAll.cxx @@ -34,14 +34,12 @@ #include #include -#ifndef STIR_NO_NAMESPACES using std::ios; using std::find; using std::iostream; using std::fstream; using std::cerr; using std::endl; -#endif #include "stir_experimental/local_helping_functions.h" #include "stir_experimental/fwd_and_bck_manipulation_for_SAF.h" diff --git a/src/experimental/buildblock/ModifiedInverseAverigingArrayFilter.cxx b/src/experimental/buildblock/ModifiedInverseAverigingArrayFilter.cxx index ae934e1f31..420acd1362 100644 --- a/src/experimental/buildblock/ModifiedInverseAverigingArrayFilter.cxx +++ b/src/experimental/buildblock/ModifiedInverseAverigingArrayFilter.cxx @@ -15,13 +15,11 @@ #include #include -#ifndef STIR_NO_NAMESPACES using std::ios; using std::fstream; using std::iostream; using std::cerr; using std::endl; -#endif diff --git a/src/experimental/buildblock/ModifiedInverseAverigingImageFilter.cxx b/src/experimental/buildblock/ModifiedInverseAverigingImageFilter.cxx index 316e518e63..5cb212a2eb 100644 --- a/src/experimental/buildblock/ModifiedInverseAverigingImageFilter.cxx +++ b/src/experimental/buildblock/ModifiedInverseAverigingImageFilter.cxx @@ -46,14 +46,12 @@ #include #include -#ifndef STIR_NO_NAMESPACES using std::ios; using std::find; using std::iostream; using std::fstream; using std::cerr; using std::endl; -#endif #include "stir_experimental/local_helping_functions.h" diff --git a/src/experimental/buildblock/NonseparableSpatiallyVaryingFilters.cxx b/src/experimental/buildblock/NonseparableSpatiallyVaryingFilters.cxx index 750e57a653..e166beb805 100644 --- a/src/experimental/buildblock/NonseparableSpatiallyVaryingFilters.cxx +++ b/src/experimental/buildblock/NonseparableSpatiallyVaryingFilters.cxx @@ -33,14 +33,12 @@ See STIR/LICENSE.txt for details #include #include -#ifndef STIR_NO_NAMESPACES using std::ios; using std::find; using std::iostream; using std::fstream; using std::cerr; using std::endl; -#endif #include "stir_experimental/local_helping_functions.h" #include "stir_experimental/fwd_and_bck_manipulation_for_SAF.h" diff --git a/src/experimental/buildblock/NonseparableSpatiallyVaryingFilters3D.cxx b/src/experimental/buildblock/NonseparableSpatiallyVaryingFilters3D.cxx index 6b41fdc244..352dc80733 100644 --- a/src/experimental/buildblock/NonseparableSpatiallyVaryingFilters3D.cxx +++ b/src/experimental/buildblock/NonseparableSpatiallyVaryingFilters3D.cxx @@ -35,7 +35,6 @@ See STIR/LICENSE.txt for details #include #include -#ifndef STIR_NO_NAMESPACES using std::ios; using std::find; using std::iostream; @@ -43,7 +42,6 @@ using std::fstream; using std::cerr; using std::endl; using std::map; -#endif #include "stir_experimental/local_helping_functions.h" #include "stir_experimental/fwd_and_bck_manipulation_for_SAF.h" diff --git a/src/experimental/buildblock/SeparableLowPassArrayFilter.cxx b/src/experimental/buildblock/SeparableLowPassArrayFilter.cxx index fc57905d91..ea608d78ff 100644 --- a/src/experimental/buildblock/SeparableLowPassArrayFilter.cxx +++ b/src/experimental/buildblock/SeparableLowPassArrayFilter.cxx @@ -6,13 +6,11 @@ #include #include -#ifndef STIR_NO_NAMESPACES using std::ios; using std::fstream; using std::iostream; using std::cerr; using std::endl; -#endif /* Copyright (C) 2000- 2002, IRSL SPDX-License-Identifier: Apache-2.0 diff --git a/src/experimental/buildblock/local_helping_functions.cxx b/src/experimental/buildblock/local_helping_functions.cxx index 61d4b1a20a..771f52a1b2 100644 --- a/src/experimental/buildblock/local_helping_functions.cxx +++ b/src/experimental/buildblock/local_helping_functions.cxx @@ -13,14 +13,12 @@ #include #include -#ifndef STIR_NO_NAMESPACES using std::ios; using std::find; using std::iostream; using std::fstream; using std::cerr; using std::endl; -#endif diff --git a/src/experimental/buildblock/multiply_plane_scale_factorsImageProcessor.cxx b/src/experimental/buildblock/multiply_plane_scale_factorsImageProcessor.cxx index b119537d7f..e7b2d15c0a 100644 --- a/src/experimental/buildblock/multiply_plane_scale_factorsImageProcessor.cxx +++ b/src/experimental/buildblock/multiply_plane_scale_factorsImageProcessor.cxx @@ -22,9 +22,7 @@ #include "stir/warning.h" #include "stir/error.h" -#ifndef STIR_NO_NAMESPACES using std::copy; -#endif START_NAMESPACE_STIR diff --git a/src/experimental/listmode/CListModeDataLMF.cxx b/src/experimental/listmode/CListModeDataLMF.cxx index eefdde5559..1cfcdd7021 100644 --- a/src/experimental/listmode/CListModeDataLMF.cxx +++ b/src/experimental/listmode/CListModeDataLMF.cxx @@ -25,11 +25,9 @@ #include "stir/error.h" #include -#ifndef STIR_NO_NAMESPACES using std::fstream; using std::streamsize; using std::streampos; -#endif START_NAMESPACE_STIR diff --git a/src/experimental/listmode_utilities/get_singles_info.cxx b/src/experimental/listmode_utilities/get_singles_info.cxx index e79a3bf46f..0de39b0dcc 100644 --- a/src/experimental/listmode_utilities/get_singles_info.cxx +++ b/src/experimental/listmode_utilities/get_singles_info.cxx @@ -23,11 +23,9 @@ #include #include -#ifndef STIR_NO_NAMESPACES using std::cerr; using std::endl; using std::string; -#endif USING_NAMESPACE_STIR diff --git a/src/experimental/listmode_utilities/lm_to_projdata_with_MC.cxx b/src/experimental/listmode_utilities/lm_to_projdata_with_MC.cxx index caf68bebac..14f0eebc9e 100644 --- a/src/experimental/listmode_utilities/lm_to_projdata_with_MC.cxx +++ b/src/experimental/listmode_utilities/lm_to_projdata_with_MC.cxx @@ -19,10 +19,8 @@ #include -#ifndef STIR_NO_NAMESPACES using std::cerr; using std::endl; -#endif /* Here's a sample .par file diff --git a/src/experimental/motion/Polaris_MT_File.cxx b/src/experimental/motion/Polaris_MT_File.cxx index e0c5e96b5a..333afbab23 100644 --- a/src/experimental/motion/Polaris_MT_File.cxx +++ b/src/experimental/motion/Polaris_MT_File.cxx @@ -19,9 +19,7 @@ #include "stir/warning.h" #include #include -#ifndef STIR_NO_NAMESPACES using std::ifstream; -#endif START_NAMESPACE_STIR diff --git a/src/experimental/motion/RigidObject3DTransformation.cxx b/src/experimental/motion/RigidObject3DTransformation.cxx index 4cc2ebdb58..2f68b492fc 100644 --- a/src/experimental/motion/RigidObject3DTransformation.cxx +++ b/src/experimental/motion/RigidObject3DTransformation.cxx @@ -31,10 +31,8 @@ #include "stir/ProjDataInfoCylindricalNoArcCorr.h" #endif -#ifndef STIR_NO_NAMESPACES using std::cerr; using std::endl; -#endif //#define DO_XY_SWAP diff --git a/src/experimental/motion_utilities/add_planes_to_image.cxx b/src/experimental/motion_utilities/add_planes_to_image.cxx index c973ff457a..9525ee379e 100644 --- a/src/experimental/motion_utilities/add_planes_to_image.cxx +++ b/src/experimental/motion_utilities/add_planes_to_image.cxx @@ -6,14 +6,12 @@ #include #include #include -#ifndef STIR_NO_NAMESPACES using std::string; using std::cerr; using std::min; using std::max; using std::endl; using std::vector; -#endif USING_NAMESPACE_STIR diff --git a/src/experimental/motion_utilities/find_motion_corrected_norm_factors.cxx b/src/experimental/motion_utilities/find_motion_corrected_norm_factors.cxx index 75a4d852ac..718e1f3c47 100644 --- a/src/experimental/motion_utilities/find_motion_corrected_norm_factors.cxx +++ b/src/experimental/motion_utilities/find_motion_corrected_norm_factors.cxx @@ -622,10 +622,7 @@ construct_proj_data(shared_ptr& output, { vector segment_sequence_in_stream(proj_data_info_ptr->get_num_segments()); { -#ifndef STIR_NO_NAMESPACES - std:: // explcitly needed by VC -#endif - vector::iterator current_segment_iter = + auto current_segment_iter = segment_sequence_in_stream.begin(); for (int segment_num=proj_data_info_ptr->get_min_segment_num(); segment_num<=proj_data_info_ptr->get_max_segment_num(); diff --git a/src/experimental/motion_utilities/remove_corrupted_sinograms.cxx b/src/experimental/motion_utilities/remove_corrupted_sinograms.cxx index 915f74d705..38a2b4965f 100644 --- a/src/experimental/motion_utilities/remove_corrupted_sinograms.cxx +++ b/src/experimental/motion_utilities/remove_corrupted_sinograms.cxx @@ -43,14 +43,12 @@ #include #include #include -#ifndef STIR_NO_NAMESPACES using std::string; using std::cerr; using std::min; using std::max; using std::endl; using std::vector; -#endif USING_NAMESPACE_STIR diff --git a/src/experimental/motion_utilities/rigid_object_transform_image.cxx b/src/experimental/motion_utilities/rigid_object_transform_image.cxx index 25ff4f423d..f741da38cc 100644 --- a/src/experimental/motion_utilities/rigid_object_transform_image.cxx +++ b/src/experimental/motion_utilities/rigid_object_transform_image.cxx @@ -43,11 +43,9 @@ namespace stir { // for doxygen #include "stir/error.h" #include -#ifndef STIR_NO_NAMESPACES using std::string; using std::cerr; using std::endl; -#endif START_NAMESPACE_STIR diff --git a/src/experimental/recon_buildblock/BinNormalisationSinogramRescaling.cxx b/src/experimental/recon_buildblock/BinNormalisationSinogramRescaling.cxx index 251d772515..3ad7da1c1c 100644 --- a/src/experimental/recon_buildblock/BinNormalisationSinogramRescaling.cxx +++ b/src/experimental/recon_buildblock/BinNormalisationSinogramRescaling.cxx @@ -26,9 +26,7 @@ #include #include -#ifndef STIR_NO_NAMESPACES using std::ifstream; -#endif START_NAMESPACE_STIR diff --git a/src/experimental/recon_buildblock/ProjMatrixByDensel.cxx b/src/experimental/recon_buildblock/ProjMatrixByDensel.cxx index 65788b948c..3bf3201b16 100644 --- a/src/experimental/recon_buildblock/ProjMatrixByDensel.cxx +++ b/src/experimental/recon_buildblock/ProjMatrixByDensel.cxx @@ -20,12 +20,6 @@ #include "stir_experimental/recon_buildblock/ProjMatrixByDensel.h" #include "stir/recon_buildblock/ProjMatrixElemsForOneDensel.h" #include "stir/Succeeded.h" -//#include - -#ifndef STIR_NO_NAMESPACES -//using std::cout; -//using std::endl; -#endif START_NAMESPACE_STIR diff --git a/src/experimental/recon_buildblock/ProjMatrixByDenselUsingRayTracing.cxx b/src/experimental/recon_buildblock/ProjMatrixByDenselUsingRayTracing.cxx index d3f9a2ed74..03511c9099 100644 --- a/src/experimental/recon_buildblock/ProjMatrixByDenselUsingRayTracing.cxx +++ b/src/experimental/recon_buildblock/ProjMatrixByDenselUsingRayTracing.cxx @@ -27,10 +27,8 @@ #include #include -#ifndef STIR_NO_NAMESPACES using std::max; using std::min; -#endif START_NAMESPACE_STIR diff --git a/src/experimental/recon_test/test_ProjMatrixByBinUsingInterpolation.cxx b/src/experimental/recon_test/test_ProjMatrixByBinUsingInterpolation.cxx index c7f9d73a58..9cede71e45 100644 --- a/src/experimental/recon_test/test_ProjMatrixByBinUsingInterpolation.cxx +++ b/src/experimental/recon_test/test_ProjMatrixByBinUsingInterpolation.cxx @@ -30,10 +30,8 @@ #include #include #include -#ifndef STIR_NO_NAMESPACES using std::stringstream; using std::cerr; -#endif START_NAMESPACE_STIR diff --git a/src/experimental/test/test_LmToProjdataWithMC.cxx b/src/experimental/test/test_LmToProjdataWithMC.cxx index 069c9bb89a..9fab569728 100644 --- a/src/experimental/test/test_LmToProjdataWithMC.cxx +++ b/src/experimental/test/test_LmToProjdataWithMC.cxx @@ -18,10 +18,8 @@ #include "stir_experimental/listmode/LmToProjDataWithMC.h" #include -#ifndef STIR_NO_NAMESPACES using std::cerr; using std::endl; -#endif START_NAMESPACE_STIR diff --git a/src/experimental/test/test_Quaternion.cxx b/src/experimental/test/test_Quaternion.cxx index 9bd355f4b3..b7efaf4fb6 100644 --- a/src/experimental/test/test_Quaternion.cxx +++ b/src/experimental/test/test_Quaternion.cxx @@ -15,10 +15,8 @@ #include "stir_experimental/Quaternion.h" #include -#ifndef STIR_NO_NAMESPACES using std::cerr; using std::endl; -#endif START_NAMESPACE_STIR diff --git a/src/experimental/test/test_proj_data_info_LOR.cxx b/src/experimental/test/test_proj_data_info_LOR.cxx index dad41dd8ac..8e9b557b9b 100644 --- a/src/experimental/test/test_proj_data_info_LOR.cxx +++ b/src/experimental/test/test_proj_data_info_LOR.cxx @@ -31,10 +31,8 @@ #include "stir/stream.h" #include "stir/warning.h" #include "stir/error.h" -#ifndef STIR_NO_NAMESPACES using std::cout; using std::endl; -#endif #include "stir/LORCoordinates.h" #include "stir/geometry/line_distances.h" diff --git a/src/experimental/utilities/CoG.cxx b/src/experimental/utilities/CoG.cxx index a099a374fc..d73ff64918 100644 --- a/src/experimental/utilities/CoG.cxx +++ b/src/experimental/utilities/CoG.cxx @@ -50,7 +50,6 @@ #include #include -#ifndef STIR_NO_NAMESPACES using std::ofstream; using std::cout; using std::setw; @@ -59,7 +58,6 @@ using std::endl; using std::min; using std::max; using std::string; -#endif int main(int argc, char *argv[]) diff --git a/src/experimental/utilities/add_time_frame_info.cxx b/src/experimental/utilities/add_time_frame_info.cxx index 69654696c9..3d1aa77e3f 100644 --- a/src/experimental/utilities/add_time_frame_info.cxx +++ b/src/experimental/utilities/add_time_frame_info.cxx @@ -28,13 +28,11 @@ #include #include -#ifndef STIR_NO_NAMESPACES using std::cerr; using std::cout; using std::ifstream; using std::istream; using std::setw; -#endif int main(int argc, char *argv[]) { diff --git a/src/experimental/utilities/apply_plane_rescale_factors.cxx b/src/experimental/utilities/apply_plane_rescale_factors.cxx index 6bde58fff6..719cd13e23 100644 --- a/src/experimental/utilities/apply_plane_rescale_factors.cxx +++ b/src/experimental/utilities/apply_plane_rescale_factors.cxx @@ -27,11 +27,9 @@ #include #include -#ifndef STIR_NO_NAMESPACES using std::cerr; using std::ifstream; using std::vector; -#endif USING_NAMESPACE_STIR diff --git a/src/experimental/utilities/change_mhead_file_type.cxx b/src/experimental/utilities/change_mhead_file_type.cxx index 1696a0e278..13c2e32547 100644 --- a/src/experimental/utilities/change_mhead_file_type.cxx +++ b/src/experimental/utilities/change_mhead_file_type.cxx @@ -19,10 +19,8 @@ #include -#ifndef STIR_NO_NAMESPACES using std::cerr; using std::endl; -#endif USING_NAMESPACE_STIR diff --git a/src/experimental/utilities/compute_plane_rescale_factors.cxx b/src/experimental/utilities/compute_plane_rescale_factors.cxx index ecc87451e7..8083fd27b9 100644 --- a/src/experimental/utilities/compute_plane_rescale_factors.cxx +++ b/src/experimental/utilities/compute_plane_rescale_factors.cxx @@ -25,10 +25,8 @@ #include "stir/warning.h" #include -#ifndef STIR_NO_NAMESPACES using std::cerr; using std::ofstream; -#endif USING_NAMESPACE_STIR diff --git a/src/experimental/utilities/create_normfactors.cxx b/src/experimental/utilities/create_normfactors.cxx index ce0deca8d1..e3ea791b85 100644 --- a/src/experimental/utilities/create_normfactors.cxx +++ b/src/experimental/utilities/create_normfactors.cxx @@ -28,12 +28,10 @@ #include #include -#ifndef STIR_NO_NAMESPACES using std::cerr; using std::endl; using std::ofstream; using std::string; -#endif USING_NAMESPACE_STIR diff --git a/src/experimental/utilities/create_normfactors3D.cxx b/src/experimental/utilities/create_normfactors3D.cxx index c93fde8ba5..310e63262d 100644 --- a/src/experimental/utilities/create_normfactors3D.cxx +++ b/src/experimental/utilities/create_normfactors3D.cxx @@ -30,12 +30,10 @@ #include #include -#ifndef STIR_NO_NAMESPACES using std::cerr; using std::endl; using std::ofstream; using std::string; -#endif USING_NAMESPACE_STIR diff --git a/src/experimental/utilities/fillwithotherprojdata.cxx b/src/experimental/utilities/fillwithotherprojdata.cxx index ad35a22216..9d37ee3e59 100644 --- a/src/experimental/utilities/fillwithotherprojdata.cxx +++ b/src/experimental/utilities/fillwithotherprojdata.cxx @@ -24,13 +24,11 @@ #include #include -#ifndef STIR_NO_NAMESPACES using std::cerr; using std::endl; using std::fstream; using std::ifstream; using std::cout; -#endif USING_NAMESPACE_STIR diff --git a/src/experimental/utilities/find_sinogram_rescaling_factors.cxx b/src/experimental/utilities/find_sinogram_rescaling_factors.cxx index c87b478895..98c6cf7351 100644 --- a/src/experimental/utilities/find_sinogram_rescaling_factors.cxx +++ b/src/experimental/utilities/find_sinogram_rescaling_factors.cxx @@ -26,12 +26,10 @@ #include -#ifndef STIR_NO_NAMESPACES using std::string; using std::cerr; using std::endl; using std::ofstream; -#endif int diff --git a/src/experimental/utilities/fit_cylinder.cxx b/src/experimental/utilities/fit_cylinder.cxx index fd612dcfe9..704abbaf79 100644 --- a/src/experimental/utilities/fit_cylinder.cxx +++ b/src/experimental/utilities/fit_cylinder.cxx @@ -25,7 +25,6 @@ #include #include -#ifndef STIR_NO_NAMESPACES using std::ofstream; using std::cout; using std::cerr; @@ -33,7 +32,6 @@ using std::endl; using std::min; using std::max; using std::string; -#endif int main(int argc, char *argv[]) diff --git a/src/experimental/utilities/interpolate_blocks.cxx b/src/experimental/utilities/interpolate_blocks.cxx index 0a0e16e240..bf2f98d395 100644 --- a/src/experimental/utilities/interpolate_blocks.cxx +++ b/src/experimental/utilities/interpolate_blocks.cxx @@ -28,7 +28,6 @@ #include #include -#ifndef STIR_NO_NAMESPACES using std::vector; using std::string; using std::cerr; @@ -37,7 +36,6 @@ using std::sort; using std::unique; using std::min; using std::max; -#endif START_NAMESPACE_STIR diff --git a/src/experimental/utilities/inverse_SSRB.cxx b/src/experimental/utilities/inverse_SSRB.cxx index 35ead11602..3481bc392d 100644 --- a/src/experimental/utilities/inverse_SSRB.cxx +++ b/src/experimental/utilities/inverse_SSRB.cxx @@ -31,11 +31,9 @@ This is a utility program which uses the stir::inverse_SSRB function , in order #include "stir/error.h" #include #include -#ifndef STIR_NO_NAMESPACES using std::endl; using std::cout; using std::cerr; -#endif USING_NAMESPACE_STIR using namespace std; /***********************************************************/ diff --git a/src/experimental/utilities/inverse_proj_data.cxx b/src/experimental/utilities/inverse_proj_data.cxx index 50ab3d5812..782ad78250 100644 --- a/src/experimental/utilities/inverse_proj_data.cxx +++ b/src/experimental/utilities/inverse_proj_data.cxx @@ -40,13 +40,11 @@ #include #include -#ifndef STIR_NO_NAMESPACES using std::ofstream; using std::fstream; using std::iostream; using std::list; using std::find; -#endif START_NAMESPACE_STIR diff --git a/src/experimental/utilities/line_profiles_through_projdata.cxx b/src/experimental/utilities/line_profiles_through_projdata.cxx index da18ddf9e6..63e983cf92 100644 --- a/src/experimental/utilities/line_profiles_through_projdata.cxx +++ b/src/experimental/utilities/line_profiles_through_projdata.cxx @@ -49,7 +49,6 @@ #include #include #include -#ifndef STIR_NO_NAMESPACES using std::iostream; using std::ofstream; using std::ifstream; @@ -58,7 +57,6 @@ using std::cerr; using std::endl; using std::cout; using std::setw; -#endif int main(int argc, char *argv[]) { diff --git a/src/experimental/utilities/list_TAC_ROI_values.cxx b/src/experimental/utilities/list_TAC_ROI_values.cxx index cd739302b2..d94e524b78 100644 --- a/src/experimental/utilities/list_TAC_ROI_values.cxx +++ b/src/experimental/utilities/list_TAC_ROI_values.cxx @@ -69,11 +69,9 @@ #include #include -#ifndef STIR_NO_NAMESPACES using std::cerr; using std::endl; using std::ofstream; -#endif START_NAMESPACE_STIR diff --git a/src/experimental/utilities/make_cylinder.cxx b/src/experimental/utilities/make_cylinder.cxx index b9902a1d11..5f58ef3bcb 100644 --- a/src/experimental/utilities/make_cylinder.cxx +++ b/src/experimental/utilities/make_cylinder.cxx @@ -11,10 +11,8 @@ #include "stir/interfile.h" #include "stir/utilities.h" -#ifndef STIR_NO_NAMESPACES using std::cerr; using std::endl; -#endif USING_NAMESPACE_STIR diff --git a/src/experimental/utilities/make_grid_image.cxx b/src/experimental/utilities/make_grid_image.cxx index aa9fe8fea7..ea477f68b3 100644 --- a/src/experimental/utilities/make_grid_image.cxx +++ b/src/experimental/utilities/make_grid_image.cxx @@ -16,9 +16,7 @@ #include "stir/Succeeded.h" #include "stir/modulo.h" -#ifndef STIR_NO_NAMESPACES using std::cerr; -#endif USING_NAMESPACE_STIR diff --git a/src/experimental/utilities/normalizedbckproj.cxx b/src/experimental/utilities/normalizedbckproj.cxx index e57b5284a0..baed815610 100644 --- a/src/experimental/utilities/normalizedbckproj.cxx +++ b/src/experimental/utilities/normalizedbckproj.cxx @@ -40,7 +40,6 @@ #include #include -#ifndef STIR_NO_NAMESPACES using std::ofstream; using std::fstream; using std::iostream; @@ -49,7 +48,6 @@ using std::list; using std::find; using std::cerr; using std::endl; -#endif diff --git a/src/experimental/utilities/precompute_denominator_SPS.cxx b/src/experimental/utilities/precompute_denominator_SPS.cxx index fa96a20de7..b1baee72ee 100644 --- a/src/experimental/utilities/precompute_denominator_SPS.cxx +++ b/src/experimental/utilities/precompute_denominator_SPS.cxx @@ -44,7 +44,6 @@ #include #include -#ifndef STIR_NO_NAMESPACES using std::ofstream; using std::fstream; using std::iostream; @@ -53,7 +52,6 @@ using std::list; using std::find; using std::cerr; using std::endl; -#endif diff --git a/src/experimental/utilities/prepare_projdata.cxx b/src/experimental/utilities/prepare_projdata.cxx index 9ac6fa92f1..0c5508a7fe 100644 --- a/src/experimental/utilities/prepare_projdata.cxx +++ b/src/experimental/utilities/prepare_projdata.cxx @@ -33,14 +33,12 @@ #include #include -#ifndef STIR_NO_NAMESPACES using std::cerr; using std::endl; using std::fstream; using std::ifstream; using std::cout; using std::string; -#endif START_NAMESPACE_STIR diff --git a/src/experimental/utilities/remove_sinograms.cxx b/src/experimental/utilities/remove_sinograms.cxx index 7c3f1ed65c..a835547425 100644 --- a/src/experimental/utilities/remove_sinograms.cxx +++ b/src/experimental/utilities/remove_sinograms.cxx @@ -22,13 +22,11 @@ #include #include -#ifndef STIR_NO_NAMESPACES using std::string; using std::cerr; using std::min; using std::max; using std::endl; -#endif USING_NAMESPACE_STIR diff --git a/src/experimental/utilities/set_blocks_to_value.cxx b/src/experimental/utilities/set_blocks_to_value.cxx index 53b689bf16..64969e4eea 100644 --- a/src/experimental/utilities/set_blocks_to_value.cxx +++ b/src/experimental/utilities/set_blocks_to_value.cxx @@ -27,7 +27,6 @@ #include #include -#ifndef STIR_NO_NAMESPACES using std::vector; using std::string; using std::cerr; @@ -36,7 +35,6 @@ using std::sort; using std::unique; using std::min; using std::max; -#endif START_NAMESPACE_STIR diff --git a/src/experimental/utilities/shift_projdata_along_axis.cxx b/src/experimental/utilities/shift_projdata_along_axis.cxx index ec38097d7c..cbb34b1f42 100644 --- a/src/experimental/utilities/shift_projdata_along_axis.cxx +++ b/src/experimental/utilities/shift_projdata_along_axis.cxx @@ -24,12 +24,10 @@ #include #include -#ifndef STIR_NO_NAMESPACES using std::string; using std::cerr; using std::min; using std::max; -#endif USING_NAMESPACE_STIR diff --git a/src/experimental/utilities/show_ecat7_header.cxx b/src/experimental/utilities/show_ecat7_header.cxx index 9814418c3d..d12c054b59 100644 --- a/src/experimental/utilities/show_ecat7_header.cxx +++ b/src/experimental/utilities/show_ecat7_header.cxx @@ -22,12 +22,10 @@ #include #include -#ifndef STIR_NO_NAMESPACES using std::cerr; using std::endl; using std::cout; using std::string; -#endif USING_NAMESPACE_STIR USING_NAMESPACE_ECAT diff --git a/src/experimental/utilities/threshold_norm_data.cxx b/src/experimental/utilities/threshold_norm_data.cxx index e14766dd45..10f93aa7cb 100644 --- a/src/experimental/utilities/threshold_norm_data.cxx +++ b/src/experimental/utilities/threshold_norm_data.cxx @@ -28,10 +28,8 @@ #include #include -#ifndef STIR_NO_NAMESPACES using std::cerr; using std::endl; -#endif diff --git a/src/experimental/utilities/zero_projdata_from_norm.cxx b/src/experimental/utilities/zero_projdata_from_norm.cxx index 004967388e..d759c2c53b 100644 --- a/src/experimental/utilities/zero_projdata_from_norm.cxx +++ b/src/experimental/utilities/zero_projdata_from_norm.cxx @@ -25,10 +25,8 @@ #include #include -#ifndef STIR_NO_NAMESPACES using std::cerr; using std::endl; -#endif diff --git a/src/include/stir/Array.inl b/src/include/stir/Array.inl index e53c74bb5d..1e9f7dc1d3 100644 --- a/src/include/stir/Array.inl +++ b/src/include/stir/Array.inl @@ -227,11 +227,7 @@ Array::find_min() const elemT minval= this->num[this->get_min_index()].find_min(); for(int i=this->get_min_index()+1; i<=this->get_max_index(); i++) { -#ifndef STIR_NO_NAMESPACES minval = std::min(this->num[i].find_min(), minval); -#else - minval = min(this->num[i].find_min(), minval); -#endif } return minval; } @@ -579,11 +575,7 @@ Array<1, elemT>::find_max() const this->check_state(); if (this->size() > 0) { -#ifndef STIR_NO_NAMESPACES return *std::max_element(this->begin(), this->end()); -#else - return *max_element(this->begin(), this->end()); -#endif } else { @@ -601,11 +593,7 @@ Array<1, elemT>::find_min() const this->check_state(); if (this->size() > 0) { -#ifndef STIR_NO_NAMESPACES return *std::min_element(this->begin(), this->end()); -#else - return *min_element(this->begin(), this->end()); -#endif } else { diff --git a/src/include/stir/Array1d.h b/src/include/stir/Array1d.h index 7a86f194b1..3f2ca2b3a8 100644 --- a/src/include/stir/Array1d.h +++ b/src/include/stir/Array1d.h @@ -318,11 +318,7 @@ Array<1, elemT>::find_max() const this->check_state(); if (this->size() > 0) { -#ifndef STIR_NO_NAMESPACES return *std::max_element(this->begin(), this->end()); -#else - return *max_element(this->begin(), this->end()); -#endif } else { @@ -338,11 +334,7 @@ Array<1, elemT>::find_min() const this->check_state(); if (this->size() > 0) { -#ifndef STIR_NO_NAMESPACES return *std::min_element(this->begin(), this->end()); -#else - return *min_element(this->begin(), this->end()); -#endif } else { diff --git a/src/include/stir/BasicCoordinate.inl b/src/include/stir/BasicCoordinate.inl index 5c34567e3f..b5a43fe82c 100644 --- a/src/include/stir/BasicCoordinate.inl +++ b/src/include/stir/BasicCoordinate.inl @@ -405,11 +405,7 @@ coordT inner_product (const BasicCoordinate& p1, const BasicCoordinate& p2) { -#ifdef STIR_NO_NAMESPACES - return inner_product(p1.begin(), p1.end(), p2.begin(), coordT(0)); -#else return std::inner_product(p1.begin(), p1.end(), p2.begin(), coordT(0)); -#endif } // TODO specialise for complex coordTs if you need them template diff --git a/src/include/stir/ByteOrder.h b/src/include/stir/ByteOrder.h index a2135b3e63..05a57c9d20 100644 --- a/src/include/stir/ByteOrder.h +++ b/src/include/stir/ByteOrder.h @@ -78,11 +78,7 @@ class revert_region public: inline static void revert(unsigned char* ptr) { -#ifndef STIR_NO_NAMESPACES std::swap(ptr[0], ptr[size - 1]); -#else - swap(ptr[0], ptr[size - 1]); -#endif revert_region::revert(ptr + 1); } }; diff --git a/src/include/stir/DiscretisedDensityOnCartesianGrid.inl b/src/include/stir/DiscretisedDensityOnCartesianGrid.inl index 821676b7ee..3c97559235 100644 --- a/src/include/stir/DiscretisedDensityOnCartesianGrid.inl +++ b/src/include/stir/DiscretisedDensityOnCartesianGrid.inl @@ -31,13 +31,7 @@ DiscretisedDensityOnCartesianGrid:: DiscretisedDensityOnCartesianGrid() : DiscretisedDensity(),grid_spacing() { -#ifndef STIR_NO_NAMESPACES std::fill(grid_spacing.begin(), grid_spacing.end(), 0.F); -#else - // hopefully your compiler understands this. - // It attempts to avoid conflicts with Array::fill - ::fill(grid_spacing.begin(), grid_spacing.end(), 0.F); -#endif } template diff --git a/src/include/stir/IO/ecat6_types.h b/src/include/stir/IO/ecat6_types.h index 5e544a5b9c..a41efb66a7 100644 --- a/src/include/stir/IO/ecat6_types.h +++ b/src/include/stir/IO/ecat6_types.h @@ -33,20 +33,10 @@ //#define STIR_ORIGINAL_ECAT6 #ifndef STIR_ORIGINAL_ECAT6 -#ifdef STIR_NO_NAMESPACES -// terrible trick to avoid conflict between stir::Sinogram and Sinogram defined in matrix.h -// when we do have namespaces, the conflict can be resolved by using ::Sinogram -#define Sinogram CTISinogram -#else #define CTISinogram ::Sinogram -#endif #include "matrix.h" -#ifdef STIR_NO_NAMESPACES -#undef Sinogram -#endif - #endif // STIR_ORIGINAL_ECAT6 START_NAMESPACE_STIR diff --git a/src/include/stir/IO/stir_ecat7.h b/src/include/stir/IO/stir_ecat7.h index 1d89d2da15..c5460a0675 100644 --- a/src/include/stir/IO/stir_ecat7.h +++ b/src/include/stir/IO/stir_ecat7.h @@ -28,13 +28,7 @@ #include "stir/NumericType.h" #ifdef HAVE_LLN_MATRIX -#ifdef STIR_NO_NAMESPACES -// terrible trick to avoid conflict between stir::Sinogram and Sinogram defined in matrix.h -// when we do have namespaces, the conflict can be resolved by using ::Sinogram -#define Sinogram CTISinogram -#else #define CTISinogram ::Sinogram -#endif #include "matrix.h" extern "C" { @@ -42,10 +36,6 @@ extern "C" { int mh_update(MatrixFile*); } -#ifdef STIR_NO_NAMESPACES -#undef Sinogram -#endif - #include #include #include "stir/shared_ptr.h" diff --git a/src/include/stir/IO/stir_ecat_common.h b/src/include/stir/IO/stir_ecat_common.h index c4174c656c..3f1346e5d3 100644 --- a/src/include/stir/IO/stir_ecat_common.h +++ b/src/include/stir/IO/stir_ecat_common.h @@ -28,7 +28,7 @@ #include //*************** namespace macros -#if !defined(STIR_NO_NAMESPACE) + # define START_NAMESPACE_ECAT namespace ecat { # define END_NAMESPACE_ECAT } # define USING_NAMESPACE_ECAT using namespace ecat; @@ -41,21 +41,6 @@ # define END_NAMESPACE_ECAT7 } # define USING_NAMESPACE_ECAT7 using namespace ecat7; -#else - -# define START_NAMESPACE_ECAT -# define END_NAMESPACE_ECAT -# define USING_NAMESPACE_ECAT - -# define START_NAMESPACE_ECAT6 -# define END_NAMESPACE_ECAT6 -# define USING_NAMESPACE_ECAT6 - -# define START_NAMESPACE_ECAT7 -# define END_NAMESPACE_ECAT7 -# define USING_NAMESPACE_ECAT7 -#endif - START_NAMESPACE_STIR class NumericType; diff --git a/src/include/stir/IndexRange.inl b/src/include/stir/IndexRange.inl index d6031497c8..1bf6848da7 100644 --- a/src/include/stir/IndexRange.inl +++ b/src/include/stir/IndexRange.inl @@ -79,12 +79,7 @@ IndexRange:: return this->get_min_index() == range2.get_min_index() && this->get_length() == range2.get_length() && -#ifndef STIR_NO_NAMESPACES - // "using std::equal" didn't work for VC 6... std::equal(this->begin(), this->end(), range2.begin()); -#else - equal(this->begin(), this->end(), range2.begin()); -#endif } diff --git a/src/include/stir/NumericVectorWithOffset.inl b/src/include/stir/NumericVectorWithOffset.inl index 8019751b10..7ac0089230 100644 --- a/src/include/stir/NumericVectorWithOffset.inl +++ b/src/include/stir/NumericVectorWithOffset.inl @@ -144,11 +144,7 @@ NumericVectorWithOffset::operator+= (const NumericVectorWithOffset &v { return *this = v; } -#ifndef STIR_NO_NAMESPACES this->grow (std::min(this->get_min_index(),v.get_min_index()), std::max(this->get_max_index(),v.get_max_index())); -#else - this->grow (min(this->get_min_index(),v.get_min_index()), max(this->get_max_index(),v.get_max_index())); -#endif for (int i=v.get_min_index(); i<=v.get_max_index(); i++) this->num[i] += v.num[i]; this->check_state(); @@ -167,11 +163,7 @@ NumericVectorWithOffset::operator-= (const NumericVectorWithOffset &v *this = v; return *this *= -1; } -#ifndef STIR_NO_NAMESPACES this->grow (std::min(this->get_min_index(),v.get_min_index()), std::max(this->get_max_index(),v.get_max_index())); -#else - this->grow (min(this->get_min_index(),v.get_min_index()), max(this->get_max_index(),v.get_max_index())); -#endif for (int i=v.get_min_index(); i<=v.get_max_index(); i++) this->num[i] -= v.num[i]; this->check_state(); @@ -191,11 +183,7 @@ NumericVectorWithOffset::operator*= (const NumericVectorWithOffset &v *this =v; return *this *= 0; } -#ifndef STIR_NO_NAMESPACES this->grow (std::min(this->get_min_index(),v.get_min_index()), std::max(this->get_max_index(),v.get_max_index())); -#else - this->grow (min(this->get_min_index(),v.get_min_index()), max(this->get_max_index(),v.get_max_index())); -#endif for (int i=v.get_min_index(); i<=v.get_max_index(); i++) this->num[i] *= v.num[i]; this->check_state(); @@ -215,11 +203,7 @@ NumericVectorWithOffset::operator/= (const NumericVectorWithOffset &v *this =v; return *this *= 0; } -#ifndef STIR_NO_NAMESPACES this->grow (std::min(this->get_min_index(),v.get_min_index()), std::max(this->get_max_index(),v.get_max_index())); -#else - this->grow (min(this->get_min_index(),v.get_min_index()), max(this->get_max_index(),v.get_max_index())); -#endif for (int i=v.get_min_index(); i<=v.get_max_index(); i++) this->num[i] /= v.num[i]; this->check_state(); diff --git a/src/include/stir/ProjDataFromStream.inl b/src/include/stir/ProjDataFromStream.inl index 04b566752f..77a717ed52 100644 --- a/src/include/stir/ProjDataFromStream.inl +++ b/src/include/stir/ProjDataFromStream.inl @@ -36,13 +36,8 @@ ProjDataFromStream::get_storage_order() const int ProjDataFromStream::find_segment_index_in_sequence(const int segment_num) const { -#ifndef STIR_NO_NAMESPACES std::vector::const_iterator iter = std::find(segment_sequence.begin(), segment_sequence.end(), segment_num); -#else - vector::const_iterator iter = - find(segment_sequence.begin(), segment_sequence.end(), segment_num); -#endif // TODO do some proper error handling here assert(iter != segment_sequence.end()); return static_cast(iter - segment_sequence.begin()); diff --git a/src/include/stir/config/gcc.h b/src/include/stir/config/gcc.h index 2c9de41830..f167fd76d6 100644 --- a/src/include/stir/config/gcc.h +++ b/src/include/stir/config/gcc.h @@ -31,10 +31,6 @@ */ #if defined __GNUC__ -# if __GNUC__ == 2 && __GNUC_MINOR__ <= 8 -# define STIR_NO_NAMESPACES -# define STIR_NO_AUTO_PTR -# endif #endif #endif diff --git a/src/include/stir/evaluation/ROIValues.h b/src/include/stir/evaluation/ROIValues.h index 19fda5a23d..a2548a2cf1 100644 --- a/src/include/stir/evaluation/ROIValues.h +++ b/src/include/stir/evaluation/ROIValues.h @@ -61,13 +61,8 @@ class ROIValues integral_of_square += iv.integral_of_square; -#ifndef STIR_NO_NAMESPACES min_value = std::min(min_value, iv.min_value); max_value = std::max(max_value, iv.max_value); -#else - min_value = min(min_value, iv.min_value); - max_value = max(max_value, iv.max_value); -#endif update(); return *this; diff --git a/src/include/stir/recon_buildblock/BinNormalisationFromECAT8.h b/src/include/stir/recon_buildblock/BinNormalisationFromECAT8.h index d5c894a64b..f9d10c68a9 100644 --- a/src/include/stir/recon_buildblock/BinNormalisationFromECAT8.h +++ b/src/include/stir/recon_buildblock/BinNormalisationFromECAT8.h @@ -33,9 +33,7 @@ #include "stir/IO/stir_ecat_common.h" #include -#ifndef STIR_NO_NAMESPACE using std::string; -#endif START_NAMESPACE_STIR START_NAMESPACE_ECAT diff --git a/src/include/stir/recon_buildblock/BinNormalisationFromGEHDF5.h b/src/include/stir/recon_buildblock/BinNormalisationFromGEHDF5.h index b9da446642..0426f5e643 100644 --- a/src/include/stir/recon_buildblock/BinNormalisationFromGEHDF5.h +++ b/src/include/stir/recon_buildblock/BinNormalisationFromGEHDF5.h @@ -35,9 +35,7 @@ #include "stir/IO/GEHDF5Wrapper.h" #include -#ifndef STIR_NO_NAMESPACE using std::string; -#endif START_NAMESPACE_STIR diff --git a/src/include/stir/recon_buildblock/RelatedBins.h b/src/include/stir/recon_buildblock/RelatedBins.h index 1e914a62ad..652e67ca45 100644 --- a/src/include/stir/recon_buildblock/RelatedBins.h +++ b/src/include/stir/recon_buildblock/RelatedBins.h @@ -50,13 +50,8 @@ class RelatedBins typedef std::size_t size_type; //! typedefs to make it partly comply with STL requirements -#ifndef STIR_NO_NAMESPACES typedef std::vector::iterator iterator; typedef std::vector::const_iterator const_iterator; -#else - typedef vector::iterator iterator; - typedef vector::const_iterator const_iterator; -#endif //!Default constructor: creates no bins, no symmetries inline RelatedBins(); diff --git a/src/include/stir/recon_buildblock/RelatedDensels.h b/src/include/stir/recon_buildblock/RelatedDensels.h index a999c0ee0c..8c6310aee2 100644 --- a/src/include/stir/recon_buildblock/RelatedDensels.h +++ b/src/include/stir/recon_buildblock/RelatedDensels.h @@ -51,13 +51,8 @@ class RelatedDensels typedef std::size_t size_type; //! typedefs to make it partly comply with STL requirements -#ifndef STIR_NO_NAMESPACES typedef std::vector::iterator iterator; typedef std::vector::const_iterator const_iterator; -#else - typedef vector::iterator iterator; - typedef vector::const_iterator const_iterator; -#endif //!Default constructor: creates no densels, no symmetries inline RelatedDensels(); diff --git a/src/include/stir/stir_math.h b/src/include/stir/stir_math.h index 93b1dcf92b..40b62955ca 100644 --- a/src/include/stir/stir_math.h +++ b/src/include/stir/stir_math.h @@ -10,8 +10,6 @@ #ifndef __stir_STIR_MATH_H__ #define __stir_STIR_MATH_H__ -#ifndef STIR_NO_NAMESPACES -using std::cerr; using std::cout; using std::endl; using std::fstream; @@ -20,7 +18,6 @@ using std::max; using std::min; using std::string; using std::vector; -#endif USING_NAMESPACE_STIR diff --git a/src/include/stir_experimental/recon_buildblock/ProjMatrixByDensel.h b/src/include/stir_experimental/recon_buildblock/ProjMatrixByDensel.h index a8328383a5..399697362b 100644 --- a/src/include/stir_experimental/recon_buildblock/ProjMatrixByDensel.h +++ b/src/include/stir_experimental/recon_buildblock/ProjMatrixByDensel.h @@ -148,11 +148,7 @@ class ProjMatrixByDensel : public RegisteredObject typedef unsigned int CacheKey; -#ifndef STIR_NO_NAMESPACES typedef std::map MapProjMatrixElemsForOneDensel; -#else - typedef map MapProjMatrixElemsForOneDensel; - #endif typedef MapProjMatrixElemsForOneDensel::iterator MapProjMatrixElemsForOneDenselIterator; typedef MapProjMatrixElemsForOneDensel::const_iterator const_MapProjMatrixElemsForOneDenselIterator; diff --git a/src/iterative/KOSMAPOSL/KOSMAPOSLReconstruction.cxx b/src/iterative/KOSMAPOSL/KOSMAPOSLReconstruction.cxx index 790188da99..b833b91513 100644 --- a/src/iterative/KOSMAPOSL/KOSMAPOSLReconstruction.cxx +++ b/src/iterative/KOSMAPOSL/KOSMAPOSLReconstruction.cxx @@ -69,10 +69,8 @@ #include using std::min; using std::max; -#ifndef STIR_NO_NAMESPACES using std::cerr; using std::endl; -#endif #include "stir/IndexRange3D.h" #include "stir/IO/read_from_file.h" #include "stir/IO/write_to_file.h" diff --git a/src/iterative/OSMAPOSL/OSMAPOSLReconstruction.cxx b/src/iterative/OSMAPOSL/OSMAPOSLReconstruction.cxx index c08b3321f7..65b563607a 100644 --- a/src/iterative/OSMAPOSL/OSMAPOSLReconstruction.cxx +++ b/src/iterative/OSMAPOSL/OSMAPOSLReconstruction.cxx @@ -61,10 +61,8 @@ #include using std::min; using std::max; -#ifndef STIR_NO_NAMESPACES using std::cerr; using std::endl; -#endif START_NAMESPACE_STIR diff --git a/src/iterative/OSSPS/OSSPSReconstruction.cxx b/src/iterative/OSSPS/OSSPSReconstruction.cxx index aec3fd3f9d..98de95f2f9 100644 --- a/src/iterative/OSSPS/OSSPSReconstruction.cxx +++ b/src/iterative/OSSPS/OSSPSReconstruction.cxx @@ -47,12 +47,10 @@ #include "boost/lambda/lambda.hpp" #include "stir/unique_ptr.h" -#ifndef STIR_NO_NAMESPACES using std::cerr; using std::endl; using boost::lambda::_1; using boost::lambda::_2; -#endif START_NAMESPACE_STIR diff --git a/src/listmode_buildblock/CListModeDataECAT.cxx b/src/listmode_buildblock/CListModeDataECAT.cxx index 6029cc512b..799357367e 100644 --- a/src/listmode_buildblock/CListModeDataECAT.cxx +++ b/src/listmode_buildblock/CListModeDataECAT.cxx @@ -29,14 +29,12 @@ #error Need HAVE_LLN_MATRIX #endif #include "boost/static_assert.hpp" -#ifndef STIR_NO_NAMESPACES using std::cerr; using std::endl; using std::ios; using std::fstream; using std::ifstream; using std::istream; -#endif START_NAMESPACE_STIR START_NAMESPACE_ECAT diff --git a/src/listmode_buildblock/CListModeDataSAFIR.cxx b/src/listmode_buildblock/CListModeDataSAFIR.cxx index bf2858335a..840046ee75 100644 --- a/src/listmode_buildblock/CListModeDataSAFIR.cxx +++ b/src/listmode_buildblock/CListModeDataSAFIR.cxx @@ -42,12 +42,10 @@ Coincidence LM Data Class for SAFIR: Implementation #include "stir/listmode/CListModeDataSAFIR.h" #include "stir/listmode/CListRecordSAFIR.h" -#ifndef STIR_NO_NAMESPACES using std::ios; using std::fstream; using std::ifstream; using std::istream; -#endif START_NAMESPACE_STIR; diff --git a/src/listmode_buildblock/LmToProjData.cxx b/src/listmode_buildblock/LmToProjData.cxx index af160d36bc..22dca9a997 100644 --- a/src/listmode_buildblock/LmToProjData.cxx +++ b/src/listmode_buildblock/LmToProjData.cxx @@ -84,7 +84,6 @@ USE_SegmentByView #include #include -#ifndef STIR_NO_NAMESPACES using std::string; using std::fstream; using std::ifstream; @@ -99,7 +98,6 @@ using std::min; using std::max; using std::vector; using std::pair; -#endif START_NAMESPACE_STIR diff --git a/src/listmode_buildblock/LmToProjDataBootstrap.cxx b/src/listmode_buildblock/LmToProjDataBootstrap.cxx index 68e54811b1..df54693982 100644 --- a/src/listmode_buildblock/LmToProjDataBootstrap.cxx +++ b/src/listmode_buildblock/LmToProjDataBootstrap.cxx @@ -30,10 +30,8 @@ #include #include -#ifndef STIR_NO_NAMESPACES using std::cerr; using std::endl; -#endif #include #include diff --git a/src/listmode_utilities/list_lm_events.cxx b/src/listmode_utilities/list_lm_events.cxx index 93fc8118f9..0930bc2f7a 100644 --- a/src/listmode_utilities/list_lm_events.cxx +++ b/src/listmode_utilities/list_lm_events.cxx @@ -34,12 +34,10 @@ #include #include -#ifndef STIR_NO_NAMESPACES using std::cerr; using std::cout; using std::endl; using std::vector; -#endif diff --git a/src/listmode_utilities/lm_fansums.cxx b/src/listmode_utilities/lm_fansums.cxx index d292dd34c4..4a89722fb7 100644 --- a/src/listmode_utilities/lm_fansums.cxx +++ b/src/listmode_utilities/lm_fansums.cxx @@ -40,7 +40,6 @@ #include #include -#ifndef STIR_NO_NAMESPACES using std::fstream; using std::ifstream; using std::ofstream; @@ -51,7 +50,6 @@ using std::endl; using std::min; using std::max; using std::vector; -#endif START_NAMESPACE_STIR diff --git a/src/listmode_utilities/lm_to_projdata.cxx b/src/listmode_utilities/lm_to_projdata.cxx index 61e8b7f7ff..f7bdd9ec4a 100644 --- a/src/listmode_utilities/lm_to_projdata.cxx +++ b/src/listmode_utilities/lm_to_projdata.cxx @@ -24,10 +24,8 @@ #include "stir/listmode/LmToProjData.h" #include "stir/IO/InputFileFormatRegistry.h" -#ifndef STIR_NO_NAMESPACES using std::cerr; using std::endl; -#endif USING_NAMESPACE_STIR diff --git a/src/listmode_utilities/print_sgl_values.cxx b/src/listmode_utilities/print_sgl_values.cxx index 12aeaf67c5..626140bb14 100644 --- a/src/listmode_utilities/print_sgl_values.cxx +++ b/src/listmode_utilities/print_sgl_values.cxx @@ -26,14 +26,12 @@ #include -#ifndef STIR_NO_NAMESPACES using std::cout; using std::cerr; using std::endl; using std::setw; using std::string; using std::vector; -#endif USING_NAMESPACE_STIR diff --git a/src/listmode_utilities/rebin_sgl_file.cxx b/src/listmode_utilities/rebin_sgl_file.cxx index 01924b76a7..6a11975905 100644 --- a/src/listmode_utilities/rebin_sgl_file.cxx +++ b/src/listmode_utilities/rebin_sgl_file.cxx @@ -28,12 +28,10 @@ #include -#ifndef STIR_NO_NAMESPACES using std::cerr; using std::endl; using std::string; using std::vector; -#endif USING_NAMESPACE_STIR diff --git a/src/listmode_utilities/scan_sgl_file.cxx b/src/listmode_utilities/scan_sgl_file.cxx index b6862b7a50..56c7db9219 100644 --- a/src/listmode_utilities/scan_sgl_file.cxx +++ b/src/listmode_utilities/scan_sgl_file.cxx @@ -23,13 +23,11 @@ #include #include -#ifndef STIR_NO_NAMESPACES using std::cout; using std::cerr; using std::endl; using std::string; using std::vector; -#endif USING_NAMESPACE_STIR diff --git a/src/recon_buildblock/BackProjectorByBinUsingProjMatrixByBin.cxx b/src/recon_buildblock/BackProjectorByBinUsingProjMatrixByBin.cxx index b8edeb6b5e..15f4d7cf52 100644 --- a/src/recon_buildblock/BackProjectorByBinUsingProjMatrixByBin.cxx +++ b/src/recon_buildblock/BackProjectorByBinUsingProjMatrixByBin.cxx @@ -186,10 +186,7 @@ actual_back_project(DiscretisedDensity<3,float>& image, min_tangential_pos_num, max_tangential_pos_num); for ( -#ifndef STIR_NO_NAMESPACES - std:: -#endif - vector::const_iterator r_ax_tang_poss_iter = related_ax_tang_poss.begin(); + auto r_ax_tang_poss_iter = related_ax_tang_poss.begin(); r_ax_tang_poss_iter != related_ax_tang_poss.end(); ++r_ax_tang_poss_iter) { diff --git a/src/recon_buildblock/BinNormalisationFromECAT7.cxx b/src/recon_buildblock/BinNormalisationFromECAT7.cxx index 3dc3b6d709..b6f1321cb5 100644 --- a/src/recon_buildblock/BinNormalisationFromECAT7.cxx +++ b/src/recon_buildblock/BinNormalisationFromECAT7.cxx @@ -46,10 +46,8 @@ #include #include -#ifndef STIR_NO_NAMESPACES using std::ofstream; using std::fstream; -#endif START_NAMESPACE_STIR START_NAMESPACE_ECAT diff --git a/src/recon_buildblock/BinNormalisationFromECAT8.cxx b/src/recon_buildblock/BinNormalisationFromECAT8.cxx index 0b8a542b07..0d3dd9eb35 100644 --- a/src/recon_buildblock/BinNormalisationFromECAT8.cxx +++ b/src/recon_buildblock/BinNormalisationFromECAT8.cxx @@ -48,11 +48,9 @@ #include #include #include -#ifndef STIR_NO_NAMESPACES using std::ofstream; using std::fstream; using std::ios; -#endif START_NAMESPACE_STIR START_NAMESPACE_ECAT diff --git a/src/recon_buildblock/BinNormalisationFromGEHDF5.cxx b/src/recon_buildblock/BinNormalisationFromGEHDF5.cxx index cf5937b879..429768db24 100644 --- a/src/recon_buildblock/BinNormalisationFromGEHDF5.cxx +++ b/src/recon_buildblock/BinNormalisationFromGEHDF5.cxx @@ -49,11 +49,9 @@ #include #include #include -#ifndef STIR_NO_NAMESPACES using std::ofstream; using std::fstream; using std::ios; -#endif START_NAMESPACE_STIR namespace GE { diff --git a/src/recon_buildblock/BinNormalisationSPECT.cxx b/src/recon_buildblock/BinNormalisationSPECT.cxx index 77c2b6bc71..2fd171393f 100644 --- a/src/recon_buildblock/BinNormalisationSPECT.cxx +++ b/src/recon_buildblock/BinNormalisationSPECT.cxx @@ -37,11 +37,9 @@ #include #include -#ifndef STIR_NO_NAMESPACES using std::ofstream; using std::ostringstream; using std::fstream; -#endif START_NAMESPACE_STIR diff --git a/src/recon_buildblock/ForwardProjectorByBinUsingProjMatrixByBin.cxx b/src/recon_buildblock/ForwardProjectorByBinUsingProjMatrixByBin.cxx index 5cb670cf00..0769b7cfe2 100644 --- a/src/recon_buildblock/ForwardProjectorByBinUsingProjMatrixByBin.cxx +++ b/src/recon_buildblock/ForwardProjectorByBinUsingProjMatrixByBin.cxx @@ -33,11 +33,9 @@ #include #include -#ifndef STIR_NO_NAMESPACE using std::find; using std::vector; using std::list; -#endif START_NAMESPACE_STIR @@ -180,10 +178,7 @@ ForwardProjectorByBinUsingProjMatrixByBin:: min_tangential_pos_num, max_tangential_pos_num); for ( -#ifndef STIR_NO_NAMESPACES - std:: -#endif - vector::iterator r_ax_poss_iter = r_ax_poss.begin(); + auto r_ax_poss_iter = r_ax_poss.begin(); r_ax_poss_iter != r_ax_poss.end(); ++r_ax_poss_iter) { diff --git a/src/recon_buildblock/ForwardProjectorByBinUsingRayTracing_Siddon.cxx b/src/recon_buildblock/ForwardProjectorByBinUsingRayTracing_Siddon.cxx index 08ef651e6d..b929b9f3cc 100644 --- a/src/recon_buildblock/ForwardProjectorByBinUsingRayTracing_Siddon.cxx +++ b/src/recon_buildblock/ForwardProjectorByBinUsingRayTracing_Siddon.cxx @@ -52,10 +52,8 @@ #include "stir/round.h" #include #include -#ifndef STIR_NO_NAMESPACE using std::min; using std::max; -#endif START_NAMESPACE_STIR template diff --git a/src/recon_buildblock/FourierRebinning.cxx b/src/recon_buildblock/FourierRebinning.cxx index 027d2e686c..e65453e815 100644 --- a/src/recon_buildblock/FourierRebinning.cxx +++ b/src/recon_buildblock/FourierRebinning.cxx @@ -50,10 +50,8 @@ #define NEGATIVE_Z_SHIFT 1 #define CHANGE_Z_SHIFT 2 -#ifndef STIR_NO_NAMESPACES using std::ios; using std::ofstream; -#endif START_NAMESPACE_STIR diff --git a/src/recon_buildblock/IterativeReconstruction.cxx b/src/recon_buildblock/IterativeReconstruction.cxx index 7074614262..6e90bada6a 100644 --- a/src/recon_buildblock/IterativeReconstruction.cxx +++ b/src/recon_buildblock/IterativeReconstruction.cxx @@ -43,11 +43,9 @@ #include "stir/warning.h" #include "stir/error.h" -#ifndef STIR_NO_NAMESPACES using std::cerr; using std::endl; using std::string; -#endif START_NAMESPACE_STIR diff --git a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx index ac686cf449..648b21040b 100644 --- a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx +++ b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx @@ -70,12 +70,10 @@ #include "stir/info.h" #include -#ifndef STIR_NO_NAMESPACES using std::vector; using std::pair; using std::ends; using std::max; -#endif START_NAMESPACE_STIR diff --git a/src/recon_buildblock/ProjMatrixByBinUsingRayTracing.cxx b/src/recon_buildblock/ProjMatrixByBinUsingRayTracing.cxx index d305ee3628..4fae773dc5 100644 --- a/src/recon_buildblock/ProjMatrixByBinUsingRayTracing.cxx +++ b/src/recon_buildblock/ProjMatrixByBinUsingRayTracing.cxx @@ -60,10 +60,8 @@ #include "stir/ProjDataInfoBlocksOnCylindricalNoArcCorr.h" #include "stir/ProjDataInfoGenericNoArcCorr.h" -#ifndef STIR_NO_NAMESPACE using std::min; using std::max; -#endif START_NAMESPACE_STIR diff --git a/src/recon_buildblock/ProjMatrixElemsForOneBin.cxx b/src/recon_buildblock/ProjMatrixElemsForOneBin.cxx index 16f9373f1d..c5196f2a01 100644 --- a/src/recon_buildblock/ProjMatrixElemsForOneBin.cxx +++ b/src/recon_buildblock/ProjMatrixElemsForOneBin.cxx @@ -37,9 +37,7 @@ //#include //#include "stir/stream.h" -#ifndef STIR_NO_NAMESPACES using std::copy; -#endif START_NAMESPACE_STIR @@ -143,12 +141,7 @@ Succeeded ProjMatrixElemsForOneBin::check_state() const void ProjMatrixElemsForOneBin::sort() { - // need explicit std:: here to resolve possible name conflict - // this might give you trouble if your compiler does not support namespaces -#if !defined(STIR_NO_NAMESPACES) || (__GNUC__ == 2 && __GNUC_MINOR__ <= 8) - std:: -#endif - sort(begin(), end(), value_type::coordinates_less); + std::sort(begin(), end(), value_type::coordinates_less); } diff --git a/src/recon_buildblock/ProjMatrixElemsForOneDensel.cxx b/src/recon_buildblock/ProjMatrixElemsForOneDensel.cxx index e145327464..e210da23a7 100644 --- a/src/recon_buildblock/ProjMatrixElemsForOneDensel.cxx +++ b/src/recon_buildblock/ProjMatrixElemsForOneDensel.cxx @@ -117,12 +117,7 @@ Succeeded ProjMatrixElemsForOneDensel::check_state() const void ProjMatrixElemsForOneDensel::sort() { - // need explicit std:: here to resolve possible name conflict - // this might give you trouble if your compiler does not support namespaces -#if !defined(STIR_NO_NAMESPACES) || (__GNUC__ == 2 && __GNUC_MINOR__ <= 8) - std:: -#endif - sort(begin(), end(), value_type::coordinates_less); + std::sort(begin(), end(), value_type::coordinates_less); } diff --git a/src/recon_buildblock/RayTraceVoxelsOnCartesianGrid.cxx b/src/recon_buildblock/RayTraceVoxelsOnCartesianGrid.cxx index 6957812220..4a6e6bed06 100644 --- a/src/recon_buildblock/RayTraceVoxelsOnCartesianGrid.cxx +++ b/src/recon_buildblock/RayTraceVoxelsOnCartesianGrid.cxx @@ -37,10 +37,8 @@ #include #include -#ifndef STIR_NO_NAMESPACE using std::min; using std::max; -#endif START_NAMESPACE_STIR diff --git a/src/recon_test/bcktest.cxx b/src/recon_test/bcktest.cxx index 1ca099d460..9c975a19c5 100644 --- a/src/recon_test/bcktest.cxx +++ b/src/recon_test/bcktest.cxx @@ -61,7 +61,6 @@ #include #include -#ifndef STIR_NO_NAMESPACES using std::ofstream; using std::fstream; using std::iostream; @@ -70,7 +69,6 @@ using std::list; using std::find; using std::cerr; using std::endl; -#endif diff --git a/src/recon_test/test_DataSymmetriesForBins_PET_CartesianGrid.cxx b/src/recon_test/test_DataSymmetriesForBins_PET_CartesianGrid.cxx index 24dd7790b0..0568d0c76a 100644 --- a/src/recon_test/test_DataSymmetriesForBins_PET_CartesianGrid.cxx +++ b/src/recon_test/test_DataSymmetriesForBins_PET_CartesianGrid.cxx @@ -36,10 +36,8 @@ #include #include #include -#ifndef STIR_NO_NAMESPACES using std::stringstream; using std::cerr; -#endif START_NAMESPACE_STIR diff --git a/src/test/IO/test_IO_DiscretisedDensity.cxx b/src/test/IO/test_IO_DiscretisedDensity.cxx index 6ecd956e1c..82383768cf 100644 --- a/src/test/IO/test_IO_DiscretisedDensity.cxx +++ b/src/test/IO/test_IO_DiscretisedDensity.cxx @@ -35,12 +35,10 @@ #include "stir/IO/test/test_IO.h" -#ifndef STIR_NO_NAMESPACES using std::cerr; using std::endl; using std::ifstream; using std::istream; -#endif START_NAMESPACE_STIR diff --git a/src/test/IO/test_IO_DynamicDiscretisedDensity.cxx b/src/test/IO/test_IO_DynamicDiscretisedDensity.cxx index 65059e13c8..92cc6f2a67 100644 --- a/src/test/IO/test_IO_DynamicDiscretisedDensity.cxx +++ b/src/test/IO/test_IO_DynamicDiscretisedDensity.cxx @@ -36,12 +36,10 @@ #include "stir/IO/test/test_IO.h" #include "stir/DynamicDiscretisedDensity.h" -#ifndef STIR_NO_NAMESPACES using std::cerr; using std::endl; using std::ifstream; using std::istream; -#endif START_NAMESPACE_STIR diff --git a/src/test/IO/test_IO_ParametricDiscretisedDensity.cxx b/src/test/IO/test_IO_ParametricDiscretisedDensity.cxx index c1ba57d417..0f570b8afc 100644 --- a/src/test/IO/test_IO_ParametricDiscretisedDensity.cxx +++ b/src/test/IO/test_IO_ParametricDiscretisedDensity.cxx @@ -37,12 +37,10 @@ #include "stir/modelling/ParametricDiscretisedDensity.h" #include "stir/ProjDataInfoCylindricalNoArcCorr.h" -#ifndef STIR_NO_NAMESPACES using std::cerr; using std::endl; using std::ifstream; using std::istream; -#endif START_NAMESPACE_STIR diff --git a/src/test/modelling/test_ParametricDiscretisedDensity.cxx b/src/test/modelling/test_ParametricDiscretisedDensity.cxx index db35f62845..2c98d37124 100644 --- a/src/test/modelling/test_ParametricDiscretisedDensity.cxx +++ b/src/test/modelling/test_ParametricDiscretisedDensity.cxx @@ -43,13 +43,11 @@ #include "stir/modelling/ParametricDiscretisedDensity.h" #include "stir/is_null_ptr.h" -#ifndef STIR_NO_NAMESPACES using std::cerr; using std::ifstream; using std::istream; using std::setw; using std::endl; -#endif START_NAMESPACE_STIR diff --git a/src/test/numerics/test_BSplines.cxx b/src/test/numerics/test_BSplines.cxx index e4440b76b6..bfa9e6ce76 100644 --- a/src/test/numerics/test_BSplines.cxx +++ b/src/test/numerics/test_BSplines.cxx @@ -28,13 +28,11 @@ #include #include "stir/shared_ptr.h" #include -#ifndef STIR_NO_NAMESPACES using std::cerr; using std::ifstream; using std::istream; using std::setw; using std::endl; -#endif START_NAMESPACE_STIR namespace BSpline { /*! diff --git a/src/test/numerics/test_BSplinesRegularGrid.cxx b/src/test/numerics/test_BSplinesRegularGrid.cxx index 386e12a88c..d9fb03227e 100644 --- a/src/test/numerics/test_BSplinesRegularGrid.cxx +++ b/src/test/numerics/test_BSplinesRegularGrid.cxx @@ -27,10 +27,8 @@ #include "stir/numerics/BSplinesRegularGrid.h" #include #include "stir/shared_ptr.h" -#ifndef STIR_NO_NAMESPACES using std::cerr; using std::endl; -#endif START_NAMESPACE_STIR namespace BSpline { diff --git a/src/test/numerics/test_BSplinesRegularGrid1D.cxx b/src/test/numerics/test_BSplinesRegularGrid1D.cxx index 39ce8b8683..097bfd248d 100644 --- a/src/test/numerics/test_BSplinesRegularGrid1D.cxx +++ b/src/test/numerics/test_BSplinesRegularGrid1D.cxx @@ -22,10 +22,9 @@ #include #include "stir/stream.h" -#ifndef STIR_NO_NAMESPACES using std::cerr; using std::endl; -#endif + START_NAMESPACE_STIR namespace BSpline { /*! diff --git a/src/test/numerics/test_IR_filters.cxx b/src/test/numerics/test_IR_filters.cxx index eceb83f01b..db70360c75 100644 --- a/src/test/numerics/test_IR_filters.cxx +++ b/src/test/numerics/test_IR_filters.cxx @@ -24,10 +24,8 @@ #include -#ifndef STIR_NO_NAMESPACES using std::cerr; using std::endl; -#endif START_NAMESPACE_STIR diff --git a/src/test/test_Array.cxx b/src/test/test_Array.cxx index baf236155f..a2137f10f5 100644 --- a/src/test/test_Array.cxx +++ b/src/test/test_Array.cxx @@ -52,14 +52,12 @@ #include #include #include -#ifndef STIR_NO_NAMESPACES using std::ofstream; using std::ifstream; using std::plus; using std::bind; using std::cerr; using std::endl; -#endif START_NAMESPACE_STIR diff --git a/src/test/test_ByteOrder.cxx b/src/test/test_ByteOrder.cxx index f692ae2c1f..da499003f7 100644 --- a/src/test/test_ByteOrder.cxx +++ b/src/test/test_ByteOrder.cxx @@ -25,10 +25,8 @@ #include -#ifndef STIR_NO_NAMESPACES using std::cerr; using std::endl; -#endif START_NAMESPACE_STIR diff --git a/src/test/test_DynamicDiscretisedDensity.cxx b/src/test/test_DynamicDiscretisedDensity.cxx index 4bab102e55..90c8a1ee3e 100644 --- a/src/test/test_DynamicDiscretisedDensity.cxx +++ b/src/test/test_DynamicDiscretisedDensity.cxx @@ -36,11 +36,9 @@ #include #include -#ifndef STIR_NO_NAMESPACES using std::cerr; using std::endl; using std::string; -#endif START_NAMESPACE_STIR diff --git a/src/test/test_ImagingModality.cxx b/src/test/test_ImagingModality.cxx index a5bf942f7c..b78f4ad79b 100644 --- a/src/test/test_ImagingModality.cxx +++ b/src/test/test_ImagingModality.cxx @@ -23,10 +23,8 @@ #include -#ifndef STIR_NO_NAMESPACES using std::cerr; using std::endl; -#endif START_NAMESPACE_STIR diff --git a/src/test/test_OutputFileFormat.cxx b/src/test/test_OutputFileFormat.cxx index 8b7a14fdad..e7d6017796 100644 --- a/src/test/test_OutputFileFormat.cxx +++ b/src/test/test_OutputFileFormat.cxx @@ -50,12 +50,10 @@ #include #include -#ifndef STIR_NO_NAMESPACES using std::cerr; using std::endl; using std::ifstream; using std::istream; -#endif START_NAMESPACE_STIR diff --git a/src/test/test_VectorWithOffset.cxx b/src/test/test_VectorWithOffset.cxx index 946069e9ee..7dfa92c7e4 100644 --- a/src/test/test_VectorWithOffset.cxx +++ b/src/test/test_VectorWithOffset.cxx @@ -34,14 +34,12 @@ #include #include -#ifndef STIR_NO_NAMESPACES using std::cerr; using std::endl; using std::sort; using std::find; using std::greater; using std::size_t; -#endif START_NAMESPACE_STIR diff --git a/src/test/test_coordinates.cxx b/src/test/test_coordinates.cxx index 6cf24e83dd..2988a0db97 100644 --- a/src/test/test_coordinates.cxx +++ b/src/test/test_coordinates.cxx @@ -31,10 +31,8 @@ #include #include -#ifndef STIR_NO_NAMESPACES using std::cerr; using std::endl; -#endif START_NAMESPACE_STIR @@ -117,19 +115,11 @@ coordinateTests::run_tests() // basic iterator tests { -#ifndef STIR_NO_NAMESPACES float *p=std::find(b.begin(), b.end(), -3); -#else - float *p=find(b.begin(), b.end(), -3); -#endif check_if_zero(p - b.begin() - 1, "iterator test"); BasicCoordinate<3, float> b_sorted; b_sorted[1]=-3;b_sorted[2]=-1;b_sorted[3]=5; -#ifndef STIR_NO_NAMESPACES std::sort(b.begin(), b.end()); -#else - sort(b.begin(), b.end()); -#endif check_if_zero(norm(b-b_sorted), "testing iterators via STL sort"); } } diff --git a/src/test/test_filename_functions.cxx b/src/test/test_filename_functions.cxx index 536c17866f..6a03ff0844 100644 --- a/src/test/test_filename_functions.cxx +++ b/src/test/test_filename_functions.cxx @@ -23,11 +23,9 @@ #include "stir/RunTests.h" #include "stir/FilePath.h" -#ifndef STIR_NO_NAMESPACES using std::cerr; using std::endl; using std::string; -#endif START_NAMESPACE_STIR diff --git a/src/test/test_find_fwhm_in_image.cxx b/src/test/test_find_fwhm_in_image.cxx index b44837e2b4..a7046fe034 100644 --- a/src/test/test_find_fwhm_in_image.cxx +++ b/src/test/test_find_fwhm_in_image.cxx @@ -34,12 +34,10 @@ #include #include -#ifndef STIR_NO_NAMESPACES using std::cerr; using std::ifstream; using std::istream; using std::endl; -#endif START_NAMESPACE_STIR diff --git a/src/test/test_interpolate.cxx b/src/test/test_interpolate.cxx index 969afbf1d9..9d5c96988c 100644 --- a/src/test/test_interpolate.cxx +++ b/src/test/test_interpolate.cxx @@ -27,10 +27,8 @@ #include "stir/utilities.h" #include "stir/stream.h" -#ifndef STIR_NO_NAMESPACES using std::cout; using std::endl; -#endif USING_NAMESPACE_STIR diff --git a/src/test/test_linear_regression.cxx b/src/test/test_linear_regression.cxx index 863544554f..c85d8bca6a 100644 --- a/src/test/test_linear_regression.cxx +++ b/src/test/test_linear_regression.cxx @@ -34,12 +34,10 @@ #include #include -#ifndef STIR_NO_NAMESPACES using std::cerr; using std::ifstream; using std::istream; using std::endl; -#endif START_NAMESPACE_STIR diff --git a/src/test/test_multiple_proj_data.cxx b/src/test/test_multiple_proj_data.cxx index 46986db5e2..ea2f3c8189 100644 --- a/src/test/test_multiple_proj_data.cxx +++ b/src/test/test_multiple_proj_data.cxx @@ -35,14 +35,12 @@ #include #include -#ifndef STIR_NO_NAMESPACES using std::cerr; using std::setw; using std::endl; using std::min; using std::max; using std::size_t; -#endif START_NAMESPACE_STIR diff --git a/src/test/test_proj_data_info.cxx b/src/test/test_proj_data_info.cxx index c583adcba9..5605eb85ac 100644 --- a/src/test/test_proj_data_info.cxx +++ b/src/test/test_proj_data_info.cxx @@ -45,14 +45,12 @@ #include #include "stir/CPUTimer.h" -#ifndef STIR_NO_NAMESPACES using std::cerr; using std::setw; using std::endl; using std::min; using std::max; using std::size_t; -#endif //#define STIR_TOF_DEBUG 1 diff --git a/src/test/test_stir_math.cxx b/src/test/test_stir_math.cxx index f45d3b26d1..1915f39474 100644 --- a/src/test/test_stir_math.cxx +++ b/src/test/test_stir_math.cxx @@ -39,11 +39,9 @@ #include #include -#ifndef STIR_NO_NAMESPACE using std::generate; using std::string; using std::cerr; -#endif START_NAMESPACE_STIR diff --git a/src/test/test_warp_image.cxx b/src/test/test_warp_image.cxx index ab902f2592..24900c94e6 100644 --- a/src/test/test_warp_image.cxx +++ b/src/test/test_warp_image.cxx @@ -27,10 +27,8 @@ #include #include -#ifndef STIR_NO_NAMESPACES using std::cerr; using std::endl; -#endif START_NAMESPACE_STIR diff --git a/src/utilities/SSRB.cxx b/src/utilities/SSRB.cxx index b6ffced025..adae826dc1 100644 --- a/src/utilities/SSRB.cxx +++ b/src/utilities/SSRB.cxx @@ -67,10 +67,8 @@ #include #include "stir/ProjDataInterfile.h" -#ifndef STIR_NO_NAMESPACES using std::string; using std::cerr; -#endif USING_NAMESPACE_STIR diff --git a/src/utilities/calculate_attenuation_coefficients.cxx b/src/utilities/calculate_attenuation_coefficients.cxx index b1ea2c69d7..e0fd030da0 100644 --- a/src/utilities/calculate_attenuation_coefficients.cxx +++ b/src/utilities/calculate_attenuation_coefficients.cxx @@ -72,10 +72,8 @@ #include #include -#ifndef STIR_NO_NAMESPACES using std::endl; using std::cerr; -#endif START_NAMESPACE_STIR diff --git a/src/utilities/compare_image.cxx b/src/utilities/compare_image.cxx index 3c26e26a92..da522b6937 100644 --- a/src/utilities/compare_image.cxx +++ b/src/utilities/compare_image.cxx @@ -41,11 +41,9 @@ if the files are identical or not. #include #include -#ifndef STIR_NO_NAMESPACES using std::cerr; using std::cout; using std::endl; -#endif diff --git a/src/utilities/construct_randoms_from_GEsingles.cxx b/src/utilities/construct_randoms_from_GEsingles.cxx index 78240ddae8..90f6cff74f 100755 --- a/src/utilities/construct_randoms_from_GEsingles.cxx +++ b/src/utilities/construct_randoms_from_GEsingles.cxx @@ -33,11 +33,9 @@ #include "stir/data/randoms_from_singles.h" #include -#ifndef STIR_NO_NAMESPACES using std::cerr; using std::endl; using std::string; -#endif USING_NAMESPACE_STIR diff --git a/src/utilities/construct_randoms_from_singles.cxx b/src/utilities/construct_randoms_from_singles.cxx index 772d921626..9db43f308f 100644 --- a/src/utilities/construct_randoms_from_singles.cxx +++ b/src/utilities/construct_randoms_from_singles.cxx @@ -29,12 +29,10 @@ #include //#include -#ifndef STIR_NO_NAMESPACES using std::cerr; using std::endl; using std::ifstream; using std::string; -#endif USING_NAMESPACE_STIR diff --git a/src/utilities/conv_gipl_to_interfile.cxx b/src/utilities/conv_gipl_to_interfile.cxx index 5f5986a88e..de89e6d72b 100644 --- a/src/utilities/conv_gipl_to_interfile.cxx +++ b/src/utilities/conv_gipl_to_interfile.cxx @@ -26,7 +26,6 @@ #include #include -#ifndef STIR_NO_NAMESPACES using std::string; using std::ios; using std::iostream; @@ -35,7 +34,6 @@ using std::endl; using std::fstream; using std::cerr; using std::endl; -#endif USING_NAMESPACE_STIR diff --git a/src/utilities/conv_interfile_to_gipl.cxx b/src/utilities/conv_interfile_to_gipl.cxx index 7849fce998..50b6694c67 100644 --- a/src/utilities/conv_interfile_to_gipl.cxx +++ b/src/utilities/conv_interfile_to_gipl.cxx @@ -26,7 +26,6 @@ #include #include -#ifndef STIR_NO_NAMESPACES using std::string; using std::ios; using std::iostream; @@ -35,7 +34,6 @@ using std::endl; using std::fstream; using std::cerr; using std::endl; -#endif USING_NAMESPACE_STIR diff --git a/src/utilities/correct_projdata.cxx b/src/utilities/correct_projdata.cxx index 968bb3694d..aceb982634 100644 --- a/src/utilities/correct_projdata.cxx +++ b/src/utilities/correct_projdata.cxx @@ -151,7 +151,6 @@ This parameter will be removed. #include #include -#ifndef STIR_NO_NAMESPACES using std::cerr; using std::endl; using std::fstream; @@ -159,7 +158,6 @@ using std::ifstream; using std::cout; using std::string; using std::vector; -#endif START_NAMESPACE_STIR diff --git a/src/utilities/create_projdata_template.cxx b/src/utilities/create_projdata_template.cxx index 176fb9648c..904052ac2e 100644 --- a/src/utilities/create_projdata_template.cxx +++ b/src/utilities/create_projdata_template.cxx @@ -31,9 +31,7 @@ #include "stir/ExamInfo.h" #include "stir/ProjDataInfo.h" -#ifndef STIR_NO_NAMESPACES using std::cerr; -#endif USING_NAMESPACE_STIR diff --git a/src/utilities/display_projdata.cxx b/src/utilities/display_projdata.cxx index a267bd4c7d..37b4cad5da 100644 --- a/src/utilities/display_projdata.cxx +++ b/src/utilities/display_projdata.cxx @@ -28,9 +28,7 @@ #include "stir/utilities.h" -#ifndef STIR_NO_NAMESPACES using std::cout; -#endif USING_NAMESPACE_STIR diff --git a/src/utilities/do_linear_regression.cxx b/src/utilities/do_linear_regression.cxx index 9ad75f7b27..2c5ac1600e 100644 --- a/src/utilities/do_linear_regression.cxx +++ b/src/utilities/do_linear_regression.cxx @@ -38,12 +38,10 @@ #include #include -#ifndef STIR_NO_NAMESPACES using std::cout; using std::cerr; using std::endl; using std::ifstream; -#endif USING_NAMESPACE_STIR diff --git a/src/utilities/ecat/conv_to_ecat6.cxx b/src/utilities/ecat/conv_to_ecat6.cxx index 1c18d116ae..3dfb2a1c10 100644 --- a/src/utilities/ecat/conv_to_ecat6.cxx +++ b/src/utilities/ecat/conv_to_ecat6.cxx @@ -61,14 +61,12 @@ Note that to store projection data in ECAT6, a 3D sinogram cannot be axially com #include "stir/warning.h" #include "stir/error.h" -#ifndef STIR_NO_NAMESPACES using std::cerr; using std::cout; using std::endl; using std::vector; using std::string; using std::size_t; -#endif USING_NAMESPACE_STIR USING_NAMESPACE_ECAT diff --git a/src/utilities/ecat/conv_to_ecat7.cxx b/src/utilities/ecat/conv_to_ecat7.cxx index a1066e18a2..783e272a49 100644 --- a/src/utilities/ecat/conv_to_ecat7.cxx +++ b/src/utilities/ecat/conv_to_ecat7.cxx @@ -50,12 +50,10 @@ be surrounded by double quotes (") when used as a command line argument. #include #include -#ifndef STIR_NO_NAMESPACES using std::cerr; using std::endl; using std::vector; using std::string; -#endif USING_NAMESPACE_STIR USING_NAMESPACE_ECAT diff --git a/src/utilities/ecat/convecat6_if.cxx b/src/utilities/ecat/convecat6_if.cxx index d7c7d42c33..60b4e0ece7 100644 --- a/src/utilities/ecat/convecat6_if.cxx +++ b/src/utilities/ecat/convecat6_if.cxx @@ -66,13 +66,11 @@ #include #include -#ifndef STIR_NO_NAMESPACES using std::cerr; using std::cout; using std::endl; using std::min; using std::max; -#endif diff --git a/src/utilities/ecat/copy_ecat7_header.cxx b/src/utilities/ecat/copy_ecat7_header.cxx index a20e7868b8..29d807658c 100644 --- a/src/utilities/ecat/copy_ecat7_header.cxx +++ b/src/utilities/ecat/copy_ecat7_header.cxx @@ -44,13 +44,11 @@ To copy a subheader (but keeping essential info) #include #include -#ifndef STIR_NO_NAMESPACES using std::cerr; using std::endl; using std::cout; using std::string; using std::ostream; -#endif USING_NAMESPACE_STIR USING_NAMESPACE_ECAT diff --git a/src/utilities/ecat/ecat_swap_corners.cxx b/src/utilities/ecat/ecat_swap_corners.cxx index 95d6b1bb36..92e1e526d3 100644 --- a/src/utilities/ecat/ecat_swap_corners.cxx +++ b/src/utilities/ecat/ecat_swap_corners.cxx @@ -102,13 +102,11 @@ #include "stir/error.h" #include #include -#ifndef STIR_NO_NAMESPACES using std::cerr; using std::endl; using std::min; using std::max; using std::swap; -#endif USING_NAMESPACE_STIR diff --git a/src/utilities/ecat/ifheaders_for_ecat7.cxx b/src/utilities/ecat/ifheaders_for_ecat7.cxx index 55d14b6448..73aba2f9db 100644 --- a/src/utilities/ecat/ifheaders_for_ecat7.cxx +++ b/src/utilities/ecat/ifheaders_for_ecat7.cxx @@ -55,14 +55,12 @@ only work on systems where this library works properly. #include -#ifndef STIR_NO_NAMESPACES using std::string; using std::ios; using std::iostream; using std::fstream; using std::cerr; using std::endl; -#endif USING_NAMESPACE_STIR diff --git a/src/utilities/ecat/print_ecat_singles_values.cxx b/src/utilities/ecat/print_ecat_singles_values.cxx index 4876c42fdc..082252e446 100644 --- a/src/utilities/ecat/print_ecat_singles_values.cxx +++ b/src/utilities/ecat/print_ecat_singles_values.cxx @@ -25,14 +25,12 @@ #include -#ifndef STIR_NO_NAMESPACES using std::cout; using std::cerr; using std::endl; using std::setw; using std::string; using std::vector; -#endif USING_NAMESPACE_STIR diff --git a/src/utilities/estimate_triple_energy_window_scatter_sinogram.cxx b/src/utilities/estimate_triple_energy_window_scatter_sinogram.cxx index 1368305a7e..3c1ecbf8ff 100644 --- a/src/utilities/estimate_triple_energy_window_scatter_sinogram.cxx +++ b/src/utilities/estimate_triple_energy_window_scatter_sinogram.cxx @@ -40,7 +40,6 @@ #include #include #include -#ifndef STIR_NO_NAMESPACES using std::cerr; using std::cout; using std::endl; @@ -50,7 +49,6 @@ using std::max; using std::min; using std::string; using std::vector; -#endif USING_NAMESPACE_STIR diff --git a/src/utilities/find_fwhm_in_image.cxx b/src/utilities/find_fwhm_in_image.cxx index 2bcdd8ef57..500b7275cd 100644 --- a/src/utilities/find_fwhm_in_image.cxx +++ b/src/utilities/find_fwhm_in_image.cxx @@ -49,9 +49,7 @@ #include #include #include -#ifndef STIR_NO_NAMESPACES using std::setw; -#endif /***********************************************************/ int main(int argc, char *argv[]) diff --git a/src/utilities/find_maxima_in_image.cxx b/src/utilities/find_maxima_in_image.cxx index beb1fe242a..2c14c69a32 100644 --- a/src/utilities/find_maxima_in_image.cxx +++ b/src/utilities/find_maxima_in_image.cxx @@ -42,12 +42,10 @@ #include -#ifndef STIR_NO_NAMESPACES using std::endl; using std::cout; using std::cerr; using std::setw; -#endif USING_NAMESPACE_STIR diff --git a/src/utilities/find_recovery_coefficients_in_image_quality_phantom_nema_nu4.cxx b/src/utilities/find_recovery_coefficients_in_image_quality_phantom_nema_nu4.cxx index 07c91cbc73..6f43154b1c 100644 --- a/src/utilities/find_recovery_coefficients_in_image_quality_phantom_nema_nu4.cxx +++ b/src/utilities/find_recovery_coefficients_in_image_quality_phantom_nema_nu4.cxx @@ -42,12 +42,10 @@ limitations under the License. #include "stir/error.h" #include "stir/Succeeded.h" -#ifndef STIR_NO_NAMESPACES using std::cerr; using std::endl; using std::ofstream; using std::vector; -#endif USING_NAMESPACE_STIR diff --git a/src/utilities/find_sum_projection_of_viewgram_and_sinogram.cxx b/src/utilities/find_sum_projection_of_viewgram_and_sinogram.cxx index 7bc1a620a7..c9cf05b984 100644 --- a/src/utilities/find_sum_projection_of_viewgram_and_sinogram.cxx +++ b/src/utilities/find_sum_projection_of_viewgram_and_sinogram.cxx @@ -38,9 +38,7 @@ limitations under the License. #include "stir/ProjDataInfo.h" #include "stir/error.h" -#ifndef STIR_NO_NAMESPACES using std::cerr; -#endif USING_NAMESPACE_STIR diff --git a/src/utilities/get_time_frame_info.cxx b/src/utilities/get_time_frame_info.cxx index 6282d34716..bda47095e8 100644 --- a/src/utilities/get_time_frame_info.cxx +++ b/src/utilities/get_time_frame_info.cxx @@ -22,11 +22,9 @@ #include #include -#ifndef STIR_NO_NAMESPACES using std::cerr; using std::endl; using std::cout; -#endif USING_NAMESPACE_STIR diff --git a/src/utilities/list_ROI_values.cxx b/src/utilities/list_ROI_values.cxx index ad9f8f6fda..a4125a6730 100644 --- a/src/utilities/list_ROI_values.cxx +++ b/src/utilities/list_ROI_values.cxx @@ -55,11 +55,9 @@ #include #include -#ifndef STIR_NO_NAMESPACES using std::cerr; using std::endl; using std::ofstream; -#endif START_NAMESPACE_STIR diff --git a/src/utilities/manip_image.cxx b/src/utilities/manip_image.cxx index c114ae5e81..1954e9c8b0 100644 --- a/src/utilities/manip_image.cxx +++ b/src/utilities/manip_image.cxx @@ -39,14 +39,12 @@ #include #include -#ifndef STIR_NO_NAMESPACES using std::iostream; using std::ofstream; using std::ios; using std::cerr; using std::endl; using std::swap; -#endif USING_NAMESPACE_STIR diff --git a/src/utilities/manip_projdata.cxx b/src/utilities/manip_projdata.cxx index 5c44894a34..9aff4a72a5 100644 --- a/src/utilities/manip_projdata.cxx +++ b/src/utilities/manip_projdata.cxx @@ -50,11 +50,9 @@ This utility programme processes (interfile) sinogram data #include #include -#ifndef STIR_NO_NAMESPACES using std::cerr; using std::endl; using std::fstream; -#endif diff --git a/src/utilities/postfilter.cxx b/src/utilities/postfilter.cxx index d22dbb6ab7..972a24c951 100644 --- a/src/utilities/postfilter.cxx +++ b/src/utilities/postfilter.cxx @@ -72,10 +72,8 @@ #include "stir/error.h" #include -#ifndef STIR_NO_NAMESPACES using std::cerr; using std::endl; -#endif START_NAMESPACE_STIR diff --git a/src/utilities/rebin_projdata.cxx b/src/utilities/rebin_projdata.cxx index 01a05d3388..d0fd175b8f 100644 --- a/src/utilities/rebin_projdata.cxx +++ b/src/utilities/rebin_projdata.cxx @@ -39,10 +39,8 @@ END:= #include "stir/error.h" #include -#ifndef STIR_NO_NAMESPACES using std::cerr; using std::endl; -#endif START_NAMESPACE_STIR diff --git a/src/utilities/separate_true_from_random_scatter_for_necr.cxx b/src/utilities/separate_true_from_random_scatter_for_necr.cxx index a0a008ed43..0600f3269c 100644 --- a/src/utilities/separate_true_from_random_scatter_for_necr.cxx +++ b/src/utilities/separate_true_from_random_scatter_for_necr.cxx @@ -43,9 +43,7 @@ limitations under the License. -#ifndef STIR_NO_NAMESPACES using std::cerr; -#endif USING_NAMESPACE_STIR diff --git a/src/utilities/shift_image.cxx b/src/utilities/shift_image.cxx index 4754a751ba..862a17d4f7 100644 --- a/src/utilities/shift_image.cxx +++ b/src/utilities/shift_image.cxx @@ -20,9 +20,7 @@ #include -#ifndef STIR_NO_NAMESPACES using std::cerr; -#endif USING_NAMESPACE_STIR diff --git a/src/utilities/shift_image_origin.cxx b/src/utilities/shift_image_origin.cxx index d775f90289..4b5991e2a0 100644 --- a/src/utilities/shift_image_origin.cxx +++ b/src/utilities/shift_image_origin.cxx @@ -16,9 +16,7 @@ #include "stir/IO/OutputFileFormat.h" #include "stir/Succeeded.h" -#ifndef STIR_NO_NAMESPACES using std::cerr; -#endif USING_NAMESPACE_STIR diff --git a/src/utilities/stir_math.cxx b/src/utilities/stir_math.cxx index 4865d708fd..147b979e4f 100644 --- a/src/utilities/stir_math.cxx +++ b/src/utilities/stir_math.cxx @@ -131,7 +131,6 @@ #include #include #include -#ifndef STIR_NO_NAMESPACES using std::cerr; using std::cout; using std::endl; @@ -141,7 +140,6 @@ using std::max; using std::min; using std::string; using std::vector; -#endif USING_NAMESPACE_STIR diff --git a/src/utilities/warp_and_accumulate_gated_images.cxx b/src/utilities/warp_and_accumulate_gated_images.cxx index 0d013f9663..008a73afae 100644 --- a/src/utilities/warp_and_accumulate_gated_images.cxx +++ b/src/utilities/warp_and_accumulate_gated_images.cxx @@ -24,9 +24,7 @@ #include #include -#ifndef STIR_NO_NAMESPACES using std::cerr; -#endif USING_NAMESPACE_STIR diff --git a/src/utilities/write_proj_matrix_by_bin.cxx b/src/utilities/write_proj_matrix_by_bin.cxx index 53dbf4aa09..8bd817166a 100644 --- a/src/utilities/write_proj_matrix_by_bin.cxx +++ b/src/utilities/write_proj_matrix_by_bin.cxx @@ -31,11 +31,9 @@ #include "stir/Coordinate3D.h" #include "stir/IO/read_from_file.h" -#ifndef STIR_NO_NAMESPACES using std::endl; using std::cerr; using std::endl; -#endif int diff --git a/src/utilities/zoom_image.cxx b/src/utilities/zoom_image.cxx index e03b478ba7..47d5eba971 100644 --- a/src/utilities/zoom_image.cxx +++ b/src/utilities/zoom_image.cxx @@ -29,9 +29,7 @@ #include "stir/error.h" -#ifndef STIR_NO_NAMESPACES using std::cerr; -#endif USING_NAMESPACE_STIR From ee93952c2638dc7770c88c2639b62f169dfbde26 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sun, 28 Jan 2024 21:57:00 +0000 Subject: [PATCH 491/509] Remove most work-arounds for VC 6.0 --- src/IO/RegisteredObject.cxx | 53 ---------- src/IO/interfile.cxx | 10 -- src/Shape_buildblock/RegisteredObject.cxx | 51 ---------- .../ArrayFilterUsingRealDFTWithPadding.cxx | 2 - src/buildblock/IndexRange.cxx | 30 +----- src/buildblock/RegisteredObject.cxx | 49 --------- src/data_buildblock/RegisteredObject.cxx | 53 ---------- .../buildblock/RegisteredObject.cxx | 52 ---------- src/experimental/motion/RegisteredObject.cxx | 51 ---------- .../stir/ArrayFilterUsingRealDFTWithPadding.h | 10 -- src/include/stir/ArrayFunction.h | 7 -- src/include/stir/BasicCoordinate.h | 4 - src/include/stir/BasicCoordinate.inl | 49 --------- src/include/stir/HighResWallClockTimer.h | 12 --- src/include/stir/IO/interfile.h | 7 -- src/include/stir/IO/read_data.h | 9 -- src/include/stir/IO/read_data.inl | 41 +------- src/include/stir/IO/write_data.h | 43 -------- src/include/stir/IO/write_data.inl | 96 +++--------------- src/include/stir/RegisteredObject.h | 14 +-- src/include/stir/RegisteredObject.inl | 2 - src/include/stir/array_index_functions.h | 7 +- src/include/stir/array_index_functions.inl | 99 ------------------- src/include/stir/assign.h | 15 --- src/include/stir/config/visualc.h | 7 +- src/include/stir/numerics/BSplines_coef.inl | 16 --- .../ForwardProjectorByBinUsingRayTracing.h | 24 ----- .../ForwardProjectorByBinUsingRayTracing.cxx | 64 ------------ src/recon_buildblock/RegisteredObject.cxx | 76 -------------- src/recon_buildblock/distributable.cxx | 13 --- .../distributableMPICacheEnabled.cxx | 13 --- src/test/test_Array.cxx | 4 +- 32 files changed, 28 insertions(+), 955 deletions(-) delete mode 100644 src/IO/RegisteredObject.cxx delete mode 100644 src/Shape_buildblock/RegisteredObject.cxx delete mode 100644 src/buildblock/RegisteredObject.cxx delete mode 100644 src/data_buildblock/RegisteredObject.cxx delete mode 100644 src/experimental/buildblock/RegisteredObject.cxx delete mode 100644 src/experimental/motion/RegisteredObject.cxx delete mode 100644 src/recon_buildblock/RegisteredObject.cxx diff --git a/src/IO/RegisteredObject.cxx b/src/IO/RegisteredObject.cxx deleted file mode 100644 index 7aadb89b2d..0000000000 --- a/src/IO/RegisteredObject.cxx +++ /dev/null @@ -1,53 +0,0 @@ -// -// -/* - Copyright (C) 2000- 2007, Hammersmith Imanet Ltd - This file is part of STIR. - - SPDX-License-Identifier: Apache-2.0 - - See STIR/LICENSE.txt for details -*/ -/*! - \file - - \brief Instantiations of stir::RegisteredObject - - Currently only necessary for VC 6.0 - - \author Kris Thielemans - -*/ - -// note: include has to be before #ifdef as it's in this file that -// __STIR_REGISTRY_NOT_INLINE is defined -#include "stir/RegisteredObject.h" - -#ifdef __STIR_REGISTRY_NOT_INLINE - -#pragma message("instantiating RegisteredObject") -#include "stir/IO/OutputFileFormat.h" -// add here all roots of hierarchies based on RegisteredObject - -START_NAMESPACE_STIR - -template -RegisteredObject::RegistryType& -RegisteredObject::registry () -{ - static RegistryType the_registry("None", 0); - return the_registry; -} - -# ifdef _MSC_VER -// prevent warning message on reinstantiation, -// note that we get a linking error if we don't have the explicit instantiation below -# pragma warning(disable:4660) -# endif - -template RegisteredObject; -// add here all roots of hierarchies based on RegisteredObject - -END_NAMESPACE_STIR - -#endif diff --git a/src/IO/interfile.cxx b/src/IO/interfile.cxx index 4233af724a..9659a4f67f 100644 --- a/src/IO/interfile.cxx +++ b/src/IO/interfile.cxx @@ -787,11 +787,7 @@ write_basic_interfile_image_header(const string& header_file_name, -#if !defined(_MSC_VER) || (_MSC_VER > 1300) template -#else -#define elemT float -#endif Succeeded write_basic_interfile(const string& filename, const Array<3,elemT>& image, @@ -810,9 +806,6 @@ write_basic_interfile(const string& filename, scale, byte_order); } -#if defined(_MSC_VER) && (_MSC_VER <= 1300) -#undef elemT -#endif template Succeeded write_basic_interfile(const string& filename, @@ -1653,8 +1646,6 @@ write_basic_interfile<>(const string& filename, const float scale, const ByteOrder byte_order); -#if !defined(_MSC_VER) || (_MSC_VER > 1300) - template Succeeded write_basic_interfile<>(const string& filename, @@ -1678,6 +1669,5 @@ write_basic_interfile<>(const string& filename, const NumericType output_type, const float scale, const ByteOrder byte_order); -#endif END_NAMESPACE_STIR diff --git a/src/Shape_buildblock/RegisteredObject.cxx b/src/Shape_buildblock/RegisteredObject.cxx deleted file mode 100644 index b4656d8dfb..0000000000 --- a/src/Shape_buildblock/RegisteredObject.cxx +++ /dev/null @@ -1,51 +0,0 @@ -// -// -/*! - - \file - \ingroup Shape3D - \brief instantiations of RegisteredObject for classes in Shape_buildblock - (only useful for Microsoft Visual Studio 6.0) - - \author Kris Thielemans - -*/ -/* - Copyright (C) 2000- 2009, Hammersmith Imanet Ltd - This file is part of STIR. - - SPDX-License-Identifier: Apache-2.0 - - See STIR/LICENSE.txt for details -*/ -// note: include has to be before #ifdef as it's in this file that -// __STIR_REGISTRY_NOT_INLINE is defined -#include "stir/RegisteredObject.h" - -#ifdef __STIR_REGISTRY_NOT_INLINE - -#pragma message("instantiating RegisteredObject") -#include "stir/Shape/Shape3D.h" - -// and others -START_NAMESPACE_STIR - -template -RegisteredObject::RegistryType& -RegisteredObject::registry () -{ - static RegistryType the_registry("None", 0); - return the_registry; -} - -# ifdef _MSC_VER -// prevent warning message on reinstantiation, -// note that we get a linking error if we don't have the explicit instantiation below -# pragma warning(disable:4660) -# endif - -template RegisteredObject; - -END_NAMESPACE_STIR - -#endif diff --git a/src/buildblock/ArrayFilterUsingRealDFTWithPadding.cxx b/src/buildblock/ArrayFilterUsingRealDFTWithPadding.cxx index 2f7c91c3ab..23a911a376 100644 --- a/src/buildblock/ArrayFilterUsingRealDFTWithPadding.cxx +++ b/src/buildblock/ArrayFilterUsingRealDFTWithPadding.cxx @@ -43,7 +43,6 @@ ArrayFilterUsingRealDFTWithPadding(const Array& real_filt error("Error constructing ArrayFilterUsingRealDFTWithPadding\n"); } -#ifndef __stir_ArrayFilterUsingRealDFTWithPadding_no_complex_kernel__ template ArrayFilterUsingRealDFTWithPadding:: ArrayFilterUsingRealDFTWithPadding(const Array Succeeded diff --git a/src/buildblock/IndexRange.cxx b/src/buildblock/IndexRange.cxx index aa9bac8f57..d2bf677ac9 100644 --- a/src/buildblock/IndexRange.cxx +++ b/src/buildblock/IndexRange.cxx @@ -76,23 +76,9 @@ IndexRange::get_regular_range( is_regular_range = regular_true; } -#if defined(_MSC_VER) && _MSC_VER<1200 - // bug in VC++ 5.0, needs explicit template args - min = join(base_type::get_min_index(), lower_dim_min); - max = join(base_type::get_max_index(), lower_dim_max); -#elif defined( __GNUC__) && (__GNUC__ == 2 && __GNUC_MINOR__ < 9) - // work around gcc 2.8.1 bug. - // It cannot call 'join' (it generates a bad mangled name for the function) - // So, we explicitly insert the code here - *min.begin() = base_type::get_min_index(); - copy(lower_dim_min.begin(), lower_dim_min.end(), min.begin()+1); - *max.begin() = base_type::get_max_index(); - copy(lower_dim_max.begin(), lower_dim_max.end(), max.begin()+1); -#else - // lines for good compilers... min = join(base_type::get_min_index(), lower_dim_min); max = join(base_type::get_max_index(), lower_dim_max); -#endif + return true; } @@ -147,14 +133,9 @@ IndexRange::get_regular_range( //is_regular_range = regular_true; } -#if defined(_MSC_VER) && _MSC_VER<1200 - // bug in VC++ 5.0, needs explicit template args - min = join(base_type::get_min_index(), lower_dim_min); - max = join(base_type::get_max_index(), lower_dim_max); -#else min = join(base_type::get_min_index(), lower_dim_min); max = join(base_type::get_max_index(), lower_dim_max); -#endif + return true; } @@ -207,14 +188,9 @@ IndexRange::get_regular_range( is_regular_range = regular_true; } -#if defined(_MSC_VER) && _MSC_VER<1200 - // bug in VC++ 5.0, needs explicit template args - min = join(base_type::get_min_index(), lower_dim_min); - max = join(base_type::get_max_index(), lower_dim_max); -#else min = join(base_type::get_min_index(), lower_dim_min); max = join(base_type::get_max_index(), lower_dim_max); -#endif + return true; } diff --git a/src/buildblock/RegisteredObject.cxx b/src/buildblock/RegisteredObject.cxx deleted file mode 100644 index ff73405ba0..0000000000 --- a/src/buildblock/RegisteredObject.cxx +++ /dev/null @@ -1,49 +0,0 @@ -// -// -/*! - - \file - \ingroup buildblock - \brief Instantiations of stir::RegisteredObject - - Currently only necessary for VC 6.0 - - \author Kris Thielemans - -*/ -/* - Copyright (C) 2000- 2009, Hammersmith Imanet Ltd - This file is part of STIR. - - SPDX-License-Identifier: Apache-2.0 - - See STIR/LICENSE.txt for details -*/ - -// note: include has to be before #ifdef as it's in this file that -// __STIR_REGISTRY_NOT_INLINE is defined -#include "stir/RegisteredObject.h" - -#ifdef __STIR_REGISTRY_NOT_INLINE -#pragma message("instantiating RegisteredObject > >") -#include "stir/DataProcessor.h" -#include "stir/DiscretisedDensity.h" - -// add here all roots of hierarchies based on RegisteredObject - -START_NAMESPACE_STIR - -template -RegisteredObject::RegistryType& -RegisteredObject::registry () -{ - static RegistryType the_registry("None", 0); - return the_registry; -} - -template RegisteredObject > >; -// add here all roots of hierarchies based on RegisteredObject - -END_NAMESPACE_STIR - -#endif diff --git a/src/data_buildblock/RegisteredObject.cxx b/src/data_buildblock/RegisteredObject.cxx deleted file mode 100644 index 0d2cf5b655..0000000000 --- a/src/data_buildblock/RegisteredObject.cxx +++ /dev/null @@ -1,53 +0,0 @@ -// -// -/* - Copyright (C) 2000- 2005, Hammersmith Imanet Ltd - This file is part of STIR. - - SPDX-License-Identifier: Apache-2.0 - - See STIR/LICENSE.txt for details -*/ -/*! - - \file - \ingroup buildblock - \brief instantiations of RegisteredObject for classes in recon_buildblock - (only useful for Microsoft Visual Studio 6.0) - - \author Kris Thielemans - -*/ - -// note: include has to be before #ifdef as it's in this file that -// __STIR_REGISTRY_NOT_INLINE is defined -#include "stir/RegisteredObject.h" -#ifdef __STIR_REGISTRY_NOT_INLINE - -#pragma message("instantiating RegisteredObject") -#include "stir/data/SinglesRates.h" - - -// and others -START_NAMESPACE_STIR - -template -RegisteredObject::RegistryType& -RegisteredObject::registry () -{ - static RegistryType the_registry("None", 0); - return the_registry; -} - -# ifdef _MSC_VER -// prevent warning message on reinstantiation, -// note that we get a linking error if we don't have the explicit instantiation below -# pragma warning(disable:4660) -# endif - - -template RegisteredObject; - -END_NAMESPACE_STIR - -#endif diff --git a/src/experimental/buildblock/RegisteredObject.cxx b/src/experimental/buildblock/RegisteredObject.cxx deleted file mode 100644 index 7eb44630a6..0000000000 --- a/src/experimental/buildblock/RegisteredObject.cxx +++ /dev/null @@ -1,52 +0,0 @@ -// -// -/* - Copyright (C) 2000- 2005, Hammersmith Imanet Ltd - This file is part of STIR. - - SPDX-License-Identifier: Apache-2.0 - - See STIR/LICENSE.txt for details -*/ -/*! - - \file - \ingroup buildblock - \brief instantiations of RegisteredObject for classes in recon_buildblock - (only useful for Microsoft Visual Studio 6.0) - - \author Kris Thielemans - -*/ - -// note: include has to be before #ifdef as it's in this file that -// __STIR_REGISTRY_NOT_INLINE is defined -#include "stir/RegisteredObject.h" -#ifdef __STIR_REGISTRY_NOT_INLINE - -//#pragma message("instantiating RegisteredObject< >") -// include - -// and others -START_NAMESPACE_STIR - -template -RegisteredObject::RegistryType& -RegisteredObject::registry () -{ - static RegistryType the_registry("None", 0); - return the_registry; -} - -# ifdef _MSC_VER -// prevent warning message on reinstantiation, -// note that we get a linking error if we don't have the explicit instantiation below -# pragma warning(disable:4660) -# endif - - -// template RegisteredObject< >; - -END_NAMESPACE_STIR - -#endif diff --git a/src/experimental/motion/RegisteredObject.cxx b/src/experimental/motion/RegisteredObject.cxx deleted file mode 100644 index c79654facc..0000000000 --- a/src/experimental/motion/RegisteredObject.cxx +++ /dev/null @@ -1,51 +0,0 @@ -// -// -/*! - - \file - - \brief Instantiations of RegisteredObject - - Currently only necessary for VC 6.0 - - \author Kris Thielemans - -*/ -/* - Copyright (C) 2000- 2004, Hammersmith Imanet Ltd - See STIR/LICENSE.txt for details -*/ -// note: include has to be before #ifdef as it's in this file that -// __STIR_REGISTRY_NOT_INLINE is defined -#include "stir/RegisteredObject.h" - -#ifdef __STIR_REGISTRY_NOT_INLINE - -#pragma message("instantiating RegisteredObject") -#include "stir_experimental/SinglesRates.h" -// add here all roots of hierarchies based on RegisteredObject - -#pragma message("instantiating RegisteredObject") -#include "stir_experimental/motion/RigidObject3DMotion.h" - - -START_NAMESPACE_STIR - -template -RegisteredObject::RegistryType& -RegisteredObject::registry () -{ - static RegistryType the_registry("None", 0); - return the_registry; -} - - -//template RegisteredObject; - -template RegisteredObject; -// add here all roots of hierarchies based on RegisteredObject - - -END_NAMESPACE_STIR - -#endif diff --git a/src/include/stir/ArrayFilterUsingRealDFTWithPadding.h b/src/include/stir/ArrayFilterUsingRealDFTWithPadding.h index 2343a6f6fb..66847076f8 100644 --- a/src/include/stir/ArrayFilterUsingRealDFTWithPadding.h +++ b/src/include/stir/ArrayFilterUsingRealDFTWithPadding.h @@ -23,14 +23,6 @@ #include "stir/IndexRange.h" #include -#if defined(_MSC_VER) && _MSC_VER<=1200 -/* VC 6.0 cannot overload the real and complex constructors - I just disable the complex version. - */ -#define __stir_ArrayFilterUsingRealDFTWithPadding_no_complex_kernel__ - -#endif - START_NAMESPACE_STIR class Succeeded; template class Array; @@ -64,14 +56,12 @@ class ArrayFilterUsingRealDFTWithPadding : */ ArrayFilterUsingRealDFTWithPadding(const Array& real_filter_kernel); -#ifndef __stir_ArrayFilterUsingRealDFTWithPadding_no_complex_kernel__ //! Construct the filter given the complex kernel coefficients /*! \see set_kernel(const Array >&) \warning Will call error() when sizes are not appropriate. \warning This function is disabled for VC 6.0 because of compiler limitations */ ArrayFilterUsingRealDFTWithPadding(const Array >& kernel_in_frequency_space); -#endif //! set the real kernel coefficients /* diff --git a/src/include/stir/ArrayFunction.h b/src/include/stir/ArrayFunction.h index 1241b41a05..47860ac0d7 100644 --- a/src/include/stir/ArrayFunction.h +++ b/src/include/stir/ArrayFunction.h @@ -212,13 +212,6 @@ apply_array_function_on_1st_index(Array& out_array, #define ActualFunctionObjectPtrIter shared_ptr > const* #endif -#ifdef BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION -// silly business for deficient compilers (including VC 6.0) -#define elemT float -#define FunctionObjectPtrIter ActualFunctionObjectPtrIter -#endif - - //! Apply a sequence of 1d array-function objects on every dimension of the input array /*! \ingroup Array The sequence of function object pointers is specified by iterators. There must be diff --git a/src/include/stir/BasicCoordinate.h b/src/include/stir/BasicCoordinate.h index 9b47e1dcf9..baa0eb83a1 100644 --- a/src/include/stir/BasicCoordinate.h +++ b/src/include/stir/BasicCoordinate.h @@ -103,12 +103,8 @@ template //! comparison inline bool operator==(const BasicCoordinate& c) const; -#if !defined(_MSC_VER) || _MSC_VER>1200 //! less-than (using lexical ordering) inline bool operator<(const BasicCoordinate& c) const; -#else - // needs to be a global function to have the overloading to work. sigh. -#endif // access to elements //! Return value at index \c t (which is 1-based) diff --git a/src/include/stir/BasicCoordinate.inl b/src/include/stir/BasicCoordinate.inl index b5a43fe82c..08173ef392 100644 --- a/src/include/stir/BasicCoordinate.inl +++ b/src/include/stir/BasicCoordinate.inl @@ -534,8 +534,6 @@ namespace detail (c1[2]==c2[2] && c1[3]1200 - // generic code template inline bool @@ -547,29 +545,9 @@ namespace detail c1[1] - inline - bool - coordinate_less_than_help(is_not_1d, - const BasicCoordinate<4, coordT>& c1, - const BasicCoordinate<4, coordT>& c2) - { - return - c1[1]1200 - // generic definition template bool @@ -580,33 +558,6 @@ operator<(const BasicCoordinate& c) const *this, c); } -#else - -// VC 6.0 cannot compile the above. So we list them one by one (sigh!) - -template -bool -inline operator<(const BasicCoordinate<1, coordT>& c1, const BasicCoordinate<1, coordT>& c) -{ - return detail::coordinate_less_than_help(detail::is_1d(), - c1, c); -} -#define DEFINE_OPERATOR_LESS(num_dimensions) \ -template \ -inline bool \ -operator<(const BasicCoordinate& c1,const BasicCoordinate& c)\ -{ \ - return detail::coordinate_less_than_help(detail::is_not_1d(), \ - c1, c); \ -} - -DEFINE_OPERATOR_LESS(2) -DEFINE_OPERATOR_LESS(3) -DEFINE_OPERATOR_LESS(4) -#undef DEFINE_OPERATOR_LESS - -#endif - END_NAMESPACE_STIR diff --git a/src/include/stir/HighResWallClockTimer.h b/src/include/stir/HighResWallClockTimer.h index eb1ce6a6bc..85fac013ee 100644 --- a/src/include/stir/HighResWallClockTimer.h +++ b/src/include/stir/HighResWallClockTimer.h @@ -46,18 +46,6 @@ #include -#if defined(_MSC_VER) && (_MSC_VER < 1100) && !defined(bool) - -enum bool -{ - false = 0, - true = (!false) -}; - -#define bool bool - -#endif - namespace stir { /*! \brief High-resolution timer diff --git a/src/include/stir/IO/interfile.h b/src/include/stir/IO/interfile.h index ce25959036..f3316cfb75 100644 --- a/src/include/stir/IO/interfile.h +++ b/src/include/stir/IO/interfile.h @@ -201,20 +201,13 @@ write_basic_interfile(const std::string&filename, compiler bug. (Otherwise, the float version is not instantiated for some reason). */ -#if !defined(_MSC_VER) || (_MSC_VER > 1300) template -#else -#define elemT float -#endif Succeeded write_basic_interfile(const std::string& filename, const Array<3,elemT>& image, const NumericType output_type = NumericType::FLOAT, const float scale= 0, const ByteOrder byte_order=ByteOrder::native); -#if defined(_MSC_VER) && (_MSC_VER <= 1300) -#undef elemT -#endif //! This outputs an Interfile header and data for a VoxelsOnCartesianGrid object /*! diff --git a/src/include/stir/IO/read_data.h b/src/include/stir/IO/read_data.h index 8989bb9128..70ec728f9f 100644 --- a/src/include/stir/IO/read_data.h +++ b/src/include/stir/IO/read_data.h @@ -26,14 +26,6 @@ class NumericType; template class NumericInfo; template class Array; -#if defined(_MSC_VER) && _MSC_VER==1200 -// VC 6.0 cannot compile this when the templates are declared first, -// and defined in the .inl -# define __STIR_WORKAROUND_TEMPLATES 1 -#endif - -#ifndef __STIR_WORKAROUND_TEMPLATES - /*! \ingroup Array_IO \brief Read the data of an Array from file. @@ -86,7 +78,6 @@ read_data(IStreamT& s, Array& data, NumericType type, ScaleT& scale, const ByteOrder byte_order=ByteOrder::native); -#endif END_NAMESPACE_STIR diff --git a/src/include/stir/IO/read_data.inl b/src/include/stir/IO/read_data.inl index 6e2c4e728e..3a82cb8f42 100644 --- a/src/include/stir/IO/read_data.inl +++ b/src/include/stir/IO/read_data.inl @@ -27,33 +27,6 @@ START_NAMESPACE_STIR -/* This file is made a bit more complicated because of various - work-arounds for older compilers. - - __STIR_WORKAROUND_TEMPLATES==1 - The first is probably specific for VC 6.0 where we need to define - parameters for byte_order etc. in these definitions. - In contrast, for the normal case, the defaults are in the .h file, - and cannot be repeated here. - I do this with some more preprocessor macros. Sigh. - - Note that write_data.inl has - __STIR_WORKAROUND_TEMPLATES==2 - for even more broken compilers. I didn't bother to put this in here. - You should be able to do this yourself (and then contribute the code). -*/ - -#ifndef __STIR_WORKAROUND_TEMPLATES -/* the normal case */ - -# define ___BYTEORDER_DEFAULT - -#else - -# define ___BYTEORDER_DEFAULT =ByteOrder::native - -#endif - namespace detail { /* Generic implementation of read_data(). See test_if_1d.h for info why we do this.*/ @@ -91,7 +64,7 @@ namespace detail template inline Succeeded read_data(IStreamT& s, Array& data, - const ByteOrder byte_order ___BYTEORDER_DEFAULT) + const ByteOrder byte_order ) { return detail::read_data_help(detail::test_if_1d(), @@ -104,7 +77,7 @@ inline Succeeded read_data(IStreamT& s, Array& data, NumericInfo input_type, ScaleT& scale_factor, - const ByteOrder byte_order ___BYTEORDER_DEFAULT) + const ByteOrder byte_order ) { if (typeid(InputType) == typeid(elemT)) { @@ -129,21 +102,16 @@ inline Succeeded read_data(IStreamT& s, Array& data, NumericType type, ScaleT& scale, - const ByteOrder byte_order ___BYTEORDER_DEFAULT) + const ByteOrder byte_order) { switch(type.id) { // define macro what to do with a specific NumericType -#if !defined(_MSC_VER) || _MSC_VER>1300 -#define TYPENAME typename -#else -#define TYPENAME -#endif #define CASE(NUMERICTYPE) \ case NUMERICTYPE : \ return \ read_data(s, data, \ - NumericInfo::type>(), \ + NumericInfo::type>(), \ scale, byte_order) // now list cases that we want @@ -158,7 +126,6 @@ read_data(IStreamT& s, CASE(NumericType::FLOAT); CASE(NumericType::DOUBLE); #undef CASE -#undef TYPENAME default: warning("read_data : type not yet supported\n, at line %d in file %s", __LINE__, __FILE__); diff --git a/src/include/stir/IO/write_data.h b/src/include/stir/IO/write_data.h index e0b2a752f6..4705cdbff2 100644 --- a/src/include/stir/IO/write_data.h +++ b/src/include/stir/IO/write_data.h @@ -25,25 +25,7 @@ class NumericType; template class NumericInfo; template class Array; -#if defined(_MSC_VER) && _MSC_VER==1200 -// VC 6.0 cannot compile this when the templates are declared first, -// and defined in the .inl -# define __STIR_WORKAROUND_TEMPLATES 1 -#endif - - -/* - If your still compiler cannot handle this code, you can try to put - -# define __STIR_WORKAROUND_TEMPLATES 2 - This enables a horrible work-around where the functions are - not templated in terms of num_dimensions. Instead, some - preprocessor trickery is used. - See the end of this file and write_data.inl for more info. -*/ - -#ifndef __STIR_WORKAROUND_TEMPLATES /*! \ingroup Array_IO \brief Write the data of an Array to file. @@ -143,36 +125,11 @@ write_data(OStreamT& s, const ByteOrder byte_order=ByteOrder::native, const bool can_corrupt_data=false); -#endif //__STIR_WORKAROUND_TEMPLATES - END_NAMESPACE_STIR -#if !defined(__STIR_WORKAROUND_TEMPLATES) || __STIR_WORKAROUND_TEMPLATES<2 - #include "stir/IO/write_data.inl" -#else - -#define num_dimensions 1 -#include "stir/IO/write_data.inl" -#undef num_dimensions -#define num_dimensions 2 -#include "stir/IO/write_data.inl" -#undef num_dimensions -#define num_dimensions 3 -#include "stir/IO/write_data.inl" -#undef num_dimensions -//#define num_dimensions 4 -//#include "stir/IO/write_data.inl" -//#undef num_dimensions - -#endif - -#ifdef __STIR_WORKAROUND_TEMPLATES -# undef __STIR_WORKAROUND_TEMPLATES -#endif - #endif diff --git a/src/include/stir/IO/write_data.inl b/src/include/stir/IO/write_data.inl index b97ce0a64d..7757f3a4cd 100644 --- a/src/include/stir/IO/write_data.inl +++ b/src/include/stir/IO/write_data.inl @@ -27,63 +27,12 @@ START_NAMESPACE_STIR -/* This file is made a bit more complicated because of various - work-arounds for older compilers. - - __STIR_WORKAROUND_TEMPLATES==1 - The first is probably specific for VC 6.0 where we need to define - parameters for byte_order etc. in these definitions. - In contrast, for the normal case, the defaults are in the .h file, - and cannot be repeated here. - I do this with some more preprocessor macros. Sigh. - - __STIR_WORKAROUND_TEMPLATES==2 - The 2nd work-around no longer templates in num_dimensions. - For the least amount of pain, we also make the inlines here serve as - declarations, so use the first work-around for this. -*/ - -#ifndef __STIR_WORKAROUND_TEMPLATES -/* the normal case */ - -# define ___BYTEORDER_DEFAULT -# define ___CAN_CORRUPT_DATA_DEFAULT -# define INT_NUM_DIMENSIONS int num_dimensions, - -#else - -# define ___BYTEORDER_DEFAULT =ByteOrder::native -# define ___CAN_CORRUPT_DATA_DEFAULT =false - -# if __STIR_WORKAROUND_TEMPLATES==1 - -# define INT_NUM_DIMENSIONS int num_dimensions, - /* This case is for VC 6.0. - Note that the order of the template arguments is important for VC 6.0. - The "int num_dimensions" template argument has to be first in the - template list, otherwise it chokes on it. - */ -# else -/* horrible work-around for very deficient compilers - We disable the num_dimensions template, and replace it by #defines. -*/ - -# ifndef num_dimensions -# error num_dimensions should be defined -# endif - -# define INT_NUM_DIMENSIONS -# endif -#endif - namespace detail { /* Generic implementation of write_data_with_fixed_scale_factor(). See test_if_1d.h for info why we do this this way. */ -#if !defined(__STIR_WORKAROUND_TEMPLATES) || __STIR_WORKAROUND_TEMPLATES<2 || num_dimensions!=1 - - template < INT_NUM_DIMENSIONS class OStreamT, + template < int num_dimensions, class OStreamT, class elemT, class OutputType, class ScaleT> inline Succeeded write_data_with_fixed_scale_factor_help( @@ -94,7 +43,7 @@ namespace detail const ByteOrder byte_order, const bool can_corrupt_data) { - for (typename Array::const_iterator iter= data.begin(); + for (auto iter= data.begin(); iter != data.end(); ++iter) { @@ -106,9 +55,7 @@ namespace detail } return Succeeded::yes; } -#endif -#if !defined(__STIR_WORKAROUND_TEMPLATES) || __STIR_WORKAROUND_TEMPLATES<2 || num_dimensions==1 // specialisation for 1D case template inline Succeeded @@ -124,7 +71,7 @@ namespace detail scale_factor!=1) { ScaleT new_scale_factor=scale_factor; - Array<1,OutputType> data_tmp = + auto data_tmp = convert_array(new_scale_factor, data, NumericInfo()); if (std::fabs(new_scale_factor-scale_factor)> scale_factor*.001) return Succeeded::no; @@ -137,18 +84,17 @@ namespace detail write_data_1d(s, data, byte_order, can_corrupt_data); } } -#endif } // end of namespace detail -template < INT_NUM_DIMENSIONS class OStreamT, +template < int num_dimensions, class OStreamT, class elemT, class OutputType, class ScaleT> Succeeded write_data_with_fixed_scale_factor(OStreamT& s, const Array& data, NumericInfo output_type, const ScaleT scale_factor, - const ByteOrder byte_order ___BYTEORDER_DEFAULT, - const bool can_corrupt_data ___CAN_CORRUPT_DATA_DEFAULT) + const ByteOrder byte_order, + const bool can_corrupt_data) { return detail:: @@ -160,14 +106,14 @@ write_data_with_fixed_scale_factor(OStreamT& s, const Array Succeeded write_data(OStreamT& s, const Array& data, NumericInfo output_type, ScaleT& scale_factor, - const ByteOrder byte_order ___BYTEORDER_DEFAULT, - const bool can_corrupt_data ___CAN_CORRUPT_DATA_DEFAULT) + const ByteOrder byte_order, + const bool can_corrupt_data) { find_scale_factor(scale_factor, data, @@ -178,12 +124,12 @@ write_data(OStreamT& s, const Array& data, can_corrupt_data); } -template < INT_NUM_DIMENSIONS class OStreamT, +template < int num_dimensions, class OStreamT, class elemT> inline Succeeded write_data(OStreamT& s, const Array& data, - const ByteOrder byte_order ___BYTEORDER_DEFAULT, - const bool can_corrupt_data ___CAN_CORRUPT_DATA_DEFAULT) + const ByteOrder byte_order, + const bool can_corrupt_data) { return write_data_with_fixed_scale_factor(s, data, NumericInfo(), @@ -191,14 +137,14 @@ write_data(OStreamT& s, const Array& data, can_corrupt_data); } -template < INT_NUM_DIMENSIONS class OStreamT, +template < int num_dimensions, class OStreamT, class elemT, class ScaleT> Succeeded write_data(OStreamT& s, const Array& data, NumericType type, ScaleT& scale, - const ByteOrder byte_order ___BYTEORDER_DEFAULT, - const bool can_corrupt_data ___CAN_CORRUPT_DATA_DEFAULT) + const ByteOrder byte_order, + const bool can_corrupt_data) { if (NumericInfo().type_id() == type) { @@ -211,16 +157,11 @@ write_data(OStreamT& s, switch(type.id) { // define macro what to do with a specific NumericType -#if !defined(_MSC_VER) || _MSC_VER>1300 -#define TYPENAME typename -#else -#define TYPENAME -#endif #define CASE(NUMERICTYPE) \ case NUMERICTYPE : \ return \ write_data(s, data, \ - NumericInfo::type>(), \ + NumericInfo::type>(), \ scale, byte_order, can_corrupt_data) // now list cases that we want @@ -235,7 +176,6 @@ write_data(OStreamT& s, CASE(NumericType::FLOAT); CASE(NumericType::DOUBLE); #undef CASE -#undef TYPENAME default: warning("write_data : type not yet supported\n, at line %d in file %s", __LINE__, __FILE__); @@ -245,9 +185,5 @@ write_data(OStreamT& s, } -#undef ___BYTEORDER_DEFAULT -#undef ___CAN_CORRUPT_DATA_DEFAULT -#undef INT_NUM_DIMENSIONS - END_NAMESPACE_STIR diff --git a/src/include/stir/RegisteredObject.h b/src/include/stir/RegisteredObject.h index 954d9a6ebe..1ad307917d 100644 --- a/src/include/stir/RegisteredObject.h +++ b/src/include/stir/RegisteredObject.h @@ -87,11 +87,6 @@ START_NAMESPACE_STIR for the default key (with a 0 factory). This is inappropriate in some cases. - \warning old versions of Visual C++ cannot inline the registry() function. As a result, - all possible instantiations of the RegisteredObject template have to be - defined in RegisteredObject.cxx file(s). You will have link errors if - you forgot to do this. - \par Limitation: In the previous (including STIR 4.x) version of this hierarchy, ParsingObject wasn't at the @@ -132,19 +127,12 @@ class RegisteredObject : public RegisteredObjectBase //! The type of the registry typedef FactoryRegistry RegistryType; -#if defined(_MSC_VER) && _MSC_VER<=1300 -# define __STIR_REGISTRY_NOT_INLINE -#endif - //! Static function returning the registry /*! \warning This function is non inline when using Visual C++ 6.0 because of a compiler limitation. This means that when using this compiler, RegisteredObject will need explicit instantiations for all derived classes. */ -#ifndef __STIR_REGISTRY_NOT_INLINE - inline -#endif - static RegistryType& registry(); + inline static RegistryType& registry(); }; diff --git a/src/include/stir/RegisteredObject.inl b/src/include/stir/RegisteredObject.inl index b6502bb475..8b879638bd 100644 --- a/src/include/stir/RegisteredObject.inl +++ b/src/include/stir/RegisteredObject.inl @@ -29,7 +29,6 @@ RegisteredObject::RegisteredObject() {} -#ifndef __STIR_REGISTRY_NOT_INLINE template typename RegisteredObject::RegistryType& RegisteredObject::registry () @@ -37,7 +36,6 @@ RegisteredObject::registry () static RegistryType the_registry("None", 0); return the_registry; } -#endif template Root* diff --git a/src/include/stir/array_index_functions.h b/src/include/stir/array_index_functions.h index b742f67084..ead839e496 100644 --- a/src/include/stir/array_index_functions.h +++ b/src/include/stir/array_index_functions.h @@ -27,10 +27,7 @@ #include "stir/BasicCoordinate.h" START_NAMESPACE_STIR - -#if !defined(_MSC_VER) || _MSC_VER>1200 -// VC 6.0 needs ugly work-arounds. We'll put these in the .inl file - + /* \ingroup Array \name Functions for writing generic code with indexing of multi-dimensional arrays */ @@ -83,8 +80,6 @@ next(BasicCoordinate& indices, const Array& a); //@} -#endif // end of VC 6.0 conditional - END_NAMESPACE_STIR diff --git a/src/include/stir/array_index_functions.inl b/src/include/stir/array_index_functions.inl index f1e2313651..eef53e466c 100644 --- a/src/include/stir/array_index_functions.inl +++ b/src/include/stir/array_index_functions.inl @@ -110,7 +110,6 @@ get_min_indices(const Array& a) { return detail::get_min_indices_help(detail::test_if_1d(), a); } -#if !defined(_MSC_VER) || _MSC_VER>1200 template inline @@ -144,102 +143,4 @@ get(const Array& a, const BasicCoordinate<1,int> &c) return a[c[1]]; } - -#else - -/* terrible code for VC 6.0 - Stop reading now unless you have that compiler (or something else severely broken). - This code works right now, (September 2004) but will no longer be supported in later versions. - - We need to list all dimensions explicitly. Sigh. - I do this with macros to avoid too much repetition. - - Aside from that, the actual code really should be the same. -*/ - -#define GET(num_dimensions, num_dimensions2) \ -template \ -inline \ -const Array& \ -get(const Array& a, const BasicCoordinate &c) \ -{ \ - return get(a[c[1]],cut_first_dimension(c)); \ -} - -#define GET_EQUAL_DIM(num_dimensions) \ -template \ -inline \ -const elemT& \ -get(const Array& a, const BasicCoordinate &c) \ -{ \ - return a[c]; \ -} - -#define GET_1D(num_dimensions) \ -template \ -inline \ -const Array& \ -get(const Array& a, const BasicCoordinate<1,int> &c) \ -{ \ - return a[c[1]]; \ -} - -GET_1D(2) -GET_1D(3) -GET_EQUAL_DIM(1) -GET_EQUAL_DIM(2) -GET_EQUAL_DIM(3) -GET(3,2) - -#undef GET_1D -#undef GET_EQUAL_DIM -#undef GET - - -#define DEFINE_NEXT_1D(num_dimensions2) \ -template \ -inline bool \ -next(BasicCoordinate<1, int>& index, \ - const Array& a) \ -{ if (a.get_min_index()>a.get_max_index())\ - return false;\ - assert(index[1]>=a.get_min_index()); \ - assert(index[1]<=a.get_max_index()); \ - index[1]++;\ - return index[1]<=a.get_max_index();\ -} - -#define DEFINE_NEXT(num_dimensions, num_dimensions2) \ -template \ -inline bool \ -next(BasicCoordinate& index, \ - const Array& a) \ -{\ - if (a.get_min_index()>a.get_max_index()) \ - return false; \ - BasicCoordinate upper_index= cut_last_dimension(index);\ - assert(index[num_dimensions]>=get(a,upper_index).get_min_index());\ - assert(index[num_dimensions]<=get(a,upper_index).get_max_index());\ - index[num_dimensions]++;\ - if (index[num_dimensions]<=get(a,upper_index).get_max_index())\ - return true;\ - if (!next(upper_index, a))\ - return false;\ - index=join(upper_index, get(a,upper_index).get_min_index());\ -} - - - -DEFINE_NEXT_1D(1) -DEFINE_NEXT_1D(2) -DEFINE_NEXT_1D(3) -DEFINE_NEXT(2,2) -DEFINE_NEXT(2,3) -DEFINE_NEXT(3,3) - -#undef DEFINE_NEXT_1D -#undef DEFINE_NEXT - -#endif // end of VC 6.0 code - END_NAMESPACE_STIR diff --git a/src/include/stir/assign.h b/src/include/stir/assign.h index a62edd77ea..9a99390c7b 100644 --- a/src/include/stir/assign.h +++ b/src/include/stir/assign.h @@ -43,20 +43,6 @@ START_NAMESPACE_STIR lead to surprising conversions. */ //@{ -// TODO hopefully next ifdef is not necessary. Otherwise we need to have more for ints etc -#if defined(_MSC_VER) && _MSC_VER<=1300 -inline -void assign(double& x, const double y) -{ - x=y; -} - -static inline -void assign(float& x, const float y) -{ - x=y; -} -#else template inline @@ -64,7 +50,6 @@ template { x=y; } -#endif template inline diff --git a/src/include/stir/config/visualc.h b/src/include/stir/config/visualc.h index 82c4fd36c8..c122920569 100644 --- a/src/include/stir/config/visualc.h +++ b/src/include/stir/config/visualc.h @@ -27,14 +27,11 @@ This include file defines a few macros and en/disables pragmas specific to Microsoft Visual C++. - It is included by sitr/common.h. You should never include it directly. + It is included by stir/common.h. You should never include it directly. */ #if defined(_MSC_VER) && _MSC_VER<=1300 -// do this only up to VC 7.0 -#define STIR_NO_COVARIANT_RETURN_TYPES -#define STIR_SPEED_UP_STD_COPY -#define STIR_ENABLE_FOR_SCOPE_WORKAROUND +#error Compiler no longer supported #endif #if defined(_MSC_VER) diff --git a/src/include/stir/numerics/BSplines_coef.inl b/src/include/stir/numerics/BSplines_coef.inl index 9f913099d1..107678e0e5 100644 --- a/src/include/stir/numerics/BSplines_coef.inl +++ b/src/include/stir/numerics/BSplines_coef.inl @@ -26,20 +26,12 @@ namespace BSpline { { template inline -#if defined(_MSC_VER) && _MSC_VER<=1300 - float -#else typename std::iterator_traits::value_type -#endif cplus0(const IterT input_begin_iterator, const IterT input_end_iterator, const constantsT z1, const constantsT precision, const bool periodicity) { -#if defined(_MSC_VER) && _MSC_VER<=1300 - typedef float out_elemT; -#else typedef typename std::iterator_traits::value_type out_elemT; -#endif const int input_size = static_cast(input_end_iterator - input_begin_iterator); // assert(input_size>BSplines_coef_vector.size()); @@ -68,15 +60,7 @@ namespace BSpline { const constantsT z1, const constantsT z2, const constantsT lamda) { -#if defined(_MSC_VER) && _MSC_VER<=1300 - typedef float out_elemT; - // typedef float in_elemT; - //typedef typename _Val_Type(c_begin_iterator) out_elemT; - //typedef typename _Val_Type(c_begin_iterator) in_elemT; -#else typedef typename std::iterator_traits::value_type out_elemT; - // typedef typename std::iterator_traits::value_type in_elemT; -#endif /* cplus(c_end_iterator-c_begin_iterator, 0) diff --git a/src/include/stir/recon_buildblock/ForwardProjectorByBinUsingRayTracing.h b/src/include/stir/recon_buildblock/ForwardProjectorByBinUsingRayTracing.h index bc07b2e94b..aaa376df66 100644 --- a/src/include/stir/recon_buildblock/ForwardProjectorByBinUsingRayTracing.h +++ b/src/include/stir/recon_buildblock/ForwardProjectorByBinUsingRayTracing.h @@ -203,17 +203,6 @@ forward_project_view_2D(Viewgram & pos_view, const VoxelsOnCartesianGrid & image, const int min_axial_pos_num, const int max_axial_pos_num, const int min_tangential_pos_num, const int max_tangential_pos_num) const; -#if defined(_MSC_VER) && _MSC_VER<1310 -/* VC 6.0 (and 7.0 ?) cannot use the normal syntax unfortunately - See also http://www.boost.org/more/microsoft_vcpp.html - - So, we forget about the template in this case. - Sigh -*/ -#define STIR_SIDDON_NO_TEMPLATE -#endif - -#ifndef STIR_SIDDON_NO_TEMPLATE //! The actual implementation of Siddon's algorithm /*! \return true if the LOR intersected the image, i.e. of Projptr (potentially) changed */ template @@ -227,19 +216,6 @@ forward_project_view_2D(Viewgram & pos_view, const float axial_pos_to_z_offset, const float norm_factor, const bool restrict_to_cylindrical_FOV); -#else - static bool - proj_Siddon(int symmetry_type, - Array<4,float> &Projptr, const VoxelsOnCartesianGrid &, - const shared_ptr proj_data_info_ptr, - const float cphi, const float sphi, const float delta, - const float s_in_mm, - const float R, const int min_ax_pos_num, const int max_ax_pos_num, const float offset, - const int num_planes_per_axial_pos, - const float axial_pos_to_z_offset, - const float norm_factor, - const bool restrict_to_cylindrical_FOV); -#endif virtual void set_defaults(); virtual void initialise_keymap(); diff --git a/src/recon_buildblock/ForwardProjectorByBinUsingRayTracing.cxx b/src/recon_buildblock/ForwardProjectorByBinUsingRayTracing.cxx index c2e7d2417c..c60a5657c3 100644 --- a/src/recon_buildblock/ForwardProjectorByBinUsingRayTracing.cxx +++ b/src/recon_buildblock/ForwardProjectorByBinUsingRayTracing.cxx @@ -516,11 +516,7 @@ forward_project_all_symmetries( /* Here tang_pos_num=0 and phi=0 or 45*/ if (proj_Siddon -#ifndef STIR_SIDDON_NO_TEMPLATE < 2>( -#else - (2, -#endif Projall, image, proj_data_info_sptr, cphi, sphi, delta + D, 0, R,min_ax_pos_num, max_ax_pos_num, offset, num_planes_per_axial_pos, axial_pos_to_z_offset, @@ -541,11 +537,7 @@ forward_project_all_symmetries( bin.tangential_pos_num() = tang_pos_num; const float s_in_mm = proj_data_info_sptr->get_s(bin); if (proj_Siddon -#ifndef STIR_SIDDON_NO_TEMPLATE <1>( -#else - (1, -#endif Projall, image, proj_data_info_sptr, cphi, sphi, delta + D, s_in_mm, R,min_ax_pos_num, max_ax_pos_num, offset, num_planes_per_axial_pos, axial_pos_to_z_offset, @@ -578,11 +570,7 @@ forward_project_all_symmetries( { /* Here tang_pos_num==0 and phi!=k*45 */ if (proj_Siddon -#ifndef STIR_SIDDON_NO_TEMPLATE <4>( -#else - (4, -#endif Projall, image, proj_data_info_sptr, cphi, sphi, delta + D, 0, R,min_ax_pos_num, max_ax_pos_num, offset, num_planes_per_axial_pos, axial_pos_to_z_offset , @@ -608,11 +596,7 @@ forward_project_all_symmetries( const float s_in_mm = proj_data_info_sptr->get_s(bin); if (proj_Siddon -#ifndef STIR_SIDDON_NO_TEMPLATE <3>( -#else - (3, -#endif Projall, image, proj_data_info_sptr, cphi, sphi, delta + D, s_in_mm, R,min_ax_pos_num, max_ax_pos_num, offset, num_planes_per_axial_pos, axial_pos_to_z_offset , @@ -925,11 +909,7 @@ void ForwardProjectorByBinUsingRayTracing::forward_project_2D(Sinogram &s /* Here tang_pos_num=0 and phi=0 or 45*/ if (proj_Siddon -#ifndef STIR_SIDDON_NO_TEMPLATE <2>( -#else - (2, -#endif Projall,image,proj_data_cyl_ptr, cphi, sphi, delta + D, 0, R,min_ax_pos, max_ax_pos, offset, num_planes_per_axial_pos, 0, @@ -943,11 +923,7 @@ void ForwardProjectorByBinUsingRayTracing::forward_project_2D(Sinogram &s /* Now tang_pos_num!=0 and phi=0 or 45 */ for (tang_pos_num = min_tang_pos_num_in_loop; tang_pos_num <= max_abs_tangential_pos_num; tang_pos_num++) { if (proj_Siddon -#ifndef STIR_SIDDON_NO_TEMPLATE <1>( -#else - (1, -#endif Projall,image,proj_data_cyl_ptr, cphi, sphi, delta + D, tang_pos_num, R,min_ax_pos,max_ax_pos, offset, num_planes_per_axial_pos, 0, @@ -968,11 +944,7 @@ void ForwardProjectorByBinUsingRayTracing::forward_project_2D(Sinogram &s for (D = 0; D < C; D++) { /* Here tang_pos_num==0 and phi!=k*45 */ if (proj_Siddon -#ifndef STIR_SIDDON_NO_TEMPLATE <4>( -#else - (4, -#endif Projall,image,proj_data_cyl_ptr, cphi, sphi, delta + D, 0, R,min_ax_pos,max_ax_pos, offset, num_planes_per_axial_pos, 0, @@ -989,11 +961,7 @@ void ForwardProjectorByBinUsingRayTracing::forward_project_2D(Sinogram &s /* Here tang_pos_num!=0 and phi!=k*45. */ for (tang_pos_num = min_tang_pos_num_in_loop; tang_pos_num <= max_abs_tangential_pos_num; tang_pos_num++) { if (proj_Siddon -#ifndef STIR_SIDDON_NO_TEMPLATE <3>( -#else - (3, -#endif Projall,image,proj_data_cyl_ptr, cphi, sphi, delta + D, tang_pos_num, R,min_ax_pos, max_ax_pos, offset, num_planes_per_axial_pos, 0, @@ -1201,11 +1169,7 @@ forward_project_all_symmetries_2D(Viewgram & pos_view, /* Here tang_pos_num=0 and phi=0 or 45*/ { if (proj_Siddon -#ifndef STIR_SIDDON_NO_TEMPLATE <2>( -#else - (2, -#endif Projall, image, proj_data_info_sptr, cphi, sphi, delta + D, 0, R,min_axial_pos_num, max_axial_pos_num, 0.F/*==offset*/, num_planes_per_axial_pos, axial_pos_to_z_offset , @@ -1223,11 +1187,7 @@ forward_project_all_symmetries_2D(Viewgram & pos_view, if (num_planes_per_axial_pos == 2) { if (proj_Siddon -#ifndef STIR_SIDDON_NO_TEMPLATE <2>( -#else - (2, -#endif Projall2, image, proj_data_info_sptr, cphi, sphi, delta + D, 0, R, min_axial_pos_num, max_axial_pos_num+1, -0.5F/*==offset*/, num_planes_per_axial_pos, axial_pos_to_z_offset , @@ -1251,11 +1211,7 @@ forward_project_all_symmetries_2D(Viewgram & pos_view, { if (proj_Siddon -#ifndef STIR_SIDDON_NO_TEMPLATE <1>( -#else - (1, -#endif Projall, image, proj_data_info_sptr, cphi, sphi, delta + D, s_in_mm, R,min_axial_pos_num, max_axial_pos_num, 0.F, num_planes_per_axial_pos, axial_pos_to_z_offset, @@ -1279,11 +1235,7 @@ forward_project_all_symmetries_2D(Viewgram & pos_view, if (num_planes_per_axial_pos == 2) { if (proj_Siddon -#ifndef STIR_SIDDON_NO_TEMPLATE <1>( -#else - (1, -#endif Projall2, image, proj_data_info_sptr, cphi, sphi, delta + D, s_in_mm, R,min_axial_pos_num, max_axial_pos_num+1, -0.5F, num_planes_per_axial_pos, axial_pos_to_z_offset, @@ -1317,11 +1269,7 @@ forward_project_all_symmetries_2D(Viewgram & pos_view, /* Here tang_pos_num==0 and phi!=k*45 */ { if (proj_Siddon -#ifndef STIR_SIDDON_NO_TEMPLATE <4>( -#else - (4, -#endif Projall, image, proj_data_info_sptr, cphi, sphi, delta + D, 0, R,min_axial_pos_num, max_axial_pos_num, 0.F, num_planes_per_axial_pos, axial_pos_to_z_offset , @@ -1340,11 +1288,7 @@ forward_project_all_symmetries_2D(Viewgram & pos_view, if (num_planes_per_axial_pos == 2) { if (proj_Siddon -#ifndef STIR_SIDDON_NO_TEMPLATE <4>( -#else - (4, -#endif Projall2, image, proj_data_info_sptr, cphi, sphi, delta + D, 0, R,min_axial_pos_num, max_axial_pos_num, -0.5F, num_planes_per_axial_pos, axial_pos_to_z_offset , @@ -1369,11 +1313,7 @@ forward_project_all_symmetries_2D(Viewgram & pos_view, { if (proj_Siddon -#ifndef STIR_SIDDON_NO_TEMPLATE <3>( -#else - (3, -#endif Projall, image, proj_data_info_sptr, cphi, sphi, delta + D, s_in_mm, R,min_axial_pos_num, max_axial_pos_num, 0.F, num_planes_per_axial_pos, axial_pos_to_z_offset , @@ -1401,11 +1341,7 @@ forward_project_all_symmetries_2D(Viewgram & pos_view, if (num_planes_per_axial_pos == 2) { if (proj_Siddon -#ifndef STIR_SIDDON_NO_TEMPLATE <3>( -#else - (3, -#endif Projall2, image, proj_data_info_sptr, cphi, sphi, delta + D, s_in_mm, R,min_axial_pos_num, max_axial_pos_num+1, -0.5F, num_planes_per_axial_pos, axial_pos_to_z_offset , diff --git a/src/recon_buildblock/RegisteredObject.cxx b/src/recon_buildblock/RegisteredObject.cxx deleted file mode 100644 index d66f95706e..0000000000 --- a/src/recon_buildblock/RegisteredObject.cxx +++ /dev/null @@ -1,76 +0,0 @@ -// -// -/*! - - \file - \ingroup recon_buildblock - \brief instantiations of stir::RegisteredObject for classes in recon_buildblock - (only useful for Microsoft Visual Studio 6.0) - - \author Kris Thielemans - -*/ -/* - Copyright (C) 2000- 2009, Hammersmith Imanet Ltd - This file is part of STIR. - - SPDX-License-Identifier: Apache-2.0 - - See STIR/LICENSE.txt for details -*/ -// note: include has to be before #ifdef as it's in this file that -// __STIR_REGISTRY_NOT_INLINE is defined -#include "stir/RegisteredObject.h" - -#ifdef __STIR_REGISTRY_NOT_INLINE - -#pragma message("instantiating RegisteredObject >") -#include "stir/recon_buildblock/GeneralisedPrior.h" - -#pragma message("instantiating RegisteredObject") -#include "stir/recon_buildblock/ProjMatrixByBin.h" - -#pragma message("instantiating RegisteredObject") -#include "stir/recon_buildblock/ProjectorByBinPair.h" - -#pragma message("instantiating RegisteredObject") -#include "stir/recon_buildblock/ForwardProjectorByBin.h" - -#pragma message("instantiating RegisteredObject") -#include "stir/recon_buildblock/BackProjectorByBin.h" - -#pragma message("instantiating RegisteredObject") -#include "stir/recon_buildblock/BinNormalisation.h" - -// and others -START_NAMESPACE_STIR - -template -RegisteredObject::RegistryType& -RegisteredObject::registry () -{ - static RegistryType the_registry("None", 0); - return the_registry; -} - -# ifdef _MSC_VER -// prevent warning message on reinstantiation, -// note that we get a linking error if we don't have the explicit instantiation below -# pragma warning(disable:4660) -# endif - - -template RegisteredObject >; - -template RegisteredObject; - -template RegisteredObject; - -template RegisteredObject; - -template RegisteredObject; - -template RegisteredObject; -END_NAMESPACE_STIR - -#endif diff --git a/src/recon_buildblock/distributable.cxx b/src/recon_buildblock/distributable.cxx index 1314b6c21c..b0285cc15d 100644 --- a/src/recon_buildblock/distributable.cxx +++ b/src/recon_buildblock/distributable.cxx @@ -181,15 +181,9 @@ void get_viewgrams(shared_ptr >& y, #ifdef STIR_OPENMP #pragma omp critical(ADDSINO) #endif -#if !defined(_MSC_VER) || _MSC_VER>1300 additive_binwise_correction_viewgrams.reset( new RelatedViewgrams (binwise_correction->get_related_viewgrams(view_segment_num, symmetries_ptr, false, timing_pos_num))); -#else - RelatedViewgrams tmp(binwise_correction-> - get_related_viewgrams(view_segment_num, symmetries_ptr, false, timing_pos_num)); - additive_binwise_correction_viewgrams.reset(new RelatedViewgrams(tmp)); -#endif } if (read_from_proj_dat) @@ -197,15 +191,8 @@ void get_viewgrams(shared_ptr >& y, #ifdef STIR_OPENMP #pragma omp critical(VIEW) #endif -#if !defined(_MSC_VER) || _MSC_VER>1300 y.reset(new RelatedViewgrams (proj_dat_ptr->get_related_viewgrams(view_segment_num, symmetries_ptr, false, timing_pos_num))); -#else - // workaround VC++ 6.0 bug - RelatedViewgrams tmp(proj_dat_ptr-> - get_related_viewgrams(view_segment_num, symmetries_ptr, false, timing_pos_num)); - y.reset(new RelatedViewgrams(tmp)); -#endif } else { diff --git a/src/recon_buildblock/distributableMPICacheEnabled.cxx b/src/recon_buildblock/distributableMPICacheEnabled.cxx index 8a650310a5..e96107ef5c 100644 --- a/src/recon_buildblock/distributableMPICacheEnabled.cxx +++ b/src/recon_buildblock/distributableMPICacheEnabled.cxx @@ -90,28 +90,15 @@ void get_viewgrams(shared_ptr >& y, { if (!is_null_ptr(binwise_correction)) { -#if !defined(_MSC_VER) || _MSC_VER>1300 additive_binwise_correction_viewgrams.reset( new RelatedViewgrams (binwise_correction->get_related_viewgrams(view_segment_num, symmetries_ptr, false, timing_pos_num))); -#else - RelatedViewgrams tmp(binwise_correction-> - get_related_viewgrams(view_segment_num, symmetries_ptr, false, timing_pos_num)); - additive_binwise_correction_viewgrams = new RelatedViewgrams(tmp); -#endif } if (read_from_proj_dat) { -#if !defined(_MSC_VER) || _MSC_VER>1300 y.reset(new RelatedViewgrams (proj_dat_ptr->get_related_viewgrams(view_segment_num, symmetries_ptr, false, timing_pos_num))); -#else - // workaround VC++ 6.0 bug - RelatedViewgrams tmp(proj_dat_ptr-> - get_related_viewgrams(view_segment_num, symmetries_ptr, false, timing_pos_num)); - y = new RelatedViewgrams(tmp); -#endif } else { diff --git a/src/test/test_Array.cxx b/src/test/test_Array.cxx index a2137f10f5..ae33d56760 100644 --- a/src/test/test_Array.cxx +++ b/src/test/test_Array.cxx @@ -571,13 +571,11 @@ ArrayTests::run_tests() check_if_zero( test3.sum() - 2*tmp2.sum() - tmp.sum(), "test operator-(float)"); } -#if !defined(_MSC_VER) || _MSC_VER>1300 - // VC 6.0 cannot compile this in_place_apply_function(test3ter, bind(plus(), std::placeholders::_1, 4.F)); test3quat += 4.F; check_if_equal(test3quat , test3ter, "test in_place_apply_function and operator+=(NUMBER)"); -#endif + // size_all with irregular range { const IndexRange<3> range(Coordinate3D(-1,1,4),Coordinate3D(1,2,6)); From e99b3b31676e3b09971c4206dbc7f61953ca4bf4 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Sun, 28 Jan 2024 22:05:59 +0000 Subject: [PATCH 492/509] remove STIR_NO_MUTABLE and workarounds --- src/buildblock/IndexRange.cxx | 117 ------------------ src/include/stir/IndexRange.h | 15 +-- src/include/stir/IndexRange.inl | 27 ---- src/include/stir/VectorWithOffset.h | 13 +- src/include/stir/VectorWithOffset.inl | 10 +- .../stir/recon_buildblock/ProjMatrixByBin.h | 23 +--- .../stir/recon_buildblock/ProjMatrixByBin.inl | 4 +- .../recon_buildblock/ProjMatrixByDensel.h | 15 +-- .../recon_buildblock/ProjMatrixByDensel.inl | 4 +- src/recon_buildblock/ProjMatrixByBin.cxx | 11 +- 10 files changed, 17 insertions(+), 222 deletions(-) diff --git a/src/buildblock/IndexRange.cxx b/src/buildblock/IndexRange.cxx index d2bf677ac9..34b1cf3ab9 100644 --- a/src/buildblock/IndexRange.cxx +++ b/src/buildblock/IndexRange.cxx @@ -24,9 +24,6 @@ START_NAMESPACE_STIR -#ifndef STIR_NO_MUTABLE - - template bool IndexRange::get_regular_range( @@ -82,120 +79,6 @@ IndexRange::get_regular_range( return true; } -#else // STIR_NO_MUTABLE - -template -bool -IndexRange::get_regular_range( - BasicCoordinate& min, - BasicCoordinate& max) const -{ - // check if empty range - if (base_type::begin() == base_type::end()) - { - std::fill(min.begin(), min.end(), 0); - std::fill(max.begin(), max.end(),-1); - return true; - } - - // if not a regular range, exit - if (is_regular_range == regular_false) - return false; - - base_type::const_iterator iter=base_type::begin(); - - BasicCoordinate lower_dim_min; - BasicCoordinate lower_dim_max; - if (!iter->get_regular_range(lower_dim_min, lower_dim_max)) - return false; - - if (is_regular_range == regular_to_do) - { - // check if all lower dimensional ranges have same regular range - BasicCoordinate lower_dim_min_try; - BasicCoordinate lower_dim_max_try; - - for (++iter; iter != base_type::end(); ++iter) - { - if (!iter->get_regular_range(lower_dim_min_try, lower_dim_max_try)) - { - //is_regular_range = regular_false; - return false; - } - if (!std::equal(lower_dim_min.begin(), lower_dim_min.end(), lower_dim_min_try.begin()) || - !std::equal(lower_dim_max.begin(), lower_dim_max.end(), lower_dim_max_try.begin())) - { - //is_regular_range = regular_false; - return false; - } - } - // yes, they do - //is_regular_range = regular_true; - } - - min = join(base_type::get_min_index(), lower_dim_min); - max = join(base_type::get_max_index(), lower_dim_max); - - return true; -} - -template -bool -IndexRange::get_regular_range( - BasicCoordinate& min, - BasicCoordinate& max) -{ - // check if empty range - if (base_type::begin() == base_type::end()) - { - std::fill(min.begin(), min.end(), 0); - std::fill(max.begin(), max.end(),-1); - return true; - } - - // if not a regular range, exit - if (is_regular_range == regular_false) - return false; - - base_type::iterator iter=base_type::begin(); - - BasicCoordinate lower_dim_min; - BasicCoordinate lower_dim_max; - if (!iter->get_regular_range(lower_dim_min, lower_dim_max)) - return false; - - if (is_regular_range == regular_to_do) - { - // check if all lower dimensional ranges have same regular range - BasicCoordinate lower_dim_min_try; - BasicCoordinate lower_dim_max_try; - - for (++iter; iter != base_type::end(); ++iter) - { - if (!iter->get_regular_range(lower_dim_min_try, lower_dim_max_try)) - { - is_regular_range = regular_false; - return false; - } - if (!std::equal(lower_dim_min.begin(), lower_dim_min.end(), lower_dim_min_try.begin()) || - !std::equal(lower_dim_max.begin(), lower_dim_max.end(), lower_dim_max_try.begin())) - { - is_regular_range = regular_false; - return false; - } - } - // yes, they do - is_regular_range = regular_true; - } - - min = join(base_type::get_min_index(), lower_dim_min); - max = join(base_type::get_max_index(), lower_dim_max); - - return true; -} - -#endif // STIR_NO_MUTABLE - /*************************************************** instantiations diff --git a/src/include/stir/IndexRange.h b/src/include/stir/IndexRange.h index c55dd6b9ec..a08e2c868b 100644 --- a/src/include/stir/IndexRange.h +++ b/src/include/stir/IndexRange.h @@ -114,25 +114,12 @@ class IndexRange : public VectorWithOffset > BasicCoordinate& min, BasicCoordinate& max) const; -#ifdef STIR_NO_MUTABLE - //! checks if the range is 'regular' - inline bool is_regular(); - - //! find regular range - bool get_regular_range( - BasicCoordinate& min, - BasicCoordinate& max); -#endif - private: //! enum to encode the current knowledge about regularity enum is_regular_type {regular_true, regular_false, regular_to_do}; //! variable storing the current knowledge about regularity -#ifndef STIR_NO_MUTABLE - mutable -#endif - is_regular_type is_regular_range; + mutable is_regular_type is_regular_range; }; diff --git a/src/include/stir/IndexRange.inl b/src/include/stir/IndexRange.inl index 1bf6848da7..35f90b5871 100644 --- a/src/include/stir/IndexRange.inl +++ b/src/include/stir/IndexRange.inl @@ -116,33 +116,6 @@ IndexRange:: return true; } -#ifdef STIR_NO_MUTABLE - -template -bool -IndexRange:: - is_regular() -{ - switch (is_regular_range) - { - case regular_true: - return true; - case regular_false: - return false; - case regular_to_do: - { - BasicCoordinate min; - BasicCoordinate max; - return - get_regular_range(min,max); - } - } - // although we never get here, VC insists on a return value... - // we check anyway - assert(false); - return true; -} -#endif // STIR_NO_MUTABLE /*************************************** 1D version ***************************************/ diff --git a/src/include/stir/VectorWithOffset.h b/src/include/stir/VectorWithOffset.h index c497b18856..f8cf52fdfe 100644 --- a/src/include/stir/VectorWithOffset.h +++ b/src/include/stir/VectorWithOffset.h @@ -278,21 +278,13 @@ class VectorWithOffset inline T* get_data_ptr(); //! member function for access to the data via a const T* -#ifndef STIR_NO_MUTABLE inline const T * get_const_data_ptr() const; -#else - inline const T * get_const_data_ptr(); -#endif //! signal end of access to T* inline void release_data_ptr(); //! signal end of access to const T* -#ifndef STIR_NO_MUTABLE inline void release_const_data_ptr() const; -#else - inline void release_const_data_ptr(); -#endif //@} //!\name basic iterator support @@ -397,10 +389,7 @@ class VectorWithOffset //! boolean to test if get_data_ptr is called // This variable is declared mutable such that get_const_data_ptr() can change it. -#ifndef STIR_NO_MUTABLE - mutable -#endif - bool pointer_access; + mutable bool pointer_access; bool _owns_memory_for_data; }; diff --git a/src/include/stir/VectorWithOffset.inl b/src/include/stir/VectorWithOffset.inl index 3268d99dba..00eaa34db6 100644 --- a/src/include/stir/VectorWithOffset.inl +++ b/src/include/stir/VectorWithOffset.inl @@ -622,10 +622,7 @@ VectorWithOffset::get_data_ptr() */ template const T * -VectorWithOffset::get_const_data_ptr() -#ifndef STIR_NO_MUTABLE -const -#endif +VectorWithOffset::get_const_data_ptr() const { assert(!pointer_access); @@ -663,10 +660,7 @@ VectorWithOffset::release_data_ptr() template void -VectorWithOffset::release_const_data_ptr() -#ifndef STIR_NO_MUTABLE -const -#endif +VectorWithOffset::release_const_data_ptr() const { assert(pointer_access); diff --git a/src/include/stir/recon_buildblock/ProjMatrixByBin.h b/src/include/stir/recon_buildblock/ProjMatrixByBin.h index 9f694446ab..65a19ad224 100644 --- a/src/include/stir/recon_buildblock/ProjMatrixByBin.h +++ b/src/include/stir/recon_buildblock/ProjMatrixByBin.h @@ -42,13 +42,6 @@ #include #endif -// define a local preprocessor symbol to keep code relatively clean -#ifdef STIR_NO_MUTABLE -#define STIR_MUTABLE_CONST -#else -#define STIR_MUTABLE_CONST const -#endif - START_NAMESPACE_STIR /* TODO @@ -120,7 +113,7 @@ class ProjMatrixByBin : inline void get_proj_matrix_elems_for_one_bin( ProjMatrixElemsForOneBin&, - const Bin&) STIR_MUTABLE_CONST; + const Bin&) const; #if 0 // TODO @@ -150,7 +143,7 @@ class ProjMatrixByBin : // void reserve_num_elements_in_cache(const std::size_t); //! Remove all elements from the cache - void clear_cache() STIR_MUTABLE_CONST; + void clear_cache() const; protected: shared_ptr symmetries_sptr; @@ -211,7 +204,7 @@ class ProjMatrixByBin : //! The method to store data in the cache. void cache_proj_matrix_elems_for_one_bin( const ProjMatrixElemsForOneBin&) - STIR_MUTABLE_CONST; + const; private: @@ -229,15 +222,11 @@ class ProjMatrixByBin : typedef MapProjMatrixElemsForOneBin::const_iterator const_MapProjMatrixElemsForOneBinIterator; //! collection of ProjMatrixElemsForOneBin (internal cache ) -#ifndef STIR_NO_MUTABLE mutable -#endif VectorWithOffset > cache_collection; #ifdef STIR_OPENMP -#ifndef STIR_NO_MUTABLE mutable -#endif - VectorWithOffset > cache_locks; + VectorWithOffset > cache_locks; #endif //! create the key for caching @@ -255,7 +244,7 @@ class ProjMatrixByBin : float r_sqrt2_gauss_sigma; //! The function which actually applies the TOF kernel on the LOR. - inline void apply_tof_kernel(ProjMatrixElemsForOneBin& probabilities) STIR_MUTABLE_CONST; + inline void apply_tof_kernel(ProjMatrixElemsForOneBin& probabilities) const; @@ -273,8 +262,6 @@ END_NAMESPACE_STIR #include "stir/recon_buildblock/ProjMatrixByBin.inl" -#undef STIR_MUTABLE_CONST - #endif // __ProjMatrixByBin_H__ diff --git a/src/include/stir/recon_buildblock/ProjMatrixByBin.inl b/src/include/stir/recon_buildblock/ProjMatrixByBin.inl index f9cb32c4ac..5fed0c3e8c 100644 --- a/src/include/stir/recon_buildblock/ProjMatrixByBin.inl +++ b/src/include/stir/recon_buildblock/ProjMatrixByBin.inl @@ -47,7 +47,7 @@ ProjMatrixByBin:: get_symmetries_sptr() const inline void ProjMatrixByBin:: get_proj_matrix_elems_for_one_bin(ProjMatrixElemsForOneBin& probabilities, - const Bin& bin) STIR_MUTABLE_CONST + const Bin& bin) const { // start_timers(); TODO, can't do this in a const member @@ -115,7 +115,7 @@ get_proj_matrix_elems_for_one_bin(ProjMatrixElemsForOneBin& probabilities, } void -ProjMatrixByBin::apply_tof_kernel(ProjMatrixElemsForOneBin& probabilities) STIR_MUTABLE_CONST +ProjMatrixByBin::apply_tof_kernel(ProjMatrixElemsForOneBin& probabilities) const { LORInAxialAndNoArcCorrSinogramCoordinates lor; diff --git a/src/include/stir_experimental/recon_buildblock/ProjMatrixByDensel.h b/src/include/stir_experimental/recon_buildblock/ProjMatrixByDensel.h index 399697362b..d2661cf4ab 100644 --- a/src/include/stir_experimental/recon_buildblock/ProjMatrixByDensel.h +++ b/src/include/stir_experimental/recon_buildblock/ProjMatrixByDensel.h @@ -28,13 +28,6 @@ #include #include "stir/shared_ptr.h" -// define a local preprocessor symbol to keep code relatively clean -#ifdef STIR_NO_MUTABLE -#define STIR_MUTABLE_CONST -#else -#define STIR_MUTABLE_CONST const -#endif - START_NAMESPACE_STIR //template class RelatedViewgrams; @@ -83,7 +76,7 @@ class ProjMatrixByDensel : public RegisteredObject inline void get_proj_matrix_elems_for_one_densel( ProjMatrixElemsForOneDensel&, - const Densel&) STIR_MUTABLE_CONST; + const Densel&) const; #if 0 // TODO @@ -142,7 +135,7 @@ class ProjMatrixByDensel : public RegisteredObject //! The method to store data in the cache. inline void cache_proj_matrix_elems_for_one_densel( const ProjMatrixElemsForOneDensel&) - STIR_MUTABLE_CONST; + const; private: @@ -153,9 +146,7 @@ class ProjMatrixByDensel : public RegisteredObject typedef MapProjMatrixElemsForOneDensel::const_iterator const_MapProjMatrixElemsForOneDenselIterator; //! collection of ProjMatrixElemsForOneDensel (internal cache ) -#ifndef STIR_NO_MUTABLE mutable -#endif MapProjMatrixElemsForOneDensel cache_collection; //! create the key for caching @@ -170,8 +161,6 @@ END_NAMESPACE_STIR #include "stir_experimental/recon_buildblock/ProjMatrixByDensel.inl" -#undef STIR_MUTABLE_CONST - #endif // __ProjMatrixByDensel_H__ diff --git a/src/include/stir_experimental/recon_buildblock/ProjMatrixByDensel.inl b/src/include/stir_experimental/recon_buildblock/ProjMatrixByDensel.inl index 8a94893542..00e0dd922d 100644 --- a/src/include/stir_experimental/recon_buildblock/ProjMatrixByDensel.inl +++ b/src/include/stir_experimental/recon_buildblock/ProjMatrixByDensel.inl @@ -23,7 +23,7 @@ START_NAMESPACE_STIR void ProjMatrixByDensel:: get_proj_matrix_elems_for_one_densel( ProjMatrixElemsForOneDensel& probabilities, - const Densel& densel) STIR_MUTABLE_CONST + const Densel& densel) const { // set to empty probabilities.erase(); @@ -85,7 +85,7 @@ ProjMatrixByDensel::cache_key(const Densel& densel) void ProjMatrixByDensel:: cache_proj_matrix_elems_for_one_densel( - const ProjMatrixElemsForOneDensel& probabilities) STIR_MUTABLE_CONST + const ProjMatrixElemsForOneDensel& probabilities) const { if ( cache_disabled ) return; diff --git a/src/recon_buildblock/ProjMatrixByBin.cxx b/src/recon_buildblock/ProjMatrixByBin.cxx index 46a430b417..4c9af6ca2f 100644 --- a/src/recon_buildblock/ProjMatrixByBin.cxx +++ b/src/recon_buildblock/ProjMatrixByBin.cxx @@ -29,13 +29,6 @@ #include "stir/recon_buildblock/ProjMatrixElemsForOneBin.h" #include "stir/TOF_conversions.h" -// define a local preprocessor symbol to keep code relatively clean -#ifdef STIR_NO_MUTABLE -#define STIR_MUTABLE_CONST -#else -#define STIR_MUTABLE_CONST const -#endif - START_NAMESPACE_STIR void ProjMatrixByBin::set_defaults() @@ -98,7 +91,7 @@ does_cache_store_only_basic_bins() const void ProjMatrixByBin:: -clear_cache() STIR_MUTABLE_CONST +clear_cache() const { #ifdef STIR_OPENMP #pragma omp critical(PROJMATRIXBYBINCLEARCACHE) @@ -213,7 +206,7 @@ ProjMatrixByBin::cache_key(const Bin& bin) const void ProjMatrixByBin:: cache_proj_matrix_elems_for_one_bin( - const ProjMatrixElemsForOneBin& probabilities) STIR_MUTABLE_CONST + const ProjMatrixElemsForOneBin& probabilities) const { if ( cache_disabled ) return; From d4652ad26fe6525d8f817e86c3ffd631072d60d4 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Mon, 29 Jan 2024 07:40:14 +0000 Subject: [PATCH 493/509] removed most BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION work-arounds kept the ones in Array as this was already done in another PR- (not yet merged) --- src/buildblock/centre_of_gravity.cxx | 4 -- src/include/stir/ArrayFunction.h | 36 ------------ src/include/stir/ArrayFunction.inl | 62 -------------------- src/include/stir/centre_of_gravity.h | 8 --- src/include/stir/convert_range.inl | 2 - src/include/stir/numerics/BSplinesDetail.inl | 17 ------ src/include/stir/numerics/norm.inl | 17 ------ 7 files changed, 146 deletions(-) diff --git a/src/buildblock/centre_of_gravity.cxx b/src/buildblock/centre_of_gravity.cxx index 52056c0a88..f84b7c642e 100644 --- a/src/buildblock/centre_of_gravity.cxx +++ b/src/buildblock/centre_of_gravity.cxx @@ -42,11 +42,7 @@ find_unweighted_centre_of_gravity_1d(const VectorWithOffset& row) return CoG; } -#ifdef BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION -#define T float -#else template -#endif T find_unweighted_centre_of_gravity(const Array<1,T>& row) { diff --git a/src/include/stir/ArrayFunction.h b/src/include/stir/ArrayFunction.h index 47860ac0d7..7701a424ab 100644 --- a/src/include/stir/ArrayFunction.h +++ b/src/include/stir/ArrayFunction.h @@ -68,14 +68,9 @@ START_NAMESPACE_STIR //! Replace elements by their logarithm, 1D version /*! \ingroup Array */ -#ifndef BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION template inline Array<1,elemT>& in_place_log(Array<1,elemT>& v); -#else -inline Array<1,float>& -in_place_log(Array<1,float>& v); -#endif //! apply log to each element of the multi-dimensional array @@ -86,14 +81,9 @@ in_place_log(Array& v); //! Replace elements by their exponentiation, 1D version /*! \ingroup Array */ -#ifndef BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION template inline Array<1,elemT>& in_place_exp(Array<1,elemT>& v); -#else -inline Array<1,float>& -in_place_exp(Array<1,float>& v); -#endif //! apply exp to each element of the multi-dimensional array /*! \ingroup Array */ @@ -105,14 +95,9 @@ in_place_exp(Array& v); /*! \ingroup Array \warning The implementation does not work with complex numbers. */ -#ifndef BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION template inline Array<1,elemT>& in_place_abs(Array<1,elemT>& v); -#else -inline Array<1,float>& -in_place_abs(Array<1,float>& v); -#endif //! store absolute value of each element of the multi-dimensional array /*! \ingroup Array @@ -227,20 +212,14 @@ apply_array_function_on_1st_index(Array& out_array, objects and (smart) pointers to function objects. At the moment, it's only the latter. */ // TODO add specialisation that uses ArrayFunctionObject::is_trivial -#ifdef BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION -template -#else template -#endif inline void in_place_apply_array_functions_on_each_index(Array& array, FunctionObjectPtrIter start, FunctionObjectPtrIter stop); //! 1d specialisation of the above. -#ifndef BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION template -#endif inline void in_place_apply_array_functions_on_each_index(Array<1, elemT>& array, FunctionObjectPtrIter start, @@ -248,7 +227,6 @@ in_place_apply_array_functions_on_each_index(Array<1, elemT>& array, -#ifndef BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION //! Apply a sequence of 1d array-function objects on every dimension of the input array, store in output array /*! \ingroup Array @@ -270,7 +248,6 @@ apply_array_functions_on_each_index(Array& out_array, const Array& in_array, FunctionObjectPtrIter start, FunctionObjectPtrIter stop); -#endif //! Apply a sequence of 1d array-function objects of a specific type on every dimension of the input array, store in output array /*! \ingroup Array @@ -280,31 +257,24 @@ apply_array_functions_on_each_index(Array& out_array, \todo Modify such that this function would handle function objects and (smart) pointers to ArrayFunctionObject objects. At the moment, it's only the latter. */ -#ifdef BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION -template -#else template -#endif inline void apply_array_functions_on_each_index(Array& out_array, const Array& in_array, ActualFunctionObjectPtrIter start, ActualFunctionObjectPtrIter stop); -#ifndef BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION //! 1d specialisation of above /*! \ingroup Array */ // has to be here to get general 1D specialisation to compile template -#endif inline void apply_array_functions_on_each_index(Array<1, elemT>& out_array, const Array<1, elemT>& in_array, ActualFunctionObjectPtrIter start, ActualFunctionObjectPtrIter stop); -#ifndef BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION template //! 1d specialisation for general function objects /*! \ingroup Array @@ -313,12 +283,6 @@ inline void apply_array_functions_on_each_index(Array<1, elemT>& out_array, const Array<1, elemT>& in_array, FunctionObjectPtrIter start, FunctionObjectPtrIter stop); -#endif - -#ifdef BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION -#undef elemT -#undef FunctionObjectPtrIter -#endif template diff --git a/src/include/stir/ArrayFunction.inl b/src/include/stir/ArrayFunction.inl index 5b4a90a0dc..47b67eadf7 100644 --- a/src/include/stir/ArrayFunction.inl +++ b/src/include/stir/ArrayFunction.inl @@ -38,7 +38,6 @@ START_NAMESPACE_STIR // element wise and in place numeric functions //---------------------------------------------------------------------- -#ifndef BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION template inline Array<1,elemT>& in_place_log(Array<1,elemT>& v) @@ -48,17 +47,6 @@ in_place_log(Array<1,elemT>& v) return v; } -#else -inline Array<1,float>& -in_place_log(Array<1,float>& v) -{ - for(int i=v.get_min_index(); i<=v.get_max_index(); i++) - v[i] = std::log(v[i]); - return v; -} -#endif - - template inline Array& in_place_log(Array& v) @@ -68,7 +56,6 @@ in_place_log(Array& v) return v; } -#ifndef BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION template inline Array<1,elemT>& in_place_exp(Array<1,elemT>& v) @@ -77,15 +64,6 @@ in_place_exp(Array<1,elemT>& v) v[i] = std::exp(v[i]); return v; } -#else -inline Array<1,float>& -in_place_exp(Array<1,float>& v) -{ - for(int i=v.get_min_index(); i<=v.get_max_index(); i++) - v[i] = std::exp(v[i]); - return v; -} -#endif template inline Array& @@ -96,7 +74,6 @@ in_place_exp(Array& v) return v; } -#ifndef BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION template inline Array<1,elemT>& in_place_abs(Array<1,elemT>& v) @@ -106,17 +83,6 @@ in_place_abs(Array<1,elemT>& v) v[i] = -v[i]; return v; } -#else -inline Array<1,float>& -in_place_abs(Array<1,float>& v) -{ - for(int i=v.get_min_index(); i<=v.get_max_index(); i++) - if (v[i] < 0) - v[i] = -v[i]; - return v; -} -#endif - template inline Array& @@ -231,17 +197,7 @@ apply_array_function_on_1st_index(Array& out_array, } -#ifdef BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION -// silly business for deficient compilers (including VC 6.0) -#define elemT float -#define FunctionObjectPtrIter ActualFunctionObjectPtrIter -#endif - -#ifdef BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION -template -#else template -#endif inline void in_place_apply_array_functions_on_each_index(Array& array, FunctionObjectPtrIter start, @@ -256,9 +212,7 @@ in_place_apply_array_functions_on_each_index(Array& array, in_place_apply_array_functions_on_each_index(*restiter, start, stop); } -#ifndef BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION template -#endif inline void in_place_apply_array_functions_on_each_index(Array<1, elemT>& array, FunctionObjectPtrIter start, FunctionObjectPtrIter stop) { @@ -267,7 +221,6 @@ in_place_apply_array_functions_on_each_index(Array<1, elemT>& array, FunctionObj } -#ifndef BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION template inline void apply_array_functions_on_each_index(Array& out_array, @@ -291,14 +244,9 @@ apply_array_functions_on_each_index(Array& out_array, apply_array_function_on_1st_index(out_array, tmp_out_array, *start); } -#endif // specialisation that uses ArrayFunctionObject::is_trivial etc -#ifdef BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION -template -#else template -#endif inline void apply_array_functions_on_each_index(Array& out_array, const Array& in_array, @@ -350,10 +298,8 @@ apply_array_functions_on_each_index(Array& out_array, } -#ifndef BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION // has to be here to get general 1D specialisation to compile template -#endif inline void apply_array_functions_on_each_index(Array<1, elemT>& out_array, const Array<1, elemT>& in_array, @@ -365,7 +311,6 @@ apply_array_functions_on_each_index(Array<1, elemT>& out_array, } -#ifndef BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION template inline void apply_array_functions_on_each_index(Array<1, elemT>& out_array, @@ -375,13 +320,6 @@ apply_array_functions_on_each_index(Array<1, elemT>& out_array, assert(start+1 == stop); (**start)(out_array, in_array); } -#endif - -#ifdef BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION -#undef elemT -#undef FunctionObjectPtrIter -#endif - /******************* functions that copy arrays using transformed indices *****/ diff --git a/src/include/stir/centre_of_gravity.h b/src/include/stir/centre_of_gravity.h index 7466f847e2..c2fccd9d62 100644 --- a/src/include/stir/centre_of_gravity.h +++ b/src/include/stir/centre_of_gravity.h @@ -55,18 +55,10 @@ find_unweighted_centre_of_gravity(const Array& ); Conceptually the same as the n-dimensional version, but returns a \c T, not a BasicCoordinate\<1,T\>. */ -#ifndef BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION template -#else -#define T float -#endif T find_unweighted_centre_of_gravity(const Array<1,T>& ); -#ifdef BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION -#undef T -#endif - //! Compute centre of gravity of an Array /*! \ingroup Array Calls find_unweighted_centre_of_gravity and divides the result with the diff --git a/src/include/stir/convert_range.inl b/src/include/stir/convert_range.inl index 05c7b64a06..120fb904cf 100644 --- a/src/include/stir/convert_range.inl +++ b/src/include/stir/convert_range.inl @@ -176,7 +176,6 @@ void } } -#ifndef BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION // specialisation for equal Iterator types // In fact, we could do better and test for boost::iterator_value::type // etc, but that requires some template trickery @@ -189,7 +188,6 @@ void scale_factor = scaleT(1); std::copy(in_begin, in_end, out_begin); } -#endif END_NAMESPACE_STIR diff --git a/src/include/stir/numerics/BSplinesDetail.inl b/src/include/stir/numerics/BSplinesDetail.inl index 7dd6ba663f..0810d65c26 100644 --- a/src/include/stir/numerics/BSplinesDetail.inl +++ b/src/include/stir/numerics/BSplinesDetail.inl @@ -260,12 +260,7 @@ namespace BSpline { } }; -#if defined(BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION) -#define T float - template <> -#else template -#endif struct compute_BSplines_value<1, num_dimensions2,T> { @@ -281,9 +276,6 @@ namespace BSpline { ); } }; -#if defined(BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION) -#undef T -#endif template struct @@ -310,12 +302,7 @@ namespace BSpline { } }; -#if defined(BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION) -#define T float - template <> -#else template -#endif struct compute_BSplines_gradient<1,num_dimensions2,T> { @@ -335,10 +322,6 @@ namespace BSpline { return result; } }; -#if defined(BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION) -#undef T -#endif - } // end of namespace detail diff --git a/src/include/stir/numerics/norm.inl b/src/include/stir/numerics/norm.inl index e0e9df4d83..317856ae7b 100644 --- a/src/include/stir/numerics/norm.inl +++ b/src/include/stir/numerics/norm.inl @@ -29,7 +29,6 @@ START_NAMESPACE_STIR -#ifndef BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION template struct NormSquared > { @@ -37,22 +36,6 @@ struct NormSquared > { return square(x.real())+ square(x.imag()); } }; -#else // BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION - -// handle float and double separately. (sigh) -#define INSTANTIATE_NormSquared(T) \ - template <> \ - struct NormSquared > \ -{ \ - double operator()(const std::complex& x) const \ - { return square(x.real())+ square(x.imag()); } \ -}; -INSTANTIATE_NormSquared(float) -INSTANTIATE_NormSquared(double) -#undef INSTANTIATE_NormSquared -#endif - - template double norm_squared (Iter begin, Iter end) { From f91bc0f4152b9cdd04266d0ffcda7ec1e1d908ad Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Mon, 29 Jan 2024 07:52:41 +0000 Subject: [PATCH 494/509] removed BOOST_NO_STRINGSTREAM workarounds --- src/analytic/FBP2D/RampFilter.cxx | 10 ---------- src/analytic/FBP3DRP/ColsherFilter.cxx | 11 +---------- src/buildblock/ExamInfo.cxx | 11 +---------- src/buildblock/KeyParser.cxx | 13 +------------ src/buildblock/ProjDataFromStream.cxx | 12 ------------ src/buildblock/ProjDataInfo.cxx | 4 ---- .../ProjDataInfoBlocksOnCylindricalNoArcCorr.cxx | 11 +---------- src/buildblock/ProjDataInfoCylindrical.cxx | 11 +---------- .../ProjDataInfoCylindricalArcCorr.cxx | 11 +---------- .../ProjDataInfoCylindricalNoArcCorr.cxx | 6 ------ src/buildblock/ProjDataInfoGeneric.cxx | 10 ---------- src/buildblock/ProjDataInfoGenericNoArcCorr.cxx | 11 +---------- src/buildblock/Scanner.cxx | 16 ---------------- src/eval_buildblock/ROIValues.cxx | 11 ----------- .../motion/RigidObject3DMotionFromPolaris.cxx | 11 +---------- src/include/stir/utilities.inl | 8 -------- .../KOSMAPOSL/KOSMAPOSLReconstruction.cxx | 4 ---- .../OSMAPOSL/OSMAPOSLReconstruction.cxx | 10 ---------- src/iterative/OSSPS/OSSPSReconstruction.cxx | 9 --------- .../GeneralisedObjectiveFunction.cxx | 6 +----- 20 files changed, 9 insertions(+), 187 deletions(-) diff --git a/src/analytic/FBP2D/RampFilter.cxx b/src/analytic/FBP2D/RampFilter.cxx index 41d43e0d32..fc46191ca1 100644 --- a/src/analytic/FBP2D/RampFilter.cxx +++ b/src/analytic/FBP2D/RampFilter.cxx @@ -28,11 +28,7 @@ #include #include "stir/error.h" #include -#ifdef BOOST_NO_STRINGSTREAM -#include -#else #include -#endif #include /* Note: #ifdef NRFFT, then the Numerical Recipes version is used (if you have it...) */ @@ -169,13 +165,7 @@ RampFilter::RampFilter(float sampledist_v, int length , float alpha_v, float fc_ std::string RampFilter:: parameter_info() const { -#ifdef BOOST_NO_STRINGSTREAM - // dangerous for out-of-range, but 'old-style' ostrstream seems to need this - char str[1000]; - ostrstream s(str, 1000); -#else std::ostringstream s; -#endif s << "RampFilter :=" << "\nFilter length := " #ifdef NRFFT diff --git a/src/analytic/FBP3DRP/ColsherFilter.cxx b/src/analytic/FBP3DRP/ColsherFilter.cxx index 235af6b592..6a7d2cee67 100644 --- a/src/analytic/FBP3DRP/ColsherFilter.cxx +++ b/src/analytic/FBP3DRP/ColsherFilter.cxx @@ -39,23 +39,14 @@ #endif #include -#ifdef BOOST_NO_STRINGSTREAM -#include -#else #include -#endif START_NAMESPACE_STIR std::string ColsherFilter::parameter_info() const { -#ifdef BOOST_NO_STRINGSTREAM - // dangerous for out-of-range, but 'old-style' ostrstream seems to need this - char str[2000]; - ostrstream s(str, 2000); -#else std::ostringstream s; -#endif + s << "\nColsherFilter Parameters :=" #ifdef NRFFT << "\nFilter height := "<< height diff --git a/src/buildblock/ExamInfo.cxx b/src/buildblock/ExamInfo.cxx index e00159dd86..1769f8dbf1 100644 --- a/src/buildblock/ExamInfo.cxx +++ b/src/buildblock/ExamInfo.cxx @@ -19,24 +19,15 @@ #include "stir/date_time_functions.h" #include -#ifdef BOOST_NO_STRINGSTREAM -#include -#else #include -#endif START_NAMESPACE_STIR std::string ExamInfo::parameter_info() const { -#ifdef BOOST_NO_STRINGSTREAM - // dangerous for out-of-range, but 'old-style' ostrstream seems to need this - char str[30000]; - ostrstream s(str, 30000); -#else std::ostringstream s; -#endif + s << "Modality: " << this->imaging_modality.get_name() << '\n'; s << "Calibration Factor: " << std::fixed << std::setprecision(12) << this->calibration_factor << std::setprecision(5) << '\n'; s << "Radionuclide Parameters:\n" << this->radionuclide.parameter_info() << '\n'; diff --git a/src/buildblock/KeyParser.cxx b/src/buildblock/KeyParser.cxx index fb8b707053..8f6d5c9802 100644 --- a/src/buildblock/KeyParser.cxx +++ b/src/buildblock/KeyParser.cxx @@ -1223,13 +1223,8 @@ namespace detail { // we will first write everything to a temporary stringstream // and then read it back, inserting the backslash -#ifdef BOOST_NO_STRINGSTREAM - // dangerous for out-of-range, but 'old-style' ostrstream seems to need this - char str[100000]; - strstream stemp(str, 100000); -#else std::stringstream stemp; -#endif + // write to stemp stemp << var; @@ -1421,13 +1416,7 @@ void KeyParser::vectorised_value_to_stream(std::ostream& s, const std::string& k string KeyParser::parameter_info() const { -#ifdef BOOST_NO_STRINGSTREAM - // dangerous for out-of-range, but 'old-style' ostrstream seems to need this - char str[100000]; - ostrstream s(str, 100000); -#else std::ostringstream s; -#endif // first find start key for (Keymap::const_iterator i=kmap.begin(); i!= kmap.end(); ++i) diff --git a/src/buildblock/ProjDataFromStream.cxx b/src/buildblock/ProjDataFromStream.cxx index c2ea406ba5..4383362725 100644 --- a/src/buildblock/ProjDataFromStream.cxx +++ b/src/buildblock/ProjDataFromStream.cxx @@ -960,24 +960,12 @@ ProjDataFromStream* ProjDataFromStream::ask_parameters(const bool on_disk) memory = (char *)read_stream_in_memory(input, file_size); } -#ifdef BOOST_NO_STRINGSTREAM - // This is the old implementation of the strstream class. - // The next constructor should work according to the doc, but it doesn't in gcc 2.8.1. - //strstream in_stream(memory, file_size, ios::in | ios::binary); - // Reason: in_stream contains an internal strstreambuf which is - // initialised as buffer(memory, file_size, memory), which prevents - // reading from it. - - strstreambuf * buffer = new strstreambuf(memory, file_size, memory+file_size); - p_in_stream.reset(new iostream(buffer)); -#else // TODO this does allocate and copy 2 times // TODO file_size could be longer than what size_t allows, but string doesn't take anything longer p_in_stream.reset(new std::stringstream (string(memory, std::size_t(file_size)), open_mode | ios::binary)); delete[] memory; -#endif } // else 'on_disk' diff --git a/src/buildblock/ProjDataInfo.cxx b/src/buildblock/ProjDataInfo.cxx index 4bdf41232b..47ef19af8c 100644 --- a/src/buildblock/ProjDataInfo.cxx +++ b/src/buildblock/ProjDataInfo.cxx @@ -51,11 +51,7 @@ #include #include #include -#ifdef BOOST_NO_STRINGSTREAM -#include -#else #include -#endif #include "stir/info.h" #include "boost/foreach.hpp" #include "boost/format.hpp" diff --git a/src/buildblock/ProjDataInfoBlocksOnCylindricalNoArcCorr.cxx b/src/buildblock/ProjDataInfoBlocksOnCylindricalNoArcCorr.cxx index de57f5a513..d5c10ec6e2 100644 --- a/src/buildblock/ProjDataInfoBlocksOnCylindricalNoArcCorr.cxx +++ b/src/buildblock/ProjDataInfoBlocksOnCylindricalNoArcCorr.cxx @@ -33,11 +33,7 @@ #include #include -#ifdef BOOST_NO_STRINGSTREAM -#include -#else #include -#endif using std::endl; using std::ends; @@ -96,13 +92,8 @@ std::string ProjDataInfoBlocksOnCylindricalNoArcCorr::parameter_info() const { - #ifdef BOOST_NO_STRINGSTREAM - // dangerous for out-of-range, but 'old-style' ostrstream seems to need this - char str[50000]; - ostrstream s(str, 50000); - #else std::ostringstream s; - #endif + s << "ProjDataInfoBlocksOnCylindricalNoArcCorr := \n"; s << base_type::parameter_info(); s << "End :=\n"; diff --git a/src/buildblock/ProjDataInfoCylindrical.cxx b/src/buildblock/ProjDataInfoCylindrical.cxx index 1ef98f3478..2e4a73f83f 100644 --- a/src/buildblock/ProjDataInfoCylindrical.cxx +++ b/src/buildblock/ProjDataInfoCylindrical.cxx @@ -28,11 +28,7 @@ #include "stir/LORCoordinates.h" #include "stir/Array.h" #include -#ifdef BOOST_NO_STRINGSTREAM -#include -#else #include -#endif #include "stir/round.h" #include "stir/numerics/norm.h" @@ -674,13 +670,8 @@ string ProjDataInfoCylindrical::parameter_info() const { -#ifdef BOOST_NO_STRINGSTREAM - // dangerous for out-of-range, but 'old-style' ostrstream seems to need this - char str[30000]; - ostrstream s(str, 30000); -#else std::ostringstream s; -#endif + s << ProjDataInfo::parameter_info(); s << "Azimuthal angle increment (deg): " << get_azimuthal_angle_sampling()*180/_PI << '\n'; s << "Azimuthal angle extent (deg): " << fabs(get_azimuthal_angle_sampling())*get_num_views()*180/_PI << '\n'; diff --git a/src/buildblock/ProjDataInfoCylindricalArcCorr.cxx b/src/buildblock/ProjDataInfoCylindricalArcCorr.cxx index 7cdbf24f77..d46f5cde87 100644 --- a/src/buildblock/ProjDataInfoCylindricalArcCorr.cxx +++ b/src/buildblock/ProjDataInfoCylindricalArcCorr.cxx @@ -31,11 +31,7 @@ #include "stir/Bin.h" #include "stir/round.h" #include "stir/LORCoordinates.h" -#ifdef BOOST_NO_STRINGSTREAM -#include -#else #include -#endif using std::endl; using std::ends; @@ -100,13 +96,8 @@ string ProjDataInfoCylindricalArcCorr::parameter_info() const { -#ifdef BOOST_NO_STRINGSTREAM - // dangerous for out-of-range, but 'old-style' ostrstream seems to need this - char str[50000]; - ostrstream s(str, 50000); -#else std::ostringstream s; -#endif + s << "ProjDataInfoCylindricalArcCorr := \n"; s << ProjDataInfoCylindrical::parameter_info(); s << "tangential sampling := " << get_tangential_sampling() << endl; diff --git a/src/buildblock/ProjDataInfoCylindricalNoArcCorr.cxx b/src/buildblock/ProjDataInfoCylindricalNoArcCorr.cxx index e2b0a605b1..62420eb5d0 100644 --- a/src/buildblock/ProjDataInfoCylindricalNoArcCorr.cxx +++ b/src/buildblock/ProjDataInfoCylindricalNoArcCorr.cxx @@ -127,13 +127,7 @@ string ProjDataInfoCylindricalNoArcCorr::parameter_info() const { -#ifdef BOOST_NO_STRINGSTREAM - // dangerous for out-of-range, but 'old-style' ostrstream seems to need this - char str[50000]; - ostrstream s(str, 50000); -#else std::ostringstream s; -#endif s << "ProjDataInfoCylindricalNoArcCorr := \n"; s << ProjDataInfoCylindrical::parameter_info(); s << "End :=\n"; diff --git a/src/buildblock/ProjDataInfoGeneric.cxx b/src/buildblock/ProjDataInfoGeneric.cxx index 5752a8800f..6f166ae413 100755 --- a/src/buildblock/ProjDataInfoGeneric.cxx +++ b/src/buildblock/ProjDataInfoGeneric.cxx @@ -30,11 +30,7 @@ #include "stir/ProjDataInfoGeneric.h" #include "stir/LORCoordinates.h" #include -#ifdef BOOST_NO_STRINGSTREAM -#include -#else #include -#endif #include "stir/round.h" #include "stir/error.h" @@ -121,13 +117,7 @@ std::string ProjDataInfoGeneric::parameter_info() const { -#ifdef BOOST_NO_STRINGSTREAM - // dangerous for out-of-range, but 'old-style' ostrstream seems to need this - char str[30000]; - ostrstream s(str, 30000); -#else std::ostringstream s; -#endif s << ProjDataInfo::parameter_info(); // TODOBLOCK Cylindrical has the following which doesn't make sense for Generic, so repeat code //s << "Azimuthal angle increment (deg): " << get_azimuthal_angle_sampling()*180/_PI << '\n'; diff --git a/src/buildblock/ProjDataInfoGenericNoArcCorr.cxx b/src/buildblock/ProjDataInfoGenericNoArcCorr.cxx index c6754c142e..6d2b16abda 100644 --- a/src/buildblock/ProjDataInfoGenericNoArcCorr.cxx +++ b/src/buildblock/ProjDataInfoGenericNoArcCorr.cxx @@ -33,11 +33,7 @@ #include #include -#ifdef BOOST_NO_STRINGSTREAM -#include -#else #include -#endif #include @@ -111,13 +107,8 @@ std::string ProjDataInfoGenericNoArcCorr::parameter_info() const { - #ifdef BOOST_NO_STRINGSTREAM - // dangerous for out-of-range, but 'old-style' ostrstream seems to need this - char str[50000]; - ostrstream s(str, 50000); - #else std::ostringstream s; - #endif + s << "ProjDataInfoGenericNoArcCorr := \n"; s << ProjDataInfoGeneric::parameter_info(); s << "End :=\n"; diff --git a/src/buildblock/Scanner.cxx b/src/buildblock/Scanner.cxx index 997dea64e7..ced4e21e91 100644 --- a/src/buildblock/Scanner.cxx +++ b/src/buildblock/Scanner.cxx @@ -41,11 +41,7 @@ #include "stir/GeometryBlocksOnCylindrical.h" #include #include -#ifdef BOOST_NO_STRINGSTREAM -#include -#else #include -#endif #include "stir/warning.h" #include "stir/error.h" @@ -1475,13 +1471,7 @@ Scanner::parameter_info() const string Scanner::list_names() const { -#ifdef BOOST_NO_STRINGSTREAM - // dangerous for out-of-range, but 'old-style' ostrstream seems to need this - char str[3000]; - ostrstream s(str, 3000); -#else std::ostringstream s; -#endif #ifdef _MSC_VER // work-around VC bug @@ -1685,13 +1675,7 @@ Scanner::get_scanner_from_name(const string& name) string Scanner:: list_all_names() { -#ifdef BOOST_NO_STRINGSTREAM - // dangerous for out-of-range, but 'old-style' ostrstream seems to need this - char str[30000]; - ostrstream s(str, 30000); -#else std::ostringstream s; -#endif Type type= E931; while (type != Unknown_scanner) diff --git a/src/eval_buildblock/ROIValues.cxx b/src/eval_buildblock/ROIValues.cxx index 6e0a00737e..148bf41a3e 100644 --- a/src/eval_buildblock/ROIValues.cxx +++ b/src/eval_buildblock/ROIValues.cxx @@ -23,12 +23,7 @@ #include "stir/evaluation/ROIValues.h" #include "stir/NumericInfo.h" #include -#ifdef BOOST_NO_STRINGSTREAM -#include -#else #include -#endif - using std::endl; using std::ends; @@ -74,13 +69,7 @@ void ROIValues::update() std::string ROIValues::report() const { -#ifdef BOOST_NO_STRINGSTREAM - // dangerous for out-of-range, but 'old-style' ostrstream seems to need this - char str[3000]; - ostrstream s(str, 3000); -#else std::ostringstream s; -#endif s << " Volume of ROI = " << roi_volume << endl; s << " Integral of density = " << integral << endl; s << " Integral of square density = " << integral_of_square << endl; diff --git a/src/experimental/motion/RigidObject3DMotionFromPolaris.cxx b/src/experimental/motion/RigidObject3DMotionFromPolaris.cxx index f66b237411..cff4d38405 100644 --- a/src/experimental/motion/RigidObject3DMotionFromPolaris.cxx +++ b/src/experimental/motion/RigidObject3DMotionFromPolaris.cxx @@ -32,11 +32,7 @@ #include #include -#ifndef BOOST_NO_STRINGSTREAM #include -#else -#include -#endif # ifdef BOOST_NO_STDC_NAMESPACE namespace std { using ::time_t; using ::tm; using ::localtime; } @@ -894,13 +890,8 @@ bool RigidObject3DMotionFromPolaris::post_processing() } if (std::fabs(norm(move_from_scanner_coords.get_quaternion())-1)>.01) { -#ifdef BOOST_NO_STRINGSTREAM - // dangerous for out-of-range, but 'old-style' ostrstream seems to need this - char str[100000]; - ostrstream s(str, 100000); -#else std::ostringstream s; -#endif + s << move_from_scanner_coords; warning("Error reading transformation_from_scanner_coordinates_filename:\n'%s'" "\nvalue for 'transformation' keyword is invalid:" diff --git a/src/include/stir/utilities.inl b/src/include/stir/utilities.inl index 35deefc08e..8ab9fa8af9 100644 --- a/src/include/stir/utilities.inl +++ b/src/include/stir/utilities.inl @@ -17,11 +17,7 @@ */ #include #include "stir/error.h" -#ifdef BOOST_NO_STRINGSTREAM -#include -#else #include -#endif START_NAMESPACE_STIR /*! @@ -47,11 +43,7 @@ ask_num (const std::string& str, << "[" << minimum_value << "," << maximum_value << " D:" << default_value << "]: "; std::getline(std::cin, input); -#ifdef BOOST_NO_STRINGSTREAM - istrstream ss(input.c_str()); -#else std::istringstream ss(input.c_str()); -#endif NUMBER value = default_value; ss >> value; diff --git a/src/iterative/KOSMAPOSL/KOSMAPOSLReconstruction.cxx b/src/iterative/KOSMAPOSL/KOSMAPOSLReconstruction.cxx index b833b91513..adc5ccc09a 100644 --- a/src/iterative/KOSMAPOSL/KOSMAPOSLReconstruction.cxx +++ b/src/iterative/KOSMAPOSL/KOSMAPOSLReconstruction.cxx @@ -59,11 +59,7 @@ #endif #include "stir/num_threads.h" -#ifdef BOOST_NO_STRINGSTREAM -#include -#else #include -#endif #include "stir/unique_ptr.h" #include diff --git a/src/iterative/OSMAPOSL/OSMAPOSLReconstruction.cxx b/src/iterative/OSMAPOSL/OSMAPOSLReconstruction.cxx index 65b563607a..6ef87b80ff 100644 --- a/src/iterative/OSMAPOSL/OSMAPOSLReconstruction.cxx +++ b/src/iterative/OSMAPOSL/OSMAPOSLReconstruction.cxx @@ -51,11 +51,7 @@ #include #include #include -#ifdef BOOST_NO_STRINGSTREAM -#include -#else #include -#endif #include "stir/unique_ptr.h" #include @@ -285,13 +281,7 @@ method_info() const // TODO add prior name? -#ifdef BOOST_NO_STRINGSTREAM - // dangerous for out-of-range, but 'old-style' ostrstream seems to need this - char str[10000]; - ostrstream s(str, 10000); -#else std::ostringstream s; -#endif if(this->inter_update_filter_interval>0) s<<"IUF-"; if(this->num_subsets>1) s<<"OS"; diff --git a/src/iterative/OSSPS/OSSPSReconstruction.cxx b/src/iterative/OSSPS/OSSPSReconstruction.cxx index 98de95f2f9..5005acb4b6 100644 --- a/src/iterative/OSSPS/OSSPSReconstruction.cxx +++ b/src/iterative/OSSPS/OSSPSReconstruction.cxx @@ -38,11 +38,7 @@ #include #include #include -#ifdef BOOST_NO_STRINGSTREAM -#include -#else #include -#endif #include #include "boost/lambda/lambda.hpp" #include "stir/unique_ptr.h" @@ -186,12 +182,7 @@ method_info() const // TODO add prior name? -#ifdef BOOST_NO_STRINGSTREAM - char str[10000]; - ostrstream s(str, 10000); -#else std::ostringstream s; -#endif // if(inter_update_filter_interval>0) s<<"IUF-"; if (!this->objective_function_sptr->prior_is_zero()) diff --git a/src/recon_buildblock/GeneralisedObjectiveFunction.cxx b/src/recon_buildblock/GeneralisedObjectiveFunction.cxx index 05867223cb..74c37e7d26 100644 --- a/src/recon_buildblock/GeneralisedObjectiveFunction.cxx +++ b/src/recon_buildblock/GeneralisedObjectiveFunction.cxx @@ -466,12 +466,8 @@ std::string GeneralisedObjectiveFunction:: get_objective_function_values_report(const TargetT& current_estimate) { -#ifdef BOOST_NO_STRINGSTREAM - char str[10000]; - ostrstream s(str, 10000); -#else std::ostringstream s; -#endif + const double no_penalty = this->compute_objective_function_without_penalty(current_estimate); const double penalty = From f0e807b2456f836e71a7a38809b308193a9d49b2 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Mon, 29 Jan 2024 08:16:20 +0000 Subject: [PATCH 495/509] remove 2 more VC6.0 workarounds --- src/buildblock/ProjDataFromStream.cxx | 13 +++---------- src/buildblock/Scanner.cxx | 6 +----- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/src/buildblock/ProjDataFromStream.cxx b/src/buildblock/ProjDataFromStream.cxx index 4383362725..e7e546265e 100644 --- a/src/buildblock/ProjDataFromStream.cxx +++ b/src/buildblock/ProjDataFromStream.cxx @@ -43,7 +43,6 @@ #include "stir/warning.h" #include "stir/error.h" -using std::find; using std::ios; using std::iostream; using std::streamoff; @@ -53,12 +52,6 @@ using std::cerr; using std::endl; using std::vector; -#ifdef _MSC_VER -// work-around for compiler bug: VC messes up std namespace -#define FIND std::find -#else -#define FIND find -#endif START_NAMESPACE_STIR //--------------------------------------------------------- @@ -431,7 +424,7 @@ ProjDataFromStream::get_offset(const Bin& this_bin) const error("ProjDataFromStream::get_offset: timing_num out of range : %d", this_bin.timing_pos_num()); const int index = - static_cast(FIND(segment_sequence.begin(), segment_sequence.end(), this_bin.segment_num()) - + static_cast(std::find(segment_sequence.begin(), segment_sequence.end(), this_bin.segment_num()) - segment_sequence.begin()); streamoff num_axial_pos_offset = 0; @@ -453,7 +446,7 @@ ProjDataFromStream::get_offset(const Bin& this_bin) const if (proj_data_info_sptr->get_num_tof_poss() > 1) { // The timing offset will be added to the segment offset to minimise the changes - const int timing_index = static_cast(FIND(timing_poss_sequence.begin(), timing_poss_sequence.end(), this_bin.timing_pos_num()) - + const int timing_index = static_cast(std::find(timing_poss_sequence.begin(), timing_poss_sequence.end(), this_bin.timing_pos_num()) - timing_poss_sequence.begin()); assert(offset_3d_data > 0); @@ -485,7 +478,7 @@ ProjDataFromStream::get_offset(const Bin& this_bin) const if (proj_data_info_sptr->get_num_tof_poss() > 1) { // The timing offset will be added to the segment offset. This approach we minimise the changes - const int timing_index = static_cast(FIND(timing_poss_sequence.begin(), timing_poss_sequence.end(), this_bin.timing_pos_num()) - + const int timing_index = static_cast(std::find(timing_poss_sequence.begin(), timing_poss_sequence.end(), this_bin.timing_pos_num()) - timing_poss_sequence.begin()); assert(offset_3d_data > 0); diff --git a/src/buildblock/Scanner.cxx b/src/buildblock/Scanner.cxx index ced4e21e91..6efbc8ca8f 100644 --- a/src/buildblock/Scanner.cxx +++ b/src/buildblock/Scanner.cxx @@ -1473,11 +1473,7 @@ string Scanner::list_names() const { std::ostringstream s; -#ifdef _MSC_VER - // work-around VC bug - std:: -#endif - list::const_iterator iterator = list_of_names.begin(); + auto iterator = list_of_names.begin(); s << *iterator; ++iterator; while(iterator!=list_of_names.end()) From e54a17a7171041f144a5ec1b736ffb68a5cc0e3b Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Mon, 29 Jan 2024 09:02:48 +0000 Subject: [PATCH 496/509] updated release notes --- documentation/release_6.0.htm | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/documentation/release_6.0.htm b/documentation/release_6.0.htm index 144cb985d0..a1e186d639 100644 --- a/documentation/release_6.0.htm +++ b/documentation/release_6.0.htm @@ -249,6 +249,14 @@

    C++ tests

    +

    New deprecations for future versions

    +
      +
    • + CMake option STIR_USE_BOOST_SHARED_PTR will be removed. It probably no longer + works anyway. Therefore stir::shared_ptr will always be std::shared_ptr. +
    • +
    +

    What's new for developers (aside from what should be obvious from the above):

    @@ -345,6 +353,15 @@

    TOF related

    +

    Code clean-up

    + +
      +
    • + Clean-up of various work-arounds such as + STIR_NO_NAMESPACES, STIR_NO_MUTABLE, BOOST_NO_TEMPLATE_SPECIALIZATION, + BOOST_NO_STRINGSTREAM and various items specifically for VC 6.0. +
    • +
    From 9429f5e398fea8ab93c60deadce9e0cc42c96d23 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Mon, 29 Jan 2024 08:34:27 +0000 Subject: [PATCH 497/509] removed unique_ptr workarounds --- src/include/stir/unique_ptr.h | 46 ----------------------------------- 1 file changed, 46 deletions(-) diff --git a/src/include/stir/unique_ptr.h b/src/include/stir/unique_ptr.h index a4d84b86a6..dafdca7ae8 100644 --- a/src/include/stir/unique_ptr.h +++ b/src/include/stir/unique_ptr.h @@ -26,55 +26,9 @@ // include this as stir/common.h has to be included by any stir .h file #include "stir/common.h" #include -#if !defined(STIR_NO_UNIQUE_PTR) // simply use std::unique_ptr namespace stir { using std::unique_ptr; } -#else -// we need to replace it with something else - -// KT attempted to use boost::movelib::unique_ptr, which is claimed to be compatible with -// std::unique_ptr but even for Cxx03 compilers. However, end 2016 this still generated errors on OSX Sierra -// with CLang (it could not return a unique_ptr). -// So, this is attempt is now disabled. -#if 0 -#include -#if (BOOST_VERSION >= 105700) -// Boost is recent enough to have a drop-in replacement -#include -namespace stir { - using boost::movelib::unique_ptr; -} -#endif -#endif - -// desperate measures. We will use a #define to auto_ptr. -// Caveat: -// This trick is likely to break on OSX as it will generate conflicts between -// this define and Apple's non-C++-11 compliant unique_ptr. -// (Reason: BOOST_NO_CXX11_SMART_PTR is set, so we define unique_ptr here, but some include files will still -// have unique_ptr with 2 template arguments, while auto_ptr needs only one). -// Best solution: tell your compiler to use C++-11 or later (normally this means adding -std=c++11) - -// We first include a bunch of system files which use std::unique_ptr such that we don't have a conflict. -// You might have to add a few more... -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define unique_ptr auto_ptr -using std::auto_ptr; -#endif #endif From 6bd669243eba5989e52a97384dfbadbe9966acc4 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Mon, 29 Jan 2024 09:34:17 +0000 Subject: [PATCH 498/509] minor update to clang-format constructor settings --- .clang-format | 1 + 1 file changed, 1 insertion(+) diff --git a/.clang-format b/.clang-format index 0d026b17ac..1606cc0cd9 100644 --- a/.clang-format +++ b/.clang-format @@ -31,5 +31,6 @@ PointerAlignment: Left SortIncludes: false SortUsingDeclarations: false SpaceBeforeParens: ControlStatements +PackConstructorInitializers: Never Standard: Cpp11 ... From 8c80439b9c8701aa9c9a6da2b3e898ccc29e328d Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Wed, 31 Jan 2024 17:53:32 +0000 Subject: [PATCH 499/509] remove forward_projector from distributable_sensitivity_computation It doesn't need it, and in previous versions, calling forward_projector::set_input could actually do a lot of work (e.g. in the current GPU versions) --- ...nLogLikelihoodWithLinearModelForMeanAndProjData.cxx | 7 +++---- src/recon_buildblock/distributable.cxx | 10 +++++++--- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx index 648b21040b..179149bd82 100644 --- a/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx +++ b/src/recon_buildblock/PoissonLogLikelihoodWithLinearModelForMeanAndProjData.cxx @@ -1,7 +1,7 @@ /* Copyright (C) 2000 PARAPET partners Copyright (C) 2000-2011, Hammersmith Imanet Ltd - Copyright (C) 2014, 2016-2023 University College London + Copyright (C) 2014, 2016-2024 University College London This file is part of STIR. SPDX-License-Identifier: Apache-2.0 AND License-ref-PARAPET-license @@ -884,7 +884,7 @@ add_subset_sensitivity(TargetT& sensitivity, const int subset_num) const this->ensure_norm_is_set_up_for_sensitivity(); - distributable_sensitivity_computation(this->projector_pair_ptr->get_forward_projector_sptr(), + distributable_sensitivity_computation( this->sens_backprojector_sptr, this->sens_symmetries_sptr, *sensitivity_this_subset_sptr, @@ -1288,7 +1288,6 @@ void distributable_accumulate_loglikelihood( } void distributable_sensitivity_computation( - const shared_ptr& forward_projector_sptr, const shared_ptr& back_projector_sptr, const shared_ptr& symmetries_sptr, DiscretisedDensity<3,float>& sensitivity, @@ -1307,7 +1306,7 @@ void distributable_sensitivity_computation( ) { - distributable_computation(forward_projector_sptr, + distributable_computation(0, back_projector_sptr, symmetries_sptr, &sensitivity, &input_image, diff --git a/src/recon_buildblock/distributable.cxx b/src/recon_buildblock/distributable.cxx index b0285cc15d..5dfa6ed088 100644 --- a/src/recon_buildblock/distributable.cxx +++ b/src/recon_buildblock/distributable.cxx @@ -405,8 +405,12 @@ void distributable_computation( #endif //double total_seq_rpc_time=0.0; //sums up times used for RPC_process_related_viewgrams - forward_projector_ptr->set_input(*input_image_ptr); - if (output_image_ptr != NULL) + // the RPC function might not need to do a forward projection, e.g. for sensitivity + // so test if it exists before doing anything with it + if (input_image_ptr && forward_projector_ptr) + forward_projector_ptr->set_input(*input_image_ptr); + // same for backprojector + if (output_image_ptr && back_projector_ptr) back_projector_ptr->start_accumulating_in_new_target(); #ifdef STIR_OPENMP @@ -518,7 +522,7 @@ void distributable_computation( count2 += std::accumulate(local_count2s.begin(), local_count2s.end(), 0); } #endif - if (output_image_ptr != NULL) + if (output_image_ptr && back_projector_ptr) back_projector_ptr->get_output(*output_image_ptr); #ifdef STIR_MPI //end of iteration processing From dd5953e12d5508b0e06289df822e8c7f4770a91d Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Wed, 31 Jan 2024 23:10:09 +0000 Subject: [PATCH 500/509] [CMake] install in versioned locations and don't cache STIR_CONFIG_DIR This breaks backwards compatibility in some sense. New locations are indicated in the release notes. --- CMakeLists.txt | 19 +++++++++++-------- documentation/release_6.0.htm | 31 +++++++++++++++++++++++++++++-- src/CMakeLists.txt | 4 ++-- src/cmake/stir_lib_target.cmake | 2 +- 4 files changed, 43 insertions(+), 13 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1ba8a58015..f5e1e05488 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -63,21 +63,24 @@ set(STIR_VERSION ####### Installation directories # Note: using absolute paths for those variables that are used in STIRConfig.h.in +set(STIR_VERSION_MM STIR-${VERSION_MAJOR}.${VERSION_MINOR}) # Use BeOS locations as in # https://gitlab.kitware.com/cmake/cmake/blob/master/Source/CMakeInstallDestinations.cmake # However, use CYGWIN location also elsewhere (as that's what's used in Ubuntu (and others?)) if(BEOS) - set(STIR_DOC_DIR "${CMAKE_INSTALL_PREFIX}/documentation/doc/stir-${VERSION_MAJOR}.${VERSION_MINOR}") + set(STIR_DOC_DIR "${CMAKE_INSTALL_PREFIX}/documentation/doc/${STIR_VERSION_MM}") else() #if(CYGWIN) - set(STIR_DOC_DIR "${CMAKE_INSTALL_PREFIX}/share/doc/stir-${VERSION_MAJOR}.${VERSION_MINOR}") + set(STIR_DOC_DIR "${CMAKE_INSTALL_PREFIX}/share/doc/${STIR_VERSION_MM}") #else() -# set(STIR_DOC_DIR "doc/stir-${VERSION_MAJOR}.${VERSION_MINOR}") +# set(STIR_DOC_DIR "doc/${STIR_VERSION_MM}") endif() -# TODO append version numbers -set(STIR_DATA_DIR "share/stir") -set(ConfigPackageLocation lib/cmake/) -set(STIR_CONFIG_DIR "${CMAKE_INSTALL_PREFIX}/share/stir/config" CACHE PATH "") +set(STIR_DATA_DIR "share/${STIR_VERSION_MM}") +set(ConfigPackageLocation "lib/cmake/${STIR_VERSION_MM}") +# installed json files +set(STIR_CONFIG_DIR "${CMAKE_INSTALL_PREFIX}/share/${STIR_VERSION_MM}/config") +# include files +set(STIR_INCLUDE_INSTALL_DIR "include/${STIR_VERSION_MM}") ####### External packages # Note: we need to have the find_package statements in the top-level CMakeLists.txt @@ -230,7 +233,7 @@ WRITE_BASIC_PACKAGE_VERSION_FILE(${CMAKE_CURRENT_BINARY_DIR}/STIRConfigVersion.c # Set STIR_INCLUDE_DIRS before exporting such that it will refer to # the installed files, not the source. -set (STIR_INCLUDE_DIRS "include") +set (STIR_INCLUDE_DIRS "${STIR_INCLUDE_INSTALL_DIR}") # Older CMake cannot import or export object libraries. So we have to export diff --git a/documentation/release_6.0.htm b/documentation/release_6.0.htm index a1e186d639..55c668c7a3 100644 --- a/documentation/release_6.0.htm +++ b/documentation/release_6.0.htm @@ -9,7 +9,8 @@

    Summary of changes in STIR release 6.0

    This version is 99% backwards compatible with STIR 5.x for the user (see below). Developers might need to make code changes as - detailed below. + detailed below. Note though that the location of installed files have changed. + Developers of other software that uses STIR via CMake will need to adapt (see below).

    Overall summary

    @@ -58,6 +59,14 @@

    Changes breaking backwards compatibility from a user-perspective

  • (deprecated) support for the AVW format via the (very old) AnalyzeAVW commercial library has been removed.
  • +
  • Most installed files are now in versioned directories. The following shows the new and old locations + relative to CMAKE_INSTALL_PREFIX, where V.v indicates the major.minor version number, e.g. 6.0: +
      +
    • documentation (including examples as subfolder): share/doc/STIR-V.v (was share/doc/stir-V.v)
    • +
    • JSON files with radionuclide database: share/STIR-V.v/config (was share/stir/config)
    • +
    + Developers also need to check the new location to use for STIR_DIR documented below. +
  • Bug fixes

    @@ -131,7 +140,7 @@

    General

  • list_lm_events now has an additional option --event-bin which lists the bin assigned for the event (according to the "native" projection data, i.e. without any mashing).
    - In addition, the --event-LOR option now also works for SPECT (it was disabled by accident). + In addition, the --event-LOR option now also works for SPECT (it was disabled by accident).
  • @@ -213,6 +222,11 @@

    Build system

    In fact, it is possible that C++-11 still works. If you really need it, you can try to modify the main CMakeLists.txt accordingly. +
  • STIR_CONFIG_DIR is no longer a CMake cached variable, such that + it automatically moves along with CMAKE_INSTALL_PREFIX. + However, if you are upgrading an existing STIR build, you might have + to delete the cached variable, or it will point to the old location. +
  • Known problems

    @@ -292,6 +306,19 @@

    Backward incompatibities

    +
  • As mentioned above, installation locations are now versioned. New locations that + could affect developers that use STIR as an external project: +
      +
    • include files: include/STIR-V.v (was include). This should be transparant + if you use find_package(STIR).
    • +
    • CMake exported STIRConfig.cmake etc: lib/cmake/STIR-V.v (was share/lib).
      + The CMake variable STIR_DIR should now be set to <STIR_CMAKE_INSTALL_PREFIX>/lib/cmake/STIR-V.v. + However, this new location increases chances that find_package finds STIR as it follows conventions better. + For instance, STIR can now by found by find_package + when setting CMAKE_PREFIX_PATH to what was used for CMAKE_INSTALL_PREFIX when + installing STIR (indicated as STIR_CMAKE_INSTALL_PREFIX above). Moreover, if you use the same + CMAKE_INSTALL_PREFIX for your project as for STIR, you shouldn't need to set STIR_DIR nor CMAKE_PREFIX_PATH. +

    New functionality

    diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3be9e35097..4101bf85d4 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -206,11 +206,11 @@ configure_file( "${CONF_INCLUDE_DIR}/stir/config.h" ) # add it to the install target -install(FILES "${CONF_INCLUDE_DIR}/stir/config.h" DESTINATION include/stir) +install(FILES "${CONF_INCLUDE_DIR}/stir/config.h" DESTINATION "${STIR_INCLUDE_INSTALL_DIR}/stir") #### install include files set (INCLUDE_DIR "${PROJECT_SOURCE_DIR}/src/include") -install(DIRECTORY "${INCLUDE_DIR}/" DESTINATION include) +install(DIRECTORY "${INCLUDE_DIR}/" DESTINATION "${STIR_INCLUDE_INSTALL_DIR}") #### find header files for re-use in stir_exe_targets.cmake and doxygen target include(FindAllHeaderFiles) diff --git a/src/cmake/stir_lib_target.cmake b/src/cmake/stir_lib_target.cmake index 841e558d6e..53481e4b0b 100644 --- a/src/cmake/stir_lib_target.cmake +++ b/src/cmake/stir_lib_target.cmake @@ -16,7 +16,7 @@ add_library(${dir} ${${dir_LIB_SOURCES}} ) target_include_directories(${dir} PUBLIC $ - $) + $) # make sure that if you use STIR, the compiler will be set to at least C++11 if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.8.0") From 3e70d90e5a5583f591d2d7641c6af7a7f1a95c03 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Wed, 31 Jan 2024 23:35:50 +0000 Subject: [PATCH 501/509] add stir_list_registries utility --- documentation/STIR-UsersGuide.tex | 3 ++ documentation/release_6.0.htm | 6 ++- src/utilities/CMakeLists.txt | 1 + src/utilities/stir_list_registries.cxx | 71 ++++++++++++++++++++++++++ 4 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 src/utilities/stir_list_registries.cxx diff --git a/documentation/STIR-UsersGuide.tex b/documentation/STIR-UsersGuide.tex index 9d0f029a77..74d30c42da 100644 --- a/documentation/STIR-UsersGuide.tex +++ b/documentation/STIR-UsersGuide.tex @@ -2872,6 +2872,9 @@ \subsubsection{Displaying STIR configuration and version information} (Equivalent functions in C++/Python are \texttt{get\_STIR\_config\_dir}, \texttt{get\_STIR\_doc\_dir} and \texttt{get\_STIR\_examples\_dir}). +\textbf{stir\_list\_registries} is a utility that list possible values of various +registries, which can be useful to know what to use in a .par file. + \subsubsection{ Displaying and performing operations on data} diff --git a/documentation/release_6.0.htm b/documentation/release_6.0.htm index a1e186d639..ce23dc537c 100644 --- a/documentation/release_6.0.htm +++ b/documentation/release_6.0.htm @@ -132,7 +132,11 @@

    General

    list_lm_events now has an additional option --event-bin which lists the bin assigned for the event (according to the "native" projection data, i.e. without any mashing).
    In addition, the --event-LOR option now also works for SPECT (it was disabled by accident). -
  • + +
  • + stir_list_registries is a new utility that list possible values of various + registries, which can be useful to know what to use in a .par file. +
  • Python (and MATLAB)

    diff --git a/src/utilities/CMakeLists.txt b/src/utilities/CMakeLists.txt index 5e5d3a8c0b..d279c024f7 100644 --- a/src/utilities/CMakeLists.txt +++ b/src/utilities/CMakeLists.txt @@ -58,6 +58,7 @@ set(${dir_EXE_SOURCES} conv_interfile_to_gipl.cxx shift_image.cxx stir_config.cxx + stir_list_registries.cxx shift_image_origin.cxx warp_and_accumulate_gated_images.cxx warp_image.cxx diff --git a/src/utilities/stir_list_registries.cxx b/src/utilities/stir_list_registries.cxx new file mode 100644 index 0000000000..82d5125083 --- /dev/null +++ b/src/utilities/stir_list_registries.cxx @@ -0,0 +1,71 @@ +/*! + + Copyright (C) 2024 University College London + This file is part of STIR. + + SPDX-License-Identifier: Apache-2.0 + + See STIR/LICENSE.txt for details + \file + \ingroup utilities + \brief Prints all registered names for many registries + + \author Kris Thielemans + */ + +#include "stir/DiscretisedDensity.h" +#include "stir/modelling/ParametricDiscretisedDensity.h" +#include "stir/DynamicDiscretisedDensity.h" +#include "stir/DataProcessor.h" +#include "stir/recon_buildblock/ForwardProjectorByBin.h" +#include "stir/recon_buildblock/BackProjectorByBin.h" +#include "stir/recon_buildblock/ProjectorByBinPair.h" +#include "stir/recon_buildblock/ProjMatrixByBin.h" +#include "stir/recon_buildblock/GeneralisedObjectiveFunction.h" +#include "stir/recon_buildblock/GeneralisedPrior.h" +#include "stir/recon_buildblock/Reconstruction.h" +#include "stir/recon_buildblock/BinNormalisation.h" +#include +#include + +USING_NAMESPACE_STIR + +int +main(int argc, char *argv[]) +{ + std::cout << "------------ ProjectorByBinPair --------------\n"; + ProjectorByBinPair::list_registered_names(std::cout); + std::cout << "------------ ForwardProjectorByBin --------------\n"; + ForwardProjectorByBin::list_registered_names(std::cout); + std::cout << "------------ BackProjectorByBin --------------\n"; + BackProjectorByBin::list_registered_names(std::cout); + std::cout << "------------ ProjMatrixByBin --------------\n"; + ProjMatrixByBin::list_registered_names(std::cout); + std::cout << "------------ BinNormalisation --------------\n"; + BinNormalisation::list_registered_names(std::cout); + + std::cout < "--------------------------------------------------------------------------\n"; + + std::cout << "------------ DataProcessor> --------------\n"; + DataProcessor >::list_registered_names(std::cout); + std::cout << "------------ GeneralisedObjectiveFunction> --------------\n"; + GeneralisedObjectiveFunction>::list_registered_names(std::cout); + std::cout << "------------ GeneralisedPrior> --------------\n"; + GeneralisedPrior>::list_registered_names(std::cout); + std::cout << "------------ Reconstruction> --------------\n"; + Reconstruction>::list_registered_names(std::cout); + + std::cout < "--------------------------------------------------------------------------\n"; + + std::cout << "------------ DataProcessor --------------\n"; + DataProcessor::list_registered_names(std::cout); + std::cout << "------------ GeneralisedObjectiveFunction --------------\n"; + GeneralisedObjectiveFunction::list_registered_names(std::cout); + std::cout << "------------ GeneralisedPrior --------------\n"; + GeneralisedPrior::list_registered_names(std::cout); + std::cout << "------------ Reconstruction --------------\n"; + Reconstruction::list_registered_names(std::cout); + + + return EXIT_SUCCESS; +} From 331a03c934f9d2300700b861d8b2433dd3c0f65f Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Wed, 31 Jan 2024 23:53:30 +0000 Subject: [PATCH 502/509] [CMake] add extensions of C++ demos --- examples/C++/src/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/C++/src/CMakeLists.txt b/examples/C++/src/CMakeLists.txt index 3e3308501f..9ef4f3fae7 100644 --- a/examples/C++/src/CMakeLists.txt +++ b/examples/C++/src/CMakeLists.txt @@ -5,7 +5,7 @@ set(dir examples) set(dir_EXE_SOURCES ${dir}_EXE_SOURCES) set(${dir_EXE_SOURCES} - demo1 demo2 demo3 demo4_obj_fun demo5_line_search + demo1.cxx demo2.cxx demo3.cxx demo4_obj_fun.cxx demo5_line_search.cxx ) From 135f0c14bcd5fe715ac371d20d06c3a761f77d9f Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Wed, 31 Jan 2024 23:36:24 +0000 Subject: [PATCH 503/509] add reconstructions for parametric images to registries They aren't directly used, but could be, and are now listed by stir_list_registries. --- src/recon_buildblock/recon_buildblock_registries.cxx | 3 +++ src/utilities/stir_list_registries.cxx | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/recon_buildblock/recon_buildblock_registries.cxx b/src/recon_buildblock/recon_buildblock_registries.cxx index 3bdc5d25b9..48c8309c84 100644 --- a/src/recon_buildblock/recon_buildblock_registries.cxx +++ b/src/recon_buildblock/recon_buildblock_registries.cxx @@ -129,6 +129,9 @@ static OSMAPOSLReconstruction >::RegisterIt dummy603 static KOSMAPOSLReconstruction >::RegisterIt dummyK ; static OSSPSReconstruction >::RegisterIt dummy604; +static OSMAPOSLReconstruction::RegisterIt dummyOSMAPOSLPVC; +static OSSPSReconstruction::RegisterIt dummyOSSPSPVC; + #ifdef STIR_WITH_NiftyPET_PROJECTOR static ForwardProjectorByBinNiftyPET::RegisterIt gpu_fwd; static BackProjectorByBinNiftyPET::RegisterIt gpu_bck; diff --git a/src/utilities/stir_list_registries.cxx b/src/utilities/stir_list_registries.cxx index 82d5125083..dc5ecfd901 100644 --- a/src/utilities/stir_list_registries.cxx +++ b/src/utilities/stir_list_registries.cxx @@ -44,7 +44,7 @@ main(int argc, char *argv[]) std::cout << "------------ BinNormalisation --------------\n"; BinNormalisation::list_registered_names(std::cout); - std::cout < "--------------------------------------------------------------------------\n"; + std::cout << "--------------------------------------------------------------------------\n"; std::cout << "------------ DataProcessor> --------------\n"; DataProcessor >::list_registered_names(std::cout); @@ -55,7 +55,7 @@ main(int argc, char *argv[]) std::cout << "------------ Reconstruction> --------------\n"; Reconstruction>::list_registered_names(std::cout); - std::cout < "--------------------------------------------------------------------------\n"; + std::cout << "--------------------------------------------------------------------------\n"; std::cout << "------------ DataProcessor --------------\n"; DataProcessor::list_registered_names(std::cout); From 4ee1583e5bf307e6b54b999062263e30cdedde01 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Wed, 31 Jan 2024 23:59:47 +0000 Subject: [PATCH 504/509] moved C++ examples from src to using_STIR_LOCAL --- .github/workflows/build-test.yml | 4 ++-- examples/C++/src/extra_stir_dirs.cmake | 10 ---------- examples/C++/{src => using_STIR_LOCAL}/CMakeLists.txt | 0 examples/C++/{src => using_STIR_LOCAL}/README.md | 0 examples/C++/{src => using_STIR_LOCAL}/demo.par | 0 examples/C++/{src => using_STIR_LOCAL}/demo1.cxx | 0 examples/C++/{src => using_STIR_LOCAL}/demo2.cxx | 0 examples/C++/{src => using_STIR_LOCAL}/demo3.cxx | 0 .../C++/{src => using_STIR_LOCAL}/demo4_obj_fun.cxx | 0 .../C++/{src => using_STIR_LOCAL}/demo4_obj_fun.par | 0 .../{src => using_STIR_LOCAL}/demo5_line_search.cxx | 0 .../{src => using_STIR_LOCAL}/demo5_line_search.par | 0 examples/C++/{src => using_STIR_LOCAL}/demoPM.par | 0 examples/C++/using_STIR_LOCAL/extra_stir_dirs.cmake | 11 +++++++++++ .../C++/{src => using_STIR_LOCAL}/generate_image.par | 0 examples/C++/{src => using_STIR_LOCAL}/small.hs | 0 examples/C++/{src => using_STIR_LOCAL}/small.s | 0 17 files changed, 13 insertions(+), 12 deletions(-) delete mode 100644 examples/C++/src/extra_stir_dirs.cmake rename examples/C++/{src => using_STIR_LOCAL}/CMakeLists.txt (100%) rename examples/C++/{src => using_STIR_LOCAL}/README.md (100%) rename examples/C++/{src => using_STIR_LOCAL}/demo.par (100%) rename examples/C++/{src => using_STIR_LOCAL}/demo1.cxx (100%) rename examples/C++/{src => using_STIR_LOCAL}/demo2.cxx (100%) rename examples/C++/{src => using_STIR_LOCAL}/demo3.cxx (100%) rename examples/C++/{src => using_STIR_LOCAL}/demo4_obj_fun.cxx (100%) rename examples/C++/{src => using_STIR_LOCAL}/demo4_obj_fun.par (100%) rename examples/C++/{src => using_STIR_LOCAL}/demo5_line_search.cxx (100%) rename examples/C++/{src => using_STIR_LOCAL}/demo5_line_search.par (100%) rename examples/C++/{src => using_STIR_LOCAL}/demoPM.par (100%) create mode 100644 examples/C++/using_STIR_LOCAL/extra_stir_dirs.cmake rename examples/C++/{src => using_STIR_LOCAL}/generate_image.par (100%) rename examples/C++/{src => using_STIR_LOCAL}/small.hs (100%) rename examples/C++/{src => using_STIR_LOCAL}/small.s (100%) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index c7b200449a..9e99d87e10 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -282,7 +282,7 @@ jobs: EXTRA_BUILD_FLAGS="-DBUILD_SWIG_PYTHON=ON -DPYTHON_EXECUTABLE=`which python`" EXTRA_BUILD_FLAGS="${EXTRA_BUILD_FLAGS} -DCMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX} -DCMAKE_BUILD_TYPE=${BUILD_TYPE}" EXTRA_BUILD_FLAGS="${EXTRA_BUILD_FLAGS} -DDOWNLOAD_ZENODO_TEST_DATA=ON" - EXTRA_BUILD_FLAGS="${EXTRA_BUILD_FLAGS} -DDISABLE_STIR_LOCAL=OFF -DSTIR_LOCAL=${GITHUB_WORKSPACE}/examples/C++/src" + EXTRA_BUILD_FLAGS="${EXTRA_BUILD_FLAGS} -DDISABLE_STIR_LOCAL=OFF -DSTIR_LOCAL=${GITHUB_WORKSPACE}/examples/C++/using_STIR_LOCAL" echo "cmake flags $BUILD_FLAGS $EXTRA_BUILD_FLAGS" mkdir build cd build @@ -370,7 +370,7 @@ jobs: # Run the Demo executables EXE_LOC=${GITHUB_WORKSPACE}/build/src/examples/C++ - cd ${GITHUB_WORKSPACE}/examples/C++/src + cd ${GITHUB_WORKSPACE}/examples/C++/using_STIR_LOCAL generate_image generate_image.par forward_project sino.hs image.hv small.hs diff --git a/examples/C++/src/extra_stir_dirs.cmake b/examples/C++/src/extra_stir_dirs.cmake deleted file mode 100644 index 31be42dae5..0000000000 --- a/examples/C++/src/extra_stir_dirs.cmake +++ /dev/null @@ -1,10 +0,0 @@ -# -# -# sample file that includes the examples directory into the STIR build. -# You should copy this file to STIR/src/local (unless you have one there already, in -# which case you need to edit that file instead of course). -# See also examples/src/README.md and the section on how to extend STIR with your own -# files in the STIR developer's Guide. - - -add_subdirectory(${PROJECT_SOURCE_DIR}/examples/C++/src examples/C++) diff --git a/examples/C++/src/CMakeLists.txt b/examples/C++/using_STIR_LOCAL/CMakeLists.txt similarity index 100% rename from examples/C++/src/CMakeLists.txt rename to examples/C++/using_STIR_LOCAL/CMakeLists.txt diff --git a/examples/C++/src/README.md b/examples/C++/using_STIR_LOCAL/README.md similarity index 100% rename from examples/C++/src/README.md rename to examples/C++/using_STIR_LOCAL/README.md diff --git a/examples/C++/src/demo.par b/examples/C++/using_STIR_LOCAL/demo.par similarity index 100% rename from examples/C++/src/demo.par rename to examples/C++/using_STIR_LOCAL/demo.par diff --git a/examples/C++/src/demo1.cxx b/examples/C++/using_STIR_LOCAL/demo1.cxx similarity index 100% rename from examples/C++/src/demo1.cxx rename to examples/C++/using_STIR_LOCAL/demo1.cxx diff --git a/examples/C++/src/demo2.cxx b/examples/C++/using_STIR_LOCAL/demo2.cxx similarity index 100% rename from examples/C++/src/demo2.cxx rename to examples/C++/using_STIR_LOCAL/demo2.cxx diff --git a/examples/C++/src/demo3.cxx b/examples/C++/using_STIR_LOCAL/demo3.cxx similarity index 100% rename from examples/C++/src/demo3.cxx rename to examples/C++/using_STIR_LOCAL/demo3.cxx diff --git a/examples/C++/src/demo4_obj_fun.cxx b/examples/C++/using_STIR_LOCAL/demo4_obj_fun.cxx similarity index 100% rename from examples/C++/src/demo4_obj_fun.cxx rename to examples/C++/using_STIR_LOCAL/demo4_obj_fun.cxx diff --git a/examples/C++/src/demo4_obj_fun.par b/examples/C++/using_STIR_LOCAL/demo4_obj_fun.par similarity index 100% rename from examples/C++/src/demo4_obj_fun.par rename to examples/C++/using_STIR_LOCAL/demo4_obj_fun.par diff --git a/examples/C++/src/demo5_line_search.cxx b/examples/C++/using_STIR_LOCAL/demo5_line_search.cxx similarity index 100% rename from examples/C++/src/demo5_line_search.cxx rename to examples/C++/using_STIR_LOCAL/demo5_line_search.cxx diff --git a/examples/C++/src/demo5_line_search.par b/examples/C++/using_STIR_LOCAL/demo5_line_search.par similarity index 100% rename from examples/C++/src/demo5_line_search.par rename to examples/C++/using_STIR_LOCAL/demo5_line_search.par diff --git a/examples/C++/src/demoPM.par b/examples/C++/using_STIR_LOCAL/demoPM.par similarity index 100% rename from examples/C++/src/demoPM.par rename to examples/C++/using_STIR_LOCAL/demoPM.par diff --git a/examples/C++/using_STIR_LOCAL/extra_stir_dirs.cmake b/examples/C++/using_STIR_LOCAL/extra_stir_dirs.cmake new file mode 100644 index 0000000000..8c8bf778db --- /dev/null +++ b/examples/C++/using_STIR_LOCAL/extra_stir_dirs.cmake @@ -0,0 +1,11 @@ +# +# +# sample file that includes the examples directory into the STIR build. +# You should copy this file to STIR/src/local (unless you have one there already, in +# which case you need to edit that file instead of course), or set the STIR_LOCAL CMake variable. +# +# See also examples/using_STIR_LOCAL/README.md and the section on how to extend STIR with your own +# files in the STIR developer's Guide. + + +add_subdirectory(${PROJECT_SOURCE_DIR}/examples/C++/using_STIR_LOCAL examples/C++/using_STIR_LOCAL) diff --git a/examples/C++/src/generate_image.par b/examples/C++/using_STIR_LOCAL/generate_image.par similarity index 100% rename from examples/C++/src/generate_image.par rename to examples/C++/using_STIR_LOCAL/generate_image.par diff --git a/examples/C++/src/small.hs b/examples/C++/using_STIR_LOCAL/small.hs similarity index 100% rename from examples/C++/src/small.hs rename to examples/C++/using_STIR_LOCAL/small.hs diff --git a/examples/C++/src/small.s b/examples/C++/using_STIR_LOCAL/small.s similarity index 100% rename from examples/C++/src/small.s rename to examples/C++/using_STIR_LOCAL/small.s From 5f1a903d671d9804545d514013f2a6c4af20fe21 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Thu, 1 Feb 2024 00:12:03 +0000 Subject: [PATCH 505/509] added examples/C++/using_installed_STIR/ --- .github/workflows/build-test.yml | 9 +++ documentation/release_6.0.htm | 7 +++ examples/C++/README.md | 7 +++ examples/C++/using_installed_STIR/.gitignore | 2 + .../C++/using_installed_STIR/CMakeLists.txt | 33 ++++++++++ examples/C++/using_installed_STIR/README.md | 44 ++++++++++++++ .../demo_create_image.cxx | 60 +++++++++++++++++++ 7 files changed, 162 insertions(+) create mode 100644 examples/C++/README.md create mode 100644 examples/C++/using_installed_STIR/.gitignore create mode 100644 examples/C++/using_installed_STIR/CMakeLists.txt create mode 100644 examples/C++/using_installed_STIR/README.md create mode 100644 examples/C++/using_installed_STIR/demo_create_image.cxx diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 9e99d87e10..a22b1feeb7 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -381,6 +381,15 @@ jobs: ${EXE_LOC}/demo4_obj_fun demo4_obj_fun.par ${EXE_LOC}/demo5_line_search demo5_line_search.par + # build and run C++/using_installed_STIR + cd ${GITHUB_WORKSPACE}/examples/C++/using_installed_STIR + cmake -S . -B build/ -DCMAKE_INSTALL_PREFIX:PATH=${CMAKE_INSTALL_PREFIX} + cmake --build build/ --config Release --target install + # run demo + demo_create_image + # just check if its output makes sense + list_image_info test.hv + - name: Python shell: bash run: | diff --git a/documentation/release_6.0.htm b/documentation/release_6.0.htm index 55c668c7a3..0f0adf940b 100644 --- a/documentation/release_6.0.htm +++ b/documentation/release_6.0.htm @@ -243,6 +243,13 @@

    Documentation changes

  • Added (some) documentation on TOF features
  • +
  • + Added examples/C++/using_installed_STIR to illustrate how to use STIR + as a "library". +
  • +
  • + Renamed examples/C++/src to examples/C++/using_STIR_LOCAL. +
  • Test changes

    diff --git a/examples/C++/README.md b/examples/C++/README.md new file mode 100644 index 0000000000..6926c1b548 --- /dev/null +++ b/examples/C++/README.md @@ -0,0 +1,7 @@ +This directory contains some files that are intended for beginning STIR C++ developers. + +- `General_Reconstruction`: (incomplete) example files to make a general reconstruction program +where the reconstruction algorithm can be selected in a `.par` file +- `using_installed_STIR`: example files for using STIR as an external library in a +new C++ project that uses CMake. +- `using_STIR_LOCAL`: example files that use the `STIR_LOCAL` extension mechanism. \ No newline at end of file diff --git a/examples/C++/using_installed_STIR/.gitignore b/examples/C++/using_installed_STIR/.gitignore new file mode 100644 index 0000000000..728a8b0e4f --- /dev/null +++ b/examples/C++/using_installed_STIR/.gitignore @@ -0,0 +1,2 @@ +build/ +test*v \ No newline at end of file diff --git a/examples/C++/using_installed_STIR/CMakeLists.txt b/examples/C++/using_installed_STIR/CMakeLists.txt new file mode 100644 index 0000000000..d477a3a14f --- /dev/null +++ b/examples/C++/using_installed_STIR/CMakeLists.txt @@ -0,0 +1,33 @@ + +#======================================================================== +# Author: Kris Thielemans +# Copyright 2016 - 2020 University College London +# Copyright 2016 - 2020 Science Technology Facilities Council +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0.txt +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +#========================================================================= + +# cmake file for building SIRF. See the SIRF User's Guide and http://www.cmake.org. + +cmake_minimum_required(VERSION 3.14.0) + +PROJECT(myproject) + +find_package(STIR 6.0 REQUIRED CONFIG) + +add_executable(demo_create_image demo_create_image.cxx) +target_include_directories(demo_create_image PUBLIC "${STIR_INCLUDE_DIRS}") +target_link_libraries(demo_create_image PUBLIC "${STIR_LIBRARIES}") + +install(TARGETS demo_create_image DESTINATION bin) diff --git a/examples/C++/using_installed_STIR/README.md b/examples/C++/using_installed_STIR/README.md new file mode 100644 index 0000000000..97ed81de4f --- /dev/null +++ b/examples/C++/using_installed_STIR/README.md @@ -0,0 +1,44 @@ +This directory contains some files that are intended for beginning STIR developers. +They illustrate how to use STIR in an external CMake project, as well as a minimal basic +C++ example. + +----------------- + +# Source code files + +`CMakeLists.txt`: CMake file to use + +`demo_create_image.cxx`: Simple program that creates an image with a shape. + +# Build instructions +This works as normal for CMake. The only thing to take care of is to let +`find_package` find the installed STIR. Below uses the `cmake` command line +utility, but the GUI should work similarly, although there it is probably +most convenient to set `STIR_DIR` (see below). + +## Configuring + +If you installed STIR to `~/devel/install` and want to install this demo in the same place, +you should be able to do +```sh +cmake -S . -B build/ -DCMAKE_INSTALL_PREFIX:PATH=~/devel/install +``` +If you want to install into a different location, you can do +```sh +cmake -S . -B build/ -DCMAKE_INSTALL_PREFIX:PATH=~/devel/install_demo -DCMAKE_PREFIX_PATH:PATH=~/devel/install +``` + +If CMake doesn't find `STIRConfig.cmake` anyway, you will have to set `STIR_DIR=~/devel/install/lib/cmake/STIR-6.0` (or whatever version). + +## Building and installing +Then you can +```sh +cmake --build build/ --config Release --target install +``` + +# What now ? +Play around, incorporate demos in the other C++ examples and add them to the current `CMakeLists.txt`, ec. + +Good luck + +Kris Thielemans diff --git a/examples/C++/using_installed_STIR/demo_create_image.cxx b/examples/C++/using_installed_STIR/demo_create_image.cxx new file mode 100644 index 0000000000..bd50492700 --- /dev/null +++ b/examples/C++/using_installed_STIR/demo_create_image.cxx @@ -0,0 +1,60 @@ +#include "stir/Shape/EllipsoidalCylinder.h" +#include "stir/PatientPosition.h" +#include "stir/ImagingModality.h" +#include "stir/CartesianCoordinate3D.h" +#include "stir/IndexRange3D.h" +#include "stir/Succeeded.h" +#include "stir/IO/write_to_file.h" +#include "stir/VoxelsOnCartesianGrid.h" +#include + +int main() +{ + + std::string output_filename("test.hv"); + int output_image_size_x = 100; + int output_image_size_y = 104; + int output_image_size_z = 20; + float output_voxel_size_x = 5.F; + float output_voxel_size_y = 4.F; + float output_voxel_size_z = 3.F; + double image_duration = 100.; // secs + double rel_start_time = 1; //secs + + auto exam_info_sptr = std::make_shared(stir::ImagingModality::PT); + { + std::vector start_times(1, rel_start_time); + std::vector durations(1, image_duration); + stir::TimeFrameDefinitions frame_defs; + frame_defs.set_num_time_frames(1); + frame_defs.set_time_frame(1, rel_start_time, image_duration); + exam_info_sptr->set_time_frame_definitions(frame_defs); + } + stir::VoxelsOnCartesianGrid + image(exam_info_sptr, + stir::IndexRange3D(0,output_image_size_z-1, + -(output_image_size_y/2), + -(output_image_size_y/2)+output_image_size_y-1, + -(output_image_size_x/2), + -(output_image_size_x/2)+output_image_size_x-1), + stir::CartesianCoordinate3D(0,0,0), + stir::CartesianCoordinate3D(output_voxel_size_z, + output_voxel_size_y, + output_voxel_size_x)); + + const auto centre = + image.get_physical_coordinates_for_indices(stir::make_coordinate(output_image_size_z/2,0,0)); + stir::EllipsoidalCylinder shape(40.F, 30.F, 20.F, centre); + shape.construct_volume(image, stir::make_coordinate(2,2,2)); + + try + { + stir::write_to_file(output_filename, image); + stir::info("Image written as " + output_filename); + } + catch (...) + { + return EXIT_FAILURE; + } + return EXIT_SUCCESS; +} From f1257cbc393c7c9dffc5b45588ccdc4967eae7b1 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Thu, 1 Feb 2024 07:50:01 +0000 Subject: [PATCH 506/509] [GHA] update location of demo executables --- .github/workflows/build-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index a22b1feeb7..0a5ad884ef 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -369,7 +369,7 @@ jobs: ./run_simulation.sh 1> /dev/null # Run the Demo executables - EXE_LOC=${GITHUB_WORKSPACE}/build/src/examples/C++ + EXE_LOC=${GITHUB_WORKSPACE}/build/src/examples/C++/using_STIR_LOCAL cd ${GITHUB_WORKSPACE}/examples/C++/using_STIR_LOCAL generate_image generate_image.par From dc4a54eb28ac0c3f3c666363ea2d9b0d6209d5de Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Thu, 1 Feb 2024 20:11:00 +0000 Subject: [PATCH 507/509] final fixes to demos --- .../C++/using_installed_STIR/CMakeLists.txt | 21 ++++--------- .../demo_create_image.cxx | 31 +++++++++++++++---- 2 files changed, 31 insertions(+), 21 deletions(-) diff --git a/examples/C++/using_installed_STIR/CMakeLists.txt b/examples/C++/using_installed_STIR/CMakeLists.txt index d477a3a14f..b0b2029df7 100644 --- a/examples/C++/using_installed_STIR/CMakeLists.txt +++ b/examples/C++/using_installed_STIR/CMakeLists.txt @@ -1,24 +1,15 @@ #======================================================================== # Author: Kris Thielemans -# Copyright 2016 - 2020 University College London -# Copyright 2016 - 2020 Science Technology Facilities Council +# Copyright (C) 2024 University College London +# This file is part of STIR. # -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0.txt -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. +# SPDX-License-Identifier: Apache-2.0 # +# See STIR/LICENSE.txt for details #========================================================================= -# cmake file for building SIRF. See the SIRF User's Guide and http://www.cmake.org. +# cmake file for building the STIR demo. See the README.md and http://www.cmake.org. cmake_minimum_required(VERSION 3.14.0) @@ -26,7 +17,7 @@ PROJECT(myproject) find_package(STIR 6.0 REQUIRED CONFIG) -add_executable(demo_create_image demo_create_image.cxx) +add_executable(demo_create_image demo_create_image.cxx $) target_include_directories(demo_create_image PUBLIC "${STIR_INCLUDE_DIRS}") target_link_libraries(demo_create_image PUBLIC "${STIR_LIBRARIES}") diff --git a/examples/C++/using_installed_STIR/demo_create_image.cxx b/examples/C++/using_installed_STIR/demo_create_image.cxx index bd50492700..f3d391c9b3 100644 --- a/examples/C++/using_installed_STIR/demo_create_image.cxx +++ b/examples/C++/using_installed_STIR/demo_create_image.cxx @@ -1,3 +1,20 @@ +/*! + \file + \ingroup examples + \brief A simple program that creates an image and fills it with a shape + + Code is loosely based on GenerateImage.cxx + \author Kris Thielemans +*/ +/* + Copyright (C) 2018-2022, 2024 University College London + This file is part of STIR. + + SPDX-License-Identifier: Apache-2.0 + + See STIR/LICENSE.txt for details +*/ + #include "stir/Shape/EllipsoidalCylinder.h" #include "stir/PatientPosition.h" #include "stir/ImagingModality.h" @@ -23,8 +40,6 @@ int main() auto exam_info_sptr = std::make_shared(stir::ImagingModality::PT); { - std::vector start_times(1, rel_start_time); - std::vector durations(1, image_duration); stir::TimeFrameDefinitions frame_defs; frame_defs.set_num_time_frames(1); frame_defs.set_time_frame(1, rel_start_time, image_duration); @@ -42,11 +57,15 @@ int main() output_voxel_size_y, output_voxel_size_x)); - const auto centre = - image.get_physical_coordinates_for_indices(stir::make_coordinate(output_image_size_z/2,0,0)); - stir::EllipsoidalCylinder shape(40.F, 30.F, 20.F, centre); - shape.construct_volume(image, stir::make_coordinate(2,2,2)); + // add shape to image + { + const auto centre = + image.get_physical_coordinates_for_indices(stir::make_coordinate(output_image_size_z/2,0,0)); + stir::EllipsoidalCylinder shape(40.F, 30.F, 20.F, centre); + shape.construct_volume(image, stir::make_coordinate(2,2,2)); + } + // write output for checking try { stir::write_to_file(output_filename, image); From c5e3e7ae3d2ff440733698f70c9d6fa463463e68 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Thu, 1 Feb 2024 23:15:03 +0000 Subject: [PATCH 508/509] [CMake] remove more old CMake workarounds --- src/cmake/SetC++Version.cmake | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/src/cmake/SetC++Version.cmake b/src/cmake/SetC++Version.cmake index f1230e9440..d9af219259 100644 --- a/src/cmake/SetC++Version.cmake +++ b/src/cmake/SetC++Version.cmake @@ -1,5 +1,4 @@ # A macro to set the C++ version -# Based on https://stackoverflow.com/questions/10851247/how-to-activate-c-11-in-cmake # Copyright (C) 2017, 2020 University College London # Author Kris Thielemans @@ -21,17 +20,8 @@ function(UseCXX VERSION) endif() endif() - if (CMAKE_VERSION VERSION_LESS "3.1") - message(WARNING "Your CMake version is older than v3.1. Attempting to set C++ version to ${VERSION} with compiler flags but this might fail. Please upgrade your CMake.") - if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_EXTENSIONS) - set (CMAKE_CXX_FLAGS "-std=gnu++${VERSION} ${CMAKE_CXX_FLAGS}" PARENT_SCOPE) - else() - set (CMAKE_CXX_FLAGS "-std=c++${VERSION} ${CMAKE_CXX_FLAGS}" PARENT_SCOPE) - endif () - else () - set (CMAKE_CXX_STANDARD ${VERSION} PARENT_SCOPE) - set (CMAKE_CXX_STANDARD_REQUIRED ON PARENT_SCOPE) - endif () + set (CMAKE_CXX_STANDARD ${VERSION} PARENT_SCOPE) + set (CMAKE_CXX_STANDARD_REQUIRED ON PARENT_SCOPE) message(STATUS "Using C++ version ${VERSION}") endfunction(UseCXX) From 9cc0d1f17bdd38ac4cf7d6d66e657c6c8b8c89b8 Mon Sep 17 00:00:00 2001 From: Kris Thielemans Date: Thu, 1 Feb 2024 23:15:56 +0000 Subject: [PATCH 509/509] Set correct C++ version for libraries --- src/cmake/stir_lib_target.cmake | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/cmake/stir_lib_target.cmake b/src/cmake/stir_lib_target.cmake index 53481e4b0b..56884b214f 100644 --- a/src/cmake/stir_lib_target.cmake +++ b/src/cmake/stir_lib_target.cmake @@ -18,13 +18,8 @@ target_include_directories(${dir} PUBLIC $ $) -# make sure that if you use STIR, the compiler will be set to at least C++11 -if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.8.0") - target_compile_features(${dir} PUBLIC cxx_std_11) -else() - # Older CMake didn't have cxx_std_11 yet, but using auto will presumably force it anyway - target_compile_features(${dir} PUBLIC cxx_auto_type) -endif() +# make sure that if you use STIR, the compiler will be set to what was set via UseCXX +target_compile_features(${dir} PUBLIC cxx_std_${CMAKE_CXX_STANDARD}) target_include_directories(${dir} PUBLIC ${Boost_INCLUDE_DIR}) SET_PROPERTY(TARGET ${dir} PROPERTY FOLDER "Libs")

    @fn|Y{%@=e%JcfC3`(X4{rJtZ#rVT$e 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