From 13252c137eb93909975ad214d7a9dc506cb125ef Mon Sep 17 00:00:00 2001 From: NikEfth Date: Mon, 14 Jul 2025 17:54:50 +0100 Subject: [PATCH 1/9] * Create CListModeDataBasedOnCoordinateMap from SAFIR and Initial PETSIRD support in CMake configuration --- CMakeLists.txt | 67 +++++++-- src/IO/CMakeLists.txt | 27 +++- .../stir/IO/SAFIRCListmodeInputFileFormat.h | 8 +- .../CListModeDataBasedOnCoordinateMap.h | 101 +++++++++++++ .../stir/listmode/CListModeDataSAFIR.h | 80 ++++------- .../CListModeDataBasedOnCoordinateMap.cxx | 135 ++++++++++++++++++ .../CListModeDataSAFIR.cxx | 109 ++++---------- src/listmode_buildblock/CMakeLists.txt | 1 + 8 files changed, 381 insertions(+), 147 deletions(-) create mode 100644 src/include/stir/listmode/CListModeDataBasedOnCoordinateMap.h create mode 100644 src/listmode_buildblock/CListModeDataBasedOnCoordinateMap.cxx diff --git a/CMakeLists.txt b/CMakeLists.txt index 8efb6aad31..f59455a6ac 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -99,17 +99,18 @@ find_package( Boost 1.36.0 REQUIRED ) #### optional external libraries. # 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_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) -option(DISABLE_NLOHMANN_JSON "disable use of nlohmann JSON libraries" OFF) -option(STIR_ENABLE_EXPERIMENTAL "disable use of STIR experimental code" OFF) # disable by default -option(DISABLE_NiftyPET_PROJECTOR "disable use of NiftyPET projector" OFF) -option(DISABLE_Parallelproj_PROJECTOR "disable use of Parallelproj projector" OFF) +option(DISABLE_LLN_MATRIX "disable use of LLN library" ON) +option(DISABLE_ITK "disable use of ITK library" ON) +option(DISABLE_HDF5 "disable use of HDF5 libraries" ON) +option(DISABLE_STIR_LOCAL "disable use of LOCAL extensions to STIR" ON) +option(DISABLE_CERN_ROOT "disable use of Cern ROOT libraries" ON) +option(DISABLE_NLOHMANN_JSON "disable use of nlohmann JSON libraries" ON) +option(STIR_ENABLE_EXPERIMENTAL "disable use of STIR experimental code" ON) # disable by default +option(DISABLE_NiftyPET_PROJECTOR "disable use of NiftyPET projector" ON) +option(DISABLE_Parallelproj_PROJECTOR "disable use of Parallelproj projector" ON) OPTION(DOWNLOAD_ZENODO_TEST_DATA "download zenodo data for tests" OFF) -option(DISABLE_UPENN "disable use of UPENN filetypes" OFF) +option(DISABLE_UPENN "disable use of UPENN filetypes" ON) +option(DISABLE_PETSIRD "disable use of PETSIRD filetypes" OFF) find_package(Git QUIET) if(GIT_FOUND AND EXISTS "${PROJECT_SOURCE_DIR}/.git") @@ -278,6 +279,52 @@ else() message(STATUS "Parallelproj projector support disabled or not available.") endif() +if(NOT DISABLE_PETSIRD) + if(GIT_FOUND AND EXISTS "${PROJECT_SOURCE_DIR}/.git") + # Check if PETSIRD is already registered as a submodule + execute_process( + COMMAND ${GIT_EXECUTABLE} config --file ${PROJECT_SOURCE_DIR}/.gitmodules --get-regexp "submodule\\.PETSIRD\\.path" + RESULT_VARIABLE SUBMOD_EXISTS + OUTPUT_QUIET + ERROR_QUIET + ) + + if(NOT SUBMOD_EXISTS EQUAL 0) + message(STATUS "Adding PETSIRD submodule...") + execute_process( + COMMAND ${GIT_EXECUTABLE} submodule add https://github.com/ETSInitiative/PETSIRD.git PETSIRD + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + RESULT_VARIABLE GIT_ADD_RESULT + ) + if(NOT GIT_ADD_RESULT EQUAL 0) + message(WARNING "Submodule add failed with code ${GIT_ADD_RESULT}") + endif() + else() + message(STATUS "PETSIRD submodule already exists in .gitmodules.") + endif() + + # Always update/init to ensure it's ready + execute_process( + COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + ) + endif() + + execute_process( + COMMAND just generate + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/PETSIRD + RESULT_VARIABLE JUST_RESULT + ) + if(JUST_RESULT EQUAL 0) + set(HAVE_PETSIRD TRUE) + set(PETSIRD_base_dir ${PROJECT_SOURCE_DIR}/PETSIRD/cpp/generated) + message(STATUS "PETSIRD generation succeeded. THE PATH is ${PETSIRD_base_dir}. HAVE_PETSIRD set to TRUE.") + else() + set(HAVE_PETSIRD FALSE) + message(WARNING "PETSIRD generation failed with code ${JUST_RESULT}. HAVE_PETSIRD not set.") + endif() +endif() + #### enable support for ctest ENABLE_TESTING() diff --git a/src/IO/CMakeLists.txt b/src/IO/CMakeLists.txt index 691258d54a..d1dc61c6bc 100644 --- a/src/IO/CMakeLists.txt +++ b/src/IO/CMakeLists.txt @@ -86,7 +86,7 @@ target_sources(${STIR_BUILDBLOCK_LIB} PRIVATE ${${dir_LIB_SOURCES}}) set(TARGET ${STIR_BUILDBLOCK_LIB}) -target_link_libraries(${TARGET}) +target_link_libraries(${TARGET} PRIVATE fmt) if (LLN_FOUND) target_include_directories(${TARGET} PUBLIC ${LLN_INCLUDE_DIRS}) @@ -137,3 +137,28 @@ if (HAVE_JSON) get_target_property(TMP nlohmann_json::nlohmann_json INTERFACE_INCLUDE_DIRECTORIES) target_include_directories(${TARGET} PRIVATE "${TMP}") endif() + +if (HAVE_PETSIRD) +#set(PETSIRD_dir ../../PETSIRD/cpp/generated) +#add_subdirectory(${PETSIRD_dir} PETSIRD_generated) + +target_include_directories(IO PUBLIC + $ + $ +) + +target_include_directories(IO PUBLIC + $ + $ +) + +target_include_directories(IO PUBLIC + $ + $ +) + +#target_link_libraries(IO PUBLIC petsird_generated) +endif() + + + diff --git a/src/include/stir/IO/SAFIRCListmodeInputFileFormat.h b/src/include/stir/IO/SAFIRCListmodeInputFileFormat.h index 3042d4dd55..2ac3fdd96d 100644 --- a/src/include/stir/IO/SAFIRCListmodeInputFileFormat.h +++ b/src/include/stir/IO/SAFIRCListmodeInputFileFormat.h @@ -140,10 +140,10 @@ class SAFIRCListmodeInputFileFormat : public InputFileFormat, publ std::unique_ptr read_from_file(const std::string& filename) const override { - info("SAFIRCListmodeInputFileFormat: read_from_file(" + std::string(filename) + ")"); - actual_do_parsing(filename); - return std::unique_ptr(new CListModeDataSAFIR>( - listmode_filename, crystal_map_filename, template_proj_data_filename, lor_randomization_sigma)); + // info("SAFIRCListmodeInputFileFormat: read_from_file(" + std::string(filename) + ")"); + // actual_do_parsing(filename); + // return std::unique_ptr(new CListModeDataSAFIR>( + // listmode_filename, crystal_map_filename, template_proj_data_filename, lor_randomization_sigma)); } protected: diff --git a/src/include/stir/listmode/CListModeDataBasedOnCoordinateMap.h b/src/include/stir/listmode/CListModeDataBasedOnCoordinateMap.h new file mode 100644 index 0000000000..2ed6f211e0 --- /dev/null +++ b/src/include/stir/listmode/CListModeDataBasedOnCoordinateMap.h @@ -0,0 +1,101 @@ +/* CListModeDataSAFIR.h + + Coincidence LM Data Class for SAFIR: Header File + Jannis Fischer + + Copyright 2015 ETH Zurich, Institute of Particle Physics + Copyright 2020 Positrigo AG, Zurich + + 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. + + */ + +/*! + + \file + \ingroup listmode + \brief Declaration of class stir::CListModeDataSAFIR + + \author Jannis Fischer +*/ + +#ifndef __stir_listmode_CListModeDataBasedOnCoordinateMap_H__ +#define __stir_listmode_CListModeDataBasedOnCoordinateMap_H__ + +#include +#include +#include +#include + +#include "stir/listmode/CListModeData.h" +#include "stir/ProjData.h" +#include "stir/ProjDataInfo.h" +#include "stir/listmode/CListRecord.h" +#include "stir/IO/InputStreamWithRecords.h" +#include "stir/shared_ptr.h" + +#include "stir/listmode/CListRecordSAFIR.h" +#include "stir/DetectorCoordinateMap.h" + +START_NAMESPACE_STIR + +template +class CListModeDataBasedOnCoordinateMap : public CListModeData +{ +public: + /*! Constructor + \par + Takes as arguments the filenames of the coicidence listmode file, the crystal map (text) file, and the template projection data + file + */ + CListModeDataBasedOnCoordinateMap(const std::string& listmode_filename, + const std::string& crystal_map_filename, + const std::string& template_proj_data_filename, + const double lor_randomization_sigma = 0.0); + + CListModeDataBasedOnCoordinateMap(const std::string& listmode_filename, const shared_ptr& proj_data_info_sptr); + + std::string get_name() const override; + shared_ptr get_empty_record_sptr() const override; + Succeeded get_next_record(CListRecord& record_of_general_type) const override; + Succeeded reset() override; + + /*! + This function should save the position in input file. This is not implemented but disabled. + Returns 0 in the moement. + \todo Maybe provide real implementation? + */ + SavedPosition save_get_position() override { return static_cast(current_lm_data_ptr->save_get_position()); } + Succeeded set_get_position(const SavedPosition& pos) override { return current_lm_data_ptr->set_get_position(pos); } + + /*! + Returns just false in the moment. + \todo Implement this properly to check for delayed events in LM files. + */ + bool has_delayeds() const override { return false; } + +protected: + std::string listmode_filename; + mutable shared_ptr> current_lm_data_ptr; + mutable std::vector saved_get_positions; + virtual Succeeded open_lm_file() const = 0; + + shared_ptr map; +}; + + + + +END_NAMESPACE_STIR + +#endif diff --git a/src/include/stir/listmode/CListModeDataSAFIR.h b/src/include/stir/listmode/CListModeDataSAFIR.h index 66ca4344e7..6fcf424531 100644 --- a/src/include/stir/listmode/CListModeDataSAFIR.h +++ b/src/include/stir/listmode/CListModeDataSAFIR.h @@ -1,43 +1,44 @@ /* CListModeDataSAFIR.h - Coincidence LM Data Class for SAFIR: Header File - Jannis Fischer +Coincidence LM Data Class for SAFIR: Header File +Jannis Fischer - Copyright 2015 ETH Zurich, Institute of Particle Physics - Copyright 2020 Positrigo AG, Zurich + Copyright 2015 ETH Zurich, Institute of Particle Physics + Copyright 2020 Positrigo AG, Zurich - 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 + 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 + 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. + 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. */ /*! - \file - \ingroup listmode - \brief Declaration of class stir::CListModeDataSAFIR +\file +\ingroup listmode +\brief Declaration of class stir::CListModeDataSAFIR - \author Jannis Fischer +\author Jannis Fischer */ #ifndef __stir_listmode_CListModeDataSAFIR_H__ #define __stir_listmode_CListModeDataSAFIR_H__ + #include #include #include #include -#include "stir/listmode/CListModeData.h" +#include "stir/listmode/CListModeDataBasedOnCoordinateMap.h" #include "stir/ProjData.h" #include "stir/ProjDataInfo.h" #include "stir/listmode/CListRecord.h" @@ -45,7 +46,6 @@ #include "stir/shared_ptr.h" #include "stir/listmode/CListRecordSAFIR.h" -#include "stir/DetectorCoordinateMap.h" START_NAMESPACE_STIR @@ -57,47 +57,19 @@ START_NAMESPACE_STIR coordinates. */ template -class CListModeDataSAFIR : public CListModeData +class CListModeDataSAFIR : public CListModeDataBasedOnCoordinateMap { public: - /*! Constructor - \par - Takes as arguments the filenames of the coicidence listmode file, the crystal map (text) file, and the template projection data - file - */ + CListModeDataSAFIR(const std::string& listmode_filename, const std::string& crystal_map_filename, const std::string& template_proj_data_filename, const double lor_randomization_sigma = 0.0); - CListModeDataSAFIR(const std::string& listmode_filename, const shared_ptr& proj_data_info_sptr); - - std::string get_name() const override; - shared_ptr get_empty_record_sptr() const override; - Succeeded get_next_record(CListRecord& record_of_general_type) const override; - Succeeded reset() override; - - /*! - This function should save the position in input file. This is not implemented but disabled. - Returns 0 in the moement. - \todo Maybe provide real implementation? - */ - SavedPosition save_get_position() override { return static_cast(current_lm_data_ptr->save_get_position()); } - Succeeded set_get_position(const SavedPosition& pos) override { return current_lm_data_ptr->set_get_position(pos); } - - /*! - Returns just false in the moment. - \todo Implement this properly to check for delayed events in LM files. - */ - bool has_delayeds() const override { return false; } - -private: - std::string listmode_filename; - mutable shared_ptr> current_lm_data_ptr; - mutable std::vector saved_get_positions; - Succeeded open_lm_file() const; - shared_ptr map; + +protected: + virtual Succeeded open_lm_file() const; + }; END_NAMESPACE_STIR - -#endif +#endif // CLISTMODEDATASAFIR_H diff --git a/src/listmode_buildblock/CListModeDataBasedOnCoordinateMap.cxx b/src/listmode_buildblock/CListModeDataBasedOnCoordinateMap.cxx new file mode 100644 index 0000000000..439b9a8dd2 --- /dev/null +++ b/src/listmode_buildblock/CListModeDataBasedOnCoordinateMap.cxx @@ -0,0 +1,135 @@ +/* CListModeDataSAFIR.cxx + +Coincidence LM Data Class for SAFIR: Implementation + + Copyright 2015 ETH Zurich, Institute of Particle Physics + Copyright 2020 Positrigo AG, Zurich + Copyright 2021 University College London + + 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. +*/ +/*! + + \file + \ingroup listmode + \brief implementation of class stir::CListModeDataSAFIR + + \author Jannis Fischer + \author Kris Thielemans + \author Markus Jehl +*/ +#include +#include +#include + +#include "stir/ExamInfo.h" +#include "stir/Succeeded.h" +#include "stir/info.h" +#include "stir/error.h" + +//#include "boost/static_assert.hpp" + +#include "stir/listmode/CListModeDataBasedOnCoordinateMap.h" +#include "stir/listmode/CListRecordSAFIR.h" + +using std::ios; +using std::fstream; +using std::ifstream; +using std::istream; + +START_NAMESPACE_STIR; + +template +CListModeDataBasedOnCoordinateMap::CListModeDataBasedOnCoordinateMap(const std::string& listmode_filename, + const std::string& crystal_map_filename, + const std::string& template_proj_data_filename, + const double lor_randomization_sigma) + : listmode_filename(listmode_filename) +{ + if (!crystal_map_filename.empty()) + { + map = MAKE_SHARED(crystal_map_filename, lor_randomization_sigma); + } + else + { + if (lor_randomization_sigma != 0) + error("SAFIR currently does not support LOR-randomisation unless a map is specified"); + } + shared_ptr _exam_info_sptr(new ExamInfo); + _exam_info_sptr->imaging_modality = ImagingModality::PT; + this->exam_info_sptr = _exam_info_sptr; + + // Here we are reading the scanner data from the template projdata + shared_ptr template_proj_data_sptr = ProjData::read_from_file(template_proj_data_filename); + this->set_proj_data_info_sptr(template_proj_data_sptr->get_proj_data_info_sptr()->create_shared_clone()); + + if (open_lm_file() == Succeeded::no) + { + error("CListModeDataSAFIR: Could not open listmode file " + listmode_filename + "\n"); + } +} + +template +CListModeDataBasedOnCoordinateMap::CListModeDataBasedOnCoordinateMap(const std::string& listmode_filename, + const shared_ptr& proj_data_info_sptr) + : listmode_filename(listmode_filename) +{ + shared_ptr _exam_info_sptr(new ExamInfo); + _exam_info_sptr->imaging_modality = ImagingModality::PT; + this->exam_info_sptr = _exam_info_sptr; + this->set_proj_data_info_sptr(proj_data_info_sptr->create_shared_clone()); + + if (open_lm_file() == Succeeded::no) + { + error("CListModeDataSAFIR: opening file \"" + listmode_filename + "\""); + } +} + +template +std::string +CListModeDataBasedOnCoordinateMap::get_name() const +{ + return listmode_filename; +} + +template +shared_ptr +CListModeDataBasedOnCoordinateMap::get_empty_record_sptr() const +{ + shared_ptr sptr(new CListRecordT); + sptr->event_SAFIR().set_scanner_sptr(this->get_proj_data_info_sptr()->get_scanner_sptr()); + sptr->event_SAFIR().set_map_sptr(map); + return static_pointer_cast(sptr); +} + +template +Succeeded +CListModeDataBasedOnCoordinateMap::get_next_record(CListRecord& record_of_general_type) const +{ + CListRecordT& record = static_cast(record_of_general_type); + Succeeded status = current_lm_data_ptr->get_next_record(record); + // if( status == Succeeded::yes ) record.event_SAFIR().set_map_sptr(map); + return status; +} + +template +Succeeded +CListModeDataBasedOnCoordinateMap::reset() +{ + return current_lm_data_ptr->reset(); +} + +// template class CListModeDataSAFIR>; +// template class CListModeDataSAFIR>; + +END_NAMESPACE_STIR diff --git a/src/listmode_buildblock/CListModeDataSAFIR.cxx b/src/listmode_buildblock/CListModeDataSAFIR.cxx index 96aa9a3cb3..79a6cbc10d 100644 --- a/src/listmode_buildblock/CListModeDataSAFIR.cxx +++ b/src/listmode_buildblock/CListModeDataSAFIR.cxx @@ -2,31 +2,31 @@ Coincidence LM Data Class for SAFIR: Implementation - Copyright 2015 ETH Zurich, Institute of Particle Physics - Copyright 2020 Positrigo AG, Zurich - Copyright 2021 University College London + Copyright 2015 ETH Zurich, Institute of Particle Physics + Copyright 2020 Positrigo AG, Zurich + Copyright 2021 University College London - 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 + 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 + 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. + 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. */ /*! - \file - \ingroup listmode - \brief implementation of class stir::CListModeDataSAFIR +\file +\ingroup listmode +\brief implementation of class stir::CListModeDataSAFIR - \author Jannis Fischer - \author Kris Thielemans - \author Markus Jehl +\author Jannis Fischer +\author Kris Thielemans +\author Markus Jehl */ #include #include @@ -51,14 +51,17 @@ START_NAMESPACE_STIR; template CListModeDataSAFIR::CListModeDataSAFIR(const std::string& listmode_filename, - const std::string& crystal_map_filename, - const std::string& template_proj_data_filename, - const double lor_randomization_sigma) - : listmode_filename(listmode_filename) + const std::string& crystal_map_filename, + const std::string& template_proj_data_filename, + const double lor_randomization_sigma) + : CListModeDataBasedOnCoordinateMap(listmode_filename, + crystal_map_filename, + template_proj_data_filename, + lor_randomization_sigma) { if (!crystal_map_filename.empty()) { - map = MAKE_SHARED(crystal_map_filename, lor_randomization_sigma); + this->map = MAKE_SHARED(crystal_map_filename, lor_randomization_sigma); } else { @@ -69,78 +72,28 @@ CListModeDataSAFIR::CListModeDataSAFIR(const std::string& listmode _exam_info_sptr->imaging_modality = ImagingModality::PT; this->exam_info_sptr = _exam_info_sptr; - // Here we are reading the scanner data from the template projdata + // Here we are reading the scanner data from the template projdata shared_ptr template_proj_data_sptr = ProjData::read_from_file(template_proj_data_filename); this->set_proj_data_info_sptr(template_proj_data_sptr->get_proj_data_info_sptr()->create_shared_clone()); - if (open_lm_file() == Succeeded::no) + if (this->open_lm_file() == Succeeded::no) { error("CListModeDataSAFIR: Could not open listmode file " + listmode_filename + "\n"); } } -template -CListModeDataSAFIR::CListModeDataSAFIR(const std::string& listmode_filename, - const shared_ptr& proj_data_info_sptr) - : listmode_filename(listmode_filename) -{ - shared_ptr _exam_info_sptr(new ExamInfo); - _exam_info_sptr->imaging_modality = ImagingModality::PT; - this->exam_info_sptr = _exam_info_sptr; - this->set_proj_data_info_sptr(proj_data_info_sptr->create_shared_clone()); - - if (open_lm_file() == Succeeded::no) - { - error("CListModeDataSAFIR: opening file \"" + listmode_filename + "\""); - } -} - -template -std::string -CListModeDataSAFIR::get_name() const -{ - return listmode_filename; -} - -template -shared_ptr -CListModeDataSAFIR::get_empty_record_sptr() const -{ - shared_ptr sptr(new CListRecordT); - sptr->event_SAFIR().set_scanner_sptr(this->get_proj_data_info_sptr()->get_scanner_sptr()); - sptr->event_SAFIR().set_map_sptr(map); - return static_pointer_cast(sptr); -} - -template -Succeeded -CListModeDataSAFIR::get_next_record(CListRecord& record_of_general_type) const -{ - CListRecordT& record = static_cast(record_of_general_type); - Succeeded status = current_lm_data_ptr->get_next_record(record); - // if( status == Succeeded::yes ) record.event_SAFIR().set_map_sptr(map); - return status; -} - -template -Succeeded -CListModeDataSAFIR::reset() -{ - return current_lm_data_ptr->reset(); -} - template Succeeded CListModeDataSAFIR::open_lm_file() const { - shared_ptr stream_ptr(new fstream(listmode_filename.c_str(), ios::in | ios::binary)); + shared_ptr stream_ptr(new fstream(this->listmode_filename.c_str(), ios::in | ios::binary)); if (!(*stream_ptr)) { return Succeeded::no; } - info("CListModeDataSAFIR: opening file \"" + listmode_filename + "\"", 2); + info("CListModeDataSAFIR: opening file \"" + this->listmode_filename + "\"", 2); stream_ptr->seekg((std::streamoff)32); - current_lm_data_ptr.reset( + this->current_lm_data_ptr.reset( new InputStreamWithRecords(stream_ptr, sizeof(CListTimeDataSAFIR), sizeof(CListTimeDataSAFIR), diff --git a/src/listmode_buildblock/CMakeLists.txt b/src/listmode_buildblock/CMakeLists.txt index 15badd35c4..ae115f726a 100644 --- a/src/listmode_buildblock/CMakeLists.txt +++ b/src/listmode_buildblock/CMakeLists.txt @@ -14,6 +14,7 @@ set(${dir_LIB_SOURCES} LmToProjDataWithRandomRejection.cxx CListModeDataECAT8_32bit.cxx CListRecordECAT8_32bit.cxx + CListModeDataBasedOnCoordinateMap.cxx CListModeDataSAFIR.cxx ) From f36761041a6be6462b9f2d19dcd34405a3c1f667 Mon Sep 17 00:00:00 2001 From: nmdicom-recon Date: Tue, 15 Jul 2025 17:07:28 +0100 Subject: [PATCH 2/9] creating template classes for PETSIRD [WIP] mainly a copy at this stage --- CMakeLists.txt | 91 +++---- src/IO/CMakeLists.txt | 43 ++-- src/IO/IO_registries.cxx | 5 + src/cmake/STIRConfig.cmake.in | 4 + src/cmake/STIRConfig.h.in | 2 + .../stir/IO/PETSIRDCListmodeInputFileFormat.h | 241 ++++++++++++++++++ .../CListModeDataBasedOnCoordinateMap.h | 73 ++++-- .../stir/listmode/CListModeDataPETSIRD.h | 90 +++++++ .../stir/listmode/CListModeDataSAFIR.h | 20 +- .../stir/listmode/CListRecordPETSIRD.h | 240 +++++++++++++++++ .../stir/listmode/CListRecordPETSIRD.inl | 135 ++++++++++ .../CListModeDataBasedOnCoordinateMap.cxx | 84 +----- .../CListModeDataPETSIRD.cxx | 106 ++++++++ .../CListModeDataSAFIR.cxx | 53 +++- src/listmode_buildblock/CMakeLists.txt | 29 ++- 15 files changed, 1030 insertions(+), 186 deletions(-) create mode 100644 src/include/stir/IO/PETSIRDCListmodeInputFileFormat.h create mode 100644 src/include/stir/listmode/CListModeDataPETSIRD.h create mode 100644 src/include/stir/listmode/CListRecordPETSIRD.h create mode 100644 src/include/stir/listmode/CListRecordPETSIRD.inl create mode 100644 src/listmode_buildblock/CListModeDataPETSIRD.cxx diff --git a/CMakeLists.txt b/CMakeLists.txt index f59455a6ac..1063dd5371 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -280,49 +280,54 @@ else() endif() if(NOT DISABLE_PETSIRD) - if(GIT_FOUND AND EXISTS "${PROJECT_SOURCE_DIR}/.git") - # Check if PETSIRD is already registered as a submodule - execute_process( - COMMAND ${GIT_EXECUTABLE} config --file ${PROJECT_SOURCE_DIR}/.gitmodules --get-regexp "submodule\\.PETSIRD\\.path" - RESULT_VARIABLE SUBMOD_EXISTS - OUTPUT_QUIET - ERROR_QUIET - ) - - if(NOT SUBMOD_EXISTS EQUAL 0) - message(STATUS "Adding PETSIRD submodule...") - execute_process( - COMMAND ${GIT_EXECUTABLE} submodule add https://github.com/ETSInitiative/PETSIRD.git PETSIRD - WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} - RESULT_VARIABLE GIT_ADD_RESULT - ) - if(NOT GIT_ADD_RESULT EQUAL 0) - message(WARNING "Submodule add failed with code ${GIT_ADD_RESULT}") - endif() - else() - message(STATUS "PETSIRD submodule already exists in .gitmodules.") - endif() - - # Always update/init to ensure it's ready - execute_process( - COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive - WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} - ) - endif() - - execute_process( - COMMAND just generate - WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/PETSIRD - RESULT_VARIABLE JUST_RESULT - ) - if(JUST_RESULT EQUAL 0) - set(HAVE_PETSIRD TRUE) - set(PETSIRD_base_dir ${PROJECT_SOURCE_DIR}/PETSIRD/cpp/generated) - message(STATUS "PETSIRD generation succeeded. THE PATH is ${PETSIRD_base_dir}. HAVE_PETSIRD set to TRUE.") - else() - set(HAVE_PETSIRD FALSE) - message(WARNING "PETSIRD generation failed with code ${JUST_RESULT}. HAVE_PETSIRD not set.") - endif() + if(GIT_FOUND AND EXISTS "${PROJECT_SOURCE_DIR}/.git") + # Check if PETSIRD is already registered as a submodule + execute_process( + COMMAND ${GIT_EXECUTABLE} config --file ${PROJECT_SOURCE_DIR}/.gitmodules --get-regexp "submodule\\.PETSIRD\\.path" + RESULT_VARIABLE SUBMOD_EXISTS + OUTPUT_QUIET + ERROR_QUIET + ) + + if(NOT SUBMOD_EXISTS EQUAL 0) + message(STATUS "Adding PETSIRD submodule...") + execute_process( + COMMAND ${GIT_EXECUTABLE} submodule add https://github.com/ETSInitiative/PETSIRD.git PETSIRD + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + RESULT_VARIABLE GIT_ADD_RESULT + ) + if(NOT GIT_ADD_RESULT EQUAL 0) + message(WARNING "Submodule add failed with code ${GIT_ADD_RESULT}") + endif() + else() + message(STATUS "PETSIRD submodule already exists in .gitmodules.") + endif() + + # Always update/init to ensure it's ready + execute_process( + COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + ) + endif() + + add_subdirectory(${PROJECT_SOURCE_DIR}/PETSIRD/cpp PETSIRD_generated) + + # execute_process( + # COMMAND just generate + # WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/PETSIRD + # RESULT_VARIABLE JUST_RESULT + # ) + # if(JUST_RESULT EQUAL 0) + set(HAVE_PETSIRD ON) + + + # set(PETSIRD_base_dir ${PROJECT_SOURCE_DIR}/PETSIRD/cpp/generated) + install(TARGETS petsird_generated EXPORT STIRTargets DESTINATION lib) + # message(STATUS "PETSIRD generation succeeded. THE PATH is ${PETSIRD_base_dir}. HAVE_PETSIRD set to TRUE.") + # else() + # set(HAVE_PETSIRD OFF) + # message(WARNING "PETSIRD generation failed with code ${JUST_RESULT}. HAVE_PETSIRD not set.") + # endif() endif() #### enable support for ctest diff --git a/src/IO/CMakeLists.txt b/src/IO/CMakeLists.txt index d1dc61c6bc..126aa138c0 100644 --- a/src/IO/CMakeLists.txt +++ b/src/IO/CMakeLists.txt @@ -139,25 +139,30 @@ if (HAVE_JSON) endif() if (HAVE_PETSIRD) -#set(PETSIRD_dir ../../PETSIRD/cpp/generated) -#add_subdirectory(${PETSIRD_dir} PETSIRD_generated) - -target_include_directories(IO PUBLIC - $ - $ -) - -target_include_directories(IO PUBLIC - $ - $ -) - -target_include_directories(IO PUBLIC - $ - $ -) - -#target_link_libraries(IO PUBLIC petsird_generated) +# # #set(PETSIRD_dir ../../PETSIRD/cpp/generated) + +# # target_include_directories(IO PUBLIC +# # $ +# # $ +# # ) + +# # target_include_directories(IO PUBLIC +# # $ +# # $ +# # ) + +# # target_include_directories(IO PUBLIC +# # $ +# # $ +# # ) +# target_include_directories(IO PUBLIC +# $ +# $ +# $ +# $ +# ) + +# target_link_libraries(IO PUBLIC petsird_generated) endif() diff --git a/src/IO/IO_registries.cxx b/src/IO/IO_registries.cxx index 242a42113c..b739170860 100644 --- a/src/IO/IO_registries.cxx +++ b/src/IO/IO_registries.cxx @@ -156,5 +156,10 @@ static InputStreamWithRecordsFromUPENNtxt::RegisterIt dummy686062; // static RegisterInputFileFormat idummy1(2); # endif +# ifdef HAVE_PETSIRD +# include "stir/IO/PETSIRDCListmodeInputFileFormat.h" +static RegisterInputFileFormat<::stir::stir::PETSIRDCListmodeInputFileFormat> LMdummyPETSIRD(10); +#endif + #endif // MINI_STIR END_NAMESPACE_STIR diff --git a/src/cmake/STIRConfig.cmake.in b/src/cmake/STIRConfig.cmake.in index cc20a4526b..d6f0e1b68d 100644 --- a/src/cmake/STIRConfig.cmake.in +++ b/src/cmake/STIRConfig.cmake.in @@ -158,6 +158,10 @@ if(@STIR_WITH_Parallelproj_PROJECTOR@) set(STIR_WITH_Parallelproj_PROJECTOR TRUE) endif() +if(@HAVE_PETSIRD@) + set(HAVE_PETSIRD TRUE) +endif() + SET(STIR_WITH_EXPERIMENTAL @STIR_ENABLE_EXPERIMENTAL@) if(STIR_WITH_EXPERIMENTAL) if(${CMAKE_VERSION} VERSION_LESS "3.12.0") diff --git a/src/cmake/STIRConfig.h.in b/src/cmake/STIRConfig.h.in index efc241dae9..5055d63a31 100644 --- a/src/cmake/STIRConfig.h.in +++ b/src/cmake/STIRConfig.h.in @@ -90,6 +90,8 @@ namespace stir { #cmakedefine HAVE_SYSTEM_GETOPT +#cmakedefine HAVE_PETSIRD + #cmakedefine STIR_DEFAULT_PROJECTOR_AS_V2 #ifndef STIR_DEFAULT_PROJECTOR_AS_V2 #define USE_PMRT diff --git a/src/include/stir/IO/PETSIRDCListmodeInputFileFormat.h b/src/include/stir/IO/PETSIRDCListmodeInputFileFormat.h new file mode 100644 index 0000000000..1ccc4371a0 --- /dev/null +++ b/src/include/stir/IO/PETSIRDCListmodeInputFileFormat.h @@ -0,0 +1,241 @@ +/* PETSIRDCListmodeInputFileFormat.h + + Class defining input file format for coincidence listmode data for PETSIRD. + + Copyright 2015 ETH Zurich, Institute of Particle Physics + Copyright 2020 Positrigo AG, Zurich + Copyright 2025 National Physical Laboratory + + 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. + */ + +/*! + + \file + \ingroup listmode + \brief Declaration of class stir::PETSIRDCListmodeInputFileFormat + + \author Jannis Fischer + \author Markus Jehl, Positrigo + \author Daniel Deidda + +*/ + +#ifndef __stir_IO_PETSIRDCListmodeInputFileFormat_H__ +#define __stir_IO_PETSIRDCListmodeInputFileFormat_H__ + +#include +#include +#include +#include + +#include "boost/algorithm/string.hpp" + +#include "stir/IO/InputFileFormat.h" +#include "stir/IO/InputFileFormat.h" +#include "stir/IO/interfile.h" +#include "stir/info.h" +#include "stir/error.h" +#include "stir/utilities.h" +#include "stir/ParsingObject.h" + +// #include "stir/listmode/CListRecordPETSIRD.h" +#include "stir/listmode/CListModeDataPETSIRD.h" + +// #include "../../../../PETSIRD/cpp/generated/yardl/yardl.h" +// #include "../../PETSIRD/cpp/generated/types.h" +// #ifdef HAVE_HDF5 +// // # include "../../PETSIRD/cpp/generated/hdf5/protocols.h" +// // using petsird::hdf5::PETSIRDReader; +// #else +// // # include "../../PETSIRD/cpp/generated/binary/protocols.h" +// // using petsird::binary::PETSIRDReader; +// #endif + + +// #include "../../PETSIRD/cpp/helpers/include/petsird_helpers.h" + +// #include "types.h" +// #include "hdf5/protocols.h" + +START_NAMESPACE_STIR + +/*! \brief Class for reading PETSIRD coincidence listmode data. + + The first 32 bytes of the binary file are interpreted as file signature and matched against the strings "MUPET CListModeData\0", +"PETSIRD". If either is successfull, the class claims it can read the file format. The +rest of the file is read as records, e.g. CListRecordPETSIRD. +*/ + +// using namespace petsird::binary; + +class PETSIRDCListmodeInputFileFormat : public InputFileFormat, public ParsingObject +{ +public: + PETSIRDCListmodeInputFileFormat() {} + const std::string get_name() const override { return "PETSIRD"; } + + //! Checks in binary data file for correct signature. + bool can_read(const FileSignature& signature, std::istream& input) const override + { + return false; // cannot read from istream + } + + //! Checks in binary data file for correct signature (can be either "PETSIRD CListModeData", "NeuroLF CListModeData" or "MUPET + //! CListModeData"). + bool can_read(const FileSignature& signature, const std::string& filename) const override + { + int nikos = 0; + std::string d = filename; + // PETSIRDReader ndn(d); + // // Looking for the right key in the parameter file + // std::ifstream par_file(filename.c_str()); + // std::string key; + // std::getline(par_file, key, ':'); + // key = standardise_interfile_keyword(key); + // if (key != std::string("clistmodedataPETSIRD parameters")) + // { + // return false; + // } + // if (!actual_do_parsing(filename)) + // return false; + // std::ifstream data_file(listmode_filename.c_str(), std::ios::binary); + // char* buffer = new char[32]; + // data_file.read(buffer, 32); + // bool cr = false; + // // depending on used template, check header of listmode file for correct format + // if (std::is_same::value) + // { + // cr = (!strncmp(buffer, "MUPET CListModeData\0", 20) || !strncmp(buffer, "PETSIRD CListModeData\0", 20)); + // } + // else if (std::is_same::value) + // { + // cr = !strncmp(buffer, "NeuroLF CListModeData\0", 20); + // } + // else + // { + // warning("PETSIRDCListModeInputFileFormat was initialised with an unexpected template."); + // } + + // if (!cr) + // { + // warning("PETSIRDCListModeInputFileFormat tried to read file " + listmode_filename + // + " but it seems to have the wrong signature."); + // } + + // delete[] buffer; + // return cr; + return true; + } + + std::unique_ptr read_from_file(std::istream& input) const override + { + error("read_from_file for PETSIRDCListmodeData with istream not implemented %s:%d. Sorry", __FILE__, __LINE__); + return unique_ptr(); + } + +// std::unique_ptr read_from_file(const std::string& filename) const override +// { + + +// // info("PETSIRDCListmodeInputFileFormat: read_from_file(" + std::string(filename) + ")"); +// // actual_do_parsing(filename); +// // return std::unique_ptr(new CListModeDataPETSIRD>( +// // listmode_filename, crystal_map_filename, template_proj_data_filename, lor_randomization_sigma)); +// } +protected: + + virtual bool + actual_can_read(const FileSignature& signature, + std::istream &input) const + { + if(!is_interfile_signature(signature.get_signature())) + return false; + else + { + const std::string signature_as_string(signature.get_signature(), signature.size()); + return signature_as_string.find("PETSIRD") != std::string::npos; + } + } + + + virtual unique_ptr read_from_file( + const std::string& filename) const + { + return unique_ptr(new CListModeDataPETSIRD(filename)); + } + + typedef ParsingObject base_type; + mutable std::string listmode_filename; + mutable std::string crystal_map_filename; + mutable std::string template_proj_data_filename; + mutable double lor_randomization_sigma; + +// bool actual_can_read(const FileSignature& signature, std::istream& input) const override +// { +// return false; // cannot read from istream +// } + + void initialise_keymap() override + { + base_type::initialise_keymap(); + this->parser.add_start_key("CListModeDataPETSIRD Parameters"); + this->parser.add_key("listmode data filename", &listmode_filename); + this->parser.add_key("crystal map filename", &crystal_map_filename); + this->parser.add_key("template projection data filename", &template_proj_data_filename); + this->parser.add_key("LOR randomization (Gaussian) sigma", &lor_randomization_sigma); + this->parser.add_stop_key("END CListModeDataPETSIRD Parameters"); + } + + void set_defaults() override + { + base_type::set_defaults(); + crystal_map_filename = ""; + template_proj_data_filename = ""; + lor_randomization_sigma = 0.0; + } + + bool actual_do_parsing(const std::string& filename) const + { + // // Ugly const_casts here, but I don't see an other nice way to use the parser + // if (const_cast*>(this)->parse(filename.c_str())) + // { + // info(const_cast*>(this)->parameter_info()); + // return true; + // } + // else + // return false; + } + + bool post_processing() override + { + if (!file_exists(listmode_filename)) + return true; + else if (!file_exists(template_proj_data_filename)) + return true; + else + { + return false; + } + return true; + } + +private: + bool file_exists(const std::string& filename) + { + std::ifstream infile(filename.c_str()); + return infile.good(); + } +}; +END_NAMESPACE_STIR +#endif diff --git a/src/include/stir/listmode/CListModeDataBasedOnCoordinateMap.h b/src/include/stir/listmode/CListModeDataBasedOnCoordinateMap.h index 2ed6f211e0..81ab98f515 100644 --- a/src/include/stir/listmode/CListModeDataBasedOnCoordinateMap.h +++ b/src/include/stir/listmode/CListModeDataBasedOnCoordinateMap.h @@ -32,9 +32,7 @@ #ifndef __stir_listmode_CListModeDataBasedOnCoordinateMap_H__ #define __stir_listmode_CListModeDataBasedOnCoordinateMap_H__ -#include #include -#include #include #include "stir/listmode/CListModeData.h" @@ -44,39 +42,24 @@ #include "stir/IO/InputStreamWithRecords.h" #include "stir/shared_ptr.h" -#include "stir/listmode/CListRecordSAFIR.h" +// #include "stir/listmode/CListRecordSAFIR.h" #include "stir/DetectorCoordinateMap.h" START_NAMESPACE_STIR -template class CListModeDataBasedOnCoordinateMap : public CListModeData { public: - /*! Constructor - \par - Takes as arguments the filenames of the coicidence listmode file, the crystal map (text) file, and the template projection data - file - */ - CListModeDataBasedOnCoordinateMap(const std::string& listmode_filename, - const std::string& crystal_map_filename, - const std::string& template_proj_data_filename, - const double lor_randomization_sigma = 0.0); - - CListModeDataBasedOnCoordinateMap(const std::string& listmode_filename, const shared_ptr& proj_data_info_sptr); std::string get_name() const override; shared_ptr get_empty_record_sptr() const override; Succeeded get_next_record(CListRecord& record_of_general_type) const override; Succeeded reset() override; - /*! - This function should save the position in input file. This is not implemented but disabled. - Returns 0 in the moement. - \todo Maybe provide real implementation? - */ - SavedPosition save_get_position() override { return static_cast(current_lm_data_ptr->save_get_position()); } - Succeeded set_get_position(const SavedPosition& pos) override { return current_lm_data_ptr->set_get_position(pos); } + virtual shared_ptr> get_current_lm_file() = 0; + + SavedPosition save_get_position() override { return static_cast(get_current_lm_file()->save_get_position()); } + Succeeded set_get_position(const SavedPosition& pos) override { return get_current_lm_file()->set_get_position(pos); } /*! Returns just false in the moment. @@ -86,7 +69,7 @@ class CListModeDataBasedOnCoordinateMap : public CListModeData protected: std::string listmode_filename; - mutable shared_ptr> current_lm_data_ptr; + mutable std::vector saved_get_positions; virtual Succeeded open_lm_file() const = 0; @@ -94,7 +77,49 @@ class CListModeDataBasedOnCoordinateMap : public CListModeData }; - +// CListModeDataBasedOnCoordinateMap::CListModeDataBasedOnCoordinateMap(const std::string& listmode_filename, +// const std::string& crystal_map_filename, +// const std::string& template_proj_data_filename, +// const double lor_randomization_sigma) +// : listmode_filename(listmode_filename) +// { +// if (!crystal_map_filename.empty()) +// { +// map = MAKE_SHARED(crystal_map_filename, lor_randomization_sigma); +// } +// else +// { +// if (lor_randomization_sigma != 0) +// error("SAFIR currently does not support LOR-randomisation unless a map is specified"); +// } +// shared_ptr _exam_info_sptr(new ExamInfo); +// _exam_info_sptr->imaging_modality = ImagingModality::PT; +// this->exam_info_sptr = _exam_info_sptr; + +// // Here we are reading the scanner data from the template projdata +// shared_ptr template_proj_data_sptr = ProjData::read_from_file(template_proj_data_filename); +// this->set_proj_data_info_sptr(template_proj_data_sptr->get_proj_data_info_sptr()->create_shared_clone()); + +// if (open_lm_file() == Succeeded::no) +// { +// error("CListModeDataSAFIR: Could not open listmode file " + listmode_filename + "\n"); +// } +// } + +// // CListModeDataBasedOnCoordinateMap::CListModeDataBasedOnCoordinateMap(const std::string& listmode_filename, +// // const shared_ptr& proj_data_info_sptr) +// // : listmode_filename(listmode_filename) +// // { +// // shared_ptr _exam_info_sptr(new ExamInfo); +// // _exam_info_sptr->imaging_modality = ImagingModality::PT; +// // this->exam_info_sptr = _exam_info_sptr; +// // this->set_proj_data_info_sptr(proj_data_info_sptr->create_shared_clone()); + +// // if (open_lm_file() == Succeeded::no) +// // { +// // error("CListModeDataSAFIR: opening file \"" + listmode_filename + "\""); +// // } +// // } END_NAMESPACE_STIR diff --git a/src/include/stir/listmode/CListModeDataPETSIRD.h b/src/include/stir/listmode/CListModeDataPETSIRD.h new file mode 100644 index 0000000000..b92669f090 --- /dev/null +++ b/src/include/stir/listmode/CListModeDataPETSIRD.h @@ -0,0 +1,90 @@ +/* CListModeDataPETSIRD.h + +Coincidence LM Data Class for PETSIRD: Header File +Jannis Fischer + + Copyright 2015 ETH Zurich, Institute of Particle Physics + Copyright 2020 Positrigo AG, Zurich + Copyright 2025 National Physical Laboratory + + 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. + + */ + +/*! + +\file +\ingroup listmode +\brief Declaration of class stir::CListModeDataPETSIRD + +\author Daniel Deidda +*/ + +#ifndef __stir_listmode_CListModeDataPETSIRD_H__ +#define __stir_listmode_CListModeDataPETSIRD_H__ + + +#include +#include +#include +#include + +#include "stir/listmode/CListModeDataBasedOnCoordinateMap.h" +#include "stir/ProjData.h" +#include "stir/ProjDataInfo.h" +#include "stir/listmode/CListRecord.h" +#include "stir/IO/InputStreamWithRecords.h" +#include "stir/shared_ptr.h" + +// #include "../../PETSIRD/cpp/helpers/include/petsird_helpers.h" +// #include "petsird_helpers/create.h" +// #include "petsird_helpers/geometry.h" + +// #include "stir/listmode/CListRecordPETSIRD.h" + +START_NAMESPACE_STIR + +/*! + \brief Class for reading PETSIRD listmode data with variable geometry + \ingroup listmode + \par + By providing crystal map and template projection data files, the coordinates are read from files and used defining the LOR + coordinates. +*/ + +class CListModeDataPETSIRD : public CListModeDataBasedOnCoordinateMap +{ +public: + + CListModeDataPETSIRD(const std::string& listmode_filename, + const std::string& crystal_map_filename, + const std::string& template_proj_data_filename, + const double lor_randomization_sigma = 0.0); + + shared_ptr get_empty_record_sptr() const override { + + return nullptr; + } + + Succeeded get_next_record(CListRecord& record_of_general_type) const override{ + + return Succeeded::no; + } + +protected: + virtual Succeeded open_lm_file() const override; + +}; + +END_NAMESPACE_STIR +#endif // CLISTMODEDATAPETSIRD_H diff --git a/src/include/stir/listmode/CListModeDataSAFIR.h b/src/include/stir/listmode/CListModeDataSAFIR.h index 6fcf424531..4f84d350b0 100644 --- a/src/include/stir/listmode/CListModeDataSAFIR.h +++ b/src/include/stir/listmode/CListModeDataSAFIR.h @@ -33,10 +33,7 @@ Jannis Fischer #define __stir_listmode_CListModeDataSAFIR_H__ -#include #include -#include -#include #include "stir/listmode/CListModeDataBasedOnCoordinateMap.h" #include "stir/ProjData.h" @@ -45,8 +42,6 @@ Jannis Fischer #include "stir/IO/InputStreamWithRecords.h" #include "stir/shared_ptr.h" -#include "stir/listmode/CListRecordSAFIR.h" - START_NAMESPACE_STIR /*! @@ -57,7 +52,7 @@ START_NAMESPACE_STIR coordinates. */ template -class CListModeDataSAFIR : public CListModeDataBasedOnCoordinateMap +class CListModeDataSAFIR : public CListModeDataBasedOnCoordinateMap { public: @@ -66,9 +61,18 @@ class CListModeDataSAFIR : public CListModeDataBasedOnCoordinateMap& proj_data_info_sptr); + + shared_ptr get_empty_record_sptr() const override; + Succeeded get_next_record(CListRecord& record_of_general_type) const override; + virtual shared_ptr> get_current_lm_file(){ + return current_lm_data_ptr; + }; + +protected: + virtual Succeeded open_lm_file() const override; + mutable shared_ptr> current_lm_data_ptr; }; END_NAMESPACE_STIR diff --git a/src/include/stir/listmode/CListRecordPETSIRD.h b/src/include/stir/listmode/CListRecordPETSIRD.h new file mode 100644 index 0000000000..7026f34f87 --- /dev/null +++ b/src/include/stir/listmode/CListRecordPETSIRD.h @@ -0,0 +1,240 @@ +/* CListRecordPETSIRD.h + + Coincidence Event Class for PETSIRD: Header File + + Copyright 2015 ETH Zurich, Institute of Particle Physics + Copyright 2017 ETH Zurich, Institute of Particle Physics and Astrophysics + Copyright 2020, 2022 Positrigo AG, Zurich + Copyright 2025 National Physical Laboratory + + 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. + + */ + +/*! + + \file + \ingroup listmode + \brief Declaration of class stir::CListEventPETSIRD and stir::CListRecordPETSIRD with supporting classes + + \author Jannis Fischer + \author Parisa Khateri + \author Markus Jehl + \author Daniel Deidda +*/ + +#ifndef __stir_listmode_CListRecordPETSIRD_H__ +#define __stir_listmode_CListRecordPETSIRD_H__ + +#include + +#include "stir/listmode/CListRecord.h" +#include "stir/DetectionPositionPair.h" +#include "stir/Succeeded.h" +#include "stir/ByteOrder.h" +#include "stir/ByteOrderDefine.h" + +#include "boost/static_assert.hpp" +#include "boost/cstdint.hpp" + +#include "stir/DetectorCoordinateMap.h" +#include "boost/make_shared.hpp" +// #include "petsird_helpers.h" +// #include "petsird_helpers/create.h" +// #include "petsird_helpers/geometry.h" + +START_NAMESPACE_STIR + +/*! +Provides interface of the record class to STIR by implementing get_LOR(). It uses an optional map from detector indices to +coordinates to specify LORAs2Points from given detection pair indices. + + \ingroup listmode +*/ + +class CListEventPETSIRD : public CListEvent +{ +public: + /*! Default constructor will not work as it does not initialize a map to relate + detector indices and space coordinates. Always use either set_scanner_sptr or set_map_sptr after default construction. + */ + inline CListEventPETSIRD() {} + + //! Returns LOR corresponding to the given event. + inline LORAs2Points get_LOR() const override; + + //! Override the default implementation + inline void get_bin(Bin& bin, const ProjDataInfo& proj_data_info) const override; + + //! This method checks if the template is valid for LmToProjData + /*! Used before the actual processing of the data (see issue #61), before calling get_bin() + * Most scanners have listmode data that correspond to non arc-corrected data and + * this check avoids a crash when an unsupported template is used as input. + */ + inline bool is_valid_template(const ProjDataInfo&) const override { return true; } + + //! Returns 0 if event is prompt and 1 if delayed + inline bool is_prompt() const override { return true; }//!(static_cast(this)->is_prompt()); } + //! Function to set map for detector indices to coordinates. + /*! Use a null pointer to disable the mapping functionality */ + inline void set_map_sptr(shared_ptr new_map_sptr) { map_sptr = new_map_sptr; } + /*! Set the scanner */ + /*! Currently only used if the map is not set. */ + inline void set_scanner_sptr(shared_ptr new_scanner_sptr) { scanner_sptr = new_scanner_sptr; } + +private: + shared_ptr map_sptr; + shared_ptr scanner_sptr; + + const DetectorCoordinateMap& map_to_use() const { return map_sptr ? *map_sptr : *this->scanner_sptr->get_detector_map_sptr(); } +}; + +// //! Class for record with coincidence data using PETSIRD bitfield definition +// /*! \ingroup listmode */ +// class CListEventDataPETSIRD +// { +// public: +// //! Writes detection position pair to reference given as argument. +// inline void get_detection_position_pair(DetectionPositionPair<>& det_pos_pair); + +// //! Returns 0 if event is prompt and 1 if delayed +// inline bool is_prompt() const { return !isDelayed; } + +// //! Returns 1 if if event is time and 0 if it is prompt +// inline bool is_time() const { return type; } + +// //! Can be used to set "promptness" of event. +// inline Succeeded set_prompt(const bool prompt = true) +// { +// isDelayed = !prompt; +// return Succeeded::yes; +// } + +// private: +// #if STIRIsNativeByteOrderBigEndian +// unsigned type : 1; +// unsigned isDelayed : 1; +// unsigned reserved : 6; +// unsigned layerB : 4; +// unsigned layerA : 4; +// unsigned detB : 16; +// unsigned detA : 16; +// unsigned ringB : 8; +// unsigned ringA : 8; +// #else +// unsigned ringA : 8; +// unsigned ringB : 8; +// unsigned detA : 16; +// unsigned detB : 16; +// unsigned layerA : 4; +// unsigned layerB : 4; +// unsigned reserved : 6; +// unsigned isDelayed : 1; +// unsigned type : 1; +// #endif +// }; + + +//! Class for record with time data using PETSIRD bitfield definition +/*! \ingroup listmode */ +class CListTimeDataPETSIRD +{ +public: + inline unsigned long get_time_in_millisecs() const { return static_cast(time); } + inline Succeeded set_time_in_millisecs(const unsigned long time_in_millisecs) + { + time = ((boost::uint64_t(1) << 49) - 1) & static_cast(time_in_millisecs); + return Succeeded::yes; + } + inline bool is_time() const { return type; } + +private: + +}; + + +class CListRecordPETSIRD : public CListRecord, public ListTime, public CListEventPETSIRD +{ +public: + //! Returns event_data (without checking if the type is really event and not time). + CListEventPETSIRD get_data() const { return this->event_data; } + + // CListRecordPETSIRD() + // : CListEventPETSIRD>() + // {} + + ~CListRecordPETSIRD() override {} + + bool is_time() const override { return time_data.is_time(); } + + bool is_event() const override { return !time_data.is_time(); } + + CListEvent& event() override { return *this; } + + const CListEvent& event() const override { return *this; } + + virtual CListEventPETSIRD& event_PETSIRD() { return *this; } + + virtual const CListEventPETSIRD& event_PETSIRD() const { return *this; } + + ListTime& time() override { return *this; } + + const ListTime& time() const override { return *this; } + + virtual bool operator==(const CListRecord& e2) const + { + return dynamic_cast(&e2) != 0 + && raw == static_cast(e2).raw; + } + + inline unsigned long get_time_in_millisecs() const override { return time_data.get_time_in_millisecs(); } + + inline Succeeded set_time_in_millisecs(const unsigned long time_in_millisecs) override + { + return time_data.set_time_in_millisecs(time_in_millisecs); + } + + inline bool is_prompt() const override { return event_data.is_prompt(); } + + Succeeded init_from_data_ptr(const char* const data_ptr, const std::size_t size_of_record, const bool do_byte_swap) + { + assert(size_of_record >= 8); + std::copy(data_ptr, data_ptr + 8, reinterpret_cast(&raw)); // TODO necessary for operator== + if (do_byte_swap) + ByteOrder::swap_order(raw); + return Succeeded::yes; + } + + std::size_t size_of_record_at_ptr(const char* const /*data_ptr*/, const std::size_t /*size*/, const bool /*do_byte_swap*/) const + { + return 8; + } + +private: + // use C++ union to save data, you can only use one at a time, + // but compiler will not check which one was used! + // Be careful not to read event data from time record and vice versa!! + // However, this is used as a feature if comparing events over the 'raw' type. + union + { + CListEventPETSIRD event_data; + CListTimeDataPETSIRD time_data; + boost::int64_t raw; + }; +}; + +END_NAMESPACE_STIR + +#include "CListRecordPETSIRD.inl" + +#endif diff --git a/src/include/stir/listmode/CListRecordPETSIRD.inl b/src/include/stir/listmode/CListRecordPETSIRD.inl new file mode 100644 index 0000000000..086c23d958 --- /dev/null +++ b/src/include/stir/listmode/CListRecordPETSIRD.inl @@ -0,0 +1,135 @@ +/* CListRecordPETSIRD.inl + + Coincidence Event Class for PETSIRD: Inline File + + Copyright 2015 ETH Zurich, Institute of Particle Physics + Copyright 2017 ETH Zurich, Institute of Particle Physics and Astrophysics + Copyright 2020, 2022 Positrigo AG, Zurich + Copyright 2021 University College London + Copyright 2025 National Physical Laboratory + + 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. + + */ +/*! + + \file + \ingroup listmode + \brief Inline implementation of class stir::CListEventPETSIRD and stir::CListRecordPETSIRD with supporting classes + + \author Jannis Fischer + \author Parisa Khateri + \author Markus Jehl + \author Kris Thielemans + \author Daniel Deidda +*/ + +#include + +#include "stir/LORCoordinates.h" +#include "stir/listmode/CListRecord.h" +#include "stir/ProjDataInfo.h" +#include "stir/Bin.h" +#include "stir/LORCoordinates.h" +#include "stir/Succeeded.h" + +#include "stir/ProjDataInfoCylindricalNoArcCorr.h" +#include "stir/ProjDataInfoBlocksOnCylindricalNoArcCorr.h" +#include "stir/ProjDataInfoGenericNoArcCorr.h" +#include "stir/CartesianCoordinate3D.h" +#include "stir/error.h" + +START_NAMESPACE_STIR + +LORAs2Points +CListEventPETSIRD::get_LOR() const +{ + LORAs2Points lor; + DetectionPositionPair<> det_pos_pair; + + // static_cast(this)->get_data().get_detection_position_pair(det_pos_pair); + + lor.p1() = map_to_use().get_coordinate_for_index(det_pos_pair.pos1()); + lor.p2() = map_to_use().get_coordinate_for_index(det_pos_pair.pos2()); + + return lor; +} + +namespace detail +{ +// template +// static inline bool +// get_bin_for_det_pos_pair(Bin& bin, DetectionPositionPair<>& det_pos_pair, const ProjDataInfo& proj_data_info) +// { +// if (auto proj_data_info_ptr = dynamic_cast(&proj_data_info)) +// { +// if (proj_data_info_ptr->get_bin_for_det_pos_pair(bin, det_pos_pair) == Succeeded::yes) +// bin.set_bin_value(1); +// else +// bin.set_bin_value(-1); +// return true; +// } +// else +// return false; +// } +// } // namespace detail + +// template +// void +// CListEventPETSIRD::get_bin(Bin& bin, const ProjDataInfo& proj_data_info) const +// { +// DetectionPositionPair<> det_pos_pair; +// static_cast(this)->get_data().get_detection_position_pair(det_pos_pair); + +// if (!map_sptr) +// { +// // transform det_pos_pair into stir conventions +// det_pos_pair.pos1() = map_to_use().get_det_pos_for_index(det_pos_pair.pos1()); +// det_pos_pair.pos2() = map_to_use().get_det_pos_for_index(det_pos_pair.pos2()); + +// if (det_pos_pair.pos1().tangential_coord() == det_pos_pair.pos2().tangential_coord()) +// { +// bin.set_bin_value(-1); +// return; +// } + +// if (!detail::get_bin_for_det_pos_pair(bin, det_pos_pair, proj_data_info)) +// { +// if (!detail::get_bin_for_det_pos_pair(bin, det_pos_pair, proj_data_info)) +// error("Wrong type of proj-data-info for PETSIRD"); +// } +// } +// else +// { +// const stir::CartesianCoordinate3D c1 = map_sptr->get_coordinate_for_index(det_pos_pair.pos1()); +// const stir::CartesianCoordinate3D c2 = map_sptr->get_coordinate_for_index(det_pos_pair.pos2()); +// const LORAs2Points lor(c1, c2); +// bin = proj_data_info.get_bin(lor); +// } +// } + +// void +// CListEventDataPETSIRD::get_detection_position_pair(DetectionPositionPair<>& det_pos_pair) +// { +// det_pos_pair.pos1().radial_coord() = layerA; +// det_pos_pair.pos2().radial_coord() = layerB; + +// det_pos_pair.pos1().axial_coord() = ringA; +// det_pos_pair.pos2().axial_coord() = ringB; + +// det_pos_pair.pos1().tangential_coord() = detA; +// det_pos_pair.pos2().tangential_coord() = detB; +// } + + +END_NAMESPACE_STIR diff --git a/src/listmode_buildblock/CListModeDataBasedOnCoordinateMap.cxx b/src/listmode_buildblock/CListModeDataBasedOnCoordinateMap.cxx index 439b9a8dd2..8f3564563a 100644 --- a/src/listmode_buildblock/CListModeDataBasedOnCoordinateMap.cxx +++ b/src/listmode_buildblock/CListModeDataBasedOnCoordinateMap.cxx @@ -30,17 +30,9 @@ Coincidence LM Data Class for SAFIR: Implementation */ #include #include -#include - -#include "stir/ExamInfo.h" #include "stir/Succeeded.h" -#include "stir/info.h" -#include "stir/error.h" - -//#include "boost/static_assert.hpp" #include "stir/listmode/CListModeDataBasedOnCoordinateMap.h" -#include "stir/listmode/CListRecordSAFIR.h" using std::ios; using std::fstream; @@ -49,87 +41,17 @@ using std::istream; START_NAMESPACE_STIR; -template -CListModeDataBasedOnCoordinateMap::CListModeDataBasedOnCoordinateMap(const std::string& listmode_filename, - const std::string& crystal_map_filename, - const std::string& template_proj_data_filename, - const double lor_randomization_sigma) - : listmode_filename(listmode_filename) -{ - if (!crystal_map_filename.empty()) - { - map = MAKE_SHARED(crystal_map_filename, lor_randomization_sigma); - } - else - { - if (lor_randomization_sigma != 0) - error("SAFIR currently does not support LOR-randomisation unless a map is specified"); - } - shared_ptr _exam_info_sptr(new ExamInfo); - _exam_info_sptr->imaging_modality = ImagingModality::PT; - this->exam_info_sptr = _exam_info_sptr; - - // Here we are reading the scanner data from the template projdata - shared_ptr template_proj_data_sptr = ProjData::read_from_file(template_proj_data_filename); - this->set_proj_data_info_sptr(template_proj_data_sptr->get_proj_data_info_sptr()->create_shared_clone()); - - if (open_lm_file() == Succeeded::no) - { - error("CListModeDataSAFIR: Could not open listmode file " + listmode_filename + "\n"); - } -} - -template -CListModeDataBasedOnCoordinateMap::CListModeDataBasedOnCoordinateMap(const std::string& listmode_filename, - const shared_ptr& proj_data_info_sptr) - : listmode_filename(listmode_filename) -{ - shared_ptr _exam_info_sptr(new ExamInfo); - _exam_info_sptr->imaging_modality = ImagingModality::PT; - this->exam_info_sptr = _exam_info_sptr; - this->set_proj_data_info_sptr(proj_data_info_sptr->create_shared_clone()); - - if (open_lm_file() == Succeeded::no) - { - error("CListModeDataSAFIR: opening file \"" + listmode_filename + "\""); - } -} - -template std::string -CListModeDataBasedOnCoordinateMap::get_name() const +CListModeDataBasedOnCoordinateMap::get_name() const { return listmode_filename; } -template -shared_ptr -CListModeDataBasedOnCoordinateMap::get_empty_record_sptr() const -{ - shared_ptr sptr(new CListRecordT); - sptr->event_SAFIR().set_scanner_sptr(this->get_proj_data_info_sptr()->get_scanner_sptr()); - sptr->event_SAFIR().set_map_sptr(map); - return static_pointer_cast(sptr); -} -template Succeeded -CListModeDataBasedOnCoordinateMap::get_next_record(CListRecord& record_of_general_type) const +CListModeDataBasedOnCoordinateMap::reset() { - CListRecordT& record = static_cast(record_of_general_type); - Succeeded status = current_lm_data_ptr->get_next_record(record); - // if( status == Succeeded::yes ) record.event_SAFIR().set_map_sptr(map); - return status; + return get_current_lm_file()->reset(); } -template -Succeeded -CListModeDataBasedOnCoordinateMap::reset() -{ - return current_lm_data_ptr->reset(); -} - -// template class CListModeDataSAFIR>; -// template class CListModeDataSAFIR>; - END_NAMESPACE_STIR diff --git a/src/listmode_buildblock/CListModeDataPETSIRD.cxx b/src/listmode_buildblock/CListModeDataPETSIRD.cxx new file mode 100644 index 0000000000..0d258c1950 --- /dev/null +++ b/src/listmode_buildblock/CListModeDataPETSIRD.cxx @@ -0,0 +1,106 @@ +/* CListModeDataPETSIRD.cxx + +Coincidence LM Data Class for PETSIRD: Implementation + + Copyright 2015 ETH Zurich, Institute of Particle Physics + Copyright 2020 Positrigo AG, Zurich + Copyright 2021 University College London + Copyright 2025 National Physical Laboratory + + + 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. +*/ +/*! + +\file +\ingroup listmode +\brief implementation of class stir::CListModeDataPETSIRD + +\author Jannis Fischer +\author Kris Thielemans +\author Markus Jehl +\author Daniel Deidda +*/ +#include +#include + +#include "stir/ExamInfo.h" +#include "stir/Succeeded.h" +#include "stir/info.h" +#include "stir/error.h" +#include "binary/protocols.h" +#include "helpers/include/petsird_helpers.h" +#include "helpers/include/petsird_helpers/create.h" +#include "helpers/include/petsird_helpers/geometry.h" +//#include "boost/static_assert.hpp" + +#include "stir/listmode/CListModeDataPETSIRD.h" +// #include "stir/listmode/CListRecordPETSIRD.h" + +using std::ios; +using std::fstream; +using std::ifstream; +using std::istream; + +START_NAMESPACE_STIR; + +CListModeDataPETSIRD::CListModeDataPETSIRD(const std::string& listmode_filename, + const std::string& crystal_map_filename, + const std::string& template_proj_data_filename, + const double lor_randomization_sigma) +{ + CListModeDataBasedOnCoordinateMap::listmode_filename = listmode_filename; + // if (!crystal_map_filename.empty()) + // { + // this->map = MAKE_SHARED(crystal_map_filename, lor_randomization_sigma); + // } + // else + // { + // if (lor_randomization_sigma != 0) + // error("PETSIRD currently does not support LOR-randomisation unless a map is specified"); + // } + // shared_ptr _exam_info_sptr(new ExamInfo); + // _exam_info_sptr->imaging_modality = ImagingModality::PT; + // this->exam_info_sptr = _exam_info_sptr; + + // // Here we are reading the scanner data from the template projdata + // shared_ptr template_proj_data_sptr = ProjData::read_from_file(template_proj_data_filename); + // this->set_proj_data_info_sptr(template_proj_data_sptr->get_proj_data_info_sptr()->create_shared_clone()); + + // if (this->open_lm_file() == Succeeded::no) + // { + // error("CListModeDataPETSIRD: Could not open listmode file " + listmode_filename + "\n"); + // } +} + +Succeeded +CListModeDataPETSIRD::open_lm_file() const +{ + // shared_ptr stream_ptr(new fstream(this->listmode_filename.c_str(), ios::in | ios::binary)); + // if (!(*stream_ptr)) + // { + // return Succeeded::no; + // } + // info("CListModeDataPETSIRD: opening file \"" + this->listmode_filename + "\"", 2); + // stream_ptr->seekg((std::streamoff)32); + // this->current_lm_data_ptr.reset( + // new InputStreamWithRecords(stream_ptr, + // sizeof(CListTimeDataPETSIRD), + // sizeof(CListTimeDataPETSIRD), + // ByteOrder::little_endian != ByteOrder::get_native_order())); + // return Succeeded::yes; +} + +// template class CListModeDataPETSIRD; + +END_NAMESPACE_STIR diff --git a/src/listmode_buildblock/CListModeDataSAFIR.cxx b/src/listmode_buildblock/CListModeDataSAFIR.cxx index 79a6cbc10d..ea0497ae43 100644 --- a/src/listmode_buildblock/CListModeDataSAFIR.cxx +++ b/src/listmode_buildblock/CListModeDataSAFIR.cxx @@ -30,7 +30,6 @@ Coincidence LM Data Class for SAFIR: Implementation */ #include #include -#include #include "stir/ExamInfo.h" #include "stir/Succeeded.h" @@ -51,14 +50,11 @@ START_NAMESPACE_STIR; template CListModeDataSAFIR::CListModeDataSAFIR(const std::string& listmode_filename, - const std::string& crystal_map_filename, - const std::string& template_proj_data_filename, - const double lor_randomization_sigma) - : CListModeDataBasedOnCoordinateMap(listmode_filename, - crystal_map_filename, - template_proj_data_filename, - lor_randomization_sigma) + const std::string& crystal_map_filename, + const std::string& template_proj_data_filename, + const double lor_randomization_sigma) { + CListModeDataBasedOnCoordinateMap::listmode_filename = listmode_filename; if (!crystal_map_filename.empty()) { this->map = MAKE_SHARED(crystal_map_filename, lor_randomization_sigma); @@ -82,6 +78,22 @@ CListModeDataSAFIR::CListModeDataSAFIR(const std::string& listmode } } +template +CListModeDataSAFIR::CListModeDataSAFIR(const std::string& listmode_filename, + const shared_ptr& proj_data_info_sptr) +{ + CListModeDataBasedOnCoordinateMap::listmode_filename = listmode_filename; + shared_ptr _exam_info_sptr(new ExamInfo); + _exam_info_sptr->imaging_modality = ImagingModality::PT; + this->exam_info_sptr = _exam_info_sptr; + this->set_proj_data_info_sptr(proj_data_info_sptr->create_shared_clone()); + + if (open_lm_file() == Succeeded::no) + { + error("CListModeDataSAFIR: opening file \"" + listmode_filename + "\""); + } +} + template Succeeded CListModeDataSAFIR::open_lm_file() const @@ -101,7 +113,28 @@ CListModeDataSAFIR::open_lm_file() const return Succeeded::yes; } -template class CListModeDataSAFIR>; -template class CListModeDataSAFIR>; +template +shared_ptr +CListModeDataSAFIR::get_empty_record_sptr() const +{ + shared_ptr sptr(new CListRecordT); + sptr->event_SAFIR().set_scanner_sptr(this->get_proj_data_info_sptr()->get_scanner_sptr()); + sptr->event_SAFIR().set_map_sptr(map); + return static_pointer_cast(sptr); +} + + +template +Succeeded +CListModeDataSAFIR::get_next_record(CListRecord& record_of_general_type) const +{ + CListRecordT& record = static_cast(record_of_general_type); + Succeeded status = current_lm_data_ptr->get_next_record(record); + // if( status == Succeeded::yes ) record.event_SAFIR().set_map_sptr(map); + return status; +} + +// template class CListModeDataSAFIR>; +// template class CListModeDataSAFIR>; END_NAMESPACE_STIR diff --git a/src/listmode_buildblock/CMakeLists.txt b/src/listmode_buildblock/CMakeLists.txt index ae115f726a..7055ca4b7c 100644 --- a/src/listmode_buildblock/CMakeLists.txt +++ b/src/listmode_buildblock/CMakeLists.txt @@ -15,7 +15,7 @@ set(${dir_LIB_SOURCES} CListModeDataECAT8_32bit.cxx CListRecordECAT8_32bit.cxx CListModeDataBasedOnCoordinateMap.cxx - CListModeDataSAFIR.cxx + CListModeDataSAFIR.cxx ) if (HAVE_HDF5) @@ -25,6 +25,12 @@ list(APPEND ${dir_LIB_SOURCES} ) endif() +if (HAVE_PETSIRD) +list(APPEND ${dir_LIB_SOURCES} + CListModeDataPETSIRD.cxx +) +endif() + if (HAVE_ECAT) list(APPEND ${dir_LIB_SOURCES} CListModeDataECAT.cxx @@ -68,3 +74,24 @@ if (HAVE_HDF5) target_include_directories(${TARGET} PRIVATE ${HDF5_INCLUDE_DIRS}) endif() endif() + +if (HAVE_PETSIRD) +#set(PETSIRD_dir ../../PETSIRD/cpp/generated) +#add_subdirectory(${PETSIRD_dir} PETSIRD_generated) + +target_include_directories(listmode_buildblock PUBLIC + $ + $ +) + +target_include_directories(listmode_buildblock PUBLIC + $ + $ +) + +target_include_directories(listmode_buildblock PUBLIC + $ + $ +) +#target_link_libraries(IO PUBLIC petsird_generated) +endif() From 40d47909bd871eabf3f754749f5657192ccf7185 Mon Sep 17 00:00:00 2001 From: NikEfth Date: Wed, 16 Jul 2025 11:03:03 -0400 Subject: [PATCH 3/9] initial attempt for CListModeDataPETSIRD [WIP] --- CMakeLists.txt | 87 ++++++------- src/IO/IO_registries.cxx | 11 +- .../stir/IO/PETSIRDCListmodeInputFileFormat.h | 119 +----------------- .../CListModeDataBasedOnCoordinateMap.h | 12 +- .../stir/listmode/CListModeDataPETSIRD.h | 25 ++-- .../stir/listmode/CListModeDataSAFIR.h | 12 +- .../stir/listmode/CListRecordPETSIRD.h | 60 ++++----- .../stir/listmode/CListRecordPETSIRD.inl | 2 +- .../CListModeDataPETSIRD.cxx | 10 +- .../CListModeDataSAFIR.cxx | 9 +- 10 files changed, 102 insertions(+), 245 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1063dd5371..789a0ea38f 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -280,54 +280,49 @@ else() endif() if(NOT DISABLE_PETSIRD) - if(GIT_FOUND AND EXISTS "${PROJECT_SOURCE_DIR}/.git") - # Check if PETSIRD is already registered as a submodule - execute_process( - COMMAND ${GIT_EXECUTABLE} config --file ${PROJECT_SOURCE_DIR}/.gitmodules --get-regexp "submodule\\.PETSIRD\\.path" - RESULT_VARIABLE SUBMOD_EXISTS - OUTPUT_QUIET - ERROR_QUIET - ) - - if(NOT SUBMOD_EXISTS EQUAL 0) - message(STATUS "Adding PETSIRD submodule...") - execute_process( - COMMAND ${GIT_EXECUTABLE} submodule add https://github.com/ETSInitiative/PETSIRD.git PETSIRD - WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} - RESULT_VARIABLE GIT_ADD_RESULT - ) - if(NOT GIT_ADD_RESULT EQUAL 0) - message(WARNING "Submodule add failed with code ${GIT_ADD_RESULT}") - endif() - else() - message(STATUS "PETSIRD submodule already exists in .gitmodules.") - endif() - - # Always update/init to ensure it's ready - execute_process( - COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive - WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} - ) - endif() - - add_subdirectory(${PROJECT_SOURCE_DIR}/PETSIRD/cpp PETSIRD_generated) - - # execute_process( - # COMMAND just generate - # WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/PETSIRD - # RESULT_VARIABLE JUST_RESULT - # ) - # if(JUST_RESULT EQUAL 0) - set(HAVE_PETSIRD ON) + if(GIT_FOUND AND EXISTS "${PROJECT_SOURCE_DIR}/.git") + # Check if PETSIRD is already registered as a submodule + execute_process( + COMMAND ${GIT_EXECUTABLE} config --file ${PROJECT_SOURCE_DIR}/.gitmodules --get-regexp "submodule\\.PETSIRD\\.path" + RESULT_VARIABLE SUBMOD_EXISTS + OUTPUT_QUIET + ERROR_QUIET + ) + + if(NOT SUBMOD_EXISTS EQUAL 0) + message(STATUS "Adding PETSIRD submodule...") + execute_process( + COMMAND ${GIT_EXECUTABLE} submodule add https://github.com/ETSInitiative/PETSIRD.git PETSIRD + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + RESULT_VARIABLE GIT_ADD_RESULT + ) + if(NOT GIT_ADD_RESULT EQUAL 0) + message(WARNING "Submodule add failed with code ${GIT_ADD_RESULT}") + endif() + else() + message(STATUS "PETSIRD submodule already exists in .gitmodules.") + endif() + # Always update/init to ensure it's ready + execute_process( + COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + ) + endif() - # set(PETSIRD_base_dir ${PROJECT_SOURCE_DIR}/PETSIRD/cpp/generated) - install(TARGETS petsird_generated EXPORT STIRTargets DESTINATION lib) - # message(STATUS "PETSIRD generation succeeded. THE PATH is ${PETSIRD_base_dir}. HAVE_PETSIRD set to TRUE.") - # else() - # set(HAVE_PETSIRD OFF) - # message(WARNING "PETSIRD generation failed with code ${JUST_RESULT}. HAVE_PETSIRD not set.") - # endif() + execute_process( + COMMAND just generate + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/PETSIRD + RESULT_VARIABLE JUST_RESULT + ) + if(JUST_RESULT EQUAL 0) + set(HAVE_PETSIRD TRUE) + set(PETSIRD_base_dir ${PROJECT_SOURCE_DIR}/PETSIRD/cpp/generated) + message(STATUS "PETSIRD generation succeeded. THE PATH is ${PETSIRD_base_dir}. HAVE_PETSIRD set to TRUE.") + else() + set(HAVE_PETSIRD FALSE) + message(WARNING "PETSIRD generation failed with code ${JUST_RESULT}. HAVE_PETSIRD not set.") + endif() endif() #### enable support for ctest diff --git a/src/IO/IO_registries.cxx b/src/IO/IO_registries.cxx index b739170860..814b7e875b 100644 --- a/src/IO/IO_registries.cxx +++ b/src/IO/IO_registries.cxx @@ -66,6 +66,10 @@ # include "stir/IO/InputStreamFromROOTFileForECATPET.h" # endif +# ifdef HAVE_PETSIRD +# include "stir/IO/PETSIRDCListmodeInputFileFormat.h" +# endif + # ifdef HAVE_UPENN # include "stir/IO/PENNListmodeInputFileFormat.h" # include "stir/IO/InputStreamWithRecordsFromUPENNbin.h" @@ -156,10 +160,9 @@ static InputStreamWithRecordsFromUPENNtxt::RegisterIt dummy686062; // static RegisterInputFileFormat idummy1(2); # endif -# ifdef HAVE_PETSIRD -# include "stir/IO/PETSIRDCListmodeInputFileFormat.h" -static RegisterInputFileFormat<::stir::stir::PETSIRDCListmodeInputFileFormat> LMdummyPETSIRD(10); -#endif +# ifdef HAVE_PETSIRD +static RegisterInputFileFormat LMdummyPETSIRD(10); +# endif #endif // MINI_STIR END_NAMESPACE_STIR diff --git a/src/include/stir/IO/PETSIRDCListmodeInputFileFormat.h b/src/include/stir/IO/PETSIRDCListmodeInputFileFormat.h index 1ccc4371a0..cc27dd2e81 100644 --- a/src/include/stir/IO/PETSIRDCListmodeInputFileFormat.h +++ b/src/include/stir/IO/PETSIRDCListmodeInputFileFormat.h @@ -34,7 +34,6 @@ #ifndef __stir_IO_PETSIRDCListmodeInputFileFormat_H__ #define __stir_IO_PETSIRDCListmodeInputFileFormat_H__ -#include #include #include #include @@ -43,31 +42,11 @@ #include "stir/IO/InputFileFormat.h" #include "stir/IO/InputFileFormat.h" -#include "stir/IO/interfile.h" -#include "stir/info.h" #include "stir/error.h" -#include "stir/utilities.h" -#include "stir/ParsingObject.h" // #include "stir/listmode/CListRecordPETSIRD.h" #include "stir/listmode/CListModeDataPETSIRD.h" -// #include "../../../../PETSIRD/cpp/generated/yardl/yardl.h" -// #include "../../PETSIRD/cpp/generated/types.h" -// #ifdef HAVE_HDF5 -// // # include "../../PETSIRD/cpp/generated/hdf5/protocols.h" -// // using petsird::hdf5::PETSIRDReader; -// #else -// // # include "../../PETSIRD/cpp/generated/binary/protocols.h" -// // using petsird::binary::PETSIRDReader; -// #endif - - -// #include "../../PETSIRD/cpp/helpers/include/petsird_helpers.h" - -// #include "types.h" -// #include "hdf5/protocols.h" - START_NAMESPACE_STIR /*! \brief Class for reading PETSIRD coincidence listmode data. @@ -77,12 +56,9 @@ START_NAMESPACE_STIR rest of the file is read as records, e.g. CListRecordPETSIRD. */ -// using namespace petsird::binary; - -class PETSIRDCListmodeInputFileFormat : public InputFileFormat, public ParsingObject +class PETSIRDCListmodeInputFileFormat : public InputFileFormat { public: - PETSIRDCListmodeInputFileFormat() {} const std::string get_name() const override { return "PETSIRD"; } //! Checks in binary data file for correct signature. @@ -144,98 +120,7 @@ class PETSIRDCListmodeInputFileFormat : public InputFileFormat, pu return unique_ptr(); } -// std::unique_ptr read_from_file(const std::string& filename) const override -// { - - -// // info("PETSIRDCListmodeInputFileFormat: read_from_file(" + std::string(filename) + ")"); -// // actual_do_parsing(filename); -// // return std::unique_ptr(new CListModeDataPETSIRD>( -// // listmode_filename, crystal_map_filename, template_proj_data_filename, lor_randomization_sigma)); -// } -protected: - - virtual bool - actual_can_read(const FileSignature& signature, - std::istream &input) const - { - if(!is_interfile_signature(signature.get_signature())) - return false; - else - { - const std::string signature_as_string(signature.get_signature(), signature.size()); - return signature_as_string.find("PETSIRD") != std::string::npos; - } - } - - - virtual unique_ptr read_from_file( - const std::string& filename) const - { - return unique_ptr(new CListModeDataPETSIRD(filename)); - } - - typedef ParsingObject base_type; - mutable std::string listmode_filename; - mutable std::string crystal_map_filename; - mutable std::string template_proj_data_filename; - mutable double lor_randomization_sigma; - -// bool actual_can_read(const FileSignature& signature, std::istream& input) const override -// { -// return false; // cannot read from istream -// } - - void initialise_keymap() override - { - base_type::initialise_keymap(); - this->parser.add_start_key("CListModeDataPETSIRD Parameters"); - this->parser.add_key("listmode data filename", &listmode_filename); - this->parser.add_key("crystal map filename", &crystal_map_filename); - this->parser.add_key("template projection data filename", &template_proj_data_filename); - this->parser.add_key("LOR randomization (Gaussian) sigma", &lor_randomization_sigma); - this->parser.add_stop_key("END CListModeDataPETSIRD Parameters"); - } - - void set_defaults() override - { - base_type::set_defaults(); - crystal_map_filename = ""; - template_proj_data_filename = ""; - lor_randomization_sigma = 0.0; - } - - bool actual_do_parsing(const std::string& filename) const - { - // // Ugly const_casts here, but I don't see an other nice way to use the parser - // if (const_cast*>(this)->parse(filename.c_str())) - // { - // info(const_cast*>(this)->parameter_info()); - // return true; - // } - // else - // return false; - } - - bool post_processing() override - { - if (!file_exists(listmode_filename)) - return true; - else if (!file_exists(template_proj_data_filename)) - return true; - else - { - return false; - } - return true; - } - -private: - bool file_exists(const std::string& filename) - { - std::ifstream infile(filename.c_str()); - return infile.good(); - } + std::unique_ptr read_from_file(const std::string& filename) const override {} }; END_NAMESPACE_STIR #endif diff --git a/src/include/stir/listmode/CListModeDataBasedOnCoordinateMap.h b/src/include/stir/listmode/CListModeDataBasedOnCoordinateMap.h index 81ab98f515..ee32535398 100644 --- a/src/include/stir/listmode/CListModeDataBasedOnCoordinateMap.h +++ b/src/include/stir/listmode/CListModeDataBasedOnCoordinateMap.h @@ -50,10 +50,9 @@ START_NAMESPACE_STIR class CListModeDataBasedOnCoordinateMap : public CListModeData { public: - std::string get_name() const override; - shared_ptr get_empty_record_sptr() const override; - Succeeded get_next_record(CListRecord& record_of_general_type) const override; + // shared_ptr get_empty_record_sptr() const override; + // Succeeded get_next_record(CListRecord& record_of_general_type) const override; Succeeded reset() override; virtual shared_ptr> get_current_lm_file() = 0; @@ -61,12 +60,6 @@ class CListModeDataBasedOnCoordinateMap : public CListModeData SavedPosition save_get_position() override { return static_cast(get_current_lm_file()->save_get_position()); } Succeeded set_get_position(const SavedPosition& pos) override { return get_current_lm_file()->set_get_position(pos); } - /*! - Returns just false in the moment. - \todo Implement this properly to check for delayed events in LM files. - */ - bool has_delayeds() const override { return false; } - protected: std::string listmode_filename; @@ -76,7 +69,6 @@ class CListModeDataBasedOnCoordinateMap : public CListModeData shared_ptr map; }; - // CListModeDataBasedOnCoordinateMap::CListModeDataBasedOnCoordinateMap(const std::string& listmode_filename, // const std::string& crystal_map_filename, // const std::string& template_proj_data_filename, diff --git a/src/include/stir/listmode/CListModeDataPETSIRD.h b/src/include/stir/listmode/CListModeDataPETSIRD.h index b92669f090..7cf2e78313 100644 --- a/src/include/stir/listmode/CListModeDataPETSIRD.h +++ b/src/include/stir/listmode/CListModeDataPETSIRD.h @@ -33,7 +33,6 @@ Jannis Fischer #ifndef __stir_listmode_CListModeDataPETSIRD_H__ #define __stir_listmode_CListModeDataPETSIRD_H__ - #include #include #include @@ -46,11 +45,7 @@ Jannis Fischer #include "stir/IO/InputStreamWithRecords.h" #include "stir/shared_ptr.h" -// #include "../../PETSIRD/cpp/helpers/include/petsird_helpers.h" -// #include "petsird_helpers/create.h" -// #include "petsird_helpers/geometry.h" - -// #include "stir/listmode/CListRecordPETSIRD.h" +#include "stir/listmode/CListRecordPETSIRD.h" START_NAMESPACE_STIR @@ -65,25 +60,21 @@ START_NAMESPACE_STIR class CListModeDataPETSIRD : public CListModeDataBasedOnCoordinateMap { public: - CListModeDataPETSIRD(const std::string& listmode_filename, - const std::string& crystal_map_filename, - const std::string& template_proj_data_filename, - const double lor_randomization_sigma = 0.0); + const std::string& crystal_map_filename, + const std::string& template_proj_data_filename, + const double lor_randomization_sigma = 0.0); - shared_ptr get_empty_record_sptr() const override { + shared_ptr get_empty_record_sptr() const override { return nullptr; } - return nullptr; - } + Succeeded get_next_record(CListRecord& record_of_general_type) const override { return Succeeded::no; } - Succeeded get_next_record(CListRecord& record_of_general_type) const override{ + virtual shared_ptr> get_current_lm_file() override {} - return Succeeded::no; - } + bool has_delayeds() const override { return false; } protected: virtual Succeeded open_lm_file() const override; - }; END_NAMESPACE_STIR diff --git a/src/include/stir/listmode/CListModeDataSAFIR.h b/src/include/stir/listmode/CListModeDataSAFIR.h index 4f84d350b0..c26d2798b0 100644 --- a/src/include/stir/listmode/CListModeDataSAFIR.h +++ b/src/include/stir/listmode/CListModeDataSAFIR.h @@ -32,7 +32,6 @@ Jannis Fischer #ifndef __stir_listmode_CListModeDataSAFIR_H__ #define __stir_listmode_CListModeDataSAFIR_H__ - #include #include "stir/listmode/CListModeDataBasedOnCoordinateMap.h" @@ -55,7 +54,6 @@ template class CListModeDataSAFIR : public CListModeDataBasedOnCoordinateMap { public: - CListModeDataSAFIR(const std::string& listmode_filename, const std::string& crystal_map_filename, const std::string& template_proj_data_filename, @@ -63,12 +61,12 @@ class CListModeDataSAFIR : public CListModeDataBasedOnCoordinateMap CListModeDataSAFIR(const std::string& listmode_filename, const shared_ptr& proj_data_info_sptr); - shared_ptr get_empty_record_sptr() const override; - Succeeded get_next_record(CListRecord& record_of_general_type) const override; + shared_ptr get_empty_record_sptr() const override; + Succeeded get_next_record(CListRecordT& record_of_general_type) const override; + + virtual shared_ptr> get_current_lm_file() { return current_lm_data_ptr; }; - virtual shared_ptr> get_current_lm_file(){ - return current_lm_data_ptr; - }; + bool has_delayeds() const override { return false; } protected: virtual Succeeded open_lm_file() const override; diff --git a/src/include/stir/listmode/CListRecordPETSIRD.h b/src/include/stir/listmode/CListRecordPETSIRD.h index 7026f34f87..c0c3caafbc 100644 --- a/src/include/stir/listmode/CListRecordPETSIRD.h +++ b/src/include/stir/listmode/CListRecordPETSIRD.h @@ -1,36 +1,36 @@ /* CListRecordPETSIRD.h - Coincidence Event Class for PETSIRD: Header File +Coincidence Event Class for PETSIRD: Header File - Copyright 2015 ETH Zurich, Institute of Particle Physics - Copyright 2017 ETH Zurich, Institute of Particle Physics and Astrophysics - Copyright 2020, 2022 Positrigo AG, Zurich - Copyright 2025 National Physical Laboratory + Copyright 2015 ETH Zurich, Institute of Particle Physics + Copyright 2017 ETH Zurich, Institute of Particle Physics and Astrophysics + Copyright 2020, 2022 Positrigo AG, Zurich + Copyright 2025 National Physical Laboratory - 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 + 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 + 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. + 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. */ /*! - \file - \ingroup listmode - \brief Declaration of class stir::CListEventPETSIRD and stir::CListRecordPETSIRD with supporting classes +\file +\ingroup listmode +\brief Declaration of class stir::CListEventPETSIRD and stir::CListRecordPETSIRD with supporting classes - \author Jannis Fischer - \author Parisa Khateri - \author Markus Jehl - \author Daniel Deidda +\author Jannis Fischer +\author Parisa Khateri +\author Markus Jehl +\author Daniel Deidda */ #ifndef __stir_listmode_CListRecordPETSIRD_H__ @@ -59,7 +59,7 @@ START_NAMESPACE_STIR Provides interface of the record class to STIR by implementing get_LOR(). It uses an optional map from detector indices to coordinates to specify LORAs2Points from given detection pair indices. - \ingroup listmode +\ingroup listmode */ class CListEventPETSIRD : public CListEvent @@ -84,7 +84,7 @@ class CListEventPETSIRD : public CListEvent inline bool is_valid_template(const ProjDataInfo&) const override { return true; } //! Returns 0 if event is prompt and 1 if delayed - inline bool is_prompt() const override { return true; }//!(static_cast(this)->is_prompt()); } + inline bool is_prompt() const override { return true; } //!(static_cast(this)->is_prompt()); } //! Function to set map for detector indices to coordinates. /*! Use a null pointer to disable the mapping functionality */ inline void set_map_sptr(shared_ptr new_map_sptr) { map_sptr = new_map_sptr; } @@ -144,25 +144,20 @@ class CListEventPETSIRD : public CListEvent // #endif // }; - //! Class for record with time data using PETSIRD bitfield definition /*! \ingroup listmode */ class CListTimeDataPETSIRD { public: - inline unsigned long get_time_in_millisecs() const { return static_cast(time); } + inline unsigned long get_time_in_millisecs() const { /*return static_cast(time);*/ } inline Succeeded set_time_in_millisecs(const unsigned long time_in_millisecs) { - time = ((boost::uint64_t(1) << 49) - 1) & static_cast(time_in_millisecs); + // time = ((boost::uint64_t(1) << 49) - 1) & static_cast(time_in_millisecs); return Succeeded::yes; } - inline bool is_time() const { return type; } - -private: - + inline bool is_time() const { /*return type; */ } }; - class CListRecordPETSIRD : public CListRecord, public ListTime, public CListEventPETSIRD { public: @@ -193,8 +188,7 @@ class CListRecordPETSIRD : public CListRecord, public ListTime, public CListEven virtual bool operator==(const CListRecord& e2) const { - return dynamic_cast(&e2) != 0 - && raw == static_cast(e2).raw; + return dynamic_cast(&e2) != 0 && raw == static_cast(e2).raw; } inline unsigned long get_time_in_millisecs() const override { return time_data.get_time_in_millisecs(); } diff --git a/src/include/stir/listmode/CListRecordPETSIRD.inl b/src/include/stir/listmode/CListRecordPETSIRD.inl index 086c23d958..a500ccc628 100644 --- a/src/include/stir/listmode/CListRecordPETSIRD.inl +++ b/src/include/stir/listmode/CListRecordPETSIRD.inl @@ -131,5 +131,5 @@ namespace detail // det_pos_pair.pos2().tangential_coord() = detB; // } - +} // namespace detail END_NAMESPACE_STIR diff --git a/src/listmode_buildblock/CListModeDataPETSIRD.cxx b/src/listmode_buildblock/CListModeDataPETSIRD.cxx index 0d258c1950..017b4a988a 100644 --- a/src/listmode_buildblock/CListModeDataPETSIRD.cxx +++ b/src/listmode_buildblock/CListModeDataPETSIRD.cxx @@ -39,10 +39,10 @@ Coincidence LM Data Class for PETSIRD: Implementation #include "stir/info.h" #include "stir/error.h" #include "binary/protocols.h" -#include "helpers/include/petsird_helpers.h" -#include "helpers/include/petsird_helpers/create.h" -#include "helpers/include/petsird_helpers/geometry.h" -//#include "boost/static_assert.hpp" +// #include "helpers/include/petsird_helpers.h" +// #include "helpers/include/petsird_helpers/create.h" +// #include "helpers/include/petsird_helpers/geometry.h" +// #include "boost/static_assert.hpp" #include "stir/listmode/CListModeDataPETSIRD.h" // #include "stir/listmode/CListRecordPETSIRD.h" @@ -52,7 +52,7 @@ using std::fstream; using std::ifstream; using std::istream; -START_NAMESPACE_STIR; +START_NAMESPACE_STIR CListModeDataPETSIRD::CListModeDataPETSIRD(const std::string& listmode_filename, const std::string& crystal_map_filename, diff --git a/src/listmode_buildblock/CListModeDataSAFIR.cxx b/src/listmode_buildblock/CListModeDataSAFIR.cxx index ea0497ae43..7d1a0dc5e5 100644 --- a/src/listmode_buildblock/CListModeDataSAFIR.cxx +++ b/src/listmode_buildblock/CListModeDataSAFIR.cxx @@ -36,7 +36,7 @@ Coincidence LM Data Class for SAFIR: Implementation #include "stir/info.h" #include "stir/error.h" -//#include "boost/static_assert.hpp" +// #include "boost/static_assert.hpp" #include "stir/listmode/CListModeDataSAFIR.h" #include "stir/listmode/CListRecordSAFIR.h" @@ -68,7 +68,7 @@ CListModeDataSAFIR::CListModeDataSAFIR(const std::string& listmode _exam_info_sptr->imaging_modality = ImagingModality::PT; this->exam_info_sptr = _exam_info_sptr; - // Here we are reading the scanner data from the template projdata + // Here we are reading the scanner data from the template projdata shared_ptr template_proj_data_sptr = ProjData::read_from_file(template_proj_data_filename); this->set_proj_data_info_sptr(template_proj_data_sptr->get_proj_data_info_sptr()->create_shared_clone()); @@ -114,7 +114,7 @@ CListModeDataSAFIR::open_lm_file() const } template -shared_ptr +shared_ptr CListModeDataSAFIR::get_empty_record_sptr() const { shared_ptr sptr(new CListRecordT); @@ -123,10 +123,9 @@ CListModeDataSAFIR::get_empty_record_sptr() const return static_pointer_cast(sptr); } - template Succeeded -CListModeDataSAFIR::get_next_record(CListRecord& record_of_general_type) const +CListModeDataSAFIR::get_next_record(CListRecordT& record_of_general_type) const { CListRecordT& record = static_cast(record_of_general_type); Succeeded status = current_lm_data_ptr->get_next_record(record); From 85bedab48ebf2a8afce73d63590a57ad95206d08 Mon Sep 17 00:00:00 2001 From: danieldeidda Date: Wed, 16 Jul 2025 17:52:58 +0100 Subject: [PATCH 4/9] reading PETSIRD header [WIP] --- src/listmode_buildblock/CListModeDataPETSIRD.cxx | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/listmode_buildblock/CListModeDataPETSIRD.cxx b/src/listmode_buildblock/CListModeDataPETSIRD.cxx index 017b4a988a..ce183d3301 100644 --- a/src/listmode_buildblock/CListModeDataPETSIRD.cxx +++ b/src/listmode_buildblock/CListModeDataPETSIRD.cxx @@ -60,6 +60,19 @@ CListModeDataPETSIRD::CListModeDataPETSIRD(const std::string& listmode_filename, const double lor_randomization_sigma) { CListModeDataBasedOnCoordinateMap::listmode_filename = listmode_filename; + petsird::Header header; + petsird::binary::PETSIRDReader petsird_reader(listmode_filename); + petsird_reader.ReadHeader(header); + petsird::ScannerInformation scanner_info = header.scanner; + petsird::ScannerGeometry scanner_geo = scanner_info.scanner_geometry; + // need to get inner_ring_radius, num_detector_layers, num_transaxial_crystals_per_block, num_axial_crystals_per_block + // transaxial_crystal_spacing,average_depth_of_interaction,axial_crystal_spacing, num_rings, ring_spacing, num_axial_blocks, + // num_oftransaxial_blocks + // these are from rep_module.object + + std::vector replicated_module_list = scanner_geo.replicated_modules; + // petsird::ReplicatedDetectorModule = scanner_geo.replicated_modules; + // if (!crystal_map_filename.empty()) // { // this->map = MAKE_SHARED(crystal_map_filename, lor_randomization_sigma); From 9d664cafcdf58f40eb02a3ce232085480f68dd05 Mon Sep 17 00:00:00 2001 From: NikEfth Date: Wed, 16 Jul 2025 16:25:43 -0400 Subject: [PATCH 5/9] Updates on the PETSIRD interface and CMake [WIP] --- CMakeLists.txt | 6 + src/IO/CMakeLists.txt | 57 +++++--- src/IO/PETSIRDCListmodeInputFileFormat.cxx | 29 ++++ .../stir/IO/PETSIRDCListmodeInputFileFormat.h | 72 ++-------- .../CListModeDataBasedOnCoordinateMap.h | 54 +------ .../stir/listmode/CListModeDataPETSIRD.h | 22 +-- .../stir/listmode/CListModeDataSAFIR.h | 8 +- .../stir/listmode/CListRecordPETSIRD.h | 136 ++++-------------- .../stir/listmode/CListRecordPETSIRD.inl | 83 ++--------- .../CListModeDataBasedOnCoordinateMap.cxx | 7 - .../CListModeDataPETSIRD.cxx | 67 ++++----- .../CListModeDataSAFIR.cxx | 7 + src/listmode_buildblock/CMakeLists.txt | 36 +++-- 13 files changed, 204 insertions(+), 380 deletions(-) create mode 100644 src/IO/PETSIRDCListmodeInputFileFormat.cxx diff --git a/CMakeLists.txt b/CMakeLists.txt index 789a0ea38f..a19168a49f 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -315,6 +315,7 @@ if(NOT DISABLE_PETSIRD) WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/PETSIRD RESULT_VARIABLE JUST_RESULT ) + if(JUST_RESULT EQUAL 0) set(HAVE_PETSIRD TRUE) set(PETSIRD_base_dir ${PROJECT_SOURCE_DIR}/PETSIRD/cpp/generated) @@ -323,6 +324,11 @@ if(NOT DISABLE_PETSIRD) set(HAVE_PETSIRD FALSE) message(WARNING "PETSIRD generation failed with code ${JUST_RESULT}. HAVE_PETSIRD not set.") endif() + set(PETSIRD_dir ${PROJECT_SOURCE_DIR}/PETSIRD/cpp/generated) + add_subdirectory(${PETSIRD_dir} PETSIRD_generated) + install(TARGETS petsird_generated + EXPORT STIRTargets + DESTINATION lib) endif() #### enable support for ctest diff --git a/src/IO/CMakeLists.txt b/src/IO/CMakeLists.txt index 126aa138c0..215fca0424 100644 --- a/src/IO/CMakeLists.txt +++ b/src/IO/CMakeLists.txt @@ -80,6 +80,12 @@ if (HAVE_HDF5) ) endif() +if (HAVE_PETSIRD) +list(APPEND ${dir_LIB_SOURCES} + PETSIRDCListmodeInputFileFormat.cxx +) +endif() + endif() # MINI_STIR target_sources(${STIR_BUILDBLOCK_LIB} PRIVATE ${${dir_LIB_SOURCES}}) @@ -139,30 +145,47 @@ if (HAVE_JSON) endif() if (HAVE_PETSIRD) -# # #set(PETSIRD_dir ../../PETSIRD/cpp/generated) +# #set(PETSIRD_dir ../../PETSIRD/cpp/generated) -# # target_include_directories(IO PUBLIC -# # $ -# # $ -# # ) -# # target_include_directories(IO PUBLIC -# # $ -# # $ -# # ) +# target_include_directories(IO PUBLIC +# $ +# $ +# $ +# $ +# ) + +# # # target_include_directories(IO PUBLIC +# # # $ +# # # $ +# # # ) + +# # # target_include_directories(IO PUBLIC +# # # $ +# # # $ +# # # ) # # target_include_directories(IO PUBLIC -# # $ +# # $ +# # $ +# # $ # # $ # # ) -# target_include_directories(IO PUBLIC -# $ -# $ -# $ -# $ -# ) -# target_link_libraries(IO PUBLIC petsird_generated) +# # target_link_libraries(IO PUBLIC petsird_generated) + +# set(PETSIRD_dir ../../PETSIRD/cpp/generated) +# add_subdirectory(${PETSIRD_dir} PETSIRD_generated) + +# install(TARGETS petsird_generated +# EXPORT STIRTargets +# DESTINATION lib) + +# target_include_directories(IO PUBLIC ${PETSIRD_dir}) +# # needed for helpers +# target_include_directories(IO PUBLIC ${PETSIRD_dir}/..) +# target_include_directories(IO PUBLIC ${PETSIRD_dir}/../helpers/include) +target_link_libraries(IO PUBLIC petsird_generated) endif() diff --git a/src/IO/PETSIRDCListmodeInputFileFormat.cxx b/src/IO/PETSIRDCListmodeInputFileFormat.cxx new file mode 100644 index 0000000000..982295c673 --- /dev/null +++ b/src/IO/PETSIRDCListmodeInputFileFormat.cxx @@ -0,0 +1,29 @@ +#include "stir/IO/PETSIRDCListmodeInputFileFormat.h" +#include "../../PETSIRD/cpp/generated/binary/protocols.h" +#include "../../PETSIRD/cpp/generated/hdf5/protocols.h" +// #include "../../PETSIRD/cpp/generated/types.h" +// #include "../../PETSIRD/cpp/helpers/include/petsird_helpers.h" + +START_NAMESPACE_STIR + +bool +PETSIRDCListmodeInputFileFormat::can_read(const FileSignature& signature, const std::string& filename) const +{ + + petsird::hdf5::PETSIRDReader* petsird_reader = new petsird::hdf5::PETSIRDReader(filename); + + if(is_null_ptr(petsird_reader)) + { + + petsird::binary::PETSIRDReader* petsird_reader = new petsird::binary::PETSIRDReader(filename); + if(is_null_ptr(petsird_reader)) + { + return false; + } + return true; + } + + return true; +} + +END_NAMESPACE_STIR diff --git a/src/include/stir/IO/PETSIRDCListmodeInputFileFormat.h b/src/include/stir/IO/PETSIRDCListmodeInputFileFormat.h index cc27dd2e81..13f04ab952 100644 --- a/src/include/stir/IO/PETSIRDCListmodeInputFileFormat.h +++ b/src/include/stir/IO/PETSIRDCListmodeInputFileFormat.h @@ -34,17 +34,11 @@ #ifndef __stir_IO_PETSIRDCListmodeInputFileFormat_H__ #define __stir_IO_PETSIRDCListmodeInputFileFormat_H__ -#include -#include -#include +// #include "boost/algorithm/string.hpp" -#include "boost/algorithm/string.hpp" - -#include "stir/IO/InputFileFormat.h" #include "stir/IO/InputFileFormat.h" #include "stir/error.h" -// #include "stir/listmode/CListRecordPETSIRD.h" #include "stir/listmode/CListModeDataPETSIRD.h" START_NAMESPACE_STIR @@ -62,65 +56,23 @@ class PETSIRDCListmodeInputFileFormat : public InputFileFormat const std::string get_name() const override { return "PETSIRD"; } //! Checks in binary data file for correct signature. - bool can_read(const FileSignature& signature, std::istream& input) const override - { - return false; // cannot read from istream - } + bool can_read(const FileSignature& signature, const std::string& filename) const override; - //! Checks in binary data file for correct signature (can be either "PETSIRD CListModeData", "NeuroLF CListModeData" or "MUPET - //! CListModeData"). - bool can_read(const FileSignature& signature, const std::string& filename) const override - { - int nikos = 0; - std::string d = filename; - // PETSIRDReader ndn(d); - // // Looking for the right key in the parameter file - // std::ifstream par_file(filename.c_str()); - // std::string key; - // std::getline(par_file, key, ':'); - // key = standardise_interfile_keyword(key); - // if (key != std::string("clistmodedataPETSIRD parameters")) - // { - // return false; - // } - // if (!actual_do_parsing(filename)) - // return false; - // std::ifstream data_file(listmode_filename.c_str(), std::ios::binary); - // char* buffer = new char[32]; - // data_file.read(buffer, 32); - // bool cr = false; - // // depending on used template, check header of listmode file for correct format - // if (std::is_same::value) - // { - // cr = (!strncmp(buffer, "MUPET CListModeData\0", 20) || !strncmp(buffer, "PETSIRD CListModeData\0", 20)); - // } - // else if (std::is_same::value) - // { - // cr = !strncmp(buffer, "NeuroLF CListModeData\0", 20); - // } - // else - // { - // warning("PETSIRDCListModeInputFileFormat was initialised with an unexpected template."); - // } - - // if (!cr) - // { - // warning("PETSIRDCListModeInputFileFormat tried to read file " + listmode_filename - // + " but it seems to have the wrong signature."); - // } - - // delete[] buffer; - // return cr; - return true; - } +protected: + bool actual_can_read(const FileSignature& signature, std::istream& input) const override { return false; } - std::unique_ptr read_from_file(std::istream& input) const override +public: + unique_ptr read_from_file(std::istream& input) const override { - error("read_from_file for PETSIRDCListmodeData with istream not implemented %s:%d. Sorry", __FILE__, __LINE__); + error("read_from_file for ROOT listmode data with istream not implemented %s:%s. Sorry", __FILE__, __LINE__); return unique_ptr(); } - std::unique_ptr read_from_file(const std::string& filename) const override {} + unique_ptr read_from_file(const std::string& filename) const override + { + info("PETSIRDCListmodeInputFileFormat: read_from_file(" + std::string(filename) + ")"); + return unique_ptr(new CListModeDataPETSIRD(filename)); + } }; END_NAMESPACE_STIR #endif diff --git a/src/include/stir/listmode/CListModeDataBasedOnCoordinateMap.h b/src/include/stir/listmode/CListModeDataBasedOnCoordinateMap.h index ee32535398..babffd05db 100644 --- a/src/include/stir/listmode/CListModeDataBasedOnCoordinateMap.h +++ b/src/include/stir/listmode/CListModeDataBasedOnCoordinateMap.h @@ -51,14 +51,12 @@ class CListModeDataBasedOnCoordinateMap : public CListModeData { public: std::string get_name() const override; - // shared_ptr get_empty_record_sptr() const override; - // Succeeded get_next_record(CListRecord& record_of_general_type) const override; - Succeeded reset() override; - virtual shared_ptr> get_current_lm_file() = 0; + // virtual shared_ptr> get_current_lm_file() = 0; - SavedPosition save_get_position() override { return static_cast(get_current_lm_file()->save_get_position()); } - Succeeded set_get_position(const SavedPosition& pos) override { return get_current_lm_file()->set_get_position(pos); } + SavedPosition save_get_position() override = 0; + + Succeeded set_get_position(const SavedPosition& pos) override = 0; protected: std::string listmode_filename; @@ -69,50 +67,6 @@ class CListModeDataBasedOnCoordinateMap : public CListModeData shared_ptr map; }; -// CListModeDataBasedOnCoordinateMap::CListModeDataBasedOnCoordinateMap(const std::string& listmode_filename, -// const std::string& crystal_map_filename, -// const std::string& template_proj_data_filename, -// const double lor_randomization_sigma) -// : listmode_filename(listmode_filename) -// { -// if (!crystal_map_filename.empty()) -// { -// map = MAKE_SHARED(crystal_map_filename, lor_randomization_sigma); -// } -// else -// { -// if (lor_randomization_sigma != 0) -// error("SAFIR currently does not support LOR-randomisation unless a map is specified"); -// } -// shared_ptr _exam_info_sptr(new ExamInfo); -// _exam_info_sptr->imaging_modality = ImagingModality::PT; -// this->exam_info_sptr = _exam_info_sptr; - -// // Here we are reading the scanner data from the template projdata -// shared_ptr template_proj_data_sptr = ProjData::read_from_file(template_proj_data_filename); -// this->set_proj_data_info_sptr(template_proj_data_sptr->get_proj_data_info_sptr()->create_shared_clone()); - -// if (open_lm_file() == Succeeded::no) -// { -// error("CListModeDataSAFIR: Could not open listmode file " + listmode_filename + "\n"); -// } -// } - -// // CListModeDataBasedOnCoordinateMap::CListModeDataBasedOnCoordinateMap(const std::string& listmode_filename, -// // const shared_ptr& proj_data_info_sptr) -// // : listmode_filename(listmode_filename) -// // { -// // shared_ptr _exam_info_sptr(new ExamInfo); -// // _exam_info_sptr->imaging_modality = ImagingModality::PT; -// // this->exam_info_sptr = _exam_info_sptr; -// // this->set_proj_data_info_sptr(proj_data_info_sptr->create_shared_clone()); - -// // if (open_lm_file() == Succeeded::no) -// // { -// // error("CListModeDataSAFIR: opening file \"" + listmode_filename + "\""); -// // } -// // } - END_NAMESPACE_STIR #endif diff --git a/src/include/stir/listmode/CListModeDataPETSIRD.h b/src/include/stir/listmode/CListModeDataPETSIRD.h index 7cf2e78313..0fe44b39be 100644 --- a/src/include/stir/listmode/CListModeDataPETSIRD.h +++ b/src/include/stir/listmode/CListModeDataPETSIRD.h @@ -47,6 +47,9 @@ Jannis Fischer #include "stir/listmode/CListRecordPETSIRD.h" +#include "../../PETSIRD/cpp/generated/binary/protocols.h" +#include "../../PETSIRD/cpp/generated/hdf5/protocols.h" + START_NAMESPACE_STIR /*! @@ -60,21 +63,24 @@ START_NAMESPACE_STIR class CListModeDataPETSIRD : public CListModeDataBasedOnCoordinateMap { public: - CListModeDataPETSIRD(const std::string& listmode_filename, - const std::string& crystal_map_filename, - const std::string& template_proj_data_filename, - const double lor_randomization_sigma = 0.0); + CListModeDataPETSIRD(const std::string& listmode_filename); + + virtual shared_ptr get_empty_record_sptr() const override; - shared_ptr get_empty_record_sptr() const override { return nullptr; } + virtual Succeeded get_next_record(CListRecord& record_of_general_type) const override; - Succeeded get_next_record(CListRecord& record_of_general_type) const override { return Succeeded::no; } + SavedPosition save_get_position() override {} - virtual shared_ptr> get_current_lm_file() override {} + Succeeded set_get_position(const SavedPosition& pos) override {} - bool has_delayeds() const override { return false; } + virtual bool has_delayeds() const override { return true; } + + Succeeded reset() override {} protected: virtual Succeeded open_lm_file() const override; + + mutable shared_ptr current_lm_data_ptr; }; END_NAMESPACE_STIR diff --git a/src/include/stir/listmode/CListModeDataSAFIR.h b/src/include/stir/listmode/CListModeDataSAFIR.h index c26d2798b0..d8d400daf7 100644 --- a/src/include/stir/listmode/CListModeDataSAFIR.h +++ b/src/include/stir/listmode/CListModeDataSAFIR.h @@ -64,10 +64,14 @@ class CListModeDataSAFIR : public CListModeDataBasedOnCoordinateMap shared_ptr get_empty_record_sptr() const override; Succeeded get_next_record(CListRecordT& record_of_general_type) const override; - virtual shared_ptr> get_current_lm_file() { return current_lm_data_ptr; }; - bool has_delayeds() const override { return false; } + Succeeded reset() override; + + SavedPosition save_get_position() override { return static_cast(current_lm_data_ptr->save_get_position()); } + + Succeeded set_get_position(const SavedPosition& pos) override { return current_lm_data_ptr->set_get_position(pos); } + protected: virtual Succeeded open_lm_file() const override; mutable shared_ptr> current_lm_data_ptr; diff --git a/src/include/stir/listmode/CListRecordPETSIRD.h b/src/include/stir/listmode/CListRecordPETSIRD.h index c0c3caafbc..2c288dd6cf 100644 --- a/src/include/stir/listmode/CListRecordPETSIRD.h +++ b/src/include/stir/listmode/CListRecordPETSIRD.h @@ -36,12 +36,9 @@ Coincidence Event Class for PETSIRD: Header File #ifndef __stir_listmode_CListRecordPETSIRD_H__ #define __stir_listmode_CListRecordPETSIRD_H__ -#include - #include "stir/listmode/CListRecord.h" #include "stir/DetectionPositionPair.h" #include "stir/Succeeded.h" -#include "stir/ByteOrder.h" #include "stir/ByteOrderDefine.h" #include "boost/static_assert.hpp" @@ -65,9 +62,6 @@ coordinates to specify LORAs2Points from given detection pair indices. class CListEventPETSIRD : public CListEvent { public: - /*! Default constructor will not work as it does not initialize a map to relate - detector indices and space coordinates. Always use either set_scanner_sptr or set_map_sptr after default construction. - */ inline CListEventPETSIRD() {} //! Returns LOR corresponding to the given event. @@ -76,22 +70,16 @@ class CListEventPETSIRD : public CListEvent //! Override the default implementation inline void get_bin(Bin& bin, const ProjDataInfo& proj_data_info) const override; - //! This method checks if the template is valid for LmToProjData - /*! Used before the actual processing of the data (see issue #61), before calling get_bin() - * Most scanners have listmode data that correspond to non arc-corrected data and - * this check avoids a crash when an unsupported template is used as input. - */ - inline bool is_valid_template(const ProjDataInfo&) const override { return true; } - //! Returns 0 if event is prompt and 1 if delayed - inline bool is_prompt() const override { return true; } //!(static_cast(this)->is_prompt()); } - //! Function to set map for detector indices to coordinates. - /*! Use a null pointer to disable the mapping functionality */ + inline bool is_prompt() const override { return true; } + inline void set_map_sptr(shared_ptr new_map_sptr) { map_sptr = new_map_sptr; } /*! Set the scanner */ /*! Currently only used if the map is not set. */ inline void set_scanner_sptr(shared_ptr new_scanner_sptr) { scanner_sptr = new_scanner_sptr; } + virtual bool is_valid_template(const ProjDataInfo&) const override { return true; } + private: shared_ptr map_sptr; shared_ptr scanner_sptr; @@ -99,54 +87,9 @@ class CListEventPETSIRD : public CListEvent const DetectorCoordinateMap& map_to_use() const { return map_sptr ? *map_sptr : *this->scanner_sptr->get_detector_map_sptr(); } }; -// //! Class for record with coincidence data using PETSIRD bitfield definition -// /*! \ingroup listmode */ -// class CListEventDataPETSIRD -// { -// public: -// //! Writes detection position pair to reference given as argument. -// inline void get_detection_position_pair(DetectionPositionPair<>& det_pos_pair); - -// //! Returns 0 if event is prompt and 1 if delayed -// inline bool is_prompt() const { return !isDelayed; } - -// //! Returns 1 if if event is time and 0 if it is prompt -// inline bool is_time() const { return type; } - -// //! Can be used to set "promptness" of event. -// inline Succeeded set_prompt(const bool prompt = true) -// { -// isDelayed = !prompt; -// return Succeeded::yes; -// } - -// private: -// #if STIRIsNativeByteOrderBigEndian -// unsigned type : 1; -// unsigned isDelayed : 1; -// unsigned reserved : 6; -// unsigned layerB : 4; -// unsigned layerA : 4; -// unsigned detB : 16; -// unsigned detA : 16; -// unsigned ringB : 8; -// unsigned ringA : 8; -// #else -// unsigned ringA : 8; -// unsigned ringB : 8; -// unsigned detA : 16; -// unsigned detB : 16; -// unsigned layerA : 4; -// unsigned layerB : 4; -// unsigned reserved : 6; -// unsigned isDelayed : 1; -// unsigned type : 1; -// #endif -// }; - //! Class for record with time data using PETSIRD bitfield definition /*! \ingroup listmode */ -class CListTimeDataPETSIRD +class CListTimePETSIRD : public ListTime { public: inline unsigned long get_time_in_millisecs() const { /*return static_cast(time);*/ } @@ -158,73 +101,42 @@ class CListTimeDataPETSIRD inline bool is_time() const { /*return type; */ } }; -class CListRecordPETSIRD : public CListRecord, public ListTime, public CListEventPETSIRD +class CListRecordPETSIRD : public CListRecord { public: - //! Returns event_data (without checking if the type is really event and not time). - CListEventPETSIRD get_data() const { return this->event_data; } - - // CListRecordPETSIRD() - // : CListEventPETSIRD>() - // {} + CListRecordPETSIRD() {} ~CListRecordPETSIRD() override {} - bool is_time() const override { return time_data.is_time(); } - - bool is_event() const override { return !time_data.is_time(); } - - CListEvent& event() override { return *this; } - - const CListEvent& event() const override { return *this; } - - virtual CListEventPETSIRD& event_PETSIRD() { return *this; } + bool is_time() const override { /*return time_data.is_time();*/ } - virtual const CListEventPETSIRD& event_PETSIRD() const { return *this; } + bool is_event() const override { /*return !time_data.is_time();*/ } - ListTime& time() override { return *this; } + ListEvent& event() override { return event_data; } + const ListEvent& event() const override { return event_data; } - const ListTime& time() const override { return *this; } + ListTime& time() override { return time_data; } + const ListTime& time() const override { return time_data; } - virtual bool operator==(const CListRecord& e2) const - { - return dynamic_cast(&e2) != 0 && raw == static_cast(e2).raw; - } - - inline unsigned long get_time_in_millisecs() const override { return time_data.get_time_in_millisecs(); } - - inline Succeeded set_time_in_millisecs(const unsigned long time_in_millisecs) override - { - return time_data.set_time_in_millisecs(time_in_millisecs); - } + // virtual bool operator==(const CListRecordPETSIRD& e2) const + // { + // // return dynamic_cast(&e2) != 0 && raw == static_cast(e2).r; + // } - inline bool is_prompt() const override { return event_data.is_prompt(); } + // inline bool is_prompt() const override { /*return event_data.is_prompt();*/ } Succeeded init_from_data_ptr(const char* const data_ptr, const std::size_t size_of_record, const bool do_byte_swap) { - assert(size_of_record >= 8); - std::copy(data_ptr, data_ptr + 8, reinterpret_cast(&raw)); // TODO necessary for operator== - if (do_byte_swap) - ByteOrder::swap_order(raw); + // assert(size_of_record >= 8); + // std::copy(data_ptr, data_ptr + 8, reinterpret_cast(&raw)); // TODO necessary for operator== + // if (do_byte_swap) + // ByteOrder::swap_order(raw); return Succeeded::yes; } - std::size_t size_of_record_at_ptr(const char* const /*data_ptr*/, const std::size_t /*size*/, const bool /*do_byte_swap*/) const - { - return 8; - } - private: - // use C++ union to save data, you can only use one at a time, - // but compiler will not check which one was used! - // Be careful not to read event data from time record and vice versa!! - // However, this is used as a feature if comparing events over the 'raw' type. - union - { - CListEventPETSIRD event_data; - CListTimeDataPETSIRD time_data; - boost::int64_t raw; - }; + CListEventPETSIRD event_data; + CListTimePETSIRD time_data; }; END_NAMESPACE_STIR diff --git a/src/include/stir/listmode/CListRecordPETSIRD.inl b/src/include/stir/listmode/CListRecordPETSIRD.inl index a500ccc628..933cf7a944 100644 --- a/src/include/stir/listmode/CListRecordPETSIRD.inl +++ b/src/include/stir/listmode/CListRecordPETSIRD.inl @@ -54,82 +54,19 @@ START_NAMESPACE_STIR LORAs2Points CListEventPETSIRD::get_LOR() const { - LORAs2Points lor; - DetectionPositionPair<> det_pos_pair; + // LORAs2Points lor; + // DetectionPositionPair<> det_pos_pair; - // static_cast(this)->get_data().get_detection_position_pair(det_pos_pair); + // // static_cast(this)->get_data().get_detection_position_pair(det_pos_pair); - lor.p1() = map_to_use().get_coordinate_for_index(det_pos_pair.pos1()); - lor.p2() = map_to_use().get_coordinate_for_index(det_pos_pair.pos2()); + // lor.p1() = map_to_use().get_coordinate_for_index(det_pos_pair.pos1()); + // lor.p2() = map_to_use().get_coordinate_for_index(det_pos_pair.pos2()); - return lor; + // return lor; } -namespace detail -{ -// template -// static inline bool -// get_bin_for_det_pos_pair(Bin& bin, DetectionPositionPair<>& det_pos_pair, const ProjDataInfo& proj_data_info) -// { -// if (auto proj_data_info_ptr = dynamic_cast(&proj_data_info)) -// { -// if (proj_data_info_ptr->get_bin_for_det_pos_pair(bin, det_pos_pair) == Succeeded::yes) -// bin.set_bin_value(1); -// else -// bin.set_bin_value(-1); -// return true; -// } -// else -// return false; -// } -// } // namespace detail - -// template -// void -// CListEventPETSIRD::get_bin(Bin& bin, const ProjDataInfo& proj_data_info) const -// { -// DetectionPositionPair<> det_pos_pair; -// static_cast(this)->get_data().get_detection_position_pair(det_pos_pair); - -// if (!map_sptr) -// { -// // transform det_pos_pair into stir conventions -// det_pos_pair.pos1() = map_to_use().get_det_pos_for_index(det_pos_pair.pos1()); -// det_pos_pair.pos2() = map_to_use().get_det_pos_for_index(det_pos_pair.pos2()); - -// if (det_pos_pair.pos1().tangential_coord() == det_pos_pair.pos2().tangential_coord()) -// { -// bin.set_bin_value(-1); -// return; -// } - -// if (!detail::get_bin_for_det_pos_pair(bin, det_pos_pair, proj_data_info)) -// { -// if (!detail::get_bin_for_det_pos_pair(bin, det_pos_pair, proj_data_info)) -// error("Wrong type of proj-data-info for PETSIRD"); -// } -// } -// else -// { -// const stir::CartesianCoordinate3D c1 = map_sptr->get_coordinate_for_index(det_pos_pair.pos1()); -// const stir::CartesianCoordinate3D c2 = map_sptr->get_coordinate_for_index(det_pos_pair.pos2()); -// const LORAs2Points lor(c1, c2); -// bin = proj_data_info.get_bin(lor); -// } -// } - -// void -// CListEventDataPETSIRD::get_detection_position_pair(DetectionPositionPair<>& det_pos_pair) -// { -// det_pos_pair.pos1().radial_coord() = layerA; -// det_pos_pair.pos2().radial_coord() = layerB; - -// det_pos_pair.pos1().axial_coord() = ringA; -// det_pos_pair.pos2().axial_coord() = ringB; - -// det_pos_pair.pos1().tangential_coord() = detA; -// det_pos_pair.pos2().tangential_coord() = detB; -// } - -} // namespace detail +void +CListEventPETSIRD::get_bin(Bin& bin, const ProjDataInfo& proj_data_info) const +{} + END_NAMESPACE_STIR diff --git a/src/listmode_buildblock/CListModeDataBasedOnCoordinateMap.cxx b/src/listmode_buildblock/CListModeDataBasedOnCoordinateMap.cxx index 8f3564563a..fda23351d5 100644 --- a/src/listmode_buildblock/CListModeDataBasedOnCoordinateMap.cxx +++ b/src/listmode_buildblock/CListModeDataBasedOnCoordinateMap.cxx @@ -47,11 +47,4 @@ CListModeDataBasedOnCoordinateMap::get_name() const return listmode_filename; } - -Succeeded -CListModeDataBasedOnCoordinateMap::reset() -{ - return get_current_lm_file()->reset(); -} - END_NAMESPACE_STIR diff --git a/src/listmode_buildblock/CListModeDataPETSIRD.cxx b/src/listmode_buildblock/CListModeDataPETSIRD.cxx index ce183d3301..febebf78ec 100644 --- a/src/listmode_buildblock/CListModeDataPETSIRD.cxx +++ b/src/listmode_buildblock/CListModeDataPETSIRD.cxx @@ -38,39 +38,33 @@ Coincidence LM Data Class for PETSIRD: Implementation #include "stir/Succeeded.h" #include "stir/info.h" #include "stir/error.h" -#include "binary/protocols.h" + // #include "helpers/include/petsird_helpers.h" // #include "helpers/include/petsird_helpers/create.h" // #include "helpers/include/petsird_helpers/geometry.h" // #include "boost/static_assert.hpp" #include "stir/listmode/CListModeDataPETSIRD.h" -// #include "stir/listmode/CListRecordPETSIRD.h" - -using std::ios; -using std::fstream; -using std::ifstream; -using std::istream; +#include "stir/listmode/CListRecordPETSIRD.h" START_NAMESPACE_STIR -CListModeDataPETSIRD::CListModeDataPETSIRD(const std::string& listmode_filename, - const std::string& crystal_map_filename, - const std::string& template_proj_data_filename, - const double lor_randomization_sigma) +CListModeDataPETSIRD::CListModeDataPETSIRD(const std::string& listmode_filename) { - CListModeDataBasedOnCoordinateMap::listmode_filename = listmode_filename; - petsird::Header header; - petsird::binary::PETSIRDReader petsird_reader(listmode_filename); - petsird_reader.ReadHeader(header); - petsird::ScannerInformation scanner_info = header.scanner; - petsird::ScannerGeometry scanner_geo = scanner_info.scanner_geometry; + this->listmode_filename = listmode_filename; + + // petsird::Header header; + // petsird::binary::PETSIRDReader petsird_reader(listmode_filename); + // petsird_reader.ReadHeader(header); + // petsird::ScannerInformation scanner_info = header.scanner; + // petsird::ScannerGeometry scanner_geo = scanner_info.scanner_geometry; + // need to get inner_ring_radius, num_detector_layers, num_transaxial_crystals_per_block, num_axial_crystals_per_block // transaxial_crystal_spacing,average_depth_of_interaction,axial_crystal_spacing, num_rings, ring_spacing, num_axial_blocks, // num_oftransaxial_blocks // these are from rep_module.object - std::vector replicated_module_list = scanner_geo.replicated_modules; + // std::vector replicated_module_list = scanner_geo.replicated_modules; // petsird::ReplicatedDetectorModule = scanner_geo.replicated_modules; // if (!crystal_map_filename.empty()) @@ -90,30 +84,31 @@ CListModeDataPETSIRD::CListModeDataPETSIRD(const std::string& listmode_filename, // shared_ptr template_proj_data_sptr = ProjData::read_from_file(template_proj_data_filename); // this->set_proj_data_info_sptr(template_proj_data_sptr->get_proj_data_info_sptr()->create_shared_clone()); - // if (this->open_lm_file() == Succeeded::no) - // { - // error("CListModeDataPETSIRD: Could not open listmode file " + listmode_filename + "\n"); - // } + if (this->open_lm_file() == Succeeded::no) + { + error("CListModeDataPETSIRD: Could not open listmode file " + listmode_filename + "\n"); + } } Succeeded CListModeDataPETSIRD::open_lm_file() const { - // shared_ptr stream_ptr(new fstream(this->listmode_filename.c_str(), ios::in | ios::binary)); - // if (!(*stream_ptr)) - // { - // return Succeeded::no; - // } - // info("CListModeDataPETSIRD: opening file \"" + this->listmode_filename + "\"", 2); - // stream_ptr->seekg((std::streamoff)32); - // this->current_lm_data_ptr.reset( - // new InputStreamWithRecords(stream_ptr, - // sizeof(CListTimeDataPETSIRD), - // sizeof(CListTimeDataPETSIRD), - // ByteOrder::little_endian != ByteOrder::get_native_order())); - // return Succeeded::yes; + current_lm_data_ptr.reset(new petsird::hdf5::PETSIRDReader(listmode_filename)); + return Succeeded::yes; +} + +shared_ptr +CListModeDataPETSIRD::get_empty_record_sptr() const +{ + shared_ptr sptr(new CListRecordPETSIRD()); + return sptr; } -// template class CListModeDataPETSIRD; +Succeeded +CListModeDataPETSIRD::get_next_record(CListRecord& record_of_general_type) const +{ + CListRecordPETSIRD& record = dynamic_cast(record_of_general_type); + // return current_lm_data_ptr->get_next_record(record); +} END_NAMESPACE_STIR diff --git a/src/listmode_buildblock/CListModeDataSAFIR.cxx b/src/listmode_buildblock/CListModeDataSAFIR.cxx index 7d1a0dc5e5..e584b6fdd6 100644 --- a/src/listmode_buildblock/CListModeDataSAFIR.cxx +++ b/src/listmode_buildblock/CListModeDataSAFIR.cxx @@ -133,6 +133,13 @@ CListModeDataSAFIR::get_next_record(CListRecordT& record_of_genera return status; } +template +Succeeded +CListModeDataSAFIR::reset() +{ + return current_lm_data_ptr->reset(); +} + // template class CListModeDataSAFIR>; // template class CListModeDataSAFIR>; diff --git a/src/listmode_buildblock/CMakeLists.txt b/src/listmode_buildblock/CMakeLists.txt index 7055ca4b7c..4aa4db00f5 100644 --- a/src/listmode_buildblock/CMakeLists.txt +++ b/src/listmode_buildblock/CMakeLists.txt @@ -79,19 +79,25 @@ if (HAVE_PETSIRD) #set(PETSIRD_dir ../../PETSIRD/cpp/generated) #add_subdirectory(${PETSIRD_dir} PETSIRD_generated) -target_include_directories(listmode_buildblock PUBLIC - $ - $ -) - -target_include_directories(listmode_buildblock PUBLIC - $ - $ -) - -target_include_directories(listmode_buildblock PUBLIC - $ - $ -) -#target_link_libraries(IO PUBLIC petsird_generated) +# target_include_directories(listmode_buildblock PUBLIC +# $ +# $ +# ) + +# target_include_directories(listmode_buildblock PUBLIC +# $ +# $ +# ) + +# target_include_directories(listmode_buildblock PUBLIC +# $ +# $ +# ) + +# target_include_directories(listmode_buildblock PUBLIC +# $ +# $ +# ) + +target_link_libraries(listmode_buildblock PUBLIC petsird_generated) endif() From b4f9d4720f152a6e151fa1c89f18215bf5ada577 Mon Sep 17 00:00:00 2001 From: danieldeidda Date: Thu, 17 Jul 2025 16:43:53 +0100 Subject: [PATCH 6/9] PETSIRD: extracting position of each detector [WIP] --- .../CListModeDataPETSIRD.cxx | 49 +++++++++++++++---- 1 file changed, 39 insertions(+), 10 deletions(-) diff --git a/src/listmode_buildblock/CListModeDataPETSIRD.cxx b/src/listmode_buildblock/CListModeDataPETSIRD.cxx index febebf78ec..3d88e212ff 100644 --- a/src/listmode_buildblock/CListModeDataPETSIRD.cxx +++ b/src/listmode_buildblock/CListModeDataPETSIRD.cxx @@ -39,9 +39,10 @@ Coincidence LM Data Class for PETSIRD: Implementation #include "stir/info.h" #include "stir/error.h" -// #include "helpers/include/petsird_helpers.h" +#include "binary/protocols.h" +#include "helpers/include/petsird_helpers.h" // #include "helpers/include/petsird_helpers/create.h" -// #include "helpers/include/petsird_helpers/geometry.h" +#include "helpers/include/petsird_helpers/geometry.h" // #include "boost/static_assert.hpp" #include "stir/listmode/CListModeDataPETSIRD.h" @@ -53,19 +54,47 @@ CListModeDataPETSIRD::CListModeDataPETSIRD(const std::string& listmode_filename) { this->listmode_filename = listmode_filename; - // petsird::Header header; - // petsird::binary::PETSIRDReader petsird_reader(listmode_filename); - // petsird_reader.ReadHeader(header); - // petsird::ScannerInformation scanner_info = header.scanner; - // petsird::ScannerGeometry scanner_geo = scanner_info.scanner_geometry; - + petsird::Header header; + petsird::binary::PETSIRDReader petsird_reader(listmode_filename); + petsird_reader.ReadHeader(header); + petsird::ScannerInformation scanner_info = header.scanner; + petsird::ScannerGeometry scanner_geo = scanner_info.scanner_geometry; + const petsird::TypeOfModule type_of_module{ 0 }; // need to get inner_ring_radius, num_detector_layers, num_transaxial_crystals_per_block, num_axial_crystals_per_block // transaxial_crystal_spacing,average_depth_of_interaction,axial_crystal_spacing, num_rings, ring_spacing, num_axial_blocks, // num_oftransaxial_blocks // these are from rep_module.object - // std::vector replicated_module_list = scanner_geo.replicated_modules; - // petsird::ReplicatedDetectorModule = scanner_geo.replicated_modules; + std::vector replicated_module_list = scanner_geo.replicated_modules; + int num_modules = scanner_geo.replicated_modules[type_of_module].transforms.size(); + int num_elements_per_module = scanner_geo.replicated_modules[type_of_module].object.detecting_elements.transforms.size(); + const auto& tof_bin_edges = header.scanner.tof_bin_edges[type_of_module][type_of_module]; + const auto num_tof_bins = tof_bin_edges.NumberOfBins(); + const auto& event_energy_bin_edges = header.scanner.event_energy_bin_edges[type_of_module]; + const auto num_event_energy_bins = event_energy_bin_edges.NumberOfBins(); + // coordinates of first detecting bin (module_id,element_id, energy_id) + // const petsird::ExpandedDetectionBin expanded_detection_bin{ 0, 0, 0 }; + const auto box_shape = petsird_helpers::geometry::get_detecting_box(header.scanner, type_of_module, expanded_detection_bin); + // get center of box (this should be in a loop to create a map + + for (uint32_t module = 0; module < num_modules; module++) + for (uint32_t elem = 0; elem < num_elements_per_module; elem++) + for (uint32_t ener = 0; ener < num_event_energy_bins; ener++) + { + petsird::ExpandedDetectionBin expanded_detection_bin{ module, elem, ener }; + const auto box_shape + = petsird_helpers::geometry::get_detecting_box(header.scanner, type_of_module, expanded_detection_bin); + CartesianCoordinate3D mean_pos; + for (auto& corner : box_shape.corners) + { // if STIR (z,y,x) -> PETSIRD (-y, -x, z) pheraps the order below needs to be changed + mean_pos.x() = +corner.c[0] / box_shape.corners.size(); + mean_pos.y() = +corner.c[1] / box_shape.corners.size(); + mean_pos.z() = +corner.c[2] / box_shape.corners.size(); + } + // save mean pos into map + } + + // petsird::ReplicatedDetectorModule = scanner_geo.replicated_modules; // if (!crystal_map_filename.empty()) // { From b32937847fc1748163f0aeb8e02dd89a92be8c12 Mon Sep 17 00:00:00 2001 From: NikEfth Date: Thu, 17 Jul 2025 19:21:30 -0400 Subject: [PATCH 7/9] PETSIRD: orientations etc [WIP] --- .github/workflows/build-test.yml | 2 +- .travis.yml | 18 +- src/IO/CMakeLists.txt | 11 +- src/IO/PETSIRDCListmodeInputFileFormat.cxx | 43 +- src/include/stir/IO/InputFileFormat.h | 2 +- src/include/stir/IO/InputStreamFromPETSIRD.h | 85 +++ .../stir/IO/PETSIRDCListmodeInputFileFormat.h | 6 +- .../stir/IO/SAFIRCListmodeInputFileFormat.h | 2 +- .../stir/listmode/CListModeDataPETSIRD.h | 79 ++- .../stir/listmode/CListRecordPETSIRD.h | 87 ++- .../stir/listmode/CListRecordPETSIRD.inl | 19 +- .../CListModeDataPETSIRD.cxx | 552 +++++++++++++++--- src/listmode_buildblock/CMakeLists.txt | 9 +- 13 files changed, 745 insertions(+), 170 deletions(-) create mode 100644 src/include/stir/IO/InputStreamFromPETSIRD.h diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 25a17f42e4..07a92249b6 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -386,7 +386,7 @@ jobs: EXTRA_BUILD_FLAGS="${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++/using_STIR_LOCAL" + EXTRA_BUILD_FLAGS="${EXTRA_BUILD_FLAGS} -DDISABLE_PETSIRD:BOOL=ON -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 diff --git a/.travis.yml b/.travis.yml index 3d6b75b578..9ad59d8f03 100644 --- a/.travis.yml +++ b/.travis.yml @@ -35,7 +35,7 @@ matrix: packages: - *addons_apt_packages - [g++-6] - env: EXTRA_BUILD_FLAGS="-DDISABLE_CERN_ROOT=1 -DSTIR_OPENMP:BOOL=OFF" CC=gcc-6 CXX=g++-6 + env: EXTRA_BUILD_FLAGS="-DDISABLE_CERN_ROOT=1 -DSTIR_OPENMP:BOOL=OFF -DDISABLE_PETSIRD:BOOL=ON" CC=gcc-6 CXX=g++-6 - os: linux python: 3 addons: @@ -43,7 +43,7 @@ matrix: packages: - *addons_apt_packages - [g++-5, libinsighttoolkit4-dev] - env: EXTRA_BUILD_FLAGS="-DDISABLE_CERN_ROOT=1 -DSTIR_OPENMP:BOOL=OFF -DDISABLE_HDF5=ON" CC=gcc-5 CXX=g++-5 + env: EXTRA_BUILD_FLAGS="-DDISABLE_CERN_ROOT=1 -DSTIR_OPENMP:BOOL=OFF -DDISABLE_HDF5=ON -DDISABLE_PETSIRD:BOOL=ON" CC=gcc-5 CXX=g++-5 - os: linux python: 3 addons: @@ -51,7 +51,7 @@ matrix: packages: - *addons_apt_packages - [g++-7, libinsighttoolkit4-dev] - env: EXTRA_BUILD_FLAGS="-DDISABLE_CERN_ROOT=0 -DSTIR_OPENMP:BOOL=ON" CC=gcc-7 CXX=g++-7 + env: EXTRA_BUILD_FLAGS="-DDISABLE_CERN_ROOT=0 -DSTIR_OPENMP:BOOL=ON -DDISABLE_PETSIRD:BOOL=ON" CC=gcc-7 CXX=g++-7 - os: linux dist: focal python: 3 @@ -60,7 +60,7 @@ matrix: packages: - *addons_apt_packages - [g++-9, libinsighttoolkit4-dev, nlohmann-json3-dev] - env: EXTRA_BUILD_FLAGS="-DDISABLE_CERN_ROOT=0 -DSTIR_OPENMP:BOOL=ON" CC=gcc-9 CXX=g++-9 + env: EXTRA_BUILD_FLAGS="-DDISABLE_CERN_ROOT=0 -DSTIR_OPENMP:BOOL=ON -DDISABLE_PETSIRD:BOOL=ON" CC=gcc-9 CXX=g++-9 - os: linux dist: focal python: 3 @@ -69,7 +69,7 @@ matrix: packages: - *addons_apt_packages - [g++-10, nlohmann-json3-dev] - env: EXTRA_BUILD_FLAGS="-DDISABLE_CERN_ROOT=1 -DSTIR_OPENMP:BOOL=ON" CC=gcc-10 CXX=g++-10 + env: EXTRA_BUILD_FLAGS="-DDISABLE_CERN_ROOT=1 -DSTIR_OPENMP:BOOL=ON -DDISABLE_PETSIRD:BOOL=ON" CC=gcc-10 CXX=g++-10 - os: linux # note: can't get it to install on focal due to a package problem dist: bionic @@ -79,14 +79,14 @@ matrix: packages: - *addons_apt_packages - [clang, libomp-dev] - env: EXTRA_BUILD_FLAGS="-DDISABLE_CERN_ROOT=1 -DSTIR_OPENMP:BOOL=ON" CC=clang CXX=clang++ + env: EXTRA_BUILD_FLAGS="-DDISABLE_CERN_ROOT=1 -DSTIR_OPENMP:BOOL=ON -DDISABLE_PETSIRD:BOOL=ON" CC=clang CXX=clang++ #### osx # note: cannot use OpenMP on OSX yet, see https://github.com/UCL/STIR/issues/117 - os: osx osx_image: xcode12.2 python: 3 - env: EXTRA_BUILD_FLAGS="-DDISABLE_CERN_ROOT=1 -DSTIR_OPENMP:BOOL=OFF" CC=gcc CXX=g++ + env: EXTRA_BUILD_FLAGS="-DDISABLE_CERN_ROOT=1 -DSTIR_OPENMP:BOOL=OFF -DDISABLE_PETSIRD:BOOL=ON" CC=gcc CXX=g++ # Disable as ROOT is currently failing via brew (as it wants to build it, and which causes a timeout) #- os: osx # python: 3 @@ -94,7 +94,7 @@ matrix: - os: osx osx_image: xcode11.3 python: 3 - env: EXTRA_BUILD_FLAGS="-DDISABLE_CERN_ROOT=1 -DSTIR_OPENMP:BOOL=OFF -DSTIR_ENABLE_EXPERIMENTAL:BOOL=ON" CC=clang CXX=clang++ + env: EXTRA_BUILD_FLAGS="-DDISABLE_CERN_ROOT=1 -DSTIR_OPENMP:BOOL=OFF -DSTIR_ENABLE_EXPERIMENTAL:BOOL=ON -DDISABLE_PETSIRD:BOOL=ON" CC=clang CXX=clang++ # osx, OpenMP using llvm clang # disable due to LLVM clang 12.0.1 problem @@ -105,7 +105,7 @@ matrix: env: global: - - BUILD_FLAGS="-DBUILD_SWIG_PYTHON:BOOL=On -DSTIR_MPI:BOOL=Off -DCMAKE_BUILD_TYPE=Release" + - BUILD_FLAGS="-DBUILD_SWIG_PYTHON:BOOL=On -DSTIR_MPI:BOOL=Off -DDISABLE_PETSIRD:BOOL=ON -DCMAKE_BUILD_TYPE=Release" # No need for sudo sudo: false diff --git a/src/IO/CMakeLists.txt b/src/IO/CMakeLists.txt index 215fca0424..39eaba6275 100644 --- a/src/IO/CMakeLists.txt +++ b/src/IO/CMakeLists.txt @@ -148,12 +148,11 @@ if (HAVE_PETSIRD) # #set(PETSIRD_dir ../../PETSIRD/cpp/generated) -# target_include_directories(IO PUBLIC -# $ -# $ -# $ -# $ -# ) +target_include_directories(IO PUBLIC + $ + $ + $ +) # # # target_include_directories(IO PUBLIC diff --git a/src/IO/PETSIRDCListmodeInputFileFormat.cxx b/src/IO/PETSIRDCListmodeInputFileFormat.cxx index 982295c673..4352cc9e20 100644 --- a/src/IO/PETSIRDCListmodeInputFileFormat.cxx +++ b/src/IO/PETSIRDCListmodeInputFileFormat.cxx @@ -7,23 +7,48 @@ START_NAMESPACE_STIR bool -PETSIRDCListmodeInputFileFormat::can_read(const FileSignature& signature, const std::string& filename) const +PETSIRDCListmodeInputFileFormat::can_read(const FileSignature& signature, const std::string& filename) { - petsird::hdf5::PETSIRDReader* petsird_reader = new petsird::hdf5::PETSIRDReader(filename); + std::array hdf5_signature = { 'H', 'D', 'F', '5' }; + std::array binary_signature = { 'y', 'a', 'r', 'd' }; - if(is_null_ptr(petsird_reader)) + std::ifstream file(filename, std::ios::binary); + if (!file.is_open()) { + std::cerr << "Cannot open file: " << filename << std::endl; + return false; + } + + std::array signature_{}; + file.read(signature_.data(), signature_.size()); + + if (signature_ == hdf5_signature) + { + use_hdf5 = true; + return use_hdf5; + } - petsird::binary::PETSIRDReader* petsird_reader = new petsird::binary::PETSIRDReader(filename); - if(is_null_ptr(petsird_reader)) - { - return false; - } + if (signature_ == binary_signature) + { + use_hdf5 = false; return true; } - return true; + // petsird::hdf5::PETSIRDReader* petsird_reader = new petsird::hdf5::PETSIRDReader(filename); + + // if (is_null_ptr(petsird_reader)) + // { + + // petsird::binary::PETSIRDReader* petsird_reader = new petsird::binary::PETSIRDReader(filename); + // if (is_null_ptr(petsird_reader)) + // { + // return false; + // } + // return true; + // } + + return false; } END_NAMESPACE_STIR diff --git a/src/include/stir/IO/InputFileFormat.h b/src/include/stir/IO/InputFileFormat.h index 03bcb6a74a..3a2b6bf7a9 100644 --- a/src/include/stir/IO/InputFileFormat.h +++ b/src/include/stir/IO/InputFileFormat.h @@ -46,7 +46,7 @@ class InputFileFormat { return this->actual_can_read(signature, input); } - virtual bool can_read(const FileSignature& signature, const std::string& filename) const + virtual bool can_read(const FileSignature& signature, const std::string& filename) { std::ifstream input; open_read_binary(input, filename); diff --git a/src/include/stir/IO/InputStreamFromPETSIRD.h b/src/include/stir/IO/InputStreamFromPETSIRD.h new file mode 100644 index 0000000000..9040e2b349 --- /dev/null +++ b/src/include/stir/IO/InputStreamFromPETSIRD.h @@ -0,0 +1,85 @@ +// /*! +// \file +// \ingroup IO +// \brief Declaration of class stir::InputStreamFromROOTFile + +// \author Nikos Efthimiou +// \author Harry Tsoumpas +// \author Kris Thielemans +// \author Robert Twyman +// */ +// /* +// * Copyright (C) 2015, 2016 University of Leeds +// Copyright (C) 2016, 2021, 2020, 2021 UCL +// Copyright (C) 2018 University of Hull +// This file is part of STIR. + +// SPDX-License-Identifier: Apache-2.0 + +// See STIR/LICENSE.txt for details +// */ + +// #ifndef __stir_IO_InputStreamFromPETSIRD_H__ +// #define __stir_IO_InputStreamFromPETSIRD_H__ + +// #include "stir/shared_ptr.h" +// #include "stir/Succeeded.h" +// #include "stir/listmode/CListRecordPETSIRD.h" +// #include "stir/RegisteredObject.h" +// #include "stir/error.h" + +// #include "../../PETSIRD/cpp/generated/binary/protocols.h" +// #include "stir/IO/InputStreamWithRecords.h" +// #include "../../PETSIRD/cpp/generated/hdf5/protocols.h" + +// START_NAMESPACE_STIR + +// class InputStreamWithRecordsFromPETSIRD : public InputStreamWithRecords +// { +// public: +// typedef std::vector::size_type SavedPosition; + +// //! Default constructor +// InputStreamFromPETSIRD(std::string filename); + +// ~InputStreamFromPETSIRD() override +// {} +// //! \details Returns the next record in the ROOT file. +// //! The code is adapted from Sadek A. Nehmeh and CR Schmidtlein, +// //! downloaded from here +// virtual Succeeded get_next_record(CListReco*/rdPETSIRD& record) = 0; +// //! Go to the first event. +// inline Succeeded reset(); +// //! Must be called before calling for the first event. +// virtual Succeeded set_up(const std::string& header_path); +// //! Save current position in a vector +// inline SavedPosition save_get_position(); +// //! Set current position +// inline Succeeded set_get_position(const SavedPosition&); +// //! Get the vector with the saved positions +// inline std::vector get_saved_get_positions() const; +// //! Set a vector with saved positions +// inline void set_saved_get_positions(const std::vector&); +// //! Returns the total number of events +// inline unsigned long int get_total_number_of_events() const; + +// inline std::string get_PETSIRD_filename() const; + +// protected: + +// //! The starting position. +// unsigned long int starting_stream_position; +// //! The total number of entries +// unsigned long int nentries; +// //! Current get position +// unsigned long int current_position; +// //! A vector with saved position indices. +// std::vector saved_get_positions; +// //! The name of the ROOT chain to be read +// }; + +// END_NAMESPACE_STIR + +// #include "stir/IO/InputStreamFromROOTFile.inl" + +// #endif diff --git a/src/include/stir/IO/PETSIRDCListmodeInputFileFormat.h b/src/include/stir/IO/PETSIRDCListmodeInputFileFormat.h index 13f04ab952..9c9f2bb07e 100644 --- a/src/include/stir/IO/PETSIRDCListmodeInputFileFormat.h +++ b/src/include/stir/IO/PETSIRDCListmodeInputFileFormat.h @@ -56,11 +56,13 @@ class PETSIRDCListmodeInputFileFormat : public InputFileFormat const std::string get_name() const override { return "PETSIRD"; } //! Checks in binary data file for correct signature. - bool can_read(const FileSignature& signature, const std::string& filename) const override; + bool can_read(const FileSignature& signature, const std::string& filename) override; protected: bool actual_can_read(const FileSignature& signature, std::istream& input) const override { return false; } + bool use_hdf5 = false; + public: unique_ptr read_from_file(std::istream& input) const override { @@ -71,7 +73,7 @@ class PETSIRDCListmodeInputFileFormat : public InputFileFormat unique_ptr read_from_file(const std::string& filename) const override { info("PETSIRDCListmodeInputFileFormat: read_from_file(" + std::string(filename) + ")"); - return unique_ptr(new CListModeDataPETSIRD(filename)); + return unique_ptr(new CListModeDataPETSIRD(filename, use_hdf5)); } }; END_NAMESPACE_STIR diff --git a/src/include/stir/IO/SAFIRCListmodeInputFileFormat.h b/src/include/stir/IO/SAFIRCListmodeInputFileFormat.h index 2ac3fdd96d..ee72a67106 100644 --- a/src/include/stir/IO/SAFIRCListmodeInputFileFormat.h +++ b/src/include/stir/IO/SAFIRCListmodeInputFileFormat.h @@ -91,7 +91,7 @@ class SAFIRCListmodeInputFileFormat : public InputFileFormat, publ //! Checks in binary data file for correct signature (can be either "SAFIR CListModeData", "NeuroLF CListModeData" or "MUPET //! CListModeData"). - bool can_read(const FileSignature& signature, const std::string& filename) const override + bool can_read(const FileSignature& signature, const std::string& filename) override { // Looking for the right key in the parameter file std::ifstream par_file(filename.c_str()); diff --git a/src/include/stir/listmode/CListModeDataPETSIRD.h b/src/include/stir/listmode/CListModeDataPETSIRD.h index 0fe44b39be..0783243db4 100644 --- a/src/include/stir/listmode/CListModeDataPETSIRD.h +++ b/src/include/stir/listmode/CListModeDataPETSIRD.h @@ -1,11 +1,8 @@ /* CListModeDataPETSIRD.h -Coincidence LM Data Class for PETSIRD: Header File -Jannis Fischer +Coincidence LM Data Class for PETSIRD - Copyright 2015 ETH Zurich, Institute of Particle Physics - Copyright 2020 Positrigo AG, Zurich - Copyright 2025 National Physical Laboratory + Copyright 2025, MGH / HST A. Martinos Center for Biomedical Imaging Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -28,27 +25,19 @@ Jannis Fischer \brief Declaration of class stir::CListModeDataPETSIRD \author Daniel Deidda +\author Nikos Efthimiou */ #ifndef __stir_listmode_CListModeDataPETSIRD_H__ #define __stir_listmode_CListModeDataPETSIRD_H__ -#include -#include -#include -#include - +#include #include "stir/listmode/CListModeDataBasedOnCoordinateMap.h" #include "stir/ProjData.h" -#include "stir/ProjDataInfo.h" #include "stir/listmode/CListRecord.h" -#include "stir/IO/InputStreamWithRecords.h" #include "stir/shared_ptr.h" -#include "stir/listmode/CListRecordPETSIRD.h" - -#include "../../PETSIRD/cpp/generated/binary/protocols.h" -#include "../../PETSIRD/cpp/generated/hdf5/protocols.h" +#include "../../PETSIRD/cpp/generated/protocols.h" START_NAMESPACE_STIR @@ -63,24 +52,68 @@ START_NAMESPACE_STIR class CListModeDataPETSIRD : public CListModeDataBasedOnCoordinateMap { public: - CListModeDataPETSIRD(const std::string& listmode_filename); + CListModeDataPETSIRD(const std::string& listmode_filename, bool use_hdf5); virtual shared_ptr get_empty_record_sptr() const override; - virtual Succeeded get_next_record(CListRecord& record_of_general_type) const override; + Succeeded get_next_record(CListRecord& record_of_general_type) const override; - SavedPosition save_get_position() override {} + SavedPosition save_get_position() override { return static_cast(curr_event_in_event_block); } - Succeeded set_get_position(const SavedPosition& pos) override {} + Succeeded set_get_position(const SavedPosition& pos) override { return Succeeded::yes; } - virtual bool has_delayeds() const override { return true; } + virtual bool has_delayeds() const override { return m_has_delayeds; } - Succeeded reset() override {} + Succeeded reset() override { return Succeeded::yes; } protected: virtual Succeeded open_lm_file() const override; - mutable shared_ptr current_lm_data_ptr; + mutable shared_ptr current_lm_data_ptr; + +private: + const bool use_hdf5; + + mutable unsigned long int curr_event_in_event_block = 0; + + mutable petsird::TimeBlock curr_time_block; + + int numberOfModules; + + int numberOfElementsIndices; + + mutable petsird::EventTimeBlock curr_event_block; + + petsird::TypeOfModulePair type_of_module_pair{ 0, 0 }; + + shared_ptr this_scanner_sptr; + + mutable bool curr_is_prompt = true; + + mutable bool m_has_delayeds; + + bool isCylindricalConfiguration(const petsird::ScannerInformation& scanner_info, + const std::vector& replicated_module_list); + + void find_uniqe_values_1D(std::set& values, const std::vector& input); + + void find_uniqe_values_2D(std::set& values, const std::vector>& input); + + int figure_out_scanner_blocks_and_rotation_axis(std::set& unique_dim1_values, + std::set& unique_dim2_values, + std::set& unique_dim3_values, + const std::vector& replicated_module_list); + void figure_out_block_element_transformations(std::set& unique_dim1_values, + std::set& unique_dim2_values, + std::set& unique_dim3_values, + float& radius, + int& radius_index, + const int rotation_axis, + const std::vector& replicated_module_list); + + void figure_out_block_angles(std::set& unique_angle_modules, + const int rot_axis, + const std::vector& replicated_module_list); }; END_NAMESPACE_STIR diff --git a/src/include/stir/listmode/CListRecordPETSIRD.h b/src/include/stir/listmode/CListRecordPETSIRD.h index 2c288dd6cf..1389db2e15 100644 --- a/src/include/stir/listmode/CListRecordPETSIRD.h +++ b/src/include/stir/listmode/CListRecordPETSIRD.h @@ -41,14 +41,12 @@ Coincidence Event Class for PETSIRD: Header File #include "stir/Succeeded.h" #include "stir/ByteOrderDefine.h" -#include "boost/static_assert.hpp" #include "boost/cstdint.hpp" #include "stir/DetectorCoordinateMap.h" -#include "boost/make_shared.hpp" -// #include "petsird_helpers.h" -// #include "petsird_helpers/create.h" -// #include "petsird_helpers/geometry.h" +#include "types.h" + +// #include "../../PETSIRD/cpp/generated/types.h" START_NAMESPACE_STIR @@ -70,9 +68,6 @@ class CListEventPETSIRD : public CListEvent //! Override the default implementation inline void get_bin(Bin& bin, const ProjDataInfo& proj_data_info) const override; - //! Returns 0 if event is prompt and 1 if delayed - inline bool is_prompt() const override { return true; } - inline void set_map_sptr(shared_ptr new_map_sptr) { map_sptr = new_map_sptr; } /*! Set the scanner */ /*! Currently only used if the map is not set. */ @@ -80,25 +75,45 @@ class CListEventPETSIRD : public CListEvent virtual bool is_valid_template(const ProjDataInfo&) const override { return true; } + virtual bool is_prompt() const override { return _prompt; } + + virtual Succeeded set_prompt(const bool prompt) override + { + _prompt = prompt; + return Succeeded::yes; + } + + void set_PETSIRD_ranges(int _numberOfModules, int _numberOfElementsIndices) + { + numberOfModules = _numberOfModules; + numberOfElementsIndices = _numberOfElementsIndices; + } + + int numberOfModules; + + int numberOfElementsIndices; + + std::pair det_0, det_1; + private: shared_ptr map_sptr; shared_ptr scanner_sptr; + bool _prompt; const DetectorCoordinateMap& map_to_use() const { return map_sptr ? *map_sptr : *this->scanner_sptr->get_detector_map_sptr(); } }; -//! Class for record with time data using PETSIRD bitfield definition -/*! \ingroup listmode */ class CListTimePETSIRD : public ListTime { public: - inline unsigned long get_time_in_millisecs() const { /*return static_cast(time);*/ } + inline unsigned long get_time_in_millisecs() const { return static_cast(time); } inline Succeeded set_time_in_millisecs(const unsigned long time_in_millisecs) { - // time = ((boost::uint64_t(1) << 49) - 1) & static_cast(time_in_millisecs); + time = time_in_millisecs; return Succeeded::yes; } - inline bool is_time() const { /*return type; */ } + inline bool is_time() const { return true; } + uint32_t time; }; class CListRecordPETSIRD : public CListRecord @@ -106,31 +121,41 @@ class CListRecordPETSIRD : public CListRecord public: CListRecordPETSIRD() {} - ~CListRecordPETSIRD() override {} - - bool is_time() const override { /*return time_data.is_time();*/ } + // ~CListRecordPETSIRD() override {} - bool is_event() const override { /*return !time_data.is_time();*/ } + bool is_time() const override { return true; /*time_data.is_time();*/ } - ListEvent& event() override { return event_data; } - const ListEvent& event() const override { return event_data; } + bool is_event() const override { return true; } - ListTime& time() override { return time_data; } - const ListTime& time() const override { return time_data; } + CListEventPETSIRD& event() override { return event_data; } + const CListEventPETSIRD& event() const override { /*return event_data;*/ } - // virtual bool operator==(const CListRecordPETSIRD& e2) const - // { - // // return dynamic_cast(&e2) != 0 && raw == static_cast(e2).r; - // } + CListTimePETSIRD& time() override { return time_data; } + const CListTimePETSIRD& time() const override { return time_data; } - // inline bool is_prompt() const override { /*return event_data.is_prompt();*/ } + bool operator==(const CListRecordPETSIRD& e2) const + { + // return dynamic_cast(&e2) != 0 && raw == static_cast(e2).r; + } - Succeeded init_from_data_ptr(const char* const data_ptr, const std::size_t size_of_record, const bool do_byte_swap) + virtual Succeeded init_from_data(const petsird::CoincidenceEvent& data, bool is_prompt = true) { - // assert(size_of_record >= 8); - // std::copy(data_ptr, data_ptr + 8, reinterpret_cast(&raw)); // TODO necessary for operator== - // if (do_byte_swap) - // ByteOrder::swap_order(raw); + auto decodeElementAndModuleIndex + = [](int linearIndex, int energyIndex, int numberOfElementsIndices, int numberOfModules) -> std::pair { + int reduced = (linearIndex - energyIndex) / numberOfModules; + int moduleIndex = reduced / numberOfElementsIndices; + int elementIndex = reduced % numberOfElementsIndices; + return { elementIndex, moduleIndex }; + }; + + event_data.det_0 + = decodeElementAndModuleIndex(data.detection_bins[0], 0, event_data.numberOfElementsIndices, event_data.numberOfModules); + event_data.det_1 + = decodeElementAndModuleIndex(data.detection_bins[1], 0, event_data.numberOfElementsIndices, event_data.numberOfModules); + + std::cout << event_data.det_0.first << " " << event_data.det_0.second << std::endl; + std::cout << event_data.det_1.first << " " << event_data.det_1.second << std::endl; + event_data.set_prompt(is_prompt); return Succeeded::yes; } diff --git a/src/include/stir/listmode/CListRecordPETSIRD.inl b/src/include/stir/listmode/CListRecordPETSIRD.inl index 933cf7a944..093297db10 100644 --- a/src/include/stir/listmode/CListRecordPETSIRD.inl +++ b/src/include/stir/listmode/CListRecordPETSIRD.inl @@ -34,8 +34,6 @@ \author Daniel Deidda */ -#include - #include "stir/LORCoordinates.h" #include "stir/listmode/CListRecord.h" #include "stir/ProjDataInfo.h" @@ -54,19 +52,22 @@ START_NAMESPACE_STIR LORAs2Points CListEventPETSIRD::get_LOR() const { - // LORAs2Points lor; - // DetectionPositionPair<> det_pos_pair; + LORAs2Points lor; + DetectionPositionPair<> det_pos_pair; - // // static_cast(this)->get_data().get_detection_position_pair(det_pos_pair); + // this->get_data().get_detection_position_pair(det_pos_pair); - // lor.p1() = map_to_use().get_coordinate_for_index(det_pos_pair.pos1()); - // lor.p2() = map_to_use().get_coordinate_for_index(det_pos_pair.pos2()); + lor.p1() = map_to_use().get_coordinate_for_index(det_pos_pair.pos1()); + lor.p2() = map_to_use().get_coordinate_for_index(det_pos_pair.pos2()); - // return lor; + return lor; } void CListEventPETSIRD::get_bin(Bin& bin, const ProjDataInfo& proj_data_info) const -{} +{ + + int nikos = 0; +} END_NAMESPACE_STIR diff --git a/src/listmode_buildblock/CListModeDataPETSIRD.cxx b/src/listmode_buildblock/CListModeDataPETSIRD.cxx index 3d88e212ff..edbd16c27b 100644 --- a/src/listmode_buildblock/CListModeDataPETSIRD.cxx +++ b/src/listmode_buildblock/CListModeDataPETSIRD.cxx @@ -2,11 +2,7 @@ Coincidence LM Data Class for PETSIRD: Implementation - Copyright 2015 ETH Zurich, Institute of Particle Physics - Copyright 2020 Positrigo AG, Zurich - Copyright 2021 University College London - Copyright 2025 National Physical Laboratory - + Copyright 2025, MGH / HST A. Martinos Center for Biomedical Imaging Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -26,93 +22,447 @@ Coincidence LM Data Class for PETSIRD: Implementation \ingroup listmode \brief implementation of class stir::CListModeDataPETSIRD -\author Jannis Fischer -\author Kris Thielemans -\author Markus Jehl \author Daniel Deidda +\author Nikos Efthimiou */ -#include -#include -#include "stir/ExamInfo.h" #include "stir/Succeeded.h" #include "stir/info.h" #include "stir/error.h" -#include "binary/protocols.h" -#include "helpers/include/petsird_helpers.h" -// #include "helpers/include/petsird_helpers/create.h" +#include "../../PETSIRD/cpp/helpers/include/petsird_helpers.h" +#include "helpers/include/petsird_helpers/create.h" #include "helpers/include/petsird_helpers/geometry.h" -// #include "boost/static_assert.hpp" + +#include "../../PETSIRD/cpp/generated/binary/protocols.h" +#include "../../PETSIRD/cpp/generated/hdf5/protocols.h" #include "stir/listmode/CListModeDataPETSIRD.h" #include "stir/listmode/CListRecordPETSIRD.h" +#include START_NAMESPACE_STIR -CListModeDataPETSIRD::CListModeDataPETSIRD(const std::string& listmode_filename) +namespace matrix { - this->listmode_filename = listmode_filename; - petsird::Header header; - petsird::binary::PETSIRDReader petsird_reader(listmode_filename); - petsird_reader.ReadHeader(header); - petsird::ScannerInformation scanner_info = header.scanner; - petsird::ScannerGeometry scanner_geo = scanner_info.scanner_geometry; - const petsird::TypeOfModule type_of_module{ 0 }; - // need to get inner_ring_radius, num_detector_layers, num_transaxial_crystals_per_block, num_axial_crystals_per_block - // transaxial_crystal_spacing,average_depth_of_interaction,axial_crystal_spacing, num_rings, ring_spacing, num_axial_blocks, - // num_oftransaxial_blocks - // these are from rep_module.object +using Mat3 = std::array, 3>; +using Vec3 = std::array; - std::vector replicated_module_list = scanner_geo.replicated_modules; - int num_modules = scanner_geo.replicated_modules[type_of_module].transforms.size(); - int num_elements_per_module = scanner_geo.replicated_modules[type_of_module].object.detecting_elements.transforms.size(); - const auto& tof_bin_edges = header.scanner.tof_bin_edges[type_of_module][type_of_module]; - const auto num_tof_bins = tof_bin_edges.NumberOfBins(); - const auto& event_energy_bin_edges = header.scanner.event_energy_bin_edges[type_of_module]; - const auto num_event_energy_bins = event_energy_bin_edges.NumberOfBins(); - // coordinates of first detecting bin (module_id,element_id, energy_id) - // const petsird::ExpandedDetectionBin expanded_detection_bin{ 0, 0, 0 }; - const auto box_shape = petsird_helpers::geometry::get_detecting_box(header.scanner, type_of_module, expanded_detection_bin); - // get center of box (this should be in a loop to create a map - - for (uint32_t module = 0; module < num_modules; module++) - for (uint32_t elem = 0; elem < num_elements_per_module; elem++) - for (uint32_t ener = 0; ener < num_event_energy_bins; ener++) +inline Mat3 +transpose(const Mat3& mat) +{ + std::array, 3> result{}; + for (int i = 0; i < 3; ++i) + for (int j = 0; j < 3; ++j) + result[j][i] = mat[i][j]; + return result; +} + +inline Mat3 +subtract(const Mat3& A, const Mat3& B) +{ + std::array, 3> result{}; + for (size_t i = 0; i < 3; ++i) + for (size_t j = 0; j < 3; ++j) + result[i][j] = A[i][j] - B[i][j]; + return result; +} + +inline Vec3 +getAxisFromSkew(const Mat3& S) +{ + return { + 0.5f * (S[2][1] - S[1][2]), // x + 0.5f * (S[0][2] - S[2][0]), // y + 0.5f * (S[1][0] - S[0][1]) // z + }; +} + +} // namespace matrix + +inline bool +get_spacing_uniform(std::vector& spacing, const std::set& unsorted_block_poss, double epsilon = 1e-4) +{ + std::vector sorted_z(unsorted_block_poss.begin(), unsorted_block_poss.end()); + for (size_t i = 1; i < sorted_z.size(); ++i) + { + spacing.push_back(std::abs(sorted_z[i] - sorted_z[i - 1])); + } + + return std::all_of(spacing.begin(), spacing.end(), [&](float s) { return std::abs(s - spacing.front()) <= epsilon; }); +} + +const std::set& +getLargestVector(const std::set& x, const std::set& y, const std::set& z) +{ + const std::set* largest = &x; + int axis = 0; + if (y.size() > largest->size()) + { + largest = &y; + axis = 1; + } + else if (z.size() > largest->size()) + { + largest = &z; + axis = 2; + } + + info(format("I believe the axial direction is the {}.", axis)); + return *largest; +} + +void +CListModeDataPETSIRD::find_uniqe_values_1D(std::set& values, const std::vector& input) +{ + for (float val : input) + { + // std::cout << val << std::endl; + values.insert(val); + } +} + +void +CListModeDataPETSIRD::find_uniqe_values_2D(std::set& values, const std::vector>& input) +{ + for (size_t row = 0; row < input.size(); ++row) + for (size_t col = 0; col < input[row].size(); ++col) + values.insert(input[row][col]); +} + +int +CListModeDataPETSIRD::figure_out_scanner_blocks_and_rotation_axis( + std::set& unique_dim1_values, + std::set& unique_dim2_values, + std::set& unique_dim3_values, + const std::vector& replicated_module_list) +{ + auto insertTranslations = [&](const petsird::RigidTransformation& trans) { + unique_dim1_values.insert(trans.matrix.at(0, 3)); + unique_dim2_values.insert(trans.matrix.at(1, 3)); + unique_dim3_values.insert(trans.matrix.at(2, 3)); + }; + + auto extractRotationMatrix = [](const petsird::RigidTransformation& trans) -> matrix::Mat3 { + matrix::Mat3 R; + for (int i = 0; i < 3; ++i) + for (int j = 0; j < 3; ++j) + R[i][j] = trans.matrix.at(i, j); + return R; + // skew = matrix::subtract(R, matrix::transpose(R)); + // auto rot = matrix::getAxisFromSkew(skew); + }; + + std::array, 3> skew; + int detected_axis = -1; + + for (const auto& module : replicated_module_list) + for (const auto& mod_trans : module.transforms) + { + insertTranslations(mod_trans); + matrix::Mat3 R = extractRotationMatrix(mod_trans); + skew = matrix::subtract(R, matrix::transpose(R)); + auto axis_vec = matrix::getAxisFromSkew(skew); + + int current_axis = -1; + for (int i = 0; i < 3; ++i) + { + if (std::abs(axis_vec[i]) > 1e-6f) + { + if (current_axis != -1) + { + warning("Rotation involves multiple axis components. Possibly non-pure rotation."); + current_axis = -2; // Sentinel for mixed axes + return -1; + } + current_axis = i; + } + } + + if (current_axis >= 0) + { + if (detected_axis == -1) + detected_axis = current_axis; + else if (detected_axis != current_axis) + warning("Inconsistent rotation axis detected between modules."); + } + } + info(format("Rotation axis of blocks inferred as axis index {}", detected_axis)); + return detected_axis; +} + +void +CListModeDataPETSIRD::figure_out_block_element_transformations( + std::set& unique_dim1_values, + std::set& unique_dim2_values, + std::set& unique_dim3_values, + float& radius, + int& radius_index, + const int rotation_axis, + const std::vector& replicated_module_list) +{ + + auto insert_translation = [&](const petsird::RigidTransformation& trans) { + unique_dim1_values.insert(trans.matrix.at(0, 3)); + unique_dim2_values.insert(trans.matrix.at(1, 3)); + unique_dim3_values.insert(trans.matrix.at(2, 3)); + }; + + auto detect_radius = [&](const petsird::RigidTransformation& trans) -> bool { + for (int i = 0; i < 3; ++i) + { + if (i == rotation_axis) + continue; + float candidate = trans.matrix.at(i, 3); + if (candidate > 0.0f) + { + radius = candidate; + radius_index = i; + return true; + } + } + return false; + }; + + for (const auto& module : replicated_module_list) + { + for (const auto& el_trans : module.object.detecting_elements.transforms) { - petsird::ExpandedDetectionBin expanded_detection_bin{ module, elem, ener }; - const auto box_shape - = petsird_helpers::geometry::get_detecting_box(header.scanner, type_of_module, expanded_detection_bin); - CartesianCoordinate3D mean_pos; - for (auto& corner : box_shape.corners) - { // if STIR (z,y,x) -> PETSIRD (-y, -x, z) pheraps the order below needs to be changed - mean_pos.x() = +corner.c[0] / box_shape.corners.size(); - mean_pos.y() = +corner.c[1] / box_shape.corners.size(); - mean_pos.z() = +corner.c[2] / box_shape.corners.size(); + if (radius == 0.0f) + { + if (!detect_radius(el_trans)) + { + error("Unable to determine radius from translation components."); + continue; + } + } + else + { + float current = el_trans.matrix.at(radius_index, 3); + if (std::abs(current - radius) > 1e-4f) + warning("Mixed radii detected. Consider checking for misaligned modules."); } - // save mean pos into map + + insert_translation(el_trans); + // std::cout << el_trans.matrix << std::endl; } + } +} - // petsird::ReplicatedDetectorModule = scanner_geo.replicated_modules; - - // if (!crystal_map_filename.empty()) - // { - // this->map = MAKE_SHARED(crystal_map_filename, lor_randomization_sigma); - // } - // else - // { - // if (lor_randomization_sigma != 0) - // error("PETSIRD currently does not support LOR-randomisation unless a map is specified"); - // } - // shared_ptr _exam_info_sptr(new ExamInfo); - // _exam_info_sptr->imaging_modality = ImagingModality::PT; - // this->exam_info_sptr = _exam_info_sptr; - - // // Here we are reading the scanner data from the template projdata - // shared_ptr template_proj_data_sptr = ProjData::read_from_file(template_proj_data_filename); - // this->set_proj_data_info_sptr(template_proj_data_sptr->get_proj_data_info_sptr()->create_shared_clone()); +void +CListModeDataPETSIRD::figure_out_block_angles(std::set& unique_angle_modules, + const int rot_axis, + const std::vector& replicated_module_list) +{ + for (const auto& module : replicated_module_list) + for (const auto& transform : module.transforms) + { + if (rot_axis == 0) + unique_angle_modules.insert( + std::fabs(int(1000.F * std::atan2(transform.matrix.at(1, 0), transform.matrix.at(2, 0))) / 1000.F)); + else if (rot_axis == 1) + unique_angle_modules.insert( + std::fabs(int(1000.F * std::atan2(transform.matrix.at(2, 0), transform.matrix.at(0, 0))) / 1000.F)); + else if (rot_axis == 2) + unique_angle_modules.insert( + std::fabs(int(1000.F * std::atan2(transform.matrix.at(1, 0), transform.matrix.at(0, 0))) / 1000.F)); + } +} +bool +CListModeDataPETSIRD::isCylindricalConfiguration(const petsird::ScannerInformation& scanner_info, + const std::vector& replicated_module_list) +{ + // Determine DOI based on material + float average_doi = 0.0; + if (scanner_info.bulk_materials.size() > 0) + { + const std::string& material = scanner_info.bulk_materials[0].name; + if (material.size() > 0) + average_doi = (material == "BGO") ? 5.0f : (material == "LSO" || material == "LYSO") ? 7.0f : 0.0f; + } + + const petsird::TypeOfModule type_of_module = replicated_module_list.size(); + + if (type_of_module > 1) + { + info("Multiple types of PETSIRD modules are not supported. Abord."); + return false; + } + + const auto& tof_bin_edges = scanner_info.tof_bin_edges[type_of_module - 1][type_of_module - 1]; + std::cout << "Num. of TOF bins " << tof_bin_edges.NumberOfBins() << std::endl; + + std::set unique_tof_values; + find_uniqe_values_2D(unique_tof_values, scanner_info.tof_resolution); + + numberOfModules = replicated_module_list[0].NumberOfObjects(); + numberOfElementsIndices = replicated_module_list[0].object.detecting_elements.NumberOfObjects(); + std::set unique_dim1_values, unique_dim2_values, unique_dim3_values; + std::set unique_tof_resolutions; + + const int rotation_axis = figure_out_scanner_blocks_and_rotation_axis( + unique_dim1_values, unique_dim2_values, unique_dim3_values, replicated_module_list); + if (rotation_axis == -1) + return false; + + const std::set& main_axis = getLargestVector(unique_dim1_values, unique_dim2_values, unique_dim3_values); + + std::vector block_axial_spacing; + get_spacing_uniform(block_axial_spacing, main_axis); + + info(format("I counted {} axial blocks with spacing {}", unique_dim3_values.size(), block_axial_spacing[0])); + + int num_transaxial_blocks = numberOfModules / main_axis.size(); + info(format("I deduce that the scanner has {} transaxial number of blocks", num_transaxial_blocks)); + + float radius = 0; + int radius_indx = -1; + + std::set unique_elements_dim1_values, unique_elements_dim2_values, unique_elements_dim3_values; + figure_out_block_element_transformations(unique_elements_dim1_values, + unique_elements_dim2_values, + unique_elements_dim3_values, + radius, + radius_indx, + rotation_axis, + replicated_module_list); + std::set unique_angle_modules; + figure_out_block_angles(unique_angle_modules, rotation_axis, replicated_module_list); + + std::vector block_angular_spacing; + if (!get_spacing_uniform(block_angular_spacing, unique_angle_modules, 1e-2)) /// epsilon * 10000) // relax epsilon here + return false; + + std::vector element_horizontal_spacing, element_vertical_spacing; + std::set unique_elements_horizontal_values, unique_elements_vertical_values; + if (radius_indx == 0) + { + if (!get_spacing_uniform(element_horizontal_spacing, unique_elements_dim3_values)) + return false; + if (!get_spacing_uniform(element_vertical_spacing, unique_elements_dim2_values)) + return false; + unique_elements_horizontal_values = unique_elements_dim3_values; + unique_elements_vertical_values = unique_elements_dim2_values; + } + else + { + error("TODO!"); + } + + this_scanner_sptr.reset( + new Scanner(Scanner::User_defined_scanner, + std::string("PETSIRD_defined_scanner"), + /* num dets per ring */ + (num_transaxial_blocks * unique_elements_vertical_values.size()), + unique_dim3_values.size() * unique_elements_horizontal_values.size() /* num of rings */, + /* number of non arccor bins */ + (num_transaxial_blocks * unique_elements_vertical_values.size()) / 2, + /* number of maximum arccor bins */ + (num_transaxial_blocks * unique_elements_vertical_values.size()) / 2, + /* inner ring radius */ + radius, + /* doi */ average_doi, + /* ring spacing */ + element_horizontal_spacing[0] * 10.f, + // bin_size_v + element_vertical_spacing[0] * 10.f, + /*intrinsic_tilt_v*/ + 0.f, + /*num_axial_blocks_per_bucket_v */ + unique_dim3_values.size(), + /*num_transaxial_blocks_per_bucket_v*/ + 1, + /*num_axial_crystals_per_block_v*/ + unique_elements_horizontal_values.size(), + /*num_transaxial_crystals_per_block_v*/ + unique_elements_vertical_values.size(), + /*num_axial_crystals_per_singles_unit_v*/ + unique_elements_horizontal_values.size(), + /*num_transaxial_crystals_per_singles_unit_v*/ + unique_elements_vertical_values.size(), + /*num_detector_layers_v*/ + 1, // num_detector_layers_v + scanner_info.energy_resolution_at_511.front(), // energy_resolution_v + 511, // reference_energy_v + 1, + 0.F, + 0.F, // non-TOF + "BlocksOnCylindrical", // scanner_geometry_v + *unique_elements_horizontal_values.begin(), // axial_crystal_spacing_v + std::round(*unique_elements_vertical_values.begin() * 10.0f) / 10.F, // transaxial_crystal_spacing_v + block_axial_spacing.front(), // axial_block_spacing_v + radius * block_angular_spacing.front(), // transaxial_block_spacing_v + "" // crystal_map_file_name_v + )); + + // /* maximum number of timing bins */ + // tof_bin_edges.NumberOfBins(), + // /* size of basic TOF bin */ + // 10, + // /* Scanner's timing resolution */ + // *unique_tof_values.begin())); + + return true; +} + +CListModeDataPETSIRD::CListModeDataPETSIRD(const std::string& listmode_filename, bool use_hdf5) + : use_hdf5(use_hdf5) +{ + CListModeDataBasedOnCoordinateMap::listmode_filename = listmode_filename; + + petsird::Header header; + if (use_hdf5) + current_lm_data_ptr.reset(new petsird::hdf5::PETSIRDReader(listmode_filename)); + else + current_lm_data_ptr.reset(new petsird::binary::PETSIRDReader(listmode_filename)); + + current_lm_data_ptr->ReadHeader(header); + petsird::ScannerInformation scanner_info = header.scanner; + petsird::ScannerGeometry scanner_geo = scanner_info.scanner_geometry; + std::vector replicated_module_list = scanner_geo.replicated_modules; + + // Get the first TimeBlock + // if ( + current_lm_data_ptr->ReadTimeBlocks(curr_time_block); + // ) + // error("CListModeDataPETSIRD: Could not read the first TimeBlock. Abord."); + + if (std::holds_alternative(curr_time_block)) + curr_event_block = std::get(curr_time_block); + else + error("CListModeDataPETSIRD: holds_alternative not true. Abord."); + + if (isCylindricalConfiguration(scanner_info, replicated_module_list)) + { + int tof_mash_factor = 1; + this->set_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) + ->create_shared_clone())); + } + else + { + error("TODO:GenericScanner"); + } + + shared_ptr _exam_info_sptr(new ExamInfo); + // Only PET scanners supported + _exam_info_sptr->imaging_modality = ImagingModality::PT; + _exam_info_sptr->originating_system = std::string("PETSIRD_defined_scanner"); + // _exam_info_sptr->set_low_energy_thres(scanner_i); + // _exam_info_sptr->set_high_energy_thres(this->root_file_sptr->get_up_energy_thres()); + + this->exam_info_sptr = _exam_info_sptr; + + // N.E.: In my experience the first time block is always empty. + // So I use this unncessessary call to skip to the next. if (this->open_lm_file() == Succeeded::no) { error("CListModeDataPETSIRD: Could not open listmode file " + listmode_filename + "\n"); @@ -122,22 +472,76 @@ CListModeDataPETSIRD::CListModeDataPETSIRD(const std::string& listmode_filename) Succeeded CListModeDataPETSIRD::open_lm_file() const { - current_lm_data_ptr.reset(new petsird::hdf5::PETSIRDReader(listmode_filename)); + // current_lm_data_ptr.reset(new petsird::hdf5::PETSIRDReader(listmode_filename)); + if (!current_lm_data_ptr->ReadTimeBlocks(curr_time_block)) + return Succeeded::no; + curr_event_block = std::get(curr_time_block); return Succeeded::yes; } shared_ptr CListModeDataPETSIRD::get_empty_record_sptr() const { - shared_ptr sptr(new CListRecordPETSIRD()); + shared_ptr sptr(new CListRecordPETSIRD); + std::dynamic_pointer_cast(sptr)->event().set_scanner_sptr( + this->get_proj_data_info_sptr()->get_scanner_sptr()); + std::dynamic_pointer_cast(sptr)->event().set_PETSIRD_ranges(numberOfModules, numberOfElementsIndices); + // std::dynamic_pointer_cast(sptr)->event_PETSIRD().set_map_sptr(map); + return sptr; } Succeeded CListModeDataPETSIRD::get_next_record(CListRecord& record_of_general_type) const { - CListRecordPETSIRD& record = dynamic_cast(record_of_general_type); - // return current_lm_data_ptr->get_next_record(record); + + auto& record = dynamic_cast(record_of_general_type); + const auto& prompt_list = curr_event_block.prompt_events.at(0).at(0); // TODO: support mulitple pairs of modules. + const auto& delayed_list = m_has_delayeds ? curr_event_block.delayed_events->at(0).at(0) : prompt_list; + + const auto& event_list = curr_is_prompt ? prompt_list : delayed_list; + + if (event_list.size() == 0) + return Succeeded::no; + + if (record.init_from_data(event_list.at(curr_event_in_event_block), curr_is_prompt) == Succeeded::no + || record_of_general_type.time().set_time_in_millisecs(curr_event_block.time_interval.start) == Succeeded::no) + { + return Succeeded::no; + } + + ++curr_event_in_event_block; + + if (curr_event_in_event_block < event_list.size()) + { + return Succeeded::yes; + } + + // - Once we hit the size of the vector + curr_event_in_event_block = 0; + + if (!m_has_delayeds || curr_is_prompt) + { + if (m_has_delayeds) + { + curr_is_prompt = false; + } + else + { + if (!current_lm_data_ptr->ReadTimeBlocks(curr_time_block)) + return Succeeded::no; + curr_event_block = std::get(curr_time_block); + } + } + else + { + curr_is_prompt = true; + if (!current_lm_data_ptr->ReadTimeBlocks(curr_time_block)) + return Succeeded::no; + curr_event_block = std::get(curr_time_block); + } + + return Succeeded::yes; } END_NAMESPACE_STIR diff --git a/src/listmode_buildblock/CMakeLists.txt b/src/listmode_buildblock/CMakeLists.txt index 4aa4db00f5..7efa4d17ef 100644 --- a/src/listmode_buildblock/CMakeLists.txt +++ b/src/listmode_buildblock/CMakeLists.txt @@ -79,10 +79,11 @@ if (HAVE_PETSIRD) #set(PETSIRD_dir ../../PETSIRD/cpp/generated) #add_subdirectory(${PETSIRD_dir} PETSIRD_generated) -# target_include_directories(listmode_buildblock PUBLIC -# $ -# $ -# ) +target_include_directories(listmode_buildblock PUBLIC + $ + $ + $ +) # target_include_directories(listmode_buildblock PUBLIC # $ From 40aa1263a8b9a23c737f712f14b72eeb71ae8a72 Mon Sep 17 00:00:00 2001 From: danieldeidda Date: Tue, 12 Aug 2025 09:33:17 +0100 Subject: [PATCH 8/9] PETSIRD: fixes for detector map [WIP] --- .../CListModeDataPETSIRD.cxx | 70 ++++++++++++++++--- src/listmode_buildblock/CMakeLists.txt | 1 + 2 files changed, 63 insertions(+), 8 deletions(-) diff --git a/src/listmode_buildblock/CListModeDataPETSIRD.cxx b/src/listmode_buildblock/CListModeDataPETSIRD.cxx index edbd16c27b..e72b33928e 100644 --- a/src/listmode_buildblock/CListModeDataPETSIRD.cxx +++ b/src/listmode_buildblock/CListModeDataPETSIRD.cxx @@ -309,11 +309,6 @@ CListModeDataPETSIRD::isCylindricalConfiguration(const petsird::ScannerInformati const std::set& main_axis = getLargestVector(unique_dim1_values, unique_dim2_values, unique_dim3_values); - std::vector block_axial_spacing; - get_spacing_uniform(block_axial_spacing, main_axis); - - info(format("I counted {} axial blocks with spacing {}", unique_dim3_values.size(), block_axial_spacing[0])); - int num_transaxial_blocks = numberOfModules / main_axis.size(); info(format("I deduce that the scanner has {} transaxial number of blocks", num_transaxial_blocks)); @@ -351,6 +346,20 @@ CListModeDataPETSIRD::isCylindricalConfiguration(const petsird::ScannerInformati error("TODO!"); } + std::vector block_axial_spacing; + get_spacing_uniform(block_axial_spacing, main_axis); + if (block_axial_spacing.size() < 1) + { + // std::set::iterator it = unique_elements_horizontal_values.begin(); + // std::advance(it,0); + float begin = *std::next(unique_elements_horizontal_values.begin(), 0); + // std::advance(it,unique_elements_horizontal_values.size()-1); + float end = *std::next(unique_elements_horizontal_values.begin(), unique_elements_horizontal_values.size() - 1); + block_axial_spacing.push_back(std::abs(end - begin)); + } + + info(format("I counted {} axial blocks with spacing {}", unique_dim3_values.size(), block_axial_spacing[0])); + this_scanner_sptr.reset( new Scanner(Scanner::User_defined_scanner, std::string("PETSIRD_defined_scanner"), @@ -433,8 +442,9 @@ CListModeDataPETSIRD::CListModeDataPETSIRD(const std::string& listmode_filename, curr_event_block = std::get(curr_time_block); else error("CListModeDataPETSIRD: holds_alternative not true. Abord."); - - if (isCylindricalConfiguration(scanner_info, replicated_module_list)) + isCylindricalConfiguration(scanner_info, replicated_module_list); + bool b = false; + if (b) // isCylindricalConfiguration(scanner_info, replicated_module_list)) { int tof_mash_factor = 1; this->set_proj_data_info_sptr(std::const_pointer_cast( @@ -449,7 +459,51 @@ CListModeDataPETSIRD::CListModeDataPETSIRD(const std::string& listmode_filename, } else { - error("TODO:GenericScanner"); + // this_scanner_sptr.get_ + DetectorCoordinateMap::det_pos_to_coord_type petsird_map; + const petsird::TypeOfModule type_of_module = replicated_module_list.size() - 1; + // const auto event_energy_bin_edges=scanner_info.event_energy_bin_edges[type_of_module]; + // const auto num_event_energy_bins = event_energy_bin_edges.NumberOfBins(); + // error("TODO:GenericScanner"); + for (uint32_t module = 0; module < numberOfModules; module++) + for (uint32_t elem = 0; elem < numberOfElementsIndices; elem++) + // for (uint32_t ener = 0; ener < num_event_energy_bins; ener++) //energy not supported yet + { + int index = module * numberOfElementsIndices + elem; + // Here we are going to assume that the index = module*numberOfElementsIndices+elem is equal to ax+ + // num_ax*tang+ num_ax*num_tang*rad then we will pass this to the map sorter (set_detector_map)) + // therefore we can get ax, tang and rad from index + /* + * rad = index/(num_ax*num_tang) + * tang =(index-rad*num_ax*num_tang)/num_ax -num_tang/2 + * ax = index mod num_ax + */ + + int rad_pos = index / (this_scanner_sptr->get_num_rings() * this_scanner_sptr->get_num_detectors_per_ring()); + int ax_pos = (index - rad_pos * this_scanner_sptr->get_num_rings() * this_scanner_sptr->get_num_detectors_per_ring()) + / this_scanner_sptr->get_num_detectors_per_ring(); + int tang_pos = index % this_scanner_sptr->get_num_detectors_per_ring(); + + DetectionPosition<> detpos(tang_pos, ax_pos, rad_pos); + petsird::ExpandedDetectionBin expanded_detection_bin{ module, elem, 1 }; + + auto box_shape = petsird_helpers::geometry::get_detecting_box(scanner_info, type_of_module, expanded_detection_bin); + CartesianCoordinate3D mean_coord; + + for (auto& corner : box_shape.corners) + { // if STIR (z,y,x) -> PETSIRD (-y, -x, z) pheraps the order below needs to be changed + mean_coord.x() = +corner.c[0] / box_shape.corners.size(); + mean_coord.y() = +corner.c[1] / box_shape.corners.size(); + mean_coord.z() = +corner.c[2] / box_shape.corners.size(); + } + + // save mean pos into map + petsird_map[detpos] = mean_coord; + std::cout << detpos.radial_coord() << "," << detpos.axial_coord() << "," << detpos.tangential_coord() << "," + << mean_coord.x() << "," << mean_coord.y() << "," << mean_coord.z() << "," << std::endl; + } + + this->map->set_detector_map(petsird_map); } shared_ptr _exam_info_sptr(new ExamInfo); diff --git a/src/listmode_buildblock/CMakeLists.txt b/src/listmode_buildblock/CMakeLists.txt index 7efa4d17ef..908b0a8373 100644 --- a/src/listmode_buildblock/CMakeLists.txt +++ b/src/listmode_buildblock/CMakeLists.txt @@ -82,6 +82,7 @@ if (HAVE_PETSIRD) target_include_directories(listmode_buildblock PUBLIC $ $ + $ $ ) From 322101d6a9009ebc7731c7e34488065148e89198 Mon Sep 17 00:00:00 2001 From: NikEfth Date: Sat, 27 Jun 2026 00:27:39 +0100 Subject: [PATCH 9/9] PETSIRD: first working version Using PETSIRD 0.9.1 --- .github/workflows/build-test.yml | 232 ++++-- CMakeLists.txt | 95 +-- documentation/devel/README.md | 4 +- documentation/devel/local-CI.md | 37 + src/CMakeLists.txt | 5 + src/IO/CMakeLists.txt | 48 +- src/IO/IO_registries.cxx | 1 + src/IO/PETSIRDCListmodeInputFileFormat.cxx | 44 +- src/buildblock/CMakeLists.txt | 11 +- src/buildblock/PETSIRDInfo.cxx | 664 ++++++++++++++++ src/buildblock/ProjDataInfo.cxx | 142 ++-- src/cmake/STIRConfig.cmake.in | 4 + src/include/stir/ArrayFunction.h | 17 + src/include/stir/ArrayFunction.inl | 9 + src/include/stir/IO/InputFileFormat.h | 2 +- src/include/stir/IO/InputStreamFromPETSIRD.h | 85 --- .../stir/IO/PETSIRDCListmodeInputFileFormat.h | 40 +- .../stir/IO/SAFIRCListmodeInputFileFormat.h | 10 +- src/include/stir/PETSIRDInfo.h | 171 +++++ .../stir/ProjDataInfoCylindricalNoArcCorr.inl | 3 +- src/include/stir/detail/PETSIRDInfo_helpers.h | 280 +++++++ .../CListModeDataBasedOnCoordinateMap.h | 30 +- .../stir/listmode/CListModeDataPETSIRD.h | 178 +++-- .../stir/listmode/CListModeDataSAFIR.h | 28 +- .../stir/listmode/CListRecordPETSIRD.h | 173 ++--- .../stir/listmode/CListRecordPETSIRD.inl | 73 -- .../BinNormalisationFromPETSIRD.h | 82 ++ .../CListModeDataBasedOnCoordinateMap.cxx | 40 +- .../CListModeDataPETSIRD.cxx | 712 ++++++------------ .../CListModeDataSAFIR.cxx | 21 +- .../CListRecordPETSIRD.cxx | 51 ++ src/listmode_buildblock/CMakeLists.txt | 33 +- .../BinNormalisationFromPETSIRD.cxx | 107 +++ src/recon_buildblock/CMakeLists.txt | 11 + .../find_basic_vs_nums_in_subset.cxx | 43 +- .../recon_buildblock_registries.cxx | 8 + ...ataSymmetriesForBins_PET_CartesianGrid.cxx | 4 +- src/swig/Makefile | 231 ++++-- src/test/CMakeLists.txt | 7 + src/test/test_ArcCorrection.cxx | 2 +- src/test/test_PETSIRDInfo_helpers.cxx | 181 +++++ 41 files changed, 2642 insertions(+), 1277 deletions(-) create mode 100644 documentation/devel/local-CI.md create mode 100644 src/buildblock/PETSIRDInfo.cxx delete mode 100644 src/include/stir/IO/InputStreamFromPETSIRD.h create mode 100644 src/include/stir/PETSIRDInfo.h create mode 100644 src/include/stir/detail/PETSIRDInfo_helpers.h delete mode 100644 src/include/stir/listmode/CListRecordPETSIRD.inl create mode 100644 src/include/stir/recon_buildblock/BinNormalisationFromPETSIRD.h create mode 100644 src/listmode_buildblock/CListRecordPETSIRD.cxx create mode 100644 src/recon_buildblock/BinNormalisationFromPETSIRD.cxx create mode 100644 src/test/test_PETSIRDInfo_helpers.cxx diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 07a92249b6..161f892882 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -33,6 +33,8 @@ on: default: false jobs: build: + env: + YARDL_VERSION: 0.6.7 runs-on: ${{ matrix.os }} strategy: @@ -47,6 +49,7 @@ jobs: parallelproj: "ON" ROOT: "ON" ITK: "OFF" + PETSIRD: "ON" - os: ubuntu-24.04 compiler: gcc # compiler_version: 9 @@ -56,6 +59,7 @@ jobs: parallelproj: "ON" ROOT: "OFF" ITK: "OFF" + PETSIRD: "OFF" - os: ubuntu-24.04 compiler: clang #compiler_version: @@ -66,6 +70,7 @@ jobs: ROOT: "OFF" # currently using ITK 5.2 which doesn't like clang 14 ITK: "OFF" + PETSIRD: "ON" - os: ubuntu-24.04 compiler: gcc compiler_version: 10 @@ -75,6 +80,7 @@ jobs: parallelproj: "OFF" ROOT: "OFF" ITK: "ON" + PETSIRD: "OFF" # gcc-12, C++20 test (Interesting, as gcc-12 does not support all of C++20 yet). # However, commented out, as this seems to hang during install step for unknown reasons. # See https://github.com/UCL/STIR/pull/1605 @@ -97,6 +103,7 @@ jobs: ROOT: "OFF" # Currently disabled due to out of disk space, see https://github.com/UCL/STIR/issues/1618 ITK: "OFF" + PETSIRD: "OFF" - os: ubuntu-24.04 # shared library build compiler: gcc @@ -107,6 +114,7 @@ jobs: parallelproj: "ON" ROOT: "OFF" ITK: "ON" + PETSIRD: "OFF" - os: ubuntu-24.04 compiler: gcc # currently CUDA doesn't support gcc 14 yet @@ -117,6 +125,7 @@ jobs: parallelproj: "ON" ROOT: "OFF" ITK: "ON" + PETSIRD: "OFF" - os: macOS-latest compiler: gcc # compiler_version: 11 @@ -127,6 +136,7 @@ jobs: BUILD_TYPE: "Debug" ROOT: "OFF" ITK: "OFF" + PETSIRD: "OFF" - os: macOS-latest compiler: clang compiler_version: 18 @@ -136,6 +146,7 @@ jobs: BUILD_TYPE: "Release" ROOT: "OFF" ITK: "OFF" + PETSIRD: "ON" - os: macOS-latest compiler: clang compiler_version: 21 @@ -147,6 +158,7 @@ jobs: BUILD_TYPE: "Release" ROOT: "OFF" ITK: "OFF" + PETSIRD: "ON" # let's run all of them, as opposed to aborting when one fails fail-fast: false @@ -162,10 +174,15 @@ jobs: df -h # saves about 2GB echo removing dotnet - sudo rm -rf /usr/share/dotnet + sudo rm -rf /usr/share/dotnet || true # saves about 10 GB echo removing agent_tools - sudo rm -rf "$AGENT_TOOLSDIRECTORY" + if [ "${ACT:-false}" = "true" ]; then + echo "Skipping AGENT_TOOLSDIRECTORY cleanup under act: ${AGENT_TOOLSDIRECTORY:-unset}" + else + sudo rm -rf "$AGENT_TOOLSDIRECTORY" || true + + fi # saves about 10 GB echo removing android files sudo rm -rf /usr/local/lib/android @@ -175,16 +192,72 @@ jobs: # no idea how to do this ;; esac - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: submodules: recursive + + - name: Checkout PETSIRD + if: matrix.PETSIRD == 'ON' + uses: actions/checkout@v6 + with: + repository: ETSInitiative/PETSIRD + ref: v0.9.1 + path: external/PETSIRD + + - name: Setup PETSIRD environment + if: matrix.PETSIRD == 'ON' + uses: mamba-org/setup-micromamba@v2 + with: + environment-file: external/PETSIRD/environment.yml + environment-name: yardl + cache-environment: true + cache-downloads: true + + - name: Add STIR dependencies in PETSIRD environment + if: matrix.PETSIRD =='ON' + shell: bash -el {0} + run: | + set -ex + micromamba install -y -n yardl -c conda-forge boost-cpp swig + test -f "${CONDA_PREFIX}/include/boost/config.hpp" + + - name: Install Yardl + if: matrix.PETSIRD == 'ON' + run: | + OS="$(uname -s)" + ARCH="$(uname -m)" + echo $OS + echo $ARCH + case "$OS" in + Linux) + YARDL_OS="linux" + ;; + Darwin) + YARDL_OS="darwin" + ;; + Windows) + YARDL_OS="windows" + ;; + esac + + curl -L https://github.com/microsoft/yardl/releases/download/v${YARDL_VERSION}/yardl_${YARDL_VERSION}_${YARDL_OS}_${ARCH}.tar.gz -o yardl_${YARDL_VERSION}_${YARDL_OS}_${ARCH}.tar.gz + mkdir yardl + tar -xzf "yardl_${YARDL_VERSION}_${YARDL_OS}_${ARCH}.tar.gz" -C yardl + rm "yardl_${YARDL_VERSION}_${YARDL_OS}_${ARCH}.tar.gz" + echo "$PWD/yardl" >> "$GITHUB_PATH" + - name: disk space - shell: bash + shell: bash -el {0} run: | case ${{matrix.os}} in (ubuntu* | macOS*) - sudo .github/workflows/GHA_increase_disk_space.sh + # For local CI, see documentation/devel/local-CI.md + if [ "${ACT:-false}" = "true" ]; then + echo "Skipping GHA_increase_disk_space.sh under act" + else + sudo .github/workflows/GHA_increase_disk_space.sh + fi ;; (windows*) # no idea what to do here @@ -192,7 +265,7 @@ jobs: esac - name: set_compiler_variables - shell: bash + shell: bash -el {0} run: | set -ex if test 'XX${{ matrix.compiler }}' = 'XXclang'; then @@ -237,25 +310,34 @@ jobs: linux-local-args: '["--toolkit"]' - name: install_dependencies - shell: bash + shell: bash -el {0} run: | set -ex # We will install some external dependencies here CMAKE_INSTALL_PREFIX=${GITHUB_WORKSPACE}/install + case ${{matrix.os}} in (ubuntu*) sudo apt update - # install compiler - if test 'XX${{ matrix.compiler }}' = 'XXclang'; then - # package is called clang, need libomp-dev for OpenMP support - sudo apt install $CC libomp-dev + if test "${{matrix.PETSIRD}}XX" == "ONXX"; then + echo "Using compilers from PETSIRD/micromamba environment" + echo "CC=${CC:-unset}" + echo "CXX=${CXX:-unset}" + micromamba install -y -n yardl -c conda-forge boost-cpp swig else - sudo apt install $CXX + # install compiler + if test 'XX${{ matrix.compiler }}' = 'XXclang'; then + # package is called clang, need libomp-dev for OpenMP support + sudo apt install -y $CC libomp-dev + else + sudo apt install -y $CXX + fi + + # other dependencies + sudo apt install -y cmake libboost-dev libhdf5-serial-dev swig python3-dev nlohmann-json3-dev ninja-build fi - # other dependencies - sudo apt install libboost-dev libhdf5-serial-dev swig python3-dev nlohmann-json3-dev ninja-build if test "${{matrix.ITK}}XX" == "ONXX"; then - sudo apt install libinsighttoolkit5-dev + sudo apt install -y libinsighttoolkit5-dev fi # free up some disk space apt autoremove --purge && sudo apt clean @@ -268,12 +350,11 @@ jobs: if ! command -v swig > /dev/null; then brew install swig fi - brew install boost nlohmann-json # Temp fix to 3.13 due to https://github.com/UCL/STIR/issues/1638 #if ! command -v python3 > /dev/null; then # brew install python #fi - brew install python@3.13 + brew install boost nlohmann-json python@3.13 brew ls python@3.13 PYTHON_EXECUTABLE=$(find /opt/homebrew/Cellar/python@3.13 -type f -name python3.13) #PYTHON_EXECUTABLE=$(which python3) @@ -286,23 +367,39 @@ jobs: PYTHON_EXECUTABLE=$(which python3) ;; esac - echo PYTHON_EXECUTABLE="$PYTHON_EXECUTABLE" >> $GITHUB_ENV - ${PYTHON_EXECUTABLE} -m venv ${GITHUB_WORKSPACE}/my-env - source ${GITHUB_WORKSPACE}/my-env/bin/activate - #python -m pip install -U pip - case ${{matrix.os}} in - (macOS*) - # attempt to get round buggy Accelerate builds, see https://github.com/numpy/numpy/issues/15947 - # but it didn't work, so commented out - # brew install openblas - # export OPENBLAS=$(brew --prefix openblas) - #python -m pip install --no-cache-dir --no-binary numpy numpy # avoid the cached .whl! - python -m pip install numpy pytest matplotlib - ;; - (*) - python -m pip install numpy pytest matplotlib - ;; - esac + + + # source ${GITHUB_WORKSPACE}/my-env/bin/activate + # echo PYTHON_EXECUTABLE="$PYTHON_EXECUTABLE" >> $GITHUB_ENV + # ${PYTHON_EXECUTABLE} -m venv ${GITHUB_WORKSPACE}/my-env + # source ${GITHUB_WORKSPACE}/my-env/bin/activate + # #python -m pip install -U pip + + if test "${{matrix.PETSIRD}}XX" == "ONXX"; then + micromamba install -y -n yardl -c conda-forge numpy pytest + PYTHON_EXECUTABLE="$(which python)" + echo PYTHON_EXECUTABLE="$PYTHON_EXECUTABLE" >> "$GITHUB_ENV" + else + ${PYTHON_EXECUTABLE} -m venv ${GITHUB_WORKSPACE}/my-env + source "${GITHUB_WORKSPACE}/my-env/bin/activate" + + case ${{matrix.os}} in + (macOS*) + # attempt to get round buggy Accelerate builds, see https://github.com/numpy/numpy/issues/15947 + # but it didn't work, so commented out + # brew install openblas + # export OPENBLAS=$(brew --prefix openblas) + #python -m pip install --no-cache-dir --no-binary numpy numpy # avoid the cached .whl! + python -m pip install numpy pytest matplotlib + ;; + (*) + python -m pip install numpy pytest matplotlib + ;; + esac + # From now on, use the venv Python. + PYTHON_EXECUTABLE="$(which python)" + echo PYTHON_EXECUTABLE="$PYTHON_EXECUTABLE" >> "$GITHUB_ENV" + fi if test "${{matrix.parallelproj}}XX" == "ONXX"; then git clone --depth 20 https://github.com/KUL-recon-lab/libparallelproj/ @@ -325,7 +422,11 @@ jobs: if test "${{matrix.ROOT}}XX" == "ONXX"; then case ${{matrix.os}} in (ubuntu*) - sudo apt install libtbb-dev libvdt-dev libgif-dev + if test "${{matrix.PETSIRD}}XX" == "ONXX"; then + micromamba install -y -n yardl -c conda-forge tbb-devel vdt giflib + else + sudo apt install -y libtbb-dev libvdt-dev libgif-dev + fi ROOT_file=root_v6.34.00.Linux-ubuntu24.04-x86_64-gcc13.2.tar.gz #root_v6.34.00.Linux-ubuntu24.10-x86_64-gcc14.2.tar.gz ;; @@ -336,19 +437,32 @@ jobs: wget https://root.cern/download/"$ROOT_file" tar -xzvf "$ROOT_file" rm "$ROOT_file" + source root/bin/thisroot.sh - echo ROOTSYS="$ROOTSYS" >> $GITHUB_ENV + # thisroot.sh adds ROOTSYS to CMAKE_PREFIX_PATH. + # Avoid ROOTConfig.cmake; let STIR find ROOT via root-config instead. + export CMAKE_PREFIX_PATH="${CONDA_PREFIX}:${CMAKE_INSTALL_PREFIX}" + echo ROOTSYS="$ROOTSYS" >> "$GITHUB_ENV" + echo CMAKE_PREFIX_PATH="$CMAKE_PREFIX_PATH" >> "$GITHUB_ENV" + echo PATH="$PATH" >> "$GITHUB_ENV" + fi + + # Install PETSIRD + if test "${{matrix.PETSIRD}}XX" == "ONXX"; then + ls + cd external/PETSIRD/ + just cmake_install_prefix="${CMAKE_INSTALL_PREFIX}" build-cpp fi # we'll install some dependencies with shared libraries, so need to let the OS know # thisroot.sh also modified the path, so save that for the recon_test_pack case ${{matrix.os}} in (ubuntu*) - echo LD_LIBRARY_PATH="${CMAKE_INSTALL_PREFIX}/lib:$LD_LIBRARY_PATH" >> $GITHUB_ENV + echo LD_LIBRARY_PATH="${CMAKE_INSTALL_PREFIX}/lib:${CONDA_PREFIX}/lib:${ROOTSYS:+${ROOTSYS}/lib:}${LD_LIBRARY_PATH}" >> "$GITHUB_ENV" echo PATH="$PATH" >> $GITHUB_ENV ;; (macOS*) - echo DYLD_FALLBACK_LIBRARY_PATH="${CMAKE_INSTALL_PREFIX}/lib:$DYLD_FALLBACK_LIBRARY_PATH" >> $GITHUB_ENV + echo DYLD_FALLBACK_LIBRARY_PATH="${CMAKE_INSTALL_PREFIX}/lib:${CONDA_PREFIX}/lib:${ROOTSYS:+${ROOTSYS}/lib:}${DYLD_FALLBACK_LIBRARY_PATH}" >> "$GITHUB_ENV" echo PATH="$PATH" >> $GITHUB_ENV ;; (windows*) @@ -363,13 +477,15 @@ jobs: max-size: "2G" - name: configure - shell: bash + shell: bash -el {0} env: BUILD_TYPE: ${{ matrix.BUILD_TYPE }} BUILD_FLAGS: ${{ matrix.BUILD_FLAGS }} run: | set -ex - source ${GITHUB_WORKSPACE}/my-env/bin/activate + if test "${{matrix.PETSIRD}}XX" != "ONXX"; then + source ${GITHUB_WORKSPACE}/my-env/bin/activate + fi #export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH" cmake --version if test "XX$CC" != "XX"; then @@ -380,13 +496,19 @@ jobs: # make available to jobs below echo CMAKE_INSTALL_PREFIX="$CMAKE_INSTALL_PREFIX" >> $GITHUB_ENV if [ -n "$ROOTSYS" ]; then - # make sure we find ROOT (and vdt, which is installed in the same place) - EXTRA_BUILD_FLAGS=-DCMAKE_PREFIX_PATH:PATH="$ROOTSYS" + if test "${{matrix.PETSIRD}}XX" == "ONXX"; then + EXTRA_BUILD_FLAGS="${EXTRA_BUILD_FLAGS} -DCMAKE_PREFIX_PATH=${ROOTSYS};${CONDA_PREFIX}" + EXTRA_BUILD_FLAGS="${EXTRA_BUILD_FLAGS} -DHDF5_ROOT=${CONDA_PREFIX}" + EXTRA_BUILD_FLAGS="${EXTRA_BUILD_FLAGS} -DVDT_LIBRARY=${CONDA_PREFIX}/lib/libvdt.so" + EXTRA_BUILD_FLAGS="${EXTRA_BUILD_FLAGS} -DVDT_INCLUDE_DIR=${CONDA_PREFIX}/include" + else + EXTRA_BUILD_FLAGS="${EXTRA_BUILD_FLAGS} -DCMAKE_PREFIX_PATH=${ROOTSYS};/usr" + fi fi EXTRA_BUILD_FLAGS="${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_PETSIRD:BOOL=ON -DDISABLE_STIR_LOCAL=OFF -DSTIR_LOCAL=${GITHUB_WORKSPACE}/examples/C++/using_STIR_LOCAL" + 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 @@ -409,16 +531,18 @@ jobs: if: ${{ github.event_name == 'workflow_dispatch' && inputs.debug_enabled == 'true' }} - name: build - shell: bash + shell: bash -el {0} env: BUILD_TYPE: ${{ matrix.BUILD_TYPE }} run: | cd ${GITHUB_WORKSPACE}/build; - source ${GITHUB_WORKSPACE}/my-env/bin/activate - cmake --build . --config ${BUILD_TYPE}} --target install + if test "${{matrix.PETSIRD}}XX" != "ONXX"; then + source "${GITHUB_WORKSPACE}/my-env/bin/activate" + fi + cmake --build . --config ${BUILD_TYPE} --target install - name: ctest - shell: bash + shell: bash -el {0} env: BUILD_TYPE: ${{ matrix.BUILD_TYPE }} run: | @@ -458,7 +582,7 @@ jobs: retention-days: 7 - name: C++ examples with STIR_LOCAL - shell: bash + shell: bash -el {0} run: | set -ex; PATH=${CMAKE_INSTALL_PREFIX}/bin:$PATH @@ -490,7 +614,7 @@ jobs: df -h . - name: C++ examples with installed STIR - shell: bash + shell: bash -el {0} run: | set -ex; # build and run C++/using_installed_STIR @@ -505,7 +629,7 @@ jobs: rm -rf build - name: recon_test_pack - shell: bash + shell: bash -el {0} env: BUILD_FLAGS: ${{ matrix.BUILD_FLAGS }} BUILD_TYPE: ${{ matrix.BUILD_TYPE }} @@ -540,7 +664,7 @@ jobs: retention-days: 7 - name: remove recon_test_pack - shell: bash + shell: bash -el {0} run: | cd ${GITHUB_WORKSPACE}/recon_test_pack # keep a few files for pytest @@ -550,10 +674,12 @@ jobs: mv ../tmp/* . - name: Python - shell: bash + shell: bash -el {0} run: | set -ex - source ${GITHUB_WORKSPACE}/my-env/bin/activate + if test "${{matrix.PETSIRD}}XX" != "ONXX"; then + source "${GITHUB_WORKSPACE}/my-env/bin/activate" + fi # Run Python tests, making sure we're using the correct Python interpreter which python export PYTHONPATH=${CMAKE_INSTALL_PREFIX}/python diff --git a/CMakeLists.txt b/CMakeLists.txt index a19168a49f..d6d7406cda 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -99,18 +99,18 @@ find_package( Boost 1.36.0 REQUIRED ) #### optional external libraries. # Listed here such that we know if we should compile extra utilities -option(DISABLE_LLN_MATRIX "disable use of LLN library" ON) -option(DISABLE_ITK "disable use of ITK library" ON) -option(DISABLE_HDF5 "disable use of HDF5 libraries" ON) -option(DISABLE_STIR_LOCAL "disable use of LOCAL extensions to STIR" ON) -option(DISABLE_CERN_ROOT "disable use of Cern ROOT libraries" ON) -option(DISABLE_NLOHMANN_JSON "disable use of nlohmann JSON libraries" ON) -option(STIR_ENABLE_EXPERIMENTAL "disable use of STIR experimental code" ON) # disable by default -option(DISABLE_NiftyPET_PROJECTOR "disable use of NiftyPET projector" ON) -option(DISABLE_Parallelproj_PROJECTOR "disable use of Parallelproj projector" ON) +option(DISABLE_LLN_MATRIX "disable use of LLN library" OFF) +option(DISABLE_ITK "disable use of ITK 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) +option(DISABLE_NLOHMANN_JSON "disable use of nlohmann JSON libraries" OFF) +option(STIR_ENABLE_EXPERIMENTAL "disable use of STIR experimental code" OFF) # disable by default +option(DISABLE_NiftyPET_PROJECTOR "disable use of NiftyPET projector" OFF) +option(DISABLE_Parallelproj_PROJECTOR "disable use of Parallelproj projector" OFF) OPTION(DOWNLOAD_ZENODO_TEST_DATA "download zenodo data for tests" OFF) -option(DISABLE_UPENN "disable use of UPENN filetypes" ON) option(DISABLE_PETSIRD "disable use of PETSIRD filetypes" OFF) +option(DISABLE_UPENN "disable use of UPENN filetypes" OFF) find_package(Git QUIET) if(GIT_FOUND AND EXISTS "${PROJECT_SOURCE_DIR}/.git") @@ -280,55 +280,38 @@ else() endif() if(NOT DISABLE_PETSIRD) - if(GIT_FOUND AND EXISTS "${PROJECT_SOURCE_DIR}/.git") - # Check if PETSIRD is already registered as a submodule - execute_process( - COMMAND ${GIT_EXECUTABLE} config --file ${PROJECT_SOURCE_DIR}/.gitmodules --get-regexp "submodule\\.PETSIRD\\.path" - RESULT_VARIABLE SUBMOD_EXISTS - OUTPUT_QUIET - ERROR_QUIET - ) - - if(NOT SUBMOD_EXISTS EQUAL 0) - message(STATUS "Adding PETSIRD submodule...") - execute_process( - COMMAND ${GIT_EXECUTABLE} submodule add https://github.com/ETSInitiative/PETSIRD.git PETSIRD - WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} - RESULT_VARIABLE GIT_ADD_RESULT - ) - if(NOT GIT_ADD_RESULT EQUAL 0) - message(WARNING "Submodule add failed with code ${GIT_ADD_RESULT}") - endif() - else() - message(STATUS "PETSIRD submodule already exists in .gitmodules.") - endif() - - # Always update/init to ensure it's ready - execute_process( - COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive - WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} - ) - endif() - - execute_process( - COMMAND just generate - WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/PETSIRD - RESULT_VARIABLE JUST_RESULT - ) + find_package(PETSIRD CONFIG) + + if(PETSIRD_FOUND) + message(STATUS "PETSIRD support enabled (found PETSIRD).") + set(HAVE_PETSIRD ON) + + # Inspect PETSIRD transitive dependencies + if(TARGET PETSIRD::petsird) + get_target_property(_petsird_libs PETSIRD::petsird INTERFACE_LINK_LIBRARIES) + + get_target_property(_petsird_features PETSIRD::petsird INTERFACE_COMPILE_FEATURES) + get_target_property(_petsird_libs PETSIRD::petsird INTERFACE_LINK_LIBRARIES) + message(STATUS "PETSIRD::petsird dependencies: ${_petsird_libs}") + message(STATUS "PETSIRD::petsird compile features: ${_petsird_features}") + + # I have to give credit to AI for the following line. + # Probably this will not be needed, but I will keep it here for future reference. + # string(REGEX MATCH "cxx_std_([0-9]+)" _petsird_std_match "${_petsird_features}") + # if(CMAKE_MATCH_1) + # UseCXX(${CMAKE_MATCH_1}) + # endif() + + else() + message(FATAL_ERROR "PETSIRD::petsird target not found after find_package(PETSIRD)") + endif() - if(JUST_RESULT EQUAL 0) - set(HAVE_PETSIRD TRUE) - set(PETSIRD_base_dir ${PROJECT_SOURCE_DIR}/PETSIRD/cpp/generated) - message(STATUS "PETSIRD generation succeeded. THE PATH is ${PETSIRD_base_dir}. HAVE_PETSIRD set to TRUE.") else() - set(HAVE_PETSIRD FALSE) - message(WARNING "PETSIRD generation failed with code ${JUST_RESULT}. HAVE_PETSIRD not set.") + message(STATUS "PETSIRD not found: PETSIRD support will be disabled.") + set(HAVE_PETSIRD OFF) endif() - set(PETSIRD_dir ${PROJECT_SOURCE_DIR}/PETSIRD/cpp/generated) - add_subdirectory(${PETSIRD_dir} PETSIRD_generated) - install(TARGETS petsird_generated - EXPORT STIRTargets - DESTINATION lib) +else() + set(HAVE_PETSIRD OFF) endif() #### enable support for ctest diff --git a/documentation/devel/README.md b/documentation/devel/README.md index f3226412f6..508ced0e6d 100644 --- a/documentation/devel/README.md +++ b/documentation/devel/README.md @@ -5,4 +5,6 @@ Please check files here for information/code practices for developers. - Do read our [contribution guidelines](../../CONTRIBUTING.md) - Set your editor settings appropriately: [instructions](editor-settings.md) - Install git hooks for serious development: [instructions](git-hooks.md) -- Read the documentation, including the STIR developers guide \ No newline at end of file +- Read the documentation, including the STIR developers guide + +- To run the CI workflow locally with `act`: [instructions](local-CI.md) diff --git a/documentation/devel/local-CI.md b/documentation/devel/local-CI.md new file mode 100644 index 0000000000..7ea0b7daab --- /dev/null +++ b/documentation/devel/local-CI.md @@ -0,0 +1,37 @@ +# Running the GitHub Actions workflow locally with `act` + +The GitHub Actions CI workflow can be tested locally using [`act`](https://github.com/nektos/act). +This is useful for debugging workflow changes before pushing to GitHub. + +## Prerequisities + +Install `act` and make sure Docker or Podman is available and running. + +On Linux, the workflow can be run with an Ubuntu 24.04 + container image compatible with GitHub Actions: + +```bash +act -W .github/workflows/build-test.yml \ + -P ubuntu-24.04=ghcr.io/catthehacker/ubuntu:act-24.04 \ + --env ACT=true +``` + +* The ```-W``` option selects the workflow file to run +* The ```-P``` option maps the GitHub Actions +runner label ```ubuntu-24.04``` to a local container image. +The image in the command above is suggested online. +* The `--env ACT=true` option sets an environment variable + used by the workflow to detect that it is running under `act`. + Some GitHub steps are skipped. + +## NOTES + +* It is highly recommended to run only one job at the time. +STIR has an array of different OSes and options. +Don't try to spin them up all together in your local workstation. +* Runnning the workflow locally with ```act``` is not always identical to GitHub. +* If Docker runs out of disk space, remove old images and containers before running + +```bash +docker system prune +``` diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index aecf58f1e5..f416e29aca 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,3 +1,4 @@ +# Copyright 2025, 2026, University Medical Center Groningen # Copyright 2011-01-01 - 2011-06-30 Hammersmith Imanet Ltd # Copyright 2011-07-01 - 2012 Kris Thielemans # Copyright 2016 ETH Zurich @@ -278,6 +279,10 @@ else() target_link_libraries(stir_registries PUBLIC CUDA::cudart) endif() +if (HAVE_PETSIRD) + target_link_libraries(stir_registries PUBLIC PETSIRD::petsird) +endif() + # go and look for CMakeLists.txt files in all those directories foreach(STIR_DIR ${STIR_DIRS} ${STIR_TEST_DIRS}) ADD_SUBDIRECTORY(${STIR_DIR}) diff --git a/src/IO/CMakeLists.txt b/src/IO/CMakeLists.txt index 39eaba6275..e78d97d363 100644 --- a/src/IO/CMakeLists.txt +++ b/src/IO/CMakeLists.txt @@ -92,7 +92,7 @@ target_sources(${STIR_BUILDBLOCK_LIB} PRIVATE ${${dir_LIB_SOURCES}}) set(TARGET ${STIR_BUILDBLOCK_LIB}) -target_link_libraries(${TARGET} PRIVATE fmt) +target_link_libraries(${TARGET}) if (LLN_FOUND) target_include_directories(${TARGET} PUBLIC ${LLN_INCLUDE_DIRS}) @@ -144,48 +144,6 @@ if (HAVE_JSON) target_include_directories(${TARGET} PRIVATE "${TMP}") endif() -if (HAVE_PETSIRD) -# #set(PETSIRD_dir ../../PETSIRD/cpp/generated) - - -target_include_directories(IO PUBLIC - $ - $ - $ -) - - -# # # target_include_directories(IO PUBLIC -# # # $ -# # # $ -# # # ) - -# # # target_include_directories(IO PUBLIC -# # # $ -# # # $ -# # # ) -# # target_include_directories(IO PUBLIC -# # $ -# # $ -# # $ -# # $ -# # ) - -# # target_link_libraries(IO PUBLIC petsird_generated) - -# set(PETSIRD_dir ../../PETSIRD/cpp/generated) -# add_subdirectory(${PETSIRD_dir} PETSIRD_generated) - -# install(TARGETS petsird_generated -# EXPORT STIRTargets -# DESTINATION lib) - -# target_include_directories(IO PUBLIC ${PETSIRD_dir}) -# # needed for helpers -# target_include_directories(IO PUBLIC ${PETSIRD_dir}/..) -# target_include_directories(IO PUBLIC ${PETSIRD_dir}/../helpers/include) -target_link_libraries(IO PUBLIC petsird_generated) +if(HAVE_PETSIRD) + target_link_libraries(${TARGET} PUBLIC PETSIRD::petsird) endif() - - - diff --git a/src/IO/IO_registries.cxx b/src/IO/IO_registries.cxx index 814b7e875b..0667f8a022 100644 --- a/src/IO/IO_registries.cxx +++ b/src/IO/IO_registries.cxx @@ -1,4 +1,5 @@ /* + Copyright 2025, 2026, University Medical Center Groningen Copyright (C) 2002-2011, Hammersmith Imanet Ltd Copyright (C) 2012, Kris Thielemans Copyright (C) 2013, Institute for Bioengineering of Catalonia diff --git a/src/IO/PETSIRDCListmodeInputFileFormat.cxx b/src/IO/PETSIRDCListmodeInputFileFormat.cxx index 4352cc9e20..1374a05233 100644 --- a/src/IO/PETSIRDCListmodeInputFileFormat.cxx +++ b/src/IO/PETSIRDCListmodeInputFileFormat.cxx @@ -1,13 +1,37 @@ +/* PETSIRDCListmodeInputFileFormat.h + + Class defining input file format for coincidence listmode data for PETSIRD. + + Copyright 2025, 2026, University Medical Center Groningen + Copyright 2025 National Physical Laboratory + + SPDX-License-Identifier: Apache-2.0 + See STIR/LICENSE.txt for details + */ #include "stir/IO/PETSIRDCListmodeInputFileFormat.h" -#include "../../PETSIRD/cpp/generated/binary/protocols.h" -#include "../../PETSIRD/cpp/generated/hdf5/protocols.h" +#include "petsird/binary/protocols.h" +#include "petsird/hdf5/protocols.h" +#include "stir/error.h" +#include "stir/format.h" +#include // #include "../../PETSIRD/cpp/generated/types.h" // #include "../../PETSIRD/cpp/helpers/include/petsird_helpers.h" START_NAMESPACE_STIR +/*! + + \file + \ingroup listmode + \brief Implementation of class stir::PETSIRDCListmodeInputFileFormat + + \author Nikos Efthimiou + \author Daniel Deidda + +*/ + bool -PETSIRDCListmodeInputFileFormat::can_read(const FileSignature& signature, const std::string& filename) +PETSIRDCListmodeInputFileFormat::can_read(const FileSignature& signature, const std::string& filename) const { std::array hdf5_signature = { 'H', 'D', 'F', '5' }; @@ -16,12 +40,20 @@ PETSIRDCListmodeInputFileFormat::can_read(const FileSignature& signature, const std::ifstream file(filename, std::ios::binary); if (!file.is_open()) { - std::cerr << "Cannot open file: " << filename << std::endl; + error(format("Cannot open file: {}", filename)); return false; } - std::array signature_{}; - file.read(signature_.data(), signature_.size()); + auto it = std::istreambuf_iterator(file); + auto end = std::istreambuf_iterator(); + + for (size_t i = 0; i < signature_.size() && it != end; ++i, ++it) + { + signature_[i] = *it; + } + + if (!file) + error("Stream error while reading file signature"); if (signature_ == hdf5_signature) { diff --git a/src/buildblock/CMakeLists.txt b/src/buildblock/CMakeLists.txt index 9f2d36c805..dceacac131 100644 --- a/src/buildblock/CMakeLists.txt +++ b/src/buildblock/CMakeLists.txt @@ -106,7 +106,12 @@ if (NOT MINI_STIR) list(APPEND ${dir_LIB_SOURCES} ProjDataGEHDF5.cxx ) -endif() + endif() + if (HAVE_PETSIRD) + list(APPEND ${dir_LIB_SOURCES} + PETSIRDInfo.cxx + ) + endif() endif() # MINI_STIR @@ -153,3 +158,7 @@ endif() if (STIR_OPENMP) target_link_libraries(${TARGET} PUBLIC ${OpenMP_EXE_LINKER_FLAGS}) endif() + +if(HAVE_PETSIRD) + target_link_libraries(${TARGET} PUBLIC PETSIRD::petsird) +endif() \ No newline at end of file diff --git a/src/buildblock/PETSIRDInfo.cxx b/src/buildblock/PETSIRDInfo.cxx new file mode 100644 index 0000000000..9ebf9e233e --- /dev/null +++ b/src/buildblock/PETSIRDInfo.cxx @@ -0,0 +1,664 @@ +/* + Copyright 2025,2026, University Medical Center Groningen + Copyright 2025, MGH / HST A. Martinos Center for Biomedical Imaging + + SPDX-License-Identifier: Apache-2.0 + See STIR/LICENSE.txt for details +*/ +/*! + +\file +\ingroup listmode +\brief implementation of class stir::CListModeDataPETSIRD + +\author Nikos Efthimiou +*/ + +#include "stir/PETSIRDInfo.h" +#include "stir/detail/PETSIRDInfo_helpers.h" + +#include "stir/warning.h" + +#include "petsird_helpers.h" +#include "petsird_helpers/create.h" +#include "petsird_helpers/geometry.h" + +#include "petsird/binary/protocols.h" +#include "petsird/hdf5/protocols.h" + +START_NAMESPACE_STIR + +/*! + \brief Infer scanner blocks and rotation axis from PETSIRD replicated modules. + \param unique_dim1_values Output set of unique translations along X. + \param unique_dim2_values Output set of unique translations along Y. + \param unique_dim3_values Output set of unique translations along Z. + \param replicated_module_list PETSIRD replicated detector modules. + \return Index of rotation axis (0=x, 1=y, 2=z) or -1 if not found. + + \details + - Extracts translation components into the provided sets. + - Uses skew-symmetric part of rotation matrices to infer axis direction. + - Emits warnings for mixed-axis rotations or inconsistencies. +*/ +int +PETSIRDInfo::figure_out_scanner_blocks_and_rotation_axis(std::set& unique_dim1_values, + std::set& unique_dim2_values, + std::set& unique_dim3_values) +{ + auto insertTranslations = [&](const petsird::RigidTransformation& trans) { + unique_dim1_values.insert(trans.matrix.at(0, 3)); + unique_dim2_values.insert(trans.matrix.at(1, 3)); + unique_dim3_values.insert(trans.matrix.at(2, 3)); + }; + + auto extractRotationMatrix = [](const petsird::RigidTransformation& trans) -> matrix::Mat3 { + matrix::Mat3 R; + for (int i = 0; i < 3; ++i) + for (int j = 0; j < 3; ++j) + R[i][j] = trans.matrix.at(i, j); + return R; + // skew = matrix::subtract(R, matrix::transpose(R)); + // auto rot = matrix::getAxisFromSkew(skew); + }; + + std::array, 3> skew; + int detected_axis = -1; + + std::vector replicated_module_list + = petsird_scanner_info_sptr->scanner_geometry.replicated_modules; + for (const auto& module : replicated_module_list) + for (const auto& mod_trans : module.transforms) + { + insertTranslations(mod_trans); + matrix::Mat3 R = extractRotationMatrix(mod_trans); + skew = matrix::subtract(R, matrix::transpose(R)); + auto axis_vec = matrix::getAxisFromSkew(skew); + + int current_axis = -1; + for (int i = 0; i < 3; ++i) + { + if (std::abs(axis_vec[i]) > 1e-6f) + { + if (current_axis != -1) + { + warning("Rotation involves multiple axis components. Possibly non-pure rotation."); + current_axis = -2; // Sentinel for mixed axes + return -1; + } + current_axis = i; + } + } + + if (current_axis >= 0) + { + if (detected_axis == -1) + detected_axis = current_axis; + else if (detected_axis != current_axis) + warning("Inconsistent rotation axis detected between modules."); + } + } + info(format("Rotation axis of blocks inferred as axis index {}", detected_axis)); + return detected_axis; +} + +/*! + \brief Determine block element translations and radius. + \param unique_dim1_values Output set of element translations along X. + \param unique_dim2_values Output set of element translations along Y. + \param unique_dim3_values Output set of element translations along Z. + \param radius Output inferred radius (positive component orthogonal to rotation axis). + \param radius_index Output index of the axis containing the radius component. + \param rotation_axis Known rotation axis (0=x, 1=y, 2=z). + \param replicated_module_list PETSIRD replicated detector modules. + + \details + - Scans element-level transforms to infer radius and collect translations. + - Emits warnings if mixed radii are detected. +*/ +void +PETSIRDInfo::figure_out_block_element_transformations(std::set& unique_dim1_values, + std::set& unique_dim2_values, + std::set& unique_dim3_values, + float& radius, + int& radius_index, + const int rotation_axis) +{ + auto insert_translation = [&](const petsird::RigidTransformation& trans) { + unique_dim1_values.insert(trans.matrix.at(0, 3)); + unique_dim2_values.insert(trans.matrix.at(1, 3)); + unique_dim3_values.insert(trans.matrix.at(2, 3)); + }; + + auto detect_radius = [&](const petsird::RigidTransformation& trans) -> bool { + for (int i = 0; i < 3; ++i) + { + if (i == rotation_axis) + continue; + float candidate = trans.matrix.at(i, 3); + if (candidate > 0.0f) + { + radius = candidate; + radius_index = i; + return true; + } + } + return false; + }; + + for (const auto& module : petsird_scanner_info_sptr->scanner_geometry.replicated_modules) + { + for (const auto& el_trans : module.object.detecting_elements.transforms) + { + if (radius == 0.0f) + { + if (!detect_radius(el_trans)) + { + error("Unable to determine radius from translation components."); + } + } + else + { + float current = el_trans.matrix.at(radius_index, 3); + if (std::abs(current - radius) > 1e-4f) + warning("Mixed radii detected. Consider checking for misaligned modules."); + } + + insert_translation(el_trans); + } + } +} + +void +PETSIRDInfo::figure_out_block_angles(std::set& unique_angle_modules, const int rot_axis) +{ + + for (const auto& module : petsird_scanner_info_sptr->scanner_geometry.replicated_modules) + for (const auto& transform : module.transforms) + { + if (rot_axis == 0) + unique_angle_modules.insert( + std::fabs(int(1000.F * std::atan2(transform.matrix.at(1, 0), transform.matrix.at(2, 0))) / 1000.F)); + else if (rot_axis == 1) + unique_angle_modules.insert( + std::fabs(int(1000.F * std::atan2(transform.matrix.at(2, 0), transform.matrix.at(0, 0))) / 1000.F)); + else if (rot_axis == 2) + unique_angle_modules.insert( + std::fabs(int(1000.F * std::atan2(transform.matrix.at(1, 0), transform.matrix.at(0, 0))) / 1000.F)); + } +} + +PETSIRDInfo::PETSIRDInfo(const petsird::Header& header, std::string scanner_geometry) + : petsird_scanner_info_sptr(std::make_shared(header.scanner)), + petsird_header_sptr(std::make_shared(header)), + forced_geometry(scanner_geometry) +{ + + if (!petsird_scanner_info_sptr) + error("PETSIRDInfo: Null PETSIRD ScannerInformation pointer provided."); + + const auto& geom = petsird_scanner_info_sptr->scanner_geometry; + + if (geom.replicated_modules.empty()) + error("PETSIRDInfo: scanner_geometry.replicated_modules is empty."); + + if (geom.replicated_modules[0].transforms.empty()) + warning("PETSIRDInfo: replicated_modules[0].transforms is empty (rotation/angles may be unreliable)."); + + if (geom.replicated_modules[0].object.detecting_elements.transforms.empty()) + error("PETSIRDInfo: detecting_elements.transforms is empty (cannot infer element spacing/radius)."); + + //! TODO: Determine the DOI based on material + float average_doi = 0.0; + if (petsird_scanner_info_sptr->bulk_materials.size() > 0) + { + const std::string& material = petsird_scanner_info_sptr->bulk_materials[0].name; + if (material.size() > 0) + average_doi = (material == "BGO") ? 5.0f : (material == "LSO" || material == "LYSO") ? 7.0f : 0.0f; + } + + type_of_module = petsird_scanner_info_sptr->scanner_geometry.replicated_modules.size() - 1; + if (type_of_module > 0) + { + error("Multiple types of PETSIRD modules are not supported. Abort."); + } + + module_pair = petsird::TypeOfModulePair{ type_of_module, type_of_module }; + + const auto& tof_bin_edges = petsird_scanner_info_sptr->tof_bin_edges[type_of_module][type_of_module]; + info(format("Num. of TOF bins in PETSIRD {}", tof_bin_edges.NumberOfBins())); + if (tof_bin_edges.NumberOfBins() > 1) + { + info(format( + "Since the PETSIRD file has TOF information, STIR will force cylindrical geometry, as long as other things checkout.")); + forced_geometry = "cylindrical"; + } + + std::set unique_tof_values; + vector_utils::find_unique_values_2D(unique_tof_values, petsird_scanner_info_sptr->tof_resolution); + //! TODO: Supports only single type of module + numberOfModules = petsird_scanner_info_sptr->scanner_geometry.replicated_modules[0].NumberOfObjects(); + //! TODO: Supports only single type of module + numberOfElementsIndices + = petsird_scanner_info_sptr->scanner_geometry.replicated_modules[0].object.detecting_elements.NumberOfObjects(); + std::set unique_dim1_values, unique_dim2_values, unique_dim3_values; + std::set unique_tof_resolutions; + + const int rotation_axis + = figure_out_scanner_blocks_and_rotation_axis(unique_dim1_values, unique_dim2_values, unique_dim3_values); + if (rotation_axis == -1) + is_cylindrical = false; + + const std::set& main_axis = vector_utils::get_largest_vector(unique_dim1_values, unique_dim2_values, unique_dim3_values); + + int num_transaxial_blocks = numberOfModules / main_axis.size(); + info(format("I deduce that the scanner has {} transaxial number of blocks", num_transaxial_blocks)); + + float radius = 0; + int radius_indx = -1; + + std::set unique_elements_dim1_values, unique_elements_dim2_values, unique_elements_dim3_values; + figure_out_block_element_transformations( + unique_elements_dim1_values, unique_elements_dim2_values, unique_elements_dim3_values, radius, radius_indx, rotation_axis); + + std::set unique_angle_modules; + figure_out_block_angles(unique_angle_modules, rotation_axis); + + std::vector block_angular_spacing; + if (!vector_utils::get_spacing_uniform( + block_angular_spacing, unique_angle_modules, 1e-2)) /// epsilon * 10000) // relax epsilon here + { + is_cylindrical = false; + } + + std::size_t group2 = 0, group3 = 0; + { + std::vector> pet_sird_positions; + + for (uint32_t module = 0; module < 1; module++) + { + for (uint32_t elem = 0; elem < numberOfElementsIndices; elem++) + { + petsird::ExpandedDetectionBin expanded_detection_bin{ module, elem, 1 }; + auto box_shape = petsird_helpers::geometry::get_detecting_box( + *petsird_scanner_info_sptr, type_of_module, expanded_detection_bin); + CartesianCoordinate3D mean_coord; + for (auto& corner : box_shape.corners) + { // if STIR (z,y,x) -> PETSIRD (-y, -x, z) pheraps the order below needs to be changed + mean_coord.x() = +corner.c[0] / box_shape.corners.size(); + mean_coord.y() = +corner.c[1] / box_shape.corners.size(); + mean_coord.z() = +corner.c[2] / box_shape.corners.size(); + } + // mean_coord.z() += this_scanner_sptr->get_axial_crystal_spacing() / + // save mean pos into map + pet_sird_positions.push_back(mean_coord); + } + } + + inferGroupSizes_dim2_dim3(pet_sird_positions, group2, group3); + } + + std::vector element_horizontal_spacing, element_vertical_spacing; + std::set unique_elements_horizontal_values, unique_elements_vertical_values; + if (radius_indx == 0) + { + if (!vector_utils::get_spacing_uniform(element_horizontal_spacing, unique_elements_dim3_values)) + { + is_cylindrical = false; + } + if (!vector_utils::get_spacing_uniform(element_vertical_spacing, unique_elements_dim2_values)) + { + is_cylindrical = false; + } + unique_elements_horizontal_values = unique_elements_dim3_values; + unique_elements_vertical_values = unique_elements_dim2_values; + } + else + { + //! TODO: Multiple radii handling + error("Multiple radii handling not implemented yet."); + } + + { + info("Printing TOF bin edges for validation (please make sure that the STIR TOF bin edges match the PETSIRD TOF bin edges):"); + for (size_t i = 0; i < tof_bin_edges.NumberOfBins(); ++i) + { + info(format("PETSIRD TOF bin edge {}: {}", i, tof_bin_edges.edges[i]), 2); + } + } + + bool has_tile_structure = group2 > 1 && group3 > 1 && (numberOfElementsIndices % (group2 * group3) == 0); + info(format("Has tile structure: {}", has_tile_structure ? "yes" : "no")); + + if (has_tile_structure) + { + // GATE-style tiled PETSIRD + blocks_per_bucket_transaxial = group2 > 1 ? unique_elements_vertical_values.size() / group2 : group2; + info(format("blocks per bucket in transaxial direction = {}", blocks_per_bucket_transaxial)); + blocks_per_bucket_axial + = group3 > 1 ? unique_elements_horizontal_values.size() / (numberOfElementsIndices / group3) : group3; + info(format("blocks per bucket in axial direction = {}", blocks_per_bucket_axial)); + num_axial_crystals_per_block = unique_elements_horizontal_values.size() / blocks_per_bucket_axial; + num_trans_crystals_per_block = unique_elements_vertical_values.size() / blocks_per_bucket_transaxial; + } + else + { + // STIR-style flattened PETSIRD + blocks_per_bucket_transaxial = 1; + blocks_per_bucket_axial = 1; + + num_trans_crystals_per_block = unique_elements_vertical_values.size(); + num_axial_crystals_per_block = unique_elements_horizontal_values.size(); + + warning("No block structure detected: falling back to flat crystal layout."); + } + + std::vector block_axial_spacing; + vector_utils::get_spacing_uniform(block_axial_spacing, main_axis); + if (block_axial_spacing.size() < 1) + { + // std::set::iterator it = unique_elements_horizontal_values.begin(); + // std::advance(it,0); + float begin = *std::next(unique_elements_horizontal_values.begin(), 0); + // std::advance(it,unique_elements_horizontal_values.size()-1); + float end = *std::next(unique_elements_horizontal_values.begin(), unique_elements_horizontal_values.size() - 1); + block_axial_spacing.push_back(std::abs(end - begin)); + } + + info(format("I counted {} axial blocks with spacing {}", unique_dim3_values.size(), block_axial_spacing[0])); + // Check if the cyrcle area is less than 5% different from the polygon + float expected_circle_area = float(M_PI * radius * radius); + float polygon_area + = float(0.5f * unique_angle_modules.size() * radius * radius * std::sin(2.f * float(M_PI) / unique_angle_modules.size())); + info(format("Circle area: {}, Polygon area: {}, pct {}", + expected_circle_area, + polygon_area, + std::abs(expected_circle_area - polygon_area) / expected_circle_area)); + // This is the NeuroLF ratio. + forced_geometry = "BlocksOnCylindrical"; + if (std::abs(expected_circle_area - polygon_area) / expected_circle_area < 0.02f || forced_geometry == "cylindrical") + { + info(format("PETSIRDInfo: The cylindrical area {} is more than 95% matching the polygon area {}. We will presume a " + "cylindrical configuration.", + expected_circle_area, + polygon_area)); + stir_scanner_sptr.reset( + new Scanner(Scanner::User_defined_scanner, + std::string("PETSIRD_defined_scanner"), + /* num dets per ring */ + (num_transaxial_blocks * unique_elements_vertical_values.size()), + unique_dim3_values.size() * unique_elements_horizontal_values.size() /* num of rings */, + /* number of non arccor bins */ + (num_transaxial_blocks * unique_elements_vertical_values.size()) / 2, + /* number of maximum arccor bins */ + (num_transaxial_blocks * unique_elements_vertical_values.size()) / 2, + /* inner ring radius */ + radius, + /* doi */ average_doi, // average_doi, + /* ring spacing */ + element_horizontal_spacing[0], //* 10.f, + // bin_size_v + radius < 140 ? element_vertical_spacing[0] / 2 : element_vertical_spacing[0], // * 10.f,, // * 10.f, + /*intrinsic_tilt_v*/ + 0.f, + /*num_axial_blocks_per_bucket_v */ + blocks_per_bucket_axial, + /*num_transaxial_blocks_per_bucket_v*/ + blocks_per_bucket_transaxial, + /*num_axial_crystals_per_block_v*/ + num_axial_crystals_per_block, + /*num_transaxial_crystals_per_block_v*/ + num_trans_crystals_per_block, + /*num_axial_crystals_per_singles_unit_v*/ + unique_elements_horizontal_values.size() / blocks_per_bucket_axial, + /*num_transaxial_crystals_per_singles_unit_v*/ + unique_elements_vertical_values.size() / blocks_per_bucket_transaxial, + /*num_detector_layers_v*/ + 1, // num_detector_layers_v + petsird_scanner_info_sptr->energy_resolution_at_511.front(), // energy_resolution_v + 511, // reference_energy_v + tof_bin_edges.NumberOfBins(), + (tof_bin_edges.edges[1] - tof_bin_edges.edges[0]) / speed_of_light_in_mm_per_ps_div2, + *unique_tof_values.begin() * 10 // non-TOF + )); + is_cylindrical = true; + is_generic_geometry = false; + is_block_configuration = false; + } + else + { + const uint32_t forced_axial_buckets = unique_dim3_values.size(); + info("PETSIRDInfo: The cylindrical area is less than 95% matching the polygon area. We will predsume a non-cylindrical " + "configuration."); + stir_scanner_sptr.reset( + new Scanner(Scanner::User_defined_scanner, + std::string("PETSIRD_defined_scanner"), + /* num dets per ring */ + (num_transaxial_blocks * unique_elements_vertical_values.size()), + unique_dim3_values.size() * unique_elements_horizontal_values.size() /* num of rings */, + /* number of non arccor bins */ + (num_transaxial_blocks * unique_elements_vertical_values.size()) / 2, + /* number of maximum arccor bins */ + (num_transaxial_blocks * unique_elements_vertical_values.size()) / 2, + /* inner ring radius */ + radius, + /* doi */ average_doi, + /* ring spacing */ + element_horizontal_spacing[0], //* 10.f, + // bin_size_v + radius < 140 ? element_vertical_spacing[0] / 2 : element_vertical_spacing[0], // * 10.f, + /*intrinsic_tilt_v*/ + 0.f, + /*num_axial_blocks_per_bucket_v */ + forced_axial_buckets, + /*num_transaxial_blocks_per_bucket_v*/ + 1, + /*num_axial_crystals_per_block_v*/ + blocks_per_bucket_axial * num_axial_crystals_per_block, + /*num_transaxial_crystals_per_block_v*/ + blocks_per_bucket_transaxial * num_trans_crystals_per_block, + /*num_axial_crystals_per_singles_unit_v*/ + unique_elements_horizontal_values.size() / blocks_per_bucket_axial, + /*num_transaxial_crystals_per_singles_unit_v*/ + unique_elements_vertical_values.size() / blocks_per_bucket_transaxial, + /*num_detector_layers_v*/ + 1, // num_detector_layers_v + petsird_scanner_info_sptr->energy_resolution_at_511.front(), // energy_resolution_v + 511, // reference_energy_v + 1, + 0.F, + 0.F, // non-TOF + "BlocksOnCylindrical", // scanner_geometry_v + (*std::next(unique_elements_horizontal_values.begin()) + - *unique_elements_horizontal_values.begin()), // axial_crystal_spacing_v + (*std::next(unique_elements_vertical_values.begin()) + - *unique_elements_vertical_values.begin()), // transaxial_crystal_spacing_v + (*std::next(unique_elements_horizontal_values.begin()) - *unique_elements_horizontal_values.begin()) + * num_axial_crystals_per_block * blocks_per_bucket_axial, // axial_block_spacing_v + (*std::next(unique_elements_vertical_values.begin()) - *unique_elements_vertical_values.begin()) + * num_trans_crystals_per_block * blocks_per_bucket_transaxial, // transaxial_block_spacing_v + "" // crystal_map_file_name_v + )); + is_cylindrical = false; + is_generic_geometry = false; + is_block_configuration = true; + } + + /// Now let's create the PETISIRD - STIR geometry mapping + petsird_to_stir = std::make_shared(); + petsird_map_sptr = std::make_shared(); + + // enum class InnerLoopDim + //{ + // Axial, + // Tangential, + // Radial + // }; + // InnerLoopDim inner_dim = InnerLoopDim::Tangential; // determined from your groupSize analysis + + // PRECOMPUTED from previous step: + std::size_t groupSize + = blocks_per_bucket_transaxial == 1 ? 1 : num_trans_crystals_per_block; // e.g. 5, or 1 if purely monotonic + // extern InnerLoopDim inner_dim; // Axial / Tangential / Radial + + // Don't need these anymore. Keeping for future reference. + // const int num_ax = blocks_per_bucket_axial * num_axial_crystals_per_block; + // const int num_tang = blocks_per_bucket_transaxial * num_trans_crystals_per_block; + + info(format("Tile size (groupSize) = {}", groupSize)); + + // const uint32_t num_rings = static_cast(stir_scanner_sptr->get_num_rings()); + // const uint32_t num_det = static_cast(stir_scanner_sptr->get_num_detectors_per_ring()); + const uint32_t axial_blocks = static_cast(stir_scanner_sptr->get_num_axial_blocks()); + const uint32_t trans_crys = static_cast(stir_scanner_sptr->get_num_transaxial_crystals_per_block()); + const uint32_t axial_crys = static_cast(stir_scanner_sptr->get_num_axial_crystals_per_block()); + // const uint32_t layers = static_cast(stir_scanner_sptr->get_num_detector_layers()); + + for (uint32_t module = 0; module < numberOfModules; ++module) + for (uint32_t elem = 0; elem < numberOfElementsIndices; ++elem) + { + petsird::ExpandedDetectionBin bin{ module, elem, 0 }; + + int tang_pos = 0, ax_pos = 0, rad_pos = 0; + + if (!has_tile_structure) + { + // -------- STIR-origin PETSIRD (flat) -------- + const uint32_t ax_mod = module % axial_blocks; + const uint32_t tang_mod = module / axial_blocks; + + const uint32_t axial_in_block = elem % axial_crys; + const uint32_t tmp = elem / axial_crys; + const uint32_t trans_in_block = tmp % trans_crys; + const uint32_t radial = tmp / trans_crys; + + tang_pos = tang_mod * trans_crys + trans_in_block; + ax_pos = ax_mod * axial_crys + axial_in_block; + rad_pos = radial; + } + else + { + // -------- GATE-origin PETSIRD (tiled) -------- + const uint32_t groupSize = num_trans_crystals_per_block; + const uint32_t tileSize = groupSize * groupSize; + + const uint32_t tile = elem / tileSize; + const uint32_t inTile = elem % tileSize; + + const uint32_t i0 = inTile % groupSize; + const uint32_t i1 = inTile / groupSize; + + const uint32_t tang_block = tile % blocks_per_bucket_transaxial; + const uint32_t axial_block = tile / blocks_per_bucket_transaxial; + + tang_pos = tang_block * groupSize + i0 + module * (num_trans_crystals_per_block * blocks_per_bucket_transaxial); + ax_pos = axial_block * groupSize + i1; + } + + (*petsird_to_stir)[bin] = DetectionPosition<>(tang_pos, ax_pos, rad_pos); + } + + // Reverse the mapping: from STIR detpos to PETSIRD mean coord + auto map = std::make_shared(); + + for (const auto& [petsird_bin, stir_pos] : (*petsird_to_stir)) + { + auto [it, inserted] = map->emplace(stir_pos, petsird_bin); + if (!inserted) + error("Non-unique STIR DetectionPosition while building reverse map"); + } + + stir_to_petsird = map; + + if (petsird_to_stir->size() != stir_to_petsird->size()) + { + info(format("PETSIRDInfo: Map size mismatch! Forward size: {0}\n Reverse size: {1}", + petsird_to_stir->size(), + stir_to_petsird->size())); + + error("Forward and reverse maps differ in size"); + } +} + +float +PETSIRDInfo::get_detection_efficiency_for_bin(const stir::DetectionPositionPair<>& dp) const +{ + const auto& detection_bin_efficiencies = petsird_scanner_info_sptr->detection_efficiencies.detection_bin_efficiencies; + + if (!detection_bin_efficiencies) + { + return 1.f; // no efficiencies available + } + + DetectionPosition<> temp_dp1; + DetectionPosition<> temp_dp2; + + if (dp.timing_pos() < 0) + { + temp_dp1 = dp.pos2(); + temp_dp2 = dp.pos1(); + } + else + { + temp_dp1 = dp.pos1(); + temp_dp2 = dp.pos2(); + } + + auto it0 = stir_to_petsird->find(temp_dp1); + if (it0 == stir_to_petsird->end()) + { + info(format("DetectionPosition pos1(): tangential {}, axial {},radial {}", + dp.pos1().tangential_coord(), + dp.pos1().axial_coord(), + dp.pos1().radial_coord())); + error("BinNormalisationFromPETSIRD: DetectionPosition not found in STIR→PETSIRD map"); + } + + auto it1 = stir_to_petsird->find(temp_dp2); + + if (it1 == stir_to_petsird->end()) + { + info(format("DetectionPosition pos2(): tangential {}, axial {}, radial {}", + dp.pos2().tangential_coord(), + dp.pos2().axial_coord(), + dp.pos2().radial_coord())); + error("BinNormalisationFromPETSIRD: DetectionPosition not found in STIR→PETSIRD map"); + } + + const auto det0 = petsird_helpers::make_detection_bin( + *petsird_scanner_info_sptr, type_of_module, it0->second); // it0->second is ExpandedDetectionBin + + const auto det1 = petsird_helpers::make_detection_bin(*petsird_scanner_info_sptr, type_of_module, it1->second); + + return petsird_helpers::get_detection_efficiency(*petsird_scanner_info_sptr.get(), module_pair, det0, det1); +} + +float +PETSIRDInfo::get_lower_energy_threshold() const +{ + if (petsird_scanner_info_sptr->event_energy_bin_edges.size() == 0) + return 0.0f; + float min_energy = std::numeric_limits::max(); + for (const auto& bin_edges : petsird_scanner_info_sptr->event_energy_bin_edges) + { + if (bin_edges.edges.front() < min_energy) + min_energy = bin_edges.edges.front(); + } + return min_energy; +} + +float +PETSIRDInfo::get_upper_energy_threshold() const +{ + if (petsird_scanner_info_sptr->event_energy_bin_edges.size() == 0) + return 0.0f; + float max_energy = std::numeric_limits::lowest(); + for (const auto& bin_edges : petsird_scanner_info_sptr->event_energy_bin_edges) + { + if (bin_edges.edges.back() > max_energy) + max_energy = bin_edges.edges.back(); + } + return max_energy; +} + +END_NAMESPACE_STIR \ No newline at end of file diff --git a/src/buildblock/ProjDataInfo.cxx b/src/buildblock/ProjDataInfo.cxx index b72804f35b..aedbf4fe0f 100644 --- a/src/buildblock/ProjDataInfo.cxx +++ b/src/buildblock/ProjDataInfo.cxx @@ -7,6 +7,7 @@ Copyright (C) 2018, University of Leeds Copyright (C) 2018, 2020-2023 University College London Copyright (C) 2016-2019, University of Hull + Copyright (C) 2025, 2026, University Medical Center Groningen This file is part of STIR. SPDX-License-Identifier: Apache-2.0 AND License-ref-PARAPET-license @@ -169,93 +170,84 @@ 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() && new_num > 0) + const bool tof_ready = scanner_ptr->is_tof_ready(); + + // Non-TOF mode (either scanner not TOF-ready or invalid mash factor) + if (!tof_ready || new_num <= 0) { - tof_mash_factor = new_num; - if (tof_mash_factor > scanner_ptr->get_max_num_timing_poss()) - error("ProjDataInfo::set_tof_mash_factor: TOF mashing factor (" + std::to_string(tof_mash_factor) - + +") must be smaller than or equal to the scanner's number of max timing bins (" - + std::to_string(scanner_ptr->get_max_num_timing_poss()) + ")."); - -#if 0 - // KT: code disabled as buggy but currently not needed - tof_increament_in_mm = tof_delta_time_to_mm(scanner_ptr->get_size_of_timing_pos()); - 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); - 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; - // if we ever re-enable this code, there is a BUG here: - // get_k relies on num_tof_bins, so this should have been set to the unmashed value from the scanner - 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_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)); + num_tof_bins = 1; + tof_mash_factor = 0; + min_tof_pos_num = 0; + max_tof_pos_num = 0; + // we assume TOF mashing factor = 0 means non-TOF and the projector + // won't use any boundary conditions + return; + } - } -#endif - // 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()); + const int max_timing_poss = scanner_ptr->get_max_num_timing_poss(); - // TODO cope with even numbers! - 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; + if (new_num > max_timing_poss) + { + error("ProjDataInfo::set_tof_mash_factor: TOF mashing factor (" + std::to_string(new_num) + + ") must be smaller than or equal to the scanner's number of " + "max timing bins (" + + std::to_string(max_timing_poss) + ")."); + } - num_tof_bins = max_tof_pos_num - min_tof_pos_num + 1; + tof_mash_factor = new_num; + // Initialise mashed TOF bins + tof_increament_in_mm = tof_delta_time_to_mm(tof_mash_factor * scanner_ptr->get_size_of_timing_pos()); - // 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."); + if (max_timing_poss % tof_mash_factor != 0) + { + error("ProjDataInfo::set_tof_mash_factor: scanner's number of timing positions (" + std::to_string(max_timing_poss) + + ") must be divisible by the TOF mashing factor (" + std::to_string(tof_mash_factor) + ")."); + } - // Upper and lower boundaries of the timing poss; - tof_bin_boundaries_mm.grow(min_tof_pos_num, max_tof_pos_num); + const int num_mashed_bins = max_timing_poss / tof_mash_factor; + num_tof_bins = num_mashed_bins; - tof_bin_boundaries_ps.grow(min_tof_pos_num, max_tof_pos_num); + // Compute min/max TOF position numbers without enforcing odd count + // + // We choose a symmetric convention: + // - If num_tof_bins is odd: bins go from -N/2 ... +N/2 + // - If num_tof_bins is even: bins go from -(N/2) ... +(N/2 - 1) + // This preserves the old behavior for odd counts and gives clean indexing for even counts. + // Note that this convention needs to match what we do in get_k(bin) + min_tof_pos_num = -num_tof_bins / 2; + max_tof_pos_num = min_tof_pos_num + num_tof_bins - 1; - for (int k = min_tof_pos_num; k <= max_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_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(format("Tbin {}: {} - {} mm ({} - {} ps) = {}", - 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) - || !scanner_ptr->is_tof_ready()) // Case new_num <=, will produce non-TOF data for a TOF compatible scanner + // Upper and lower boundaries of the timing positions + tof_bin_boundaries_mm.grow(min_tof_pos_num, max_tof_pos_num); + tof_bin_boundaries_ps.grow(min_tof_pos_num, max_tof_pos_num); + + for (int k = min_tof_pos_num; k <= max_tof_pos_num; ++k) { - num_tof_bins = 1; - tof_mash_factor = 0; - min_tof_pos_num = 0; - max_tof_pos_num = 0; - // we assume TOF mashing factor = 0 means non-TOF and the projecter won't use any boundary conditions + Bin bin; + bin.timing_pos_num() = k; + + const float sampling = get_sampling_in_k(bin); + const float center = get_k(bin); + + const float cur_low = center - sampling / 2.f; + const float cur_high = center + sampling / 2.f; + + 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(cur_low)); + tof_bin_boundaries_ps[k].high_lim = static_cast(mm_to_tof_delta_time(cur_high)); + + info(format("Tbin {}: {} - {} mm ({} - {} ps) = {}", + 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, + sampling)); } } diff --git a/src/cmake/STIRConfig.cmake.in b/src/cmake/STIRConfig.cmake.in index d6f0e1b68d..38a1daa023 100644 --- a/src/cmake/STIRConfig.cmake.in +++ b/src/cmake/STIRConfig.cmake.in @@ -3,6 +3,8 @@ # Author: Kris Thielemans # Author Richard Brown # Copyright 2016, 2019, 2020, 2022, 2023 University College London +# Copyright 2025, University Medical Center Groningen +# # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -159,6 +161,8 @@ if(@STIR_WITH_Parallelproj_PROJECTOR@) endif() if(@HAVE_PETSIRD@) + find_package(PETSIRD CONFIG REQUIRED ${STIR_FIND_TYPE}) + message(STATUS "PETSIRD support in STIR enabled.") set(HAVE_PETSIRD TRUE) endif() diff --git a/src/include/stir/ArrayFunction.h b/src/include/stir/ArrayFunction.h index 2d7b20f172..31ec91d62e 100644 --- a/src/include/stir/ArrayFunction.h +++ b/src/include/stir/ArrayFunction.h @@ -1,6 +1,7 @@ /* Copyright (C) 2000 PARAPET partners Copyright (C) 2000- 2007, Hammersmith Imanet Ltd + Copyright (C) 2026, University Medical Center Groningen This file is part of STIR. SPDX-License-Identifier: Apache-2.0 AND License-ref-PARAPET-license @@ -59,6 +60,7 @@ #include "stir/Array.h" #include "stir/shared_ptr.h" #include "stir/ArrayFunctionObject.h" +#include START_NAMESPACE_STIR @@ -267,6 +269,21 @@ inline void transform_array_to_periodic_indices(Array& out_array template inline void transform_array_from_periodic_indices(Array& out_array, const Array& in_array); +template +inline void find_unique_values(std::set& values, InputIt begin, InputIt end); + +// inline bool +// get_spacing_uniform(std::vector& spacing, const std::set& unsorted_block_poss, double epsilon = 1e-4) +// { +// std::vector sorted_z(unsorted_block_poss.begin(), unsorted_block_poss.end()); +// for (size_t i = 1; i < sorted_z.size(); ++i) +// { +// spacing.push_back(std::abs(sorted_z[i] - sorted_z[i - 1])); +// } + +// return std::all_of(spacing.begin(), spacing.end(), [&](float s) { return std::abs(s - spacing.front()) <= epsilon; }); +// } + END_NAMESPACE_STIR #include "stir/ArrayFunction.inl" diff --git a/src/include/stir/ArrayFunction.inl b/src/include/stir/ArrayFunction.inl index 612b7d8c03..67f01e384a 100644 --- a/src/include/stir/ArrayFunction.inl +++ b/src/include/stir/ArrayFunction.inl @@ -1,6 +1,7 @@ /* Copyright (C) 2000 PARAPET partners Copyright (C) 2000- 2007, Hammersmith Imanet Ltd + Copyright (C) 2026, University Medical Center Groningen This file is part of STIR. SPDX-License-Identifier: Apache-2.0 AND License-ref-PARAPET-license @@ -348,4 +349,12 @@ transform_array_from_periodic_indices(Array& out_array, c } while (next(index, out_array)); } +template +inline void +find_unique_values(std::set& values, InputIt begin, InputIt end) +{ + for (auto iter = begin; iter != end; ++iter) + values.insert(*iter); +} + END_NAMESPACE_STIR diff --git a/src/include/stir/IO/InputFileFormat.h b/src/include/stir/IO/InputFileFormat.h index 3a2b6bf7a9..03bcb6a74a 100644 --- a/src/include/stir/IO/InputFileFormat.h +++ b/src/include/stir/IO/InputFileFormat.h @@ -46,7 +46,7 @@ class InputFileFormat { return this->actual_can_read(signature, input); } - virtual bool can_read(const FileSignature& signature, const std::string& filename) + virtual bool can_read(const FileSignature& signature, const std::string& filename) const { std::ifstream input; open_read_binary(input, filename); diff --git a/src/include/stir/IO/InputStreamFromPETSIRD.h b/src/include/stir/IO/InputStreamFromPETSIRD.h deleted file mode 100644 index 9040e2b349..0000000000 --- a/src/include/stir/IO/InputStreamFromPETSIRD.h +++ /dev/null @@ -1,85 +0,0 @@ -// /*! -// \file -// \ingroup IO -// \brief Declaration of class stir::InputStreamFromROOTFile - -// \author Nikos Efthimiou -// \author Harry Tsoumpas -// \author Kris Thielemans -// \author Robert Twyman -// */ -// /* -// * Copyright (C) 2015, 2016 University of Leeds -// Copyright (C) 2016, 2021, 2020, 2021 UCL -// Copyright (C) 2018 University of Hull -// This file is part of STIR. - -// SPDX-License-Identifier: Apache-2.0 - -// See STIR/LICENSE.txt for details -// */ - -// #ifndef __stir_IO_InputStreamFromPETSIRD_H__ -// #define __stir_IO_InputStreamFromPETSIRD_H__ - -// #include "stir/shared_ptr.h" -// #include "stir/Succeeded.h" -// #include "stir/listmode/CListRecordPETSIRD.h" -// #include "stir/RegisteredObject.h" -// #include "stir/error.h" - -// #include "../../PETSIRD/cpp/generated/binary/protocols.h" -// #include "stir/IO/InputStreamWithRecords.h" -// #include "../../PETSIRD/cpp/generated/hdf5/protocols.h" - -// START_NAMESPACE_STIR - -// class InputStreamWithRecordsFromPETSIRD : public InputStreamWithRecords -// { -// public: -// typedef std::vector::size_type SavedPosition; - -// //! Default constructor -// InputStreamFromPETSIRD(std::string filename); - -// ~InputStreamFromPETSIRD() override -// {} -// //! \details Returns the next record in the ROOT file. -// //! The code is adapted from Sadek A. Nehmeh and CR Schmidtlein, -// //! downloaded from here -// virtual Succeeded get_next_record(CListReco*/rdPETSIRD& record) = 0; -// //! Go to the first event. -// inline Succeeded reset(); -// //! Must be called before calling for the first event. -// virtual Succeeded set_up(const std::string& header_path); -// //! Save current position in a vector -// inline SavedPosition save_get_position(); -// //! Set current position -// inline Succeeded set_get_position(const SavedPosition&); -// //! Get the vector with the saved positions -// inline std::vector get_saved_get_positions() const; -// //! Set a vector with saved positions -// inline void set_saved_get_positions(const std::vector&); -// //! Returns the total number of events -// inline unsigned long int get_total_number_of_events() const; - -// inline std::string get_PETSIRD_filename() const; - -// protected: - -// //! The starting position. -// unsigned long int starting_stream_position; -// //! The total number of entries -// unsigned long int nentries; -// //! Current get position -// unsigned long int current_position; -// //! A vector with saved position indices. -// std::vector saved_get_positions; -// //! The name of the ROOT chain to be read -// }; - -// END_NAMESPACE_STIR - -// #include "stir/IO/InputStreamFromROOTFile.inl" - -// #endif diff --git a/src/include/stir/IO/PETSIRDCListmodeInputFileFormat.h b/src/include/stir/IO/PETSIRDCListmodeInputFileFormat.h index 9c9f2bb07e..474831416c 100644 --- a/src/include/stir/IO/PETSIRDCListmodeInputFileFormat.h +++ b/src/include/stir/IO/PETSIRDCListmodeInputFileFormat.h @@ -1,32 +1,18 @@ -/* PETSIRDCListmodeInputFileFormat.h - - Class defining input file format for coincidence listmode data for PETSIRD. - - Copyright 2015 ETH Zurich, Institute of Particle Physics - Copyright 2020 Positrigo AG, Zurich +/* + Copyright 2025, 2026 University Medical Center Groningen Copyright 2025 National Physical Laboratory - 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 */ /*! \file \ingroup listmode - \brief Declaration of class stir::PETSIRDCListmodeInputFileFormat + \brief Class defining input file format for coincidence listmode data for PETSIRD. - \author Jannis Fischer - \author Markus Jehl, Positrigo + \author Nikos Efthimiou \author Daniel Deidda */ @@ -44,11 +30,7 @@ START_NAMESPACE_STIR /*! \brief Class for reading PETSIRD coincidence listmode data. - - The first 32 bytes of the binary file are interpreted as file signature and matched against the strings "MUPET CListModeData\0", -"PETSIRD". If either is successfull, the class claims it can read the file format. The -rest of the file is read as records, e.g. CListRecordPETSIRD. -*/ + */ class PETSIRDCListmodeInputFileFormat : public InputFileFormat { @@ -56,23 +38,23 @@ class PETSIRDCListmodeInputFileFormat : public InputFileFormat const std::string get_name() const override { return "PETSIRD"; } //! Checks in binary data file for correct signature. - bool can_read(const FileSignature& signature, const std::string& filename) override; + bool can_read(const FileSignature& signature, const std::string& filename) const override; protected: bool actual_can_read(const FileSignature& signature, std::istream& input) const override { return false; } - bool use_hdf5 = false; + mutable bool use_hdf5 = false; public: unique_ptr read_from_file(std::istream& input) const override { - error("read_from_file for ROOT listmode data with istream not implemented %s:%s. Sorry", __FILE__, __LINE__); + error("read_from_file for PETSIRD listmode data with istream not implemented. Sorry"); return unique_ptr(); } unique_ptr read_from_file(const std::string& filename) const override { - info("PETSIRDCListmodeInputFileFormat: read_from_file(" + std::string(filename) + ")"); + info("PETSIRDCListmodeInputFileFormat: read_from_file(" + filename + ")"); return unique_ptr(new CListModeDataPETSIRD(filename, use_hdf5)); } }; diff --git a/src/include/stir/IO/SAFIRCListmodeInputFileFormat.h b/src/include/stir/IO/SAFIRCListmodeInputFileFormat.h index ee72a67106..3042d4dd55 100644 --- a/src/include/stir/IO/SAFIRCListmodeInputFileFormat.h +++ b/src/include/stir/IO/SAFIRCListmodeInputFileFormat.h @@ -91,7 +91,7 @@ class SAFIRCListmodeInputFileFormat : public InputFileFormat, publ //! Checks in binary data file for correct signature (can be either "SAFIR CListModeData", "NeuroLF CListModeData" or "MUPET //! CListModeData"). - bool can_read(const FileSignature& signature, const std::string& filename) override + bool can_read(const FileSignature& signature, const std::string& filename) const override { // Looking for the right key in the parameter file std::ifstream par_file(filename.c_str()); @@ -140,10 +140,10 @@ class SAFIRCListmodeInputFileFormat : public InputFileFormat, publ std::unique_ptr read_from_file(const std::string& filename) const override { - // info("SAFIRCListmodeInputFileFormat: read_from_file(" + std::string(filename) + ")"); - // actual_do_parsing(filename); - // return std::unique_ptr(new CListModeDataSAFIR>( - // listmode_filename, crystal_map_filename, template_proj_data_filename, lor_randomization_sigma)); + info("SAFIRCListmodeInputFileFormat: read_from_file(" + std::string(filename) + ")"); + actual_do_parsing(filename); + return std::unique_ptr(new CListModeDataSAFIR>( + listmode_filename, crystal_map_filename, template_proj_data_filename, lor_randomization_sigma)); } protected: diff --git a/src/include/stir/PETSIRDInfo.h b/src/include/stir/PETSIRDInfo.h new file mode 100644 index 0000000000..4594b06d34 --- /dev/null +++ b/src/include/stir/PETSIRDInfo.h @@ -0,0 +1,171 @@ +/* + Copyright 2025, University Medical Center Groningen + + SPDX-License-Identifier: Apache-2.0 + See STIR/LICENSE.txt for detail +*/ +#ifndef __stir_listmode_PETSIRDInfo_H__ +#define __stir_listmode_PETSIRDInfo_H__ +/*! + \file PETSIRDInfo.h + \ingroup listmode + \brief Declaration of class stir::PETSIRDInfo + + \author Nikos Efthimiou +*/ + +#include "stir/DetectionPosition.h" +#include "stir/DetectionPositionPair.h" +#include "petsird/protocols.h" +#include "stir/Scanner.h" +#include "stir/DetectorCoordinateMap.h" +#include +#include + +#include "petsird_helpers.h" +#include "petsird_helpers/create.h" // for make_detection_bin +#include "petsird_helpers/geometry.h" // depending on where get_detection_efficiency lives + +START_NAMESPACE_STIR + +/*! + \brief Comparator for ordering petsird::ExpandedDetectionBin in std::map. + + \details + Orders by module_index, then element_index, then energy_index. +*/ +struct ExpandedDetectionBinLess +{ + + bool operator()(const petsird::ExpandedDetectionBin& a, const petsird::ExpandedDetectionBin& b) const + { + // Adjust field names if needed (I assume: module, element, energy_bin) + if (a.module_index < b.module_index) + return true; + if (a.module_index > b.module_index) + return false; + + if (a.element_index < b.element_index) + return true; + if (a.element_index > b.element_index) + return false; + return a.energy_index < b.energy_index; + } +}; + +/*! + \brief Mapping type from petsird::ExpandedDetectionBin to stir::DetectionPosition. +*/ +using PETSIRDToSTIRDetectorIndexMap + = std::map, ExpandedDetectionBinLess>; + +using STIRToPETSIRDDetectorIndexMap = std::map, petsird::ExpandedDetectionBin>; + +/*! + \brief Class to hold PETSIRD-related information for STIR and do any necessary conversions. +*/ +class PETSIRDInfo +{ +public: + explicit PETSIRDInfo(const petsird::Header& header, std::string scanner_geometry = ""); + + // void initialize(); + + inline std::shared_ptr get_scanner_sptr() const { return stir_scanner_sptr; } + + inline shared_ptr get_petsird_scanner_info_sptr() const { return petsird_scanner_info_sptr; } + + inline shared_ptr get_petsird_to_stir_map() const { return petsird_to_stir; } + + inline shared_ptr get_stir_to_petsird_map() const { return stir_to_petsird; } + + inline shared_ptr get_petsird_map_sptr() const { return petsird_map_sptr; } + + inline bool is_generic_geometry_used() const { return is_generic_geometry; } + + inline bool is_block_configuration_used() const { return is_block_configuration; } + + inline bool is_cylindrical_configuration_used() const { return is_cylindrical; }; + + float get_detection_efficiency_for_bin(const stir::DetectionPositionPair<>& dp) const; + + float get_lower_energy_threshold() const; + + float get_upper_energy_threshold() const; + +private: + /*! + \brief Infer scanner blocks and rotation axis from PETSIRD replicated modules. + \param unique_dim1_values Output set of unique translations along X. + \param unique_dim2_values Output set of unique translations along Y. + \param unique_dim3_values Output set of unique translations along Z. + \param replicated_module_list PETSIRD replicated detector modules. + \return Index of rotation axis (0=x, 1=y, 2=z) or -1 if not found. + */ + int figure_out_scanner_blocks_and_rotation_axis(std::set& unique_dim1_values, + std::set& unique_dim2_values, + std::set& unique_dim3_values); + /*! + \brief Compute unique module rotation angles around the given axis. + \param unique_angle_modules Output set of unique angles (radians). + \param rot_axis Rotation axis index (0=x, 1=y, 2=z). + */ + void figure_out_block_angles(std::set& unique_angle_modules, const int rot_axis); + /*! + \brief Determine block element translations and radius. + \param unique_dim1_values Output set of element translations along X. + \param unique_dim2_values Output set of element translations along Y. + \param unique_dim3_values Output set of element translations along Z. + \param radius Output inferred radius. + \param radius_index Output axis index containing the radius component. + \param rotation_axis Known rotation axis (0=x, 1=y, 2=z). + \param replicated_module_list PETSIRD replicated detector modules. +*/ + void figure_out_block_element_transformations(std::set& unique_dim1_values, + std::set& unique_dim2_values, + std::set& unique_dim3_values, + float& radius, + int& radius_index, + const int rotation_axis); + + //! Scanner information as provided by PETSIRD. + shared_ptr petsird_scanner_info_sptr; + //! Active scanner instance. + shared_ptr stir_scanner_sptr; + + shared_ptr petsird_header_sptr; + //! Number of replicated modules. + uint32_t numberOfModules; + //! Number of element indices per module. + uint32_t numberOfElementsIndices; + //! Transaxial blocks per bucket (scanner metadata). + uint32_t blocks_per_bucket_transaxial; + //! Axial blocks per bucket (scanner metadata). + uint32_t blocks_per_bucket_axial; + //! Number of axial crystals per block. + uint32_t num_axial_crystals_per_block; + //! Number of transaxial crystals per block. + uint32_t num_trans_crystals_per_block; + //! True if we should be using the cylindrical geometry + bool is_cylindrical = true; + + bool is_generic_geometry = false; + + bool is_block_configuration = false; + + petsird::TypeOfModule type_of_module; + + petsird::TypeOfModulePair module_pair; + + std::string forced_geometry = ""; + //! Mapping from PETSIRD expanded bins to STIR detection positions. + shared_ptr petsird_to_stir; + + shared_ptr stir_to_petsird; + //! Mapping from STIR detection positions to PETSIRD coordinates. + shared_ptr petsird_map_sptr; +}; + +END_NAMESPACE_STIR + +#endif \ No newline at end of file diff --git a/src/include/stir/ProjDataInfoCylindricalNoArcCorr.inl b/src/include/stir/ProjDataInfoCylindricalNoArcCorr.inl index 9f3678de3f..6627799b24 100644 --- a/src/include/stir/ProjDataInfoCylindricalNoArcCorr.inl +++ b/src/include/stir/ProjDataInfoCylindricalNoArcCorr.inl @@ -12,6 +12,7 @@ */ /* + Copyright (C) 2026, University Medical Center Groningen Copyright (C) 2000- 2005, Hammersmith Imanet Ltd This file is part of STIR. @@ -124,7 +125,7 @@ ProjDataInfoCylindricalNoArcCorr::get_bin_for_det_pair( } else { - bin.timing_pos_num() = -timing_pos_num; + bin.timing_pos_num() = -timing_pos_num - (get_num_tof_poss() % 2 == 0); return get_segment_axial_pos_num_for_ring_pair(bin.segment_num(), bin.axial_pos_num(), ring_num2, ring_num1); } } diff --git a/src/include/stir/detail/PETSIRDInfo_helpers.h b/src/include/stir/detail/PETSIRDInfo_helpers.h new file mode 100644 index 0000000000..d325cf075f --- /dev/null +++ b/src/include/stir/detail/PETSIRDInfo_helpers.h @@ -0,0 +1,280 @@ +/* + Copyright 2026, University Medical Center Groningen + Copyright 2025, MGH / HST A. Martinos Center for Biomedical Imaging + + SPDX-License-Identifier: Apache-2.0 + See STIR/LICENSE.txt for details. + + */ + +/*! + \file + \ingroup buildblock + + \brief Helper functions for PETSIRD + + \author Nikos Efthimiou +*/ + +#ifndef __stir_IO_PETSIRDInfo_helpers_H__ +#define __stir_IO_PETSIRDInfo_helpers_H__ + +#include "stir/CartesianCoordinate3D.h" +#include +#include +#include "stir/format.h" +#include "stir/info.h" +#include + +/*! + \namespace matrix + \brief Lightweight 3x3 matrix and 3D vector helpers used during PETSIRD geometry analysis. + + Provides utilities to: + - transpose a 3x3 matrix, + - subtract two 3x3 matrices, + - extract a rotation axis vector from the skew-symmetric part of a matrix. + + \details + - Mat3: std::array,3> for compact fixed-size storage. + - Vec3: std::array for simple 3D vectors. + - getAxisFromSkew(): + Given S = R - R^T (skew-symmetric part of a rotation matrix R), + returns the axis proportional to: + (S_z,y - S_y,z)/2, (S_x,z - S_z,x)/2, (S_y,x - S_x,y)/2. + For a pure rotation, S encodes the axis direction. + - These helpers assume small numerical noise; thresholds are handled by callers. + - No external dependencies; intended for quick geometric inference (e.g., rotation axis detection). + + \todo + - Might worth it to use stir::Arrays here. I started a bit of this work but lots pending. See the test_petsird_info_helpers.cxx + for some of the work done so far. + - The function are moved in ArrayFunctions +*/ + +namespace matrix +{ + +using Mat3 = std::array, 3>; +using Vec3 = std::array; + +/*! + \brief Transpose a 3x3 matrix. + \param mat Input matrix. + \return Transposed matrix. +*/ +inline Mat3 +transpose(const Mat3& mat) +{ + std::array, 3> result{}; + for (int i = 0; i < 3; ++i) + for (int j = 0; j < 3; ++j) + result[j][i] = mat[i][j]; + return result; +} + +/*! + \brief Subtract two 3x3 matrices (A - B). + \param A Left-hand matrix. + \param B Right-hand matrix. + \return Result of A - B. +*/ +inline Mat3 +subtract(const Mat3& A, const Mat3& B) +{ + std::array, 3> result{}; + for (size_t i = 0; i < 3; ++i) + for (size_t j = 0; j < 3; ++j) + result[i][j] = A[i][j] - B[i][j]; + return result; +} + +/*! + \brief Extract rotation axis from the skew-symmetric matrix S = R - R^T. + \param S Skew-symmetric matrix. + \return Axis vector proportional to the rotation axis. +*/ +inline Vec3 +getAxisFromSkew(const Mat3& S) +{ + return { + 0.5f * (S[2][1] - S[1][2]), // x + 0.5f * (S[0][2] - S[2][0]), // y + 0.5f * (S[1][0] - S[0][1]) // z + }; +} + +} // namespace matrix + +/*! + \namespace vector_utils + \brief Helpers for spacing analysis and axis inference from coordinate sets. + + \details + - \ref vector_utils::get_spacing_uniform computes successive spacings between + sorted unique values and tests if they are uniform within a tolerance. + - \ref vector_utils::getLargestVector returns the largest of three coordinate + sets and logs which axis is inferred as “axial”. +*/ +namespace vector_utils +{ +/*! + \brief Compute spacings between sorted unique values and test uniformity. + \param spacing Output vector. Appends |x[i] - x[i-1]| for i=1..N-1, where x is the sorted version of \a unsorted_block_poss. + \param unsorted_block_poss Set of unique positions (e.g., angles or translations). + \param epsilon Tolerance for uniformity check (default 1e-4). + \return True if all spacings differ from the first spacing by <= \a epsilon, false otherwise. + + \details + - If \a spacing ends up empty (e.g., input size < 2), returns true (trivially uniform). +*/ +inline bool +get_spacing_uniform(std::vector& spacing, const std::set& unsorted_block_poss, double epsilon = 1e-4) +{ + std::vector sorted_z(unsorted_block_poss.begin(), unsorted_block_poss.end()); + for (size_t i = 1; i < sorted_z.size(); ++i) + { + spacing.push_back(std::abs(sorted_z[i] - sorted_z[i - 1])); + } + + return std::all_of(spacing.begin(), spacing.end(), [&](float s) { return std::abs(s - spacing.front()) <= epsilon; }); +} + +/*! + \brief Return the largest of three sets and report inferred axial direction. + \param x Values along X. + \param y Values along Y. + \param z Values along Z. + \return Const reference to the largest set among \a x, \a y, \a z. + + \details + Logs the index of the inferred axial direction: 0 (x), 1 (y), or 2 (z). +*/ +const std::set& +get_largest_vector(const std::set& x, const std::set& y, const std::set& z) +{ + const std::set* largest = &x; + int axis = 0; + if (y.size() > largest->size()) + { + largest = &y; + axis = 1; + } + else if (z.size() > largest->size()) + { + largest = &z; + axis = 2; + } + + // stir::info(fmt::format("I believe the axial direction is the {}.", axis)); + return *largest; +} + +/*! + \brief Collect unique values from a 1D vector. + \param values Output set for unique values. + \param input Input vector. +*/ +void +find_unique_values_1D(std::set& values, const std::vector& input) +{ + for (float val : input) + { + // std::cout << val << std::endl; + values.insert(val); + } +} + +/*! + \brief Collect unique values from a 2D vector (matrix). + \param values Output set for unique values. + \param input Input 2D vector [rows][cols]. +*/ +void +find_unique_values_2D(std::set& values, const std::vector>& input) +{ + for (size_t row = 0; row < input.size(); ++row) + for (size_t col = 0; col < input[row].size(); ++col) + values.insert(input[row][col]); +} + +} // namespace vector_utils + +bool +almostEqual(double a, double b, double tol = 1e-6) +{ + return std::fabs(a - b) <= tol; +} + +// Detect groupSize along dim2 (y) and loops along dim3 (z) +// Returns true on success, false if pattern doesn't match the assumed structure. +bool +inferGroupSizes_dim2_dim3(const std::vector>& pts, + std::size_t& groupSize_dim2, + std::size_t& groupSize_dim3, + float tol = 1e-5f) +{ + const std::size_t n = pts.size(); + groupSize_dim2 = groupSize_dim3 = 0; + + if (n == 0) + return false; + + if (n == 1) + { + groupSize_dim2 = 1; + groupSize_dim3 = 1; + return true; + } + + // STIR CartesianCoordinate3D is (z, y, x) + const float x0 = pts[0].x(); + const float z0 = pts[0].z(); + + // 1) Find how many initial points keep x and z the same + std::size_t runLen = 1; + while (runLen < n && almostEqual(pts[runLen].x(), x0, tol) && almostEqual(pts[runLen].z(), z0, tol)) + { + ++runLen; + } + + groupSize_dim2 = runLen; + + // Must tile the full array + if (groupSize_dim2 == 0 || n % groupSize_dim2 != 0) + return false; + + groupSize_dim3 = n / groupSize_dim2; + + // 2) Check each block of size groupSize_dim2 has constant x,z + for (std::size_t b = 0; b < groupSize_dim3; ++b) + { + std::size_t start = b * groupSize_dim2; + float xb = pts[start].x(); + float zb = pts[start].z(); + + for (std::size_t i = 1; i < groupSize_dim2; ++i) + { + const auto& p = pts[start + i]; + if (!almostEqual(p.x(), xb, tol) || !almostEqual(p.z(), zb, tol)) + { + return false; // pattern breaks inside a block + } + } + } + + // 3) Optionally check that z actually changes between blocks + for (std::size_t b = 1; b < groupSize_dim3; ++b) + { + float z_prev = pts[(b - 1) * groupSize_dim2].z(); + float z_curr = pts[b * groupSize_dim2].z(); + if (almostEqual(z_prev, z_curr, tol)) + { + return false; // outer loop didn't move in z + } + } + + return true; +} + +#endif \ No newline at end of file diff --git a/src/include/stir/listmode/CListModeDataBasedOnCoordinateMap.h b/src/include/stir/listmode/CListModeDataBasedOnCoordinateMap.h index babffd05db..1e9497fa42 100644 --- a/src/include/stir/listmode/CListModeDataBasedOnCoordinateMap.h +++ b/src/include/stir/listmode/CListModeDataBasedOnCoordinateMap.h @@ -1,22 +1,9 @@ -/* CListModeDataSAFIR.h - - Coincidence LM Data Class for SAFIR: Header File - Jannis Fischer - - Copyright 2015 ETH Zurich, Institute of Particle Physics +/* + Copyright 2026, University Medical Center Groningen Copyright 2020 Positrigo AG, Zurich - 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 */ @@ -24,8 +11,9 @@ \file \ingroup listmode - \brief Declaration of class stir::CListModeDataSAFIR + \brief Declaration of class stir::CListModeDataBasedOnCoordinateMap + \author Nikos Efthimiou \author Jannis Fischer */ @@ -36,14 +24,10 @@ #include #include "stir/listmode/CListModeData.h" -#include "stir/ProjData.h" -#include "stir/ProjDataInfo.h" -#include "stir/listmode/CListRecord.h" -#include "stir/IO/InputStreamWithRecords.h" +#include "stir/DetectorCoordinateMap.h" #include "stir/shared_ptr.h" // #include "stir/listmode/CListRecordSAFIR.h" -#include "stir/DetectorCoordinateMap.h" START_NAMESPACE_STIR diff --git a/src/include/stir/listmode/CListModeDataPETSIRD.h b/src/include/stir/listmode/CListModeDataPETSIRD.h index 0783243db4..ab0bf8df03 100644 --- a/src/include/stir/listmode/CListModeDataPETSIRD.h +++ b/src/include/stir/listmode/CListModeDataPETSIRD.h @@ -1,119 +1,151 @@ -/* CListModeDataPETSIRD.h - -Coincidence LM Data Class for PETSIRD +/* + Copyright 2025, 2026, University Medical Center Groningen Copyright 2025, MGH / HST A. Martinos Center for Biomedical Imaging - 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. */ -/*! - -\file -\ingroup listmode -\brief Declaration of class stir::CListModeDataPETSIRD - -\author Daniel Deidda -\author Nikos Efthimiou -*/ - #ifndef __stir_listmode_CListModeDataPETSIRD_H__ #define __stir_listmode_CListModeDataPETSIRD_H__ -#include #include "stir/listmode/CListModeDataBasedOnCoordinateMap.h" -#include "stir/ProjData.h" #include "stir/listmode/CListRecord.h" -#include "stir/shared_ptr.h" - -#include "../../PETSIRD/cpp/generated/protocols.h" +#include "stir/listmode/CListRecordPETSIRD.h" +#include "stir/Succeeded.h" +#include "petsird_helpers.h" +#include "stir/PETSIRDInfo.h" +#include "petsird/binary/protocols.h" +#include "petsird/hdf5/protocols.h" START_NAMESPACE_STIR /*! - \brief Class for reading PETSIRD listmode data with variable geometry + \class CListModeDataPETSIRD + \brief Reader for PETSIRD listmode data supporting variable geometry. \ingroup listmode + \author Nikos Efthimiou + + \par Overview + - Supports HDF5 and binary PETSIRD formats. + - Infers scanner geometry: + - Cylindrical → creates cylindrical scanner. + - Block-based → creates block-based scanner. + - Otherwise → creates generic scanner using crystal positions. + - Builds a DetectorCoordinateMap when needed and stores to disk. \par - By providing crystal map and template projection data files, the coordinates are read from files and used defining the LOR - coordinates. -*/ + Infering the scanner geometry makes a lot of assumptions about what PET is. + In particular, it assumes: + \li A PET scanner is made of rings of detectors + \li The largest axis is the axial one. + \li So far we support only a single layer. This is partly hard-coded for simplicity. (look in the code for relevant TODOs and + comments.) + \li Some of the hardcoded assumptions are in CListRecordPETSIRD as well. + + \note Exact PETSIRD format specification is defined in the PETSIRD project documentation. + \note Initially, I wanted to: + - Is close to a cylindrical geometry ? + - then yes use a cylindrical scanner that is simpler. + - Else, is it made of blocks arranged on a cylinder. + + However, now I do the following: + - Is close to cylindrical geometry? + - yes use cylindrical scanner + - Check if blocks-on-cylinder configuration, are a good match. + - yes use blocks-on-cylinder scanner + - else use generic scanner and export the map to the disk. + + If listmode reconstruction is done, the map is regenerated on-the-fly. + +*/ class CListModeDataPETSIRD : public CListModeDataBasedOnCoordinateMap { +private: + //! Snapshot of the current PETSIRD list-mode reader position. + /*! + Stores enough state to resume reading from a previously saved position + in the PETSIRD stream. This includes the current prompt/delayed stream, + the event index within the currently cached event block, the time-block + index, and optionally cached block contents. + */ + struct PetsirdCursor + { + //! Whether the cursor points to the prompt-event stream. + /*! + True for prompt events, false for delayed events. + */ + bool is_prompt = true; + //! Index of the next event within the cached event block. + std::size_t event_in_block = 0; + //! Index of the time block associated with this cursor. + std::size_t time_block_index = 0; + //! Cached PETSIRD time block at this cursor position. + petsird::TimeBlock time_block; + //! Cached PETSIRD event-time block at this cursor position. + petsird::EventTimeBlock event_block; + //! Whether time_block and event_block contain valid cached data. + bool has_cached_blocks = false; + }; + public: + /*! + \brief Construct reader. + \param listmode_filename Path to PETSIRD listmode file. + \param use_hdf5 If true, use HDF5 reader; otherwise use binary reader. + */ CListModeDataPETSIRD(const std::string& listmode_filename, bool use_hdf5); virtual shared_ptr get_empty_record_sptr() const override; Succeeded get_next_record(CListRecord& record_of_general_type) const override; - SavedPosition save_get_position() override { return static_cast(curr_event_in_event_block); } + SavedPosition save_get_position() override; + + Succeeded reopen_and_prime(); - Succeeded set_get_position(const SavedPosition& pos) override { return Succeeded::yes; } + Succeeded seek_to_event_block_index(std::size_t target_event_block_index) const; + + Succeeded set_get_position(const SavedPosition& pos) override; virtual bool has_delayeds() const override { return m_has_delayeds; } - Succeeded reset() override { return Succeeded::yes; } + Succeeded reset() override; protected: virtual Succeeded open_lm_file() const override; - mutable shared_ptr current_lm_data_ptr; + shared_ptr current_lm_data_ptr; private: + //! Whether to use the HDF5-based PETSIRD reader. const bool use_hdf5; - + //! Index of the current event within the currently loaded event block. mutable unsigned long int curr_event_in_event_block = 0; - + //! Currently loaded PETSIRD time block. mutable petsird::TimeBlock curr_time_block; - - int numberOfModules; - - int numberOfElementsIndices; - + //! Currently loaded PETSIRD event-time block. mutable petsird::EventTimeBlock curr_event_block; - - petsird::TypeOfModulePair type_of_module_pair{ 0, 0 }; - - shared_ptr this_scanner_sptr; - + //! Prompt/delayed classification of the current event. + //! True if the current event is a prompt event, false if it is delayed. mutable bool curr_is_prompt = true; - + //! Whether the PETSIRD data contains delayed events. mutable bool m_has_delayeds; - - bool isCylindricalConfiguration(const petsird::ScannerInformation& scanner_info, - const std::vector& replicated_module_list); - - void find_uniqe_values_1D(std::set& values, const std::vector& input); - - void find_uniqe_values_2D(std::set& values, const std::vector>& input); - - int figure_out_scanner_blocks_and_rotation_axis(std::set& unique_dim1_values, - std::set& unique_dim2_values, - std::set& unique_dim3_values, - const std::vector& replicated_module_list); - void figure_out_block_element_transformations(std::set& unique_dim1_values, - std::set& unique_dim2_values, - std::set& unique_dim3_values, - float& radius, - int& radius_index, - const int rotation_axis, - const std::vector& replicated_module_list); - - void figure_out_block_angles(std::set& unique_angle_modules, - const int rot_axis, - const std::vector& replicated_module_list); + //! Shared PETSIRD scanner and acquisition information. + shared_ptr petsird_info_sptr; + //! Cursor used to restore the most recently saved reader position + mutable PetsirdCursor m_saved_cursor; + //! Index of the current time block in the PETSIRD stream + mutable std::size_t m_time_block_index = 0; + //! Saved reader positions indexed by SavedPosition handles. + /*! + Each entry stores a cursor state that can later be restored, allowing + random access or rollback to previously saved positions in the PETSIRD + list-mode stream. + */ + mutable std::vector m_saved_positions; }; END_NAMESPACE_STIR diff --git a/src/include/stir/listmode/CListModeDataSAFIR.h b/src/include/stir/listmode/CListModeDataSAFIR.h index d8d400daf7..be6d8e8493 100644 --- a/src/include/stir/listmode/CListModeDataSAFIR.h +++ b/src/include/stir/listmode/CListModeDataSAFIR.h @@ -1,23 +1,10 @@ -/* CListModeDataSAFIR.h - -Coincidence LM Data Class for SAFIR: Header File -Jannis Fischer +/* Copyright 2015 ETH Zurich, Institute of Particle Physics Copyright 2020 Positrigo AG, Zurich - 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 detail. */ /*! @@ -54,6 +41,11 @@ template class CListModeDataSAFIR : public CListModeDataBasedOnCoordinateMap { public: + /*! Constructor + \par + Takes as arguments the filenames of the coicidence listmode file, the crystal map (text) file, and the template projection data + file + */ CListModeDataSAFIR(const std::string& listmode_filename, const std::string& crystal_map_filename, const std::string& template_proj_data_filename, @@ -61,8 +53,8 @@ class CListModeDataSAFIR : public CListModeDataBasedOnCoordinateMap CListModeDataSAFIR(const std::string& listmode_filename, const shared_ptr& proj_data_info_sptr); - shared_ptr get_empty_record_sptr() const override; - Succeeded get_next_record(CListRecordT& record_of_general_type) const override; + shared_ptr get_empty_record_sptr() const override; + Succeeded get_next_record(CListRecord& record_of_general_type) const override; bool has_delayeds() const override { return false; } diff --git a/src/include/stir/listmode/CListRecordPETSIRD.h b/src/include/stir/listmode/CListRecordPETSIRD.h index 1389db2e15..36314a5bb2 100644 --- a/src/include/stir/listmode/CListRecordPETSIRD.h +++ b/src/include/stir/listmode/CListRecordPETSIRD.h @@ -1,106 +1,80 @@ -/* CListRecordPETSIRD.h +/* -Coincidence Event Class for PETSIRD: Header File - - Copyright 2015 ETH Zurich, Institute of Particle Physics - Copyright 2017 ETH Zurich, Institute of Particle Physics and Astrophysics - Copyright 2020, 2022 Positrigo AG, Zurich + Copyright 2025, 2026 University Medical Center Groningen Copyright 2025 National Physical Laboratory - 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. */ /*! -\file +\file CListRecordPETSIRD \ingroup listmode \brief Declaration of class stir::CListEventPETSIRD and stir::CListRecordPETSIRD with supporting classes -\author Jannis Fischer -\author Parisa Khateri -\author Markus Jehl +\author Nikos Efthimiou \author Daniel Deidda */ #ifndef __stir_listmode_CListRecordPETSIRD_H__ #define __stir_listmode_CListRecordPETSIRD_H__ +#include "stir/listmode/CListEventScannerWithDiscreteDetectors.h" #include "stir/listmode/CListRecord.h" #include "stir/DetectionPositionPair.h" #include "stir/Succeeded.h" #include "stir/ByteOrderDefine.h" -#include "boost/cstdint.hpp" - #include "stir/DetectorCoordinateMap.h" -#include "types.h" - -// #include "../../PETSIRD/cpp/generated/types.h" +#include "stir/PETSIRDInfo.h" START_NAMESPACE_STIR -/*! -Provides interface of the record class to STIR by implementing get_LOR(). It uses an optional map from detector indices to -coordinates to specify LORAs2Points from given detection pair indices. - -\ingroup listmode -*/ - -class CListEventPETSIRD : public CListEvent +template +class CListEventPETSIRD : public CListEventScannerWithDiscreteDetectors { public: - inline CListEventPETSIRD() {} + inline CListEventPETSIRD(shared_ptr proj_data_info_sptr, + DetectionPositionPair<>* det_pos_pair, + bool* is_prompt) + : CListEventScannerWithDiscreteDetectors(proj_data_info_sptr), + det_pos_pair_ptr(det_pos_pair), + is_prompt_ptr(is_prompt) + {} - //! Returns LOR corresponding to the given event. - inline LORAs2Points get_LOR() const override; + // inline void get_bin(Bin& bin, const ProjDataInfo& proj_data_info) const override; - //! Override the default implementation - inline void get_bin(Bin& bin, const ProjDataInfo& proj_data_info) const override; + inline bool is_prompt() const override { return *this->is_prompt_ptr; } - inline void set_map_sptr(shared_ptr new_map_sptr) { map_sptr = new_map_sptr; } - /*! Set the scanner */ - /*! Currently only used if the map is not set. */ - inline void set_scanner_sptr(shared_ptr new_scanner_sptr) { scanner_sptr = new_scanner_sptr; } - - virtual bool is_valid_template(const ProjDataInfo&) const override { return true; } + bool operator==(const CListEventPETSIRD& other) const + { + if (this == &other) + return true; - virtual bool is_prompt() const override { return _prompt; } + return is_prompt() == other.is_prompt() && get_detection_position() == other.get_detection_position(); + } - virtual Succeeded set_prompt(const bool prompt) override + inline Succeeded set_prompt(const bool prompt) override { - _prompt = prompt; + *this->is_prompt_ptr = prompt; return Succeeded::yes; } - void set_PETSIRD_ranges(int _numberOfModules, int _numberOfElementsIndices) + virtual void get_detection_position(DetectionPositionPair<>& det_pos_pair) const override { - numberOfModules = _numberOfModules; - numberOfElementsIndices = _numberOfElementsIndices; + det_pos_pair = *this->det_pos_pair_ptr; } - int numberOfModules; - - int numberOfElementsIndices; - - std::pair det_0, det_1; + virtual void set_detection_position(const DetectionPositionPair<>& det_pos_pair) override + { + *this->det_pos_pair_ptr = det_pos_pair; + } private: - shared_ptr map_sptr; - shared_ptr scanner_sptr; - bool _prompt; - - const DetectorCoordinateMap& map_to_use() const { return map_sptr ? *map_sptr : *this->scanner_sptr->get_detector_map_sptr(); } + DetectionPositionPair<>* det_pos_pair_ptr = nullptr; + bool* is_prompt_ptr = nullptr; }; class CListTimePETSIRD : public ListTime @@ -112,6 +86,7 @@ class CListTimePETSIRD : public ListTime time = time_in_millisecs; return Succeeded::yes; } + bool operator==(const CListTimePETSIRD& other) const { return time == other.time; } inline bool is_time() const { return true; } uint32_t time; }; @@ -119,53 +94,71 @@ class CListTimePETSIRD : public ListTime class CListRecordPETSIRD : public CListRecord { public: - CListRecordPETSIRD() {} - - // ~CListRecordPETSIRD() override {} + CListRecordPETSIRD(shared_ptr petsird_info_sptr, shared_ptr proj_data_info_sptr) + : event_data(make_event_data(proj_data_info_sptr, this->det_pos_pair, this->is_prompt_event)), + petsird_info_sptr(std::move(petsird_info_sptr)), + proj_data_info_sptr(std::move(proj_data_info_sptr)) + {} bool is_time() const override { return true; /*time_data.is_time();*/ } bool is_event() const override { return true; } - CListEventPETSIRD& event() override { return event_data; } - const CListEventPETSIRD& event() const override { /*return event_data;*/ } + CListEvent& event() override { return *event_data; } + const CListEvent& event() const override { return *event_data; } CListTimePETSIRD& time() override { return time_data; } const CListTimePETSIRD& time() const override { return time_data; } - bool operator==(const CListRecordPETSIRD& e2) const - { - // return dynamic_cast(&e2) != 0 && raw == static_cast(e2).r; - } + bool operator==(const CListRecordPETSIRD& e2) const { return event_data == e2.event_data && time_data == e2.time_data; } - virtual Succeeded init_from_data(const petsird::CoincidenceEvent& data, bool is_prompt = true) + Succeeded init_from_data(petsird::CoincidenceEvent& event, const bool is_prompt = true) { - auto decodeElementAndModuleIndex - = [](int linearIndex, int energyIndex, int numberOfElementsIndices, int numberOfModules) -> std::pair { - int reduced = (linearIndex - energyIndex) / numberOfModules; - int moduleIndex = reduced / numberOfElementsIndices; - int elementIndex = reduced % numberOfElementsIndices; - return { elementIndex, moduleIndex }; - }; - - event_data.det_0 - = decodeElementAndModuleIndex(data.detection_bins[0], 0, event_data.numberOfElementsIndices, event_data.numberOfModules); - event_data.det_1 - = decodeElementAndModuleIndex(data.detection_bins[1], 0, event_data.numberOfElementsIndices, event_data.numberOfModules); - - std::cout << event_data.det_0.first << " " << event_data.det_0.second << std::endl; - std::cout << event_data.det_1.first << " " << event_data.det_1.second << std::endl; - event_data.set_prompt(is_prompt); + const auto scanner_info_sptr = petsird_info_sptr->get_petsird_scanner_info_sptr(); + + const auto exp_det_0 + = petsird_helpers::expand_detection_bin(*scanner_info_sptr, + 0, // TODO type_of_module, currently we only support single module types. + event.detection_bins[0]); + const auto exp_det_1 + = petsird_helpers::expand_detection_bin(*scanner_info_sptr, + 0, // TODO type_of_module, currently we only support single module types. + event.detection_bins[1]); + auto it0 = petsird_info_sptr->get_petsird_to_stir_map()->find(exp_det_1); + auto it1 = petsird_info_sptr->get_petsird_to_stir_map()->find(exp_det_0); + if (it0 == petsird_info_sptr->get_petsird_to_stir_map()->end() || it1 == petsird_info_sptr->get_petsird_to_stir_map()->end()) + { + error("get_stir_det_pos_from_PETSIRD_id: one or both PETSIRD ids not found", + exp_det_0.module_index, + exp_det_0.element_index, + exp_det_0.energy_index); + } + + // Warning: this assumes that the PETSIRD TOF bins and the STIR ProjDataInfo + // timing positions have the same binning/mashing and number of TOF bins. + // If the STIR proj_data_info uses a different TOF mashing factor or TOF range, + // this simple offset conversion is not valid. + this->det_pos_pair = DetectionPositionPair<>( + it0->second, it1->second, static_cast(event.tof_idx) + this->proj_data_info_sptr->get_min_tof_pos_num()); + + is_prompt_event = is_prompt; return Succeeded::yes; } private: - CListEventPETSIRD event_data; + static std::unique_ptr + make_event_data(shared_ptr proj_data_info, DetectionPositionPair<>& det_pos_pair, bool& is_prompt_event); + + std::unique_ptr event_data; CListTimePETSIRD time_data; + + shared_ptr petsird_info_sptr; + shared_ptr proj_data_info_sptr; + + bool is_prompt_event = true; + DetectionPositionPair<> det_pos_pair; }; END_NAMESPACE_STIR -#include "CListRecordPETSIRD.inl" - #endif diff --git a/src/include/stir/listmode/CListRecordPETSIRD.inl b/src/include/stir/listmode/CListRecordPETSIRD.inl deleted file mode 100644 index 093297db10..0000000000 --- a/src/include/stir/listmode/CListRecordPETSIRD.inl +++ /dev/null @@ -1,73 +0,0 @@ -/* CListRecordPETSIRD.inl - - Coincidence Event Class for PETSIRD: Inline File - - Copyright 2015 ETH Zurich, Institute of Particle Physics - Copyright 2017 ETH Zurich, Institute of Particle Physics and Astrophysics - Copyright 2020, 2022 Positrigo AG, Zurich - Copyright 2021 University College London - Copyright 2025 National Physical Laboratory - - 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. - - */ -/*! - - \file - \ingroup listmode - \brief Inline implementation of class stir::CListEventPETSIRD and stir::CListRecordPETSIRD with supporting classes - - \author Jannis Fischer - \author Parisa Khateri - \author Markus Jehl - \author Kris Thielemans - \author Daniel Deidda -*/ - -#include "stir/LORCoordinates.h" -#include "stir/listmode/CListRecord.h" -#include "stir/ProjDataInfo.h" -#include "stir/Bin.h" -#include "stir/LORCoordinates.h" -#include "stir/Succeeded.h" - -#include "stir/ProjDataInfoCylindricalNoArcCorr.h" -#include "stir/ProjDataInfoBlocksOnCylindricalNoArcCorr.h" -#include "stir/ProjDataInfoGenericNoArcCorr.h" -#include "stir/CartesianCoordinate3D.h" -#include "stir/error.h" - -START_NAMESPACE_STIR - -LORAs2Points -CListEventPETSIRD::get_LOR() const -{ - LORAs2Points lor; - DetectionPositionPair<> det_pos_pair; - - // this->get_data().get_detection_position_pair(det_pos_pair); - - lor.p1() = map_to_use().get_coordinate_for_index(det_pos_pair.pos1()); - lor.p2() = map_to_use().get_coordinate_for_index(det_pos_pair.pos2()); - - return lor; -} - -void -CListEventPETSIRD::get_bin(Bin& bin, const ProjDataInfo& proj_data_info) const -{ - - int nikos = 0; -} - -END_NAMESPACE_STIR diff --git a/src/include/stir/recon_buildblock/BinNormalisationFromPETSIRD.h b/src/include/stir/recon_buildblock/BinNormalisationFromPETSIRD.h new file mode 100644 index 0000000000..43adb84dd6 --- /dev/null +++ b/src/include/stir/recon_buildblock/BinNormalisationFromPETSIRD.h @@ -0,0 +1,82 @@ +// +// +/*! + \file + \ingroup normalisation + + \brief Declaration of class stir::BinNormalisationFromPETSIRD + + \author Nikos Efthimiou +*/ +/* + Copyright (C) 2025, 2026, University Medical Center Groningen + This file is part of STIR. + + SPDX-License-Identifier: Apache-2.0 + + See STIR/LICENSE.txt for details +*/ + +#ifndef __stir_recon_buildblock_BinNormalisationFromPETSIRD_H__ +#define __stir_recon_buildblock_BinNormalisationFromPETSIRD_H__ + +#include "stir/recon_buildblock/BinNormalisation.h" +#include "stir/recon_buildblock/BinNormalisationWithCalibration.h" +#include "stir/RegisteredParsingObject.h" +#include "stir/shared_ptr.h" +#include "stir/PETSIRDInfo.h" + +using std::string; + +START_NAMESPACE_STIR + +class BinNormalisationFromPETSIRD + : public RegisteredParsingObject +{ +private: + using base_type = BinNormalisationWithCalibration; + +public: + //! Name which will be used when parsing a BinNormalisation object + static const char* const registered_name; + + BinNormalisationFromPETSIRD(); + + BinNormalisationFromPETSIRD(const std::string& filename); + + Succeeded set_up(const shared_ptr& exam_info_sptr, const shared_ptr&) override; + + float get_uncalibrated_bin_efficiency(const Bin& bin) const override; + + inline bool with_detector_efficiencies() const { return m_with_detector_efficiencies; } + inline bool with_dead_time() const { return m_with_dead_time; } + inline bool with_geometric_factors() const { return m_with_geometric_factors; } + +private: + void set_defaults() override; + + void initialise_keymap() override; + + bool post_processing() override; + + void read_norm_data(const string& filename); + + string normalisation_filename; + + //! Flag to enable/disable detector efficiency + bool m_with_detector_efficiencies; + //! Flag to enable/disable dead time correction + bool m_with_dead_time; + //! Flag to enable/disable geometric factors + bool m_with_geometric_factors; + // shared_ptr petsird_info_sptr; + shared_ptr petsird_data_sptr; + + shared_ptr scanner_info_sptr; + + shared_ptr petsird_info_sptr; +}; + +END_NAMESPACE_STIR + +#endif diff --git a/src/listmode_buildblock/CListModeDataBasedOnCoordinateMap.cxx b/src/listmode_buildblock/CListModeDataBasedOnCoordinateMap.cxx index fda23351d5..3b83ad2a05 100644 --- a/src/listmode_buildblock/CListModeDataBasedOnCoordinateMap.cxx +++ b/src/listmode_buildblock/CListModeDataBasedOnCoordinateMap.cxx @@ -1,44 +1,14 @@ -/* CListModeDataSAFIR.cxx - -Coincidence LM Data Class for SAFIR: Implementation - - Copyright 2015 ETH Zurich, Institute of Particle Physics - Copyright 2020 Positrigo AG, Zurich +/* + Copyright 2015 ETH Zurich, Institute of Particle Physics + Copyright 2020 Positrigo AG, Zurich Copyright 2021 University College London - 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 detail.. */ -/*! - - \file - \ingroup listmode - \brief implementation of class stir::CListModeDataSAFIR - - \author Jannis Fischer - \author Kris Thielemans - \author Markus Jehl -*/ -#include -#include -#include "stir/Succeeded.h" #include "stir/listmode/CListModeDataBasedOnCoordinateMap.h" -using std::ios; -using std::fstream; -using std::ifstream; -using std::istream; - START_NAMESPACE_STIR; std::string diff --git a/src/listmode_buildblock/CListModeDataPETSIRD.cxx b/src/listmode_buildblock/CListModeDataPETSIRD.cxx index e72b33928e..ebab07722b 100644 --- a/src/listmode_buildblock/CListModeDataPETSIRD.cxx +++ b/src/listmode_buildblock/CListModeDataPETSIRD.cxx @@ -1,420 +1,26 @@ -/* CListModeDataPETSIRD.cxx - -Coincidence LM Data Class for PETSIRD: Implementation - +/* + Copyright 2025, University Medical Center Groningen Copyright 2025, MGH / HST A. Martinos Center for Biomedical Imaging - 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. -*/ -/*! - -\file -\ingroup listmode -\brief implementation of class stir::CListModeDataPETSIRD - -\author Daniel Deidda -\author Nikos Efthimiou + SPDX-License-Identifier: Apache-2.0 + See STIR/LICENSE.txt for detail */ -#include "stir/Succeeded.h" +#include "stir/format.h" #include "stir/info.h" #include "stir/error.h" -#include "../../PETSIRD/cpp/helpers/include/petsird_helpers.h" -#include "helpers/include/petsird_helpers/create.h" -#include "helpers/include/petsird_helpers/geometry.h" - -#include "../../PETSIRD/cpp/generated/binary/protocols.h" -#include "../../PETSIRD/cpp/generated/hdf5/protocols.h" - #include "stir/listmode/CListModeDataPETSIRD.h" -#include "stir/listmode/CListRecordPETSIRD.h" -#include START_NAMESPACE_STIR +/*! + \file + \ingroup listmode + \brief implementation of class stir::CListModeDataPETSIRD -namespace matrix -{ - -using Mat3 = std::array, 3>; -using Vec3 = std::array; - -inline Mat3 -transpose(const Mat3& mat) -{ - std::array, 3> result{}; - for (int i = 0; i < 3; ++i) - for (int j = 0; j < 3; ++j) - result[j][i] = mat[i][j]; - return result; -} - -inline Mat3 -subtract(const Mat3& A, const Mat3& B) -{ - std::array, 3> result{}; - for (size_t i = 0; i < 3; ++i) - for (size_t j = 0; j < 3; ++j) - result[i][j] = A[i][j] - B[i][j]; - return result; -} - -inline Vec3 -getAxisFromSkew(const Mat3& S) -{ - return { - 0.5f * (S[2][1] - S[1][2]), // x - 0.5f * (S[0][2] - S[2][0]), // y - 0.5f * (S[1][0] - S[0][1]) // z - }; -} - -} // namespace matrix - -inline bool -get_spacing_uniform(std::vector& spacing, const std::set& unsorted_block_poss, double epsilon = 1e-4) -{ - std::vector sorted_z(unsorted_block_poss.begin(), unsorted_block_poss.end()); - for (size_t i = 1; i < sorted_z.size(); ++i) - { - spacing.push_back(std::abs(sorted_z[i] - sorted_z[i - 1])); - } - - return std::all_of(spacing.begin(), spacing.end(), [&](float s) { return std::abs(s - spacing.front()) <= epsilon; }); -} - -const std::set& -getLargestVector(const std::set& x, const std::set& y, const std::set& z) -{ - const std::set* largest = &x; - int axis = 0; - if (y.size() > largest->size()) - { - largest = &y; - axis = 1; - } - else if (z.size() > largest->size()) - { - largest = &z; - axis = 2; - } - - info(format("I believe the axial direction is the {}.", axis)); - return *largest; -} - -void -CListModeDataPETSIRD::find_uniqe_values_1D(std::set& values, const std::vector& input) -{ - for (float val : input) - { - // std::cout << val << std::endl; - values.insert(val); - } -} - -void -CListModeDataPETSIRD::find_uniqe_values_2D(std::set& values, const std::vector>& input) -{ - for (size_t row = 0; row < input.size(); ++row) - for (size_t col = 0; col < input[row].size(); ++col) - values.insert(input[row][col]); -} - -int -CListModeDataPETSIRD::figure_out_scanner_blocks_and_rotation_axis( - std::set& unique_dim1_values, - std::set& unique_dim2_values, - std::set& unique_dim3_values, - const std::vector& replicated_module_list) -{ - auto insertTranslations = [&](const petsird::RigidTransformation& trans) { - unique_dim1_values.insert(trans.matrix.at(0, 3)); - unique_dim2_values.insert(trans.matrix.at(1, 3)); - unique_dim3_values.insert(trans.matrix.at(2, 3)); - }; - - auto extractRotationMatrix = [](const petsird::RigidTransformation& trans) -> matrix::Mat3 { - matrix::Mat3 R; - for (int i = 0; i < 3; ++i) - for (int j = 0; j < 3; ++j) - R[i][j] = trans.matrix.at(i, j); - return R; - // skew = matrix::subtract(R, matrix::transpose(R)); - // auto rot = matrix::getAxisFromSkew(skew); - }; - - std::array, 3> skew; - int detected_axis = -1; - - for (const auto& module : replicated_module_list) - for (const auto& mod_trans : module.transforms) - { - insertTranslations(mod_trans); - matrix::Mat3 R = extractRotationMatrix(mod_trans); - skew = matrix::subtract(R, matrix::transpose(R)); - auto axis_vec = matrix::getAxisFromSkew(skew); - - int current_axis = -1; - for (int i = 0; i < 3; ++i) - { - if (std::abs(axis_vec[i]) > 1e-6f) - { - if (current_axis != -1) - { - warning("Rotation involves multiple axis components. Possibly non-pure rotation."); - current_axis = -2; // Sentinel for mixed axes - return -1; - } - current_axis = i; - } - } - - if (current_axis >= 0) - { - if (detected_axis == -1) - detected_axis = current_axis; - else if (detected_axis != current_axis) - warning("Inconsistent rotation axis detected between modules."); - } - } - info(format("Rotation axis of blocks inferred as axis index {}", detected_axis)); - return detected_axis; -} - -void -CListModeDataPETSIRD::figure_out_block_element_transformations( - std::set& unique_dim1_values, - std::set& unique_dim2_values, - std::set& unique_dim3_values, - float& radius, - int& radius_index, - const int rotation_axis, - const std::vector& replicated_module_list) -{ - - auto insert_translation = [&](const petsird::RigidTransformation& trans) { - unique_dim1_values.insert(trans.matrix.at(0, 3)); - unique_dim2_values.insert(trans.matrix.at(1, 3)); - unique_dim3_values.insert(trans.matrix.at(2, 3)); - }; - - auto detect_radius = [&](const petsird::RigidTransformation& trans) -> bool { - for (int i = 0; i < 3; ++i) - { - if (i == rotation_axis) - continue; - float candidate = trans.matrix.at(i, 3); - if (candidate > 0.0f) - { - radius = candidate; - radius_index = i; - return true; - } - } - return false; - }; - - for (const auto& module : replicated_module_list) - { - for (const auto& el_trans : module.object.detecting_elements.transforms) - { - if (radius == 0.0f) - { - if (!detect_radius(el_trans)) - { - error("Unable to determine radius from translation components."); - continue; - } - } - else - { - float current = el_trans.matrix.at(radius_index, 3); - if (std::abs(current - radius) > 1e-4f) - warning("Mixed radii detected. Consider checking for misaligned modules."); - } - - insert_translation(el_trans); - // std::cout << el_trans.matrix << std::endl; - } - } -} - -void -CListModeDataPETSIRD::figure_out_block_angles(std::set& unique_angle_modules, - const int rot_axis, - const std::vector& replicated_module_list) -{ - for (const auto& module : replicated_module_list) - for (const auto& transform : module.transforms) - { - if (rot_axis == 0) - unique_angle_modules.insert( - std::fabs(int(1000.F * std::atan2(transform.matrix.at(1, 0), transform.matrix.at(2, 0))) / 1000.F)); - else if (rot_axis == 1) - unique_angle_modules.insert( - std::fabs(int(1000.F * std::atan2(transform.matrix.at(2, 0), transform.matrix.at(0, 0))) / 1000.F)); - else if (rot_axis == 2) - unique_angle_modules.insert( - std::fabs(int(1000.F * std::atan2(transform.matrix.at(1, 0), transform.matrix.at(0, 0))) / 1000.F)); - } -} - -bool -CListModeDataPETSIRD::isCylindricalConfiguration(const petsird::ScannerInformation& scanner_info, - const std::vector& replicated_module_list) -{ - // Determine DOI based on material - float average_doi = 0.0; - if (scanner_info.bulk_materials.size() > 0) - { - const std::string& material = scanner_info.bulk_materials[0].name; - if (material.size() > 0) - average_doi = (material == "BGO") ? 5.0f : (material == "LSO" || material == "LYSO") ? 7.0f : 0.0f; - } - - const petsird::TypeOfModule type_of_module = replicated_module_list.size(); - - if (type_of_module > 1) - { - info("Multiple types of PETSIRD modules are not supported. Abord."); - return false; - } - - const auto& tof_bin_edges = scanner_info.tof_bin_edges[type_of_module - 1][type_of_module - 1]; - std::cout << "Num. of TOF bins " << tof_bin_edges.NumberOfBins() << std::endl; - - std::set unique_tof_values; - find_uniqe_values_2D(unique_tof_values, scanner_info.tof_resolution); - - numberOfModules = replicated_module_list[0].NumberOfObjects(); - numberOfElementsIndices = replicated_module_list[0].object.detecting_elements.NumberOfObjects(); - std::set unique_dim1_values, unique_dim2_values, unique_dim3_values; - std::set unique_tof_resolutions; - - const int rotation_axis = figure_out_scanner_blocks_and_rotation_axis( - unique_dim1_values, unique_dim2_values, unique_dim3_values, replicated_module_list); - if (rotation_axis == -1) - return false; - - const std::set& main_axis = getLargestVector(unique_dim1_values, unique_dim2_values, unique_dim3_values); - - int num_transaxial_blocks = numberOfModules / main_axis.size(); - info(format("I deduce that the scanner has {} transaxial number of blocks", num_transaxial_blocks)); - - float radius = 0; - int radius_indx = -1; - - std::set unique_elements_dim1_values, unique_elements_dim2_values, unique_elements_dim3_values; - figure_out_block_element_transformations(unique_elements_dim1_values, - unique_elements_dim2_values, - unique_elements_dim3_values, - radius, - radius_indx, - rotation_axis, - replicated_module_list); - std::set unique_angle_modules; - figure_out_block_angles(unique_angle_modules, rotation_axis, replicated_module_list); - - std::vector block_angular_spacing; - if (!get_spacing_uniform(block_angular_spacing, unique_angle_modules, 1e-2)) /// epsilon * 10000) // relax epsilon here - return false; - - std::vector element_horizontal_spacing, element_vertical_spacing; - std::set unique_elements_horizontal_values, unique_elements_vertical_values; - if (radius_indx == 0) - { - if (!get_spacing_uniform(element_horizontal_spacing, unique_elements_dim3_values)) - return false; - if (!get_spacing_uniform(element_vertical_spacing, unique_elements_dim2_values)) - return false; - unique_elements_horizontal_values = unique_elements_dim3_values; - unique_elements_vertical_values = unique_elements_dim2_values; - } - else - { - error("TODO!"); - } - - std::vector block_axial_spacing; - get_spacing_uniform(block_axial_spacing, main_axis); - if (block_axial_spacing.size() < 1) - { - // std::set::iterator it = unique_elements_horizontal_values.begin(); - // std::advance(it,0); - float begin = *std::next(unique_elements_horizontal_values.begin(), 0); - // std::advance(it,unique_elements_horizontal_values.size()-1); - float end = *std::next(unique_elements_horizontal_values.begin(), unique_elements_horizontal_values.size() - 1); - block_axial_spacing.push_back(std::abs(end - begin)); - } - - info(format("I counted {} axial blocks with spacing {}", unique_dim3_values.size(), block_axial_spacing[0])); - - this_scanner_sptr.reset( - new Scanner(Scanner::User_defined_scanner, - std::string("PETSIRD_defined_scanner"), - /* num dets per ring */ - (num_transaxial_blocks * unique_elements_vertical_values.size()), - unique_dim3_values.size() * unique_elements_horizontal_values.size() /* num of rings */, - /* number of non arccor bins */ - (num_transaxial_blocks * unique_elements_vertical_values.size()) / 2, - /* number of maximum arccor bins */ - (num_transaxial_blocks * unique_elements_vertical_values.size()) / 2, - /* inner ring radius */ - radius, - /* doi */ average_doi, - /* ring spacing */ - element_horizontal_spacing[0] * 10.f, - // bin_size_v - element_vertical_spacing[0] * 10.f, - /*intrinsic_tilt_v*/ - 0.f, - /*num_axial_blocks_per_bucket_v */ - unique_dim3_values.size(), - /*num_transaxial_blocks_per_bucket_v*/ - 1, - /*num_axial_crystals_per_block_v*/ - unique_elements_horizontal_values.size(), - /*num_transaxial_crystals_per_block_v*/ - unique_elements_vertical_values.size(), - /*num_axial_crystals_per_singles_unit_v*/ - unique_elements_horizontal_values.size(), - /*num_transaxial_crystals_per_singles_unit_v*/ - unique_elements_vertical_values.size(), - /*num_detector_layers_v*/ - 1, // num_detector_layers_v - scanner_info.energy_resolution_at_511.front(), // energy_resolution_v - 511, // reference_energy_v - 1, - 0.F, - 0.F, // non-TOF - "BlocksOnCylindrical", // scanner_geometry_v - *unique_elements_horizontal_values.begin(), // axial_crystal_spacing_v - std::round(*unique_elements_vertical_values.begin() * 10.0f) / 10.F, // transaxial_crystal_spacing_v - block_axial_spacing.front(), // axial_block_spacing_v - radius * block_angular_spacing.front(), // transaxial_block_spacing_v - "" // crystal_map_file_name_v - )); - - // /* maximum number of timing bins */ - // tof_bin_edges.NumberOfBins(), - // /* size of basic TOF bin */ - // 10, - // /* Scanner's timing resolution */ - // *unique_tof_values.begin())); - - return true; -} + \author Daniel Deidda + \author Nikos Efthimiou +*/ CListModeDataPETSIRD::CListModeDataPETSIRD(const std::string& listmode_filename, bool use_hdf5) : use_hdf5(use_hdf5) @@ -427,100 +33,44 @@ CListModeDataPETSIRD::CListModeDataPETSIRD(const std::string& listmode_filename, else current_lm_data_ptr.reset(new petsird::binary::PETSIRDReader(listmode_filename)); + m_has_delayeds = header.scanner.delayed_events_are_stored; + current_lm_data_ptr->ReadHeader(header); - petsird::ScannerInformation scanner_info = header.scanner; - petsird::ScannerGeometry scanner_geo = scanner_info.scanner_geometry; - std::vector replicated_module_list = scanner_geo.replicated_modules; // Get the first TimeBlock - // if ( current_lm_data_ptr->ReadTimeBlocks(curr_time_block); - // ) - // error("CListModeDataPETSIRD: Could not read the first TimeBlock. Abord."); + if (!current_lm_data_ptr->ReadTimeBlocks(curr_time_block)) + error("CListModeDataPETSIRD: Could not read the first TimeBlock. Abort."); + + ++m_time_block_index; if (std::holds_alternative(curr_time_block)) curr_event_block = std::get(curr_time_block); else - error("CListModeDataPETSIRD: holds_alternative not true. Abord."); - isCylindricalConfiguration(scanner_info, replicated_module_list); - bool b = false; - if (b) // isCylindricalConfiguration(scanner_info, replicated_module_list)) - { - int tof_mash_factor = 1; - this->set_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) - ->create_shared_clone())); - } - else - { - // this_scanner_sptr.get_ - DetectorCoordinateMap::det_pos_to_coord_type petsird_map; - const petsird::TypeOfModule type_of_module = replicated_module_list.size() - 1; - // const auto event_energy_bin_edges=scanner_info.event_energy_bin_edges[type_of_module]; - // const auto num_event_energy_bins = event_energy_bin_edges.NumberOfBins(); - // error("TODO:GenericScanner"); - for (uint32_t module = 0; module < numberOfModules; module++) - for (uint32_t elem = 0; elem < numberOfElementsIndices; elem++) - // for (uint32_t ener = 0; ener < num_event_energy_bins; ener++) //energy not supported yet - { - int index = module * numberOfElementsIndices + elem; - // Here we are going to assume that the index = module*numberOfElementsIndices+elem is equal to ax+ - // num_ax*tang+ num_ax*num_tang*rad then we will pass this to the map sorter (set_detector_map)) - // therefore we can get ax, tang and rad from index - /* - * rad = index/(num_ax*num_tang) - * tang =(index-rad*num_ax*num_tang)/num_ax -num_tang/2 - * ax = index mod num_ax - */ - - int rad_pos = index / (this_scanner_sptr->get_num_rings() * this_scanner_sptr->get_num_detectors_per_ring()); - int ax_pos = (index - rad_pos * this_scanner_sptr->get_num_rings() * this_scanner_sptr->get_num_detectors_per_ring()) - / this_scanner_sptr->get_num_detectors_per_ring(); - int tang_pos = index % this_scanner_sptr->get_num_detectors_per_ring(); - - DetectionPosition<> detpos(tang_pos, ax_pos, rad_pos); - petsird::ExpandedDetectionBin expanded_detection_bin{ module, elem, 1 }; - - auto box_shape = petsird_helpers::geometry::get_detecting_box(scanner_info, type_of_module, expanded_detection_bin); - CartesianCoordinate3D mean_coord; - - for (auto& corner : box_shape.corners) - { // if STIR (z,y,x) -> PETSIRD (-y, -x, z) pheraps the order below needs to be changed - mean_coord.x() = +corner.c[0] / box_shape.corners.size(); - mean_coord.y() = +corner.c[1] / box_shape.corners.size(); - mean_coord.z() = +corner.c[2] / box_shape.corners.size(); - } - - // save mean pos into map - petsird_map[detpos] = mean_coord; - std::cout << detpos.radial_coord() << "," << detpos.axial_coord() << "," << detpos.tangential_coord() << "," - << mean_coord.x() << "," << mean_coord.y() << "," << mean_coord.z() << "," << std::endl; - } - - this->map->set_detector_map(petsird_map); - } + error("CListModeDataPETSIRD: holds_alternative not true. Abort."); + + petsird_info_sptr = std::make_shared(header); + auto stir_scanner_sptr = petsird_info_sptr->get_scanner_sptr(); + + int tof_mash_factor = 1; + this->set_proj_data_info_sptr(std::dynamic_pointer_cast( + ProjDataInfo::construct_proj_data_info(petsird_info_sptr->get_scanner_sptr(), + 1, + petsird_info_sptr->get_scanner_sptr()->get_num_rings() - 1, + petsird_info_sptr->get_scanner_sptr()->get_num_detectors_per_ring() / 2, + petsird_info_sptr->get_scanner_sptr()->get_max_num_non_arccorrected_bins(), + /* arc_correction*/ false, + tof_mash_factor) + ->create_shared_clone())); shared_ptr _exam_info_sptr(new ExamInfo); // Only PET scanners supported _exam_info_sptr->imaging_modality = ImagingModality::PT; _exam_info_sptr->originating_system = std::string("PETSIRD_defined_scanner"); - // _exam_info_sptr->set_low_energy_thres(scanner_i); - // _exam_info_sptr->set_high_energy_thres(this->root_file_sptr->get_up_energy_thres()); + _exam_info_sptr->set_low_energy_thres(petsird_info_sptr->get_lower_energy_threshold()); + _exam_info_sptr->set_high_energy_thres(petsird_info_sptr->get_upper_energy_threshold()); this->exam_info_sptr = _exam_info_sptr; - - // N.E.: In my experience the first time block is always empty. - // So I use this unncessessary call to skip to the next. - if (this->open_lm_file() == Succeeded::no) - { - error("CListModeDataPETSIRD: Could not open listmode file " + listmode_filename + "\n"); - } } Succeeded @@ -536,29 +86,25 @@ CListModeDataPETSIRD::open_lm_file() const shared_ptr CListModeDataPETSIRD::get_empty_record_sptr() const { - shared_ptr sptr(new CListRecordPETSIRD); - std::dynamic_pointer_cast(sptr)->event().set_scanner_sptr( - this->get_proj_data_info_sptr()->get_scanner_sptr()); - std::dynamic_pointer_cast(sptr)->event().set_PETSIRD_ranges(numberOfModules, numberOfElementsIndices); - // std::dynamic_pointer_cast(sptr)->event_PETSIRD().set_map_sptr(map); - + shared_ptr sptr(new CListRecordPETSIRD(petsird_info_sptr, get_proj_data_info_sptr())); return sptr; } Succeeded CListModeDataPETSIRD::get_next_record(CListRecord& record_of_general_type) const { - auto& record = dynamic_cast(record_of_general_type); - const auto& prompt_list = curr_event_block.prompt_events.at(0).at(0); // TODO: support mulitple pairs of modules. - const auto& delayed_list = m_has_delayeds ? curr_event_block.delayed_events->at(0).at(0) : prompt_list; + const auto& prompt_list = curr_event_block.prompt_events.at(0).at(0); // TODO: support multiple pairs of modules. + const auto& delayed_list = m_has_delayeds ? curr_event_block.delayed_events.at(0).at(0) : prompt_list; const auto& event_list = curr_is_prompt ? prompt_list : delayed_list; if (event_list.size() == 0) return Succeeded::no; - if (record.init_from_data(event_list.at(curr_event_in_event_block), curr_is_prompt) == Succeeded::no + auto event = event_list.at(curr_event_in_event_block); + + if (record.init_from_data(event, curr_is_prompt) == Succeeded::no || record_of_general_type.time().set_time_in_millisecs(curr_event_block.time_interval.start) == Succeeded::no) { return Succeeded::no; @@ -583,7 +129,11 @@ CListModeDataPETSIRD::get_next_record(CListRecord& record_of_general_type) const else { if (!current_lm_data_ptr->ReadTimeBlocks(curr_time_block)) - return Succeeded::no; + { + current_lm_data_ptr->Close(); + return Succeeded::no; + } + ++m_time_block_index; curr_event_block = std::get(curr_time_block); } } @@ -591,11 +141,179 @@ CListModeDataPETSIRD::get_next_record(CListRecord& record_of_general_type) const { curr_is_prompt = true; if (!current_lm_data_ptr->ReadTimeBlocks(curr_time_block)) - return Succeeded::no; + { + current_lm_data_ptr->Close(); + return Succeeded::no; + } + ++m_time_block_index; curr_event_block = std::get(curr_time_block); } return Succeeded::yes; } +ListModeData::SavedPosition +CListModeDataPETSIRD::save_get_position() +{ + PetsirdCursor c; + c.is_prompt = curr_is_prompt; + c.event_in_block = curr_event_in_event_block; + c.time_block_index = m_time_block_index; + + // Cache current blocks so set_get_position is instant (recommended) + c.time_block = this->curr_time_block; + c.event_block = this->curr_event_block; + c.has_cached_blocks = true; + + m_saved_positions.push_back(std::move(c)); + return static_cast(m_saved_positions.size() - 1); +} + +Succeeded +CListModeDataPETSIRD::reopen_and_prime() +{ + // ensure PETSIRD state machine is satisfied + if (current_lm_data_ptr) + { + try + { + current_lm_data_ptr->Close(); + } + catch (...) + {} + } + // current_lm_data_ptr.reset(); + if (use_hdf5) + current_lm_data_ptr.reset(new petsird::hdf5::PETSIRDReader(this->listmode_filename)); + else + current_lm_data_ptr.reset(new petsird::binary::PETSIRDReader(this->listmode_filename)); + + petsird::Header header; + current_lm_data_ptr->ReadHeader(header); + // m_eof_reached = false; + curr_event_in_event_block = 0; + curr_is_prompt = true; + m_time_block_index = 0; + // read until first EventTimeBlock + while (true) + { + if (!current_lm_data_ptr->ReadTimeBlocks(this->curr_time_block)) + { + // m_eof_reached = true; + current_lm_data_ptr->Close(); + return Succeeded::no; + } + if (std::holds_alternative(this->curr_time_block)) + { + this->curr_event_block = std::get(this->curr_time_block); + return Succeeded::yes; + } + } +} + +Succeeded +CListModeDataPETSIRD::seek_to_event_block_index(std::size_t target_event_block_index) const +{ + // assumes we are primed at event_block_index = 0 + std::size_t idx = 0; + while (idx < target_event_block_index) + { + // read next until EventTimeBlock + while (true) + { + if (!current_lm_data_ptr->ReadTimeBlocks(this->curr_time_block)) + { + // m_eof_reached = true; + current_lm_data_ptr->Close(); + return Succeeded::no; + } + if (std::holds_alternative(this->curr_time_block)) + break; + } + this->curr_event_block = std::get(this->curr_time_block); + ++idx; + } + return Succeeded::yes; +} + +Succeeded +CListModeDataPETSIRD::set_get_position(const SavedPosition& pos) +{ + if (pos >= m_saved_positions.size()) + return Succeeded::no; + const auto& c = m_saved_positions[pos]; + + // If you cached the actual blocks, you STILL must ensure the reader state + // will not be used incorrectly. Easiest: reopen+seek anyway (robust), + // then overwrite curr_* with cached data. + if (reopen_and_prime() == Succeeded::no) + return Succeeded::no; + if (seek_to_event_block_index(c.time_block_index) == Succeeded::no) + return Succeeded::no; + + // restore logical cursor + curr_is_prompt = c.is_prompt; + curr_event_in_event_block = c.event_in_block; + m_time_block_index = c.time_block_index; + if (c.has_cached_blocks) + { + this->curr_time_block = c.time_block; + this->curr_event_block = c.event_block; + } + return Succeeded::yes; +} + +Succeeded +CListModeDataPETSIRD::reset() +{ + /* \todo Not sure if this is the best way to reset the reader. + It ensures we are in a clean state, but it might be slow if the file is large and/or on a slow disk. + */ + // if (current_lm_data_ptr) + // { + // try + // { + // current_lm_data_ptr->Close(); + // } + // catch (...) + // { + // // If Close throws, treat as failure (or swallow if you must) + // return Succeeded::no; + // } + // } + + if (use_hdf5) + current_lm_data_ptr.reset(new petsird::hdf5::PETSIRDReader(this->listmode_filename)); + else + current_lm_data_ptr.reset(new petsird::binary::PETSIRDReader(this->listmode_filename)); + + curr_event_in_event_block = 0; + curr_is_prompt = true; + m_time_block_index = 0; + + try + { + while (true) + { + info(format("Reading TimeBlock index {}", m_time_block_index), 2); + if (!current_lm_data_ptr->ReadTimeBlocks(this->curr_time_block)) + return Succeeded::no; + + ++m_time_block_index; + + if (std::holds_alternative(this->curr_time_block)) + { + this->curr_event_block = std::get(this->curr_time_block); + break; + } + } + } + catch (...) + { + return Succeeded::no; + } + + return Succeeded::yes; +} + END_NAMESPACE_STIR diff --git a/src/listmode_buildblock/CListModeDataSAFIR.cxx b/src/listmode_buildblock/CListModeDataSAFIR.cxx index e584b6fdd6..b793e418ba 100644 --- a/src/listmode_buildblock/CListModeDataSAFIR.cxx +++ b/src/listmode_buildblock/CListModeDataSAFIR.cxx @@ -1,10 +1,7 @@ -/* CListModeDataSAFIR.cxx - -Coincidence LM Data Class for SAFIR: Implementation - +/* Copyright 2015 ETH Zurich, Institute of Particle Physics Copyright 2020 Positrigo AG, Zurich - Copyright 2021 University College London + Copyright 2021 University College London Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -114,7 +111,7 @@ CListModeDataSAFIR::open_lm_file() const } template -shared_ptr +shared_ptr CListModeDataSAFIR::get_empty_record_sptr() const { shared_ptr sptr(new CListRecordT); @@ -125,12 +122,10 @@ CListModeDataSAFIR::get_empty_record_sptr() const template Succeeded -CListModeDataSAFIR::get_next_record(CListRecordT& record_of_general_type) const +CListModeDataSAFIR::get_next_record(CListRecord& record_of_general_type) const { - CListRecordT& record = static_cast(record_of_general_type); - Succeeded status = current_lm_data_ptr->get_next_record(record); - // if( status == Succeeded::yes ) record.event_SAFIR().set_map_sptr(map); - return status; + auto& record = static_cast(record_of_general_type); + return current_lm_data_ptr->get_next_record(record); } template @@ -140,7 +135,7 @@ CListModeDataSAFIR::reset() return current_lm_data_ptr->reset(); } -// template class CListModeDataSAFIR>; -// template class CListModeDataSAFIR>; +template class CListModeDataSAFIR>; +template class CListModeDataSAFIR>; END_NAMESPACE_STIR diff --git a/src/listmode_buildblock/CListRecordPETSIRD.cxx b/src/listmode_buildblock/CListRecordPETSIRD.cxx new file mode 100644 index 0000000000..4846360f92 --- /dev/null +++ b/src/listmode_buildblock/CListRecordPETSIRD.cxx @@ -0,0 +1,51 @@ +/* + Copyright 2026, University Medical Center Groningen + This file is part of STIR. + + SPDX-License-Identifier: Apache-2.0 + + See STIR/LICENSE.txt for details +*/ +/*! + \file + \ingroup listmode + \brief Implementation of classes stir::CListRecordPETSIRD + + \author Nikos Efthimiou +*/ + +#include "stir/listmode/CListRecordPETSIRD.h" +#include "stir/ProjDataInfoCylindricalNoArcCorr.h" +#include "stir/ProjDataInfoBlocksOnCylindricalNoArcCorr.h" +#include "stir/ProjDataInfoGenericNoArcCorr.h" + +START_NAMESPACE_STIR + +std::unique_ptr +CListRecordPETSIRD::make_event_data(shared_ptr proj_data_info_sptr, + DetectionPositionPair<>& det_pos_pair, + bool& is_prompt_event) +{ + if (dynamic_cast(proj_data_info_sptr.get()) != nullptr) + { + return std::make_unique>( + proj_data_info_sptr, &det_pos_pair, &is_prompt_event); + } + + if (dynamic_cast(proj_data_info_sptr.get()) != nullptr) + { + return std::make_unique>( + proj_data_info_sptr, &det_pos_pair, &is_prompt_event); + } + + if (dynamic_cast(proj_data_info_sptr.get()) != nullptr) + { + return std::make_unique>( + proj_data_info_sptr, &det_pos_pair, &is_prompt_event); + } + + error("Unsupported ProjDataInfo type in CListRecordPETSIRD::make_event_data"); + return nullptr; +} + +END_NAMESPACE_STIR \ No newline at end of file diff --git a/src/listmode_buildblock/CMakeLists.txt b/src/listmode_buildblock/CMakeLists.txt index 908b0a8373..3055c56c88 100644 --- a/src/listmode_buildblock/CMakeLists.txt +++ b/src/listmode_buildblock/CMakeLists.txt @@ -58,6 +58,10 @@ if (STIR_WITH_NiftyPET_PROJECTOR) ) endif() +if(HAVE_PETSIRD) + list(APPEND ${dir_LIB_SOURCES} + CListRecordPETSIRD.cxx) +endif() #$(dir)_REGISTRY_SOURCES:= $(dir)_registries @@ -75,31 +79,6 @@ if (HAVE_HDF5) endif() endif() -if (HAVE_PETSIRD) -#set(PETSIRD_dir ../../PETSIRD/cpp/generated) -#add_subdirectory(${PETSIRD_dir} PETSIRD_generated) - -target_include_directories(listmode_buildblock PUBLIC - $ - $ - $ - $ -) - -# target_include_directories(listmode_buildblock PUBLIC -# $ -# $ -# ) - -# target_include_directories(listmode_buildblock PUBLIC -# $ -# $ -# ) - -# target_include_directories(listmode_buildblock PUBLIC -# $ -# $ -# ) - -target_link_libraries(listmode_buildblock PUBLIC petsird_generated) +if(HAVE_PETSIRD) + target_link_libraries(${TARGET} PUBLIC PETSIRD::petsird) endif() diff --git a/src/recon_buildblock/BinNormalisationFromPETSIRD.cxx b/src/recon_buildblock/BinNormalisationFromPETSIRD.cxx new file mode 100644 index 0000000000..40dc1132b2 --- /dev/null +++ b/src/recon_buildblock/BinNormalisationFromPETSIRD.cxx @@ -0,0 +1,107 @@ +/* + Copyright (C) 2025, University Medical Center Groningen + This file is part of STIR. + + SPDX-License-Identifier: Apache-2.0 + See STIR/LICENSE.txt for details +*/ +/*! + \file BinNormalisationFromPETSIRD.cxx + \ingroup normalisation + + \brief Implementation for class stir::BinNormalisationFromPETSIRD + + \author Nikos Efthimiou +*/ + +#include "petsird/binary/protocols.h" +#include "petsird/hdf5/protocols.h" +#include "stir/recon_buildblock/BinNormalisationFromPETSIRD.h" +#include "stir/ProjDataInfoBlocksOnCylindricalNoArcCorr.h" +#include "stir/ProjDataInfoCylindricalNoArcCorr.h" + +START_NAMESPACE_STIR + +const char* const BinNormalisationFromPETSIRD::registered_name = "From PETSIRD"; + +void +BinNormalisationFromPETSIRD::set_defaults() +{ + base_type::set_defaults(); + normalisation_filename = ""; + m_with_detector_efficiencies = true; + m_with_dead_time = true; + m_with_geometric_factors = true; +} + +void +BinNormalisationFromPETSIRD::initialise_keymap() +{ + base_type::initialise_keymap(); + parser.add_start_key("Bin Normalisation From PETSIRD"); + parser.add_key("normalisation_filename", &normalisation_filename); + parser.add_stop_key("End Bin Normalisation From PETSIRD"); +} + +bool +BinNormalisationFromPETSIRD::post_processing() +{ + if (base_type::post_processing()) + return true; + read_norm_data(normalisation_filename); + return false; +} + +BinNormalisationFromPETSIRD::BinNormalisationFromPETSIRD() +{ + set_defaults(); +} + +BinNormalisationFromPETSIRD::BinNormalisationFromPETSIRD(const std::string& filename) +{ + read_norm_data(filename); +} + +float +BinNormalisationFromPETSIRD::get_uncalibrated_bin_efficiency(const Bin& bin) const +{ + + DetectionPositionPair<> dp; + + if (const auto* proj_cyl = dynamic_cast(proj_data_info_sptr.get())) + { + proj_cyl->get_det_pos_pair_for_bin(dp, bin); + } + else if (const auto* proj_blk = dynamic_cast(proj_data_info_sptr.get())) + { + proj_blk->get_det_pos_pair_for_bin(dp, bin); + } + else + { + error("BinNormalisationFromPETSIRD: ProjDataInfo is neither Cylindrical nor BlocksOnCylindrical"); + } + + return petsird_info_sptr->get_detection_efficiency_for_bin(dp); +} + +Succeeded +BinNormalisationFromPETSIRD::set_up(const shared_ptr& exam_info_sptr, + const shared_ptr& proj_data_info_ptr_v) +{ + base_type::set_up(exam_info_sptr, proj_data_info_ptr_v); + + return Succeeded::yes; +} + +void +BinNormalisationFromPETSIRD::read_norm_data(const string& filename) +{ + petsird::Header header; + petsird_data_sptr.reset(new petsird::binary::PETSIRDReader(filename)); + + petsird_data_sptr->ReadHeader(header); + + petsird_info_sptr = std::make_shared(header); +} + +END_NAMESPACE_STIR \ No newline at end of file diff --git a/src/recon_buildblock/CMakeLists.txt b/src/recon_buildblock/CMakeLists.txt index 3ddde2de2b..a3169db0cf 100644 --- a/src/recon_buildblock/CMakeLists.txt +++ b/src/recon_buildblock/CMakeLists.txt @@ -1,3 +1,4 @@ +# Copyright 2025 - University Medical Center Groningen # Copyright 2011-01-01 - 2011-06-30 Hammersmith Imanet Ltd # Copyright 2011-07-01 - 2013 Kris Thielemans @@ -105,6 +106,12 @@ list(APPEND ${dir_LIB_SOURCES} BinNormalisationFromECAT8.cxx ) +if (HAVE_PETSIRD) + list(APPEND ${dir_LIB_SOURCES} + BinNormalisationFromPETSIRD.cxx + ) +endif() + if (HAVE_HDF5) list(APPEND ${dir_LIB_SOURCES} BinNormalisationFromGEHDF5.cxx @@ -202,3 +209,7 @@ target_include_directories(${TARGET} PUBLIC #${CUVEC_INCLUDE_DIR}) if (NOT STIR_WITH_CUDA) target_compile_definitions(${TARGET} PUBLIC CUVEC_DISABLE_CUDA) endif() + +if(HAVE_PETSIRD) + target_link_libraries(${TARGET} PUBLIC PETSIRD::petsird) +endif() diff --git a/src/recon_buildblock/find_basic_vs_nums_in_subset.cxx b/src/recon_buildblock/find_basic_vs_nums_in_subset.cxx index 5364d4fc9b..bf3aa6747f 100644 --- a/src/recon_buildblock/find_basic_vs_nums_in_subset.cxx +++ b/src/recon_buildblock/find_basic_vs_nums_in_subset.cxx @@ -39,31 +39,34 @@ find_basic_vs_nums_in_subset(const ProjDataInfo& proj_data_info, std::vector vs_nums_to_process; for (int segment_num = min_segment_num; segment_num <= max_segment_num; segment_num++) { - 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) - { - for (int view = proj_data_info.get_min_view_num() + subset_num; view <= proj_data_info.get_max_view_num(); - view += num_subsets) - { - const ViewSegmentNumbers view_segment_num(view, segment_num); + // 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) + { + for (int view = proj_data_info.get_min_view_num() + subset_num; view <= proj_data_info.get_max_view_num(); + view += num_subsets) + { + // std::cout << "view: " << view << std::endl; + // std::cout << "MIN:" << -proj_data_info.get_min_tof_pos_num() << " timing_pos_num: " << timing_pos_num << " MAX: " + // < rel_vs; - symmetries.get_related_view_segment_numbers(rel_vs, view_segment_num); - for (std::vector::const_iterator iter = rel_vs.begin(); iter != rel_vs.end(); ++iter) - { - assert(iter->segment_num() >= min_segment_num); - assert(iter->segment_num() <= max_segment_num); - } + // test if symmetries didn't take us out of the segment range + std::vector rel_vs; + symmetries.get_related_view_segment_numbers(rel_vs, view_segment_num); + for (std::vector::const_iterator iter = rel_vs.begin(); iter != rel_vs.end(); ++iter) + { + assert(iter->segment_num() >= min_segment_num); + assert(iter->segment_num() <= max_segment_num); + } #endif - } - } + } + } } return vs_nums_to_process; } diff --git a/src/recon_buildblock/recon_buildblock_registries.cxx b/src/recon_buildblock/recon_buildblock_registries.cxx index c6c35a6b4d..740d0ca70e 100644 --- a/src/recon_buildblock/recon_buildblock_registries.cxx +++ b/src/recon_buildblock/recon_buildblock_registries.cxx @@ -83,6 +83,10 @@ # include "stir/recon_buildblock/CUDA/CudaRelativeDifferencePrior.h" #endif +#ifdef HAVE_PETSIRD +# include "stir/recon_buildblock/BinNormalisationFromPETSIRD.h" +#endif + #ifdef STIR_WITH_Parallelproj_PROJECTOR # include "stir/recon_buildblock/Parallelproj_projector/ForwardProjectorByBinParallelproj.h" # include "stir/recon_buildblock/Parallelproj_projector/BackProjectorByBinParallelproj.h" @@ -175,6 +179,10 @@ END_NAMESPACE_ECAT static GE::RDF_HDF5::BinNormalisationFromGEHDF5::RegisterIt dummy104; #endif +#ifdef HAVE_PETSIRD +static BinNormalisationFromPETSIRD::RegisterIt dummy105; +#endif + static FourierRebinning::RegisterIt dummyFORE; END_NAMESPACE_STIR diff --git a/src/recon_test/test_DataSymmetriesForBins_PET_CartesianGrid.cxx b/src/recon_test/test_DataSymmetriesForBins_PET_CartesianGrid.cxx index 0f68e53fa2..b31e03cb5b 100644 --- a/src/recon_test/test_DataSymmetriesForBins_PET_CartesianGrid.cxx +++ b/src/recon_test/test_DataSymmetriesForBins_PET_CartesianGrid.cxx @@ -768,7 +768,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*/ 116)); + /*tof_mashing*/ 117)); run_tests_for_1_projdata(proj_data_info_sptr); } @@ -783,7 +783,7 @@ DataSymmetriesForBins_PET_CartesianGridTests::run_tests() /*num_views=*/scanner_sptr->get_num_detectors_per_ring() / 8, /*num_tang_poss=*/16, /*arc_corrected*/ false, - /*tof_mashing*/ 112)); + /*tof_mashing*/ 82)); run_tests_for_1_projdata(proj_data_info_sptr); } } diff --git a/src/swig/Makefile b/src/swig/Makefile index 2b54b1bdd6..b066f3a6b9 100644 --- a/src/swig/Makefile +++ b/src/swig/Makefile @@ -1,63 +1,200 @@ -# This is a temporary makefile used for development only. -# Do not use. Instead, use CMake for building -SWIG=swig +# CMAKE generated file: DO NOT EDIT! +# Generated by "Unix Makefiles" Generator, CMake Version 4.2 -all: _stir.so +# Default target executed when no arguments are given to make. +default_target: all +.PHONY : default_target -stir_PYTHONwrap.cxx: stir.i Makefile - $(SWIG) -python -c++ -builtin -I../include/ -I/usr/include -DSTART_NAMESPACE_STIR="namespace stir {" -DEND_NAMESPACE_STIR="}" -o $@ $< +# Allow only one "make -f Makefile2" at a time, but pass parallelism. +.NOTPARALLEL: -stir_OCTAVEwrap.cpp: stir.i Makefile - $(SWIG) -octave -c++ -I../include/ -I/usr/include -DSTART_NAMESPACE_STIR="namespace stir {" -DEND_NAMESPACE_STIR="}" -o $@ $< +#============================================================================= +# Special targets provided by cmake. -stir_MATLABwrap.cpp: stir.i Makefile - $(SWIG) -matlab -c++ -I../include/ -I/usr/include -DSTART_NAMESPACE_STIR="namespace stir {" -DEND_NAMESPACE_STIR="}" -o $@ $< +# Disable implicit rules so canonical targets will work. +.SUFFIXES: -stir.e: stir.i Makefile - $(SWIG) -python -c++ -builtin -E -I../include/ -DSTART_NAMESPACE_STIR="namespace stir {" -DEND_NAMESPACE_STIR="}" $< > stir.e +# Disable VCS-based implicit rules. +% : %,v -CFLAGS=-fpic -I../include/ -g -I /usr/include/python2.7/ -DSWIG +# Disable VCS-based implicit rules. +% : RCS/% -%.o : %.cxx - $(CXX) $(CFLAGS) -o$@ -c $< +# Disable VCS-based implicit rules. +% : RCS/%,v -FILES=Scanner.o interfile_keyword_functions.o utilities.o error.o warning.o VoxelsOnCartesianGrid.o ProjDataInfo.o Sinogram.o Viewgram.o SegmentBySinogram.o SegmentByView.o \ -ProjDataInfoCylindrical.o ProjDataInfoCylindricalArcCorr.o ProjDataInfoCylindricalNoArcCorr.o RelatedViewgrams.o DataSymmetriesForViewSegmentNumbers.o IndexRange.o DiscretisedDensity.o +# Disable VCS-based implicit rules. +% : SCCS/s.% -LIBS=../../build/stir/gcc/DebugShared/buildblock/libbuildblock.so \ -../../build/stir/gcc/DebugShared/data_buildblock/libdata_buildblock.so \ -../../build/stir/gcc/DebugShared/IO/libIO.so \ -../../build/stir/gcc/DebugShared/modelling_buildblock/libmodelling_buildblock.so \ -../../build/stir/gcc/DebugShared/numerics_buildblock/libnumerics_buildblock.so \ -../../build/stir/gcc/DebugShared/local/IO/GE/liblocal_IO_GE.so \ -../../build/stir/gcc/DebugShared/recon_buildblock/librecon_buildblock.so \ -../../build/stir/gcc/DebugShared/listmode_buildblock/liblistmode_buildblock.so \ -../../build/stir/gcc/DebugShared/iterative/OSMAPOSL/libiterative_OSMAPOSL.so \ -../../build/stir/gcc/DebugShared/swig/CMakeFiles/_stir.dir/__/IO/IO_registries.cxx.o \ -../../build/stir/gcc/DebugShared/swig/CMakeFiles/_stir.dir/__/recon_buildblock/recon_buildblock_registries.cxx.o \ -../../build/stir/gcc/DebugShared/swig/CMakeFiles/_stir.dir/__/buildblock/buildblock_registries.cxx.o +# Disable VCS-based implicit rules. +% : s.% +.SUFFIXES: .hpux_make_needs_suffix_list -LIBSa=-lbuildblock \ --ldata_buildblock \ --lIO \ --lmodelling_buildblock \ --lnumerics_buildblock \ --llocal_IO_GE \ --lrecon_buildblock \ --llistmode_buildblock \ --literative_OSMAPOSL \ -../../build/stir/gcc/DebugShared/swig/CMakeFiles/_stir.dir/__/IO/IO_registries.cxx.o \ -../../build/stir/gcc/DebugShared/swig/CMakeFiles/_stir.dir/__/recon_buildblock/recon_buildblock_registries.cxx.o \ -../../build/stir/gcc/DebugShared/swig/CMakeFiles/_stir.dir/__/buildblock/buildblock_registries.cxx.o \ --L/home/kris/binDebugShared/lib +# Command-line flag to silence nested $(MAKE). +$(VERBOSE)MAKESILENT = -s +#Suppress display of executed commands. +$(VERBOSE).SILENT: -.PRECIOUS: stir_PYTHONwrap.cxx stir_OCTAVEwrap.cpp +# A target that is always out of date. +cmake_force: +.PHONY : cmake_force -_stir.so: stir_PYTHONwrap.o $(LIBS) Makefile - g++ -g -shared stir_PYTHONwrap.o $(LIBS) -o $@ +#============================================================================= +# Set environment variables for the build. +# The shell in which to execute make rules. +SHELL = /bin/sh + +# The CMake executable. +CMAKE_COMMAND = /home/n.efthymiou/miniforge3/envs/petsird/bin/cmake + +# The command to remove a file. +RM = /home/n.efthymiou/miniforge3/envs/petsird/bin/cmake -E rm -f + +# Escaping for special characters. +EQUALS = = + +# The top-level source directory on which CMake was run. +CMAKE_SOURCE_DIR = /home/n.efthymiou/Developer/ETSI/STIR + +# The top-level build directory on which CMake was run. +CMAKE_BINARY_DIR = /home/n.efthymiou/Developer/ETSI/STIR + +#============================================================================= +# Targets provided globally by CMake. + +# Special rule for the target test +test: + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --cyan "Running tests..." + /home/n.efthymiou/miniforge3/envs/petsird/bin/ctest $(ARGS) +.PHONY : test + +# Special rule for the target test +test/fast: test +.PHONY : test/fast + +# Special rule for the target edit_cache +edit_cache: + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --cyan "Running CMake cache editor..." + /home/n.efthymiou/miniforge3/envs/petsird/bin/ccmake -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) +.PHONY : edit_cache + +# Special rule for the target edit_cache +edit_cache/fast: edit_cache +.PHONY : edit_cache/fast + +# Special rule for the target rebuild_cache +rebuild_cache: + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --cyan "Running CMake to regenerate build system..." + /home/n.efthymiou/miniforge3/envs/petsird/bin/cmake --regenerate-during-build -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) +.PHONY : rebuild_cache + +# Special rule for the target rebuild_cache +rebuild_cache/fast: rebuild_cache +.PHONY : rebuild_cache/fast + +# Special rule for the target list_install_components +list_install_components: + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --cyan "Available install components are: \"DOC\" \"Unspecified\"" +.PHONY : list_install_components + +# Special rule for the target list_install_components +list_install_components/fast: list_install_components +.PHONY : list_install_components/fast + +# Special rule for the target install +install: preinstall + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --cyan "Install the project..." + /home/n.efthymiou/miniforge3/envs/petsird/bin/cmake -P cmake_install.cmake +.PHONY : install + +# Special rule for the target install +install/fast: preinstall/fast + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --cyan "Install the project..." + /home/n.efthymiou/miniforge3/envs/petsird/bin/cmake -P cmake_install.cmake +.PHONY : install/fast + +# Special rule for the target install/local +install/local: preinstall + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --cyan "Installing only the local directory..." + /home/n.efthymiou/miniforge3/envs/petsird/bin/cmake -DCMAKE_INSTALL_LOCAL_ONLY=1 -P cmake_install.cmake +.PHONY : install/local + +# Special rule for the target install/local +install/local/fast: preinstall/fast + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --cyan "Installing only the local directory..." + /home/n.efthymiou/miniforge3/envs/petsird/bin/cmake -DCMAKE_INSTALL_LOCAL_ONLY=1 -P cmake_install.cmake +.PHONY : install/local/fast + +# Special rule for the target install/strip +install/strip: preinstall + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --cyan "Installing the project stripped..." + /home/n.efthymiou/miniforge3/envs/petsird/bin/cmake -DCMAKE_INSTALL_DO_STRIP=1 -P cmake_install.cmake +.PHONY : install/strip + +# Special rule for the target install/strip +install/strip/fast: preinstall/fast + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --cyan "Installing the project stripped..." + /home/n.efthymiou/miniforge3/envs/petsird/bin/cmake -DCMAKE_INSTALL_DO_STRIP=1 -P cmake_install.cmake +.PHONY : install/strip/fast + +# The main all target +all: cmake_check_build_system + cd /home/n.efthymiou/Developer/ETSI/STIR && $(CMAKE_COMMAND) -E cmake_progress_start /home/n.efthymiou/Developer/ETSI/STIR/CMakeFiles /home/n.efthymiou/Developer/ETSI/STIR/src/swig//CMakeFiles/progress.marks + cd /home/n.efthymiou/Developer/ETSI/STIR && $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 src/swig/all + $(CMAKE_COMMAND) -E cmake_progress_start /home/n.efthymiou/Developer/ETSI/STIR/CMakeFiles 0 +.PHONY : all + +# The main clean target +clean: + cd /home/n.efthymiou/Developer/ETSI/STIR && $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 src/swig/clean +.PHONY : clean + +# The main clean target +clean/fast: clean +.PHONY : clean/fast + +# Prepare targets for installation. +preinstall: all + cd /home/n.efthymiou/Developer/ETSI/STIR && $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 src/swig/preinstall +.PHONY : preinstall + +# Prepare targets for installation. +preinstall/fast: + cd /home/n.efthymiou/Developer/ETSI/STIR && $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 src/swig/preinstall +.PHONY : preinstall/fast + +# clear depends +depend: + cd /home/n.efthymiou/Developer/ETSI/STIR && $(CMAKE_COMMAND) -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) --check-build-system CMakeFiles/Makefile.cmake 1 +.PHONY : depend + +# Help Target +help: + @echo "The following are some of the valid targets for this Makefile:" + @echo "... all (the default if no target is provided)" + @echo "... clean" + @echo "... depend" + @echo "... edit_cache" + @echo "... install" + @echo "... install/local" + @echo "... install/strip" + @echo "... list_install_components" + @echo "... rebuild_cache" + @echo "... test" +.PHONY : help + + + +#============================================================================= +# Special targets to cleanup operation of make. + +# Special rule to run CMake to check the build system integrity. +# No rule that depends on this can have commands that come from listfiles +# because they might be regenerated. +cmake_check_build_system: + cd /home/n.efthymiou/Developer/ETSI/STIR && $(CMAKE_COMMAND) -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) --check-build-system CMakeFiles/Makefile.cmake 0 +.PHONY : cmake_check_build_system -stir.oct: stir_OCTAVEwrap.cpp $(LIBS) Makefile - CXXFLAGS=-g mkoctfile -v -I../include/ -g -DSWIG -o $@ $< $(LIBSa) diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index d1d69a91e8..084f28b638 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -77,6 +77,13 @@ list (APPEND buildblock_simple_tests test_interpolate_projdata.cxx ) +if (HAVE_PETSIRD) + list (APPEND buildblock_simple_tests + test_PETSIRDInfo_helpers.cxx + ) + endif() + + endif() # MINI_STIR include(stir_test_exe_targets) diff --git a/src/test/test_ArcCorrection.cxx b/src/test/test_ArcCorrection.cxx index cb43fd3813..c4099e9406 100644 --- a/src/test/test_ArcCorrection.cxx +++ b/src/test/test_ArcCorrection.cxx @@ -176,7 +176,7 @@ ArcCorrectionTests::run_tests_tof() /*views*/ 112, /*tang_pos*/ 357, /*arc_corrected*/ false, - /*tof_mashing_factor*/ 116)); + /*tof_mashing_factor*/ 117)); cerr << "Using default range and bin-size\n"; { diff --git a/src/test/test_PETSIRDInfo_helpers.cxx b/src/test/test_PETSIRDInfo_helpers.cxx new file mode 100644 index 0000000000..4b7ec72891 --- /dev/null +++ b/src/test/test_PETSIRDInfo_helpers.cxx @@ -0,0 +1,181 @@ + +/* + Copyright (C) 2025, University Medical Center Groningen + This file is part of STIR. + + SPDX-License-Identifier: Apache-2.0 + See STIR/LICENSE.txt for details +*/ +/*! + + \file + \ingroup test + + \brief Test program for stir::PETSIRD hierarchy + + \author Nikos Efthimiou + + \todo The helper code is being transitioned from std::vector-based arrays + in PETSIRDInfo_helpers.h to stir::Array. This test currently checks + find_unique_values() with stir::Array inputs, while the production + PETSIRDInfo_helpers code may still use the older std::vector-based + data structures. +*/ +#include "stir/detail/PETSIRDInfo_helpers.h" +#include "stir/RunTests.h" +#include "stir/Succeeded.h" +#include "stir/Array.h" +#include "stir/make_array.h" +#include "stir/ArrayFunction.h" + +START_NAMESPACE_STIR + +class PETSIRDTests : public RunTests +{ +public: + void run_tests() override; + +private: + void test_find_unique_values_1D(); + void test_find_unique_values_2D(); + void test_get_largest_vector(); + void test_get_spacing_uniform(); + void test_get_AxisFromSkewMatrix(); + void test_infer_group_sizes_dim2_dim3(); +}; + +void +PETSIRDTests::run_tests() +{ + test_find_unique_values_1D(); + test_find_unique_values_2D(); + test_get_largest_vector(); + test_get_spacing_uniform(); + test_get_AxisFromSkewMatrix(); + test_infer_group_sizes_dim2_dim3(); +} + +void +PETSIRDTests::test_find_unique_values_1D() +{ + stir::Array<1, float> input = make_1d_array(1.0f, 2.0f, 3.0f, 2.0f, 4.0f, 1.0f, 5.0f); + stir::Array<1, float> expected = make_1d_array(1.0f, 2.0f, 3.0f, 4.0f, 5.0f); + std::set result; + + find_unique_values(result, input.begin_all_const(), input.end_all_const()); + for (const auto& val : expected) + { + this->check(result.find(val) != result.end(), format("Value {} should be in the unique set", val)); + } +} + +void +PETSIRDTests::test_find_unique_values_2D() +{ + stir::Array<2, float> input + = make_array(make_1d_array(1.0f, 2.0f, 3.0f), make_1d_array(4.0f, 2.0f, 6.0f), make_1d_array(1.0f, 8.0f, 9.0f)); + stir::Array<1, float> expected = make_1d_array(1.0f, 2.0f, 3.0f, 4.0f, 6.0f, 8.0f, 9.0f); + std::set result; + + find_unique_values(result, input.begin_all_const(), input.end_all_const()); + + for (const auto& val : expected) + { + this->check(result.find(val) != result.end(), format("Value {} should be in the unique set", val)); + } +} + +void +PETSIRDTests::test_get_largest_vector() +{ + std::set x = { 1.0f, 2.0f }; + std::set y = { 1.0f, 2.0f, 3.0f }; + std::set z = { 1.0f }; + + const std::set& largest = vector_utils::get_largest_vector(x, y, z); + this->check_if_equal(largest.size(), y.size(), "Y should be the largest vector"); +} + +void +PETSIRDTests::test_get_spacing_uniform() +{ + std::set values = { 0.0f, 2.0f, 4.0f, 6.0f, 8.0f }; + std::vector spacing; + std::set spacings; + bool is_uniform = vector_utils::get_spacing_uniform(spacing, values); + + this->check(is_uniform, "Spacing should be uniform"); + vector_utils::find_unique_values_1D(spacings, spacing); + + this->check_if_equal(static_cast(spacings.size()), 1u, "There should be one unique spacing value"); + this->check_if_equal(*spacings.begin(), 2.0f, "Spacing value should be 2.0f"); +} + +void +PETSIRDTests::test_get_AxisFromSkewMatrix() +{ + float angle_rad = static_cast(M_PI) / 6.0f; // 30 degrees + { + // Create a rotation matrix around z axis + matrix::Mat3 R = { { { 1.0f, 0.0f, 0.0f }, { 0.0f, 1.0f, 0.0f }, { 0.0f, 0.0f, 1.0f } } }; + R[0][0] = std::cos(angle_rad); + R[0][1] = -std::sin(angle_rad); + R[1][0] = std::sin(angle_rad); + R[1][1] = std::cos(angle_rad); + + std::array, 3> skew = matrix::subtract(R, matrix::transpose(R)); + auto axis = matrix::getAxisFromSkew(skew); + this->check_if_equal(axis[0], 0.0f, "X component of rotation axis should be 0"); + this->check_if_equal(axis[1], 0.0f, "Y component of rotation axis should be 0"); + this->check_if_equal(axis[2], 1.0f, "Z component of rotation axis should be 1"); + } + { + // Create a rotation matrix around x axis + matrix::Mat3 R = { { { 1.0f, 0.0f, 0.0f }, { 0.0f, 1.0f, 0.0f }, { 0.0f, 0.0f, 1.0f } } }; + R[1][1] = std::cos(angle_rad); + R[1][2] = -std::sin(angle_rad); + R[2][1] = std::sin(angle_rad); + R[2][2] = std::cos(angle_rad); + + std::array, 3> skew = matrix::subtract(R, matrix::transpose(R)); + auto axis = matrix::getAxisFromSkew(skew); + this->check_if_equal(axis[0], 1.0f, "X component of rotation axis should be 1"); + this->check_if_equal(axis[1], 0.0f, "Y component of rotation axis should be 0"); + this->check_if_equal(axis[2], 0.0f, "Z component of rotation axis should be 0"); + } +} + +void +PETSIRDTests::test_infer_group_sizes_dim2_dim3() +{ + std::vector> pts; + // Create a grid of points with groupSize_dim2 = 3 and groupSize_dim3 = 4 + for (int z = 0; z < 4; ++z) + { + for (int y = 0; y < 3; ++y) + { + // Test with float numbers and something in the x coordintate so that we don't have all zeros + pts.emplace_back(static_cast(z + 0.4), static_cast(z + 0.35), 0.1f + z / 2.f); + } + } + + std::size_t groupSize_dim2 = 0; + std::size_t groupSize_dim3 = 0; + bool success = inferGroupSizes_dim2_dim3(pts, groupSize_dim2, groupSize_dim3); + + this->check(success, "inferGroupSizes_dim2_dim3 should succeed"); + this->check_if_equal(groupSize_dim2, static_cast(3), "groupSize_dim2 should be 3"); + this->check_if_equal(groupSize_dim3, static_cast(4), "groupSize_dim3 should be 4"); +} + +END_NAMESPACE_STIR + +USING_NAMESPACE_STIR + +int +main() +{ + PETSIRDTests test; + test.run_tests(); + return test.main_return_value(); +}