diff --git a/Modules/DICOMReader/Resources/configurations/3D/classicreader.xml b/Modules/DICOMReader/Resources/configurations/3D/classicreader.xml index 4ee1f2d1e2..c8e22f51d0 100644 --- a/Modules/DICOMReader/Resources/configurations/3D/classicreader.xml +++ b/Modules/DICOMReader/Resources/configurations/3D/classicreader.xml @@ -1,18 +1,6 @@ - - - - - - - - - - + class="ClassicDICOMSeriesReader"> diff --git a/Modules/DICOMReader/Resources/configurations/3D/imageposition.xml b/Modules/DICOMReader/Resources/configurations/3D/imageposition.xml index dcaf911507..81ce92b3d4 100644 --- a/Modules/DICOMReader/Resources/configurations/3D/imageposition.xml +++ b/Modules/DICOMReader/Resources/configurations/3D/imageposition.xml @@ -1,22 +1,24 @@ - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + diff --git a/Modules/DICOMReader/Resources/configurations/3D/imageposition_byacquisition.xml b/Modules/DICOMReader/Resources/configurations/3D/imageposition_byacquisition.xml index 783873bdf7..0aea5c2037 100644 --- a/Modules/DICOMReader/Resources/configurations/3D/imageposition_byacquisition.xml +++ b/Modules/DICOMReader/Resources/configurations/3D/imageposition_byacquisition.xml @@ -1,23 +1,26 @@ - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + diff --git a/Modules/DICOMReader/Resources/configurations/3D/imagetime.xml b/Modules/DICOMReader/Resources/configurations/3D/imagetime.xml index f03b4e0485..67cd452768 100644 --- a/Modules/DICOMReader/Resources/configurations/3D/imagetime.xml +++ b/Modules/DICOMReader/Resources/configurations/3D/imagetime.xml @@ -1,21 +1,23 @@ - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + diff --git a/Modules/DICOMReader/Resources/configurations/3D/instancenumber.xml b/Modules/DICOMReader/Resources/configurations/3D/instancenumber.xml index a412da3a77..556b668389 100644 --- a/Modules/DICOMReader/Resources/configurations/3D/instancenumber.xml +++ b/Modules/DICOMReader/Resources/configurations/3D/instancenumber.xml @@ -1,21 +1,23 @@ - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + diff --git a/Modules/DICOMReader/Resources/configurations/3D/slicelocation.xml b/Modules/DICOMReader/Resources/configurations/3D/slicelocation.xml index 33974e6303..b30ab99907 100644 --- a/Modules/DICOMReader/Resources/configurations/3D/slicelocation.xml +++ b/Modules/DICOMReader/Resources/configurations/3D/slicelocation.xml @@ -1,21 +1,23 @@ - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + diff --git a/Modules/DICOMReader/Resources/configurations/3DnT/classicreader.xml b/Modules/DICOMReader/Resources/configurations/3DnT/classicreader.xml index 4bb181dda8..c5df7b2951 100644 --- a/Modules/DICOMReader/Resources/configurations/3DnT/classicreader.xml +++ b/Modules/DICOMReader/Resources/configurations/3DnT/classicreader.xml @@ -1,16 +1,20 @@ - - - - - - - - - - + + + + + + + + + + + + diff --git a/Modules/DICOMReader/Testing/files.cmake b/Modules/DICOMReader/Testing/files.cmake index 848c755de2..3333b9f2ed 100644 --- a/Modules/DICOMReader/Testing/files.cmake +++ b/Modules/DICOMReader/Testing/files.cmake @@ -1,12 +1,13 @@ set(MODULE_TESTS + mitkDICOMReaderConfiguratorTest.cpp ) set(MODULE_CUSTOM_TESTS mitkDICOMFileReaderTest.cpp mitkDICOMITKSeriesGDCMReaderBasicsTest.cpp ) set(TEST_CPP_FILES mitkDICOMNullFileReader.cpp mitkDICOMFilenameSorter.cpp ) diff --git a/Modules/DICOMReader/Testing/mitkDICOMFilenameSorter.cpp b/Modules/DICOMReader/Testing/mitkDICOMFilenameSorter.cpp index f4f83cbef5..bdd6ae0c5f 100644 --- a/Modules/DICOMReader/Testing/mitkDICOMFilenameSorter.cpp +++ b/Modules/DICOMReader/Testing/mitkDICOMFilenameSorter.cpp @@ -1,80 +1,87 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkDICOMFilenameSorter.h" #include mitk::DICOMFilenameSorter ::DICOMFilenameSorter() :DICOMDatasetSorter() { } mitk::DICOMFilenameSorter ::~DICOMFilenameSorter() { } mitk::DICOMFilenameSorter ::DICOMFilenameSorter(const DICOMFilenameSorter& other ) :DICOMDatasetSorter(other) { } mitk::DICOMFilenameSorter& mitk::DICOMFilenameSorter ::operator=(const DICOMFilenameSorter& other) { if (this != &other) { DICOMDatasetSorter::operator=(other); } return *this; } +bool +mitk::DICOMFilenameSorter +::operator==(const DICOMDatasetSorter& other) const +{ + return dynamic_cast(&other) != NULL; +} + mitk::DICOMTagList mitk::DICOMFilenameSorter ::GetTagsOfInterest() { return DICOMTagList(); } void mitk::DICOMFilenameSorter ::PrintConfiguration(std::ostream& os, const std::string& indent) const { os << indent << "Sort alphabetically based on filenames" << std::endl; } bool mitk::DICOMFilenameSorter::FilenameSort ::operator() (const mitk::DICOMDatasetAccess* left, const mitk::DICOMDatasetAccess* right) { return left->GetFilenameIfAvailable().compare( right->GetFilenameIfAvailable() ) < 0 ; } void mitk::DICOMFilenameSorter ::Sort() { DICOMDatasetList output = GetInput(); // copy std::sort( output.begin(), output.end(), FilenameSort() ); this->SetNumberOfOutputs(1); this->SetOutput(0, output); } diff --git a/Modules/DICOMReader/Testing/mitkDICOMFilenameSorter.h b/Modules/DICOMReader/Testing/mitkDICOMFilenameSorter.h index 49a6056869..0d1ba8e694 100644 --- a/Modules/DICOMReader/Testing/mitkDICOMFilenameSorter.h +++ b/Modules/DICOMReader/Testing/mitkDICOMFilenameSorter.h @@ -1,58 +1,60 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef mitkDICOMFilenameSorter_h #define mitkDICOMFilenameSorter_h #include "mitkDICOMDatasetSorter.h" namespace mitk { /** \ingroup DICOMReaderModule \brief sort files based on filename (last resort). */ class DICOMFilenameSorter : public DICOMDatasetSorter { public: mitkClassMacro( DICOMFilenameSorter, DICOMDatasetSorter ) itkNewMacro( DICOMFilenameSorter ) virtual DICOMTagList GetTagsOfInterest(); virtual void Sort(); virtual void PrintConfiguration(std::ostream& os, const std::string& indent = "") const; + virtual bool operator==(const DICOMDatasetSorter& other) const; + protected: struct FilenameSort { bool operator() (const mitk::DICOMDatasetAccess* left, const mitk::DICOMDatasetAccess* right); }; DICOMFilenameSorter(); virtual ~DICOMFilenameSorter(); DICOMFilenameSorter(const DICOMFilenameSorter& other); DICOMFilenameSorter& operator=(const DICOMFilenameSorter& other); }; } #endif diff --git a/Modules/DICOMReader/Testing/mitkDICOMNullFileReader.cpp b/Modules/DICOMReader/Testing/mitkDICOMNullFileReader.cpp index 8afd7266fb..a2c1f30b44 100644 --- a/Modules/DICOMReader/Testing/mitkDICOMNullFileReader.cpp +++ b/Modules/DICOMReader/Testing/mitkDICOMNullFileReader.cpp @@ -1,96 +1,102 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkDICOMNullFileReader.h" mitk::DICOMNullFileReader ::DICOMNullFileReader() :DICOMFileReader() { } mitk::DICOMNullFileReader ::~DICOMNullFileReader() { } mitk::DICOMNullFileReader ::DICOMNullFileReader(const DICOMNullFileReader& other ) :itk::Object() ,DICOMFileReader(other) { } mitk::DICOMNullFileReader& mitk::DICOMNullFileReader ::operator=(const DICOMNullFileReader& other) { if (this != &other) { DICOMFileReader::operator=(other); } return *this; } +bool +mitk::DICOMNullFileReader +::operator==(const DICOMFileReader& other) const +{ + return dynamic_cast(&other) != NULL; // same class, we don't have members +} void mitk::DICOMNullFileReader ::InternalPrintConfiguration(std::ostream& os) const { os << "Nothing to configure" << std::endl; } void mitk::DICOMNullFileReader ::AnalyzeInputFiles() { this->ClearOutputs(); StringList inputFilenames = this->GetInputFiles(); this->SetNumberOfOutputs( inputFilenames.size() ); //generates one output for each input unsigned int o = 0; for (StringList::const_iterator inputIter = inputFilenames.begin(); inputIter != inputFilenames.end(); ++o, ++inputIter) { DICOMImageBlockDescriptor block; DICOMImageFrameList outputFrames; outputFrames.push_back( DICOMImageFrameInfo::New(*inputIter) ); block.SetImageFrameList( outputFrames ); this->SetOutput( o, block ); } } // void AllocateOutputImages(); bool mitk::DICOMNullFileReader ::LoadImages() { // does nothing return true; } bool mitk::DICOMNullFileReader ::CanHandleFile(const std::string& itkNotUsed(filename)) { return true; // can handle all } diff --git a/Modules/DICOMReader/Testing/mitkDICOMNullFileReader.h b/Modules/DICOMReader/Testing/mitkDICOMNullFileReader.h index 77e671c995..3b4ad007ff 100644 --- a/Modules/DICOMReader/Testing/mitkDICOMNullFileReader.h +++ b/Modules/DICOMReader/Testing/mitkDICOMNullFileReader.h @@ -1,55 +1,57 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef mitkDICOMNullFileReader_h #define mitkDICOMNullFileReader_h #include "mitkDICOMFileReader.h" namespace mitk { class DICOMNullFileReader : public DICOMFileReader { public: mitkClassMacro( DICOMNullFileReader, DICOMFileReader ); mitkCloneMacro( DICOMNullFileReader ); itkNewMacro( DICOMNullFileReader ); virtual void AnalyzeInputFiles(); // void AllocateOutputImages(); virtual bool LoadImages(); virtual bool CanHandleFile(const std::string& filename); + bool operator==(const DICOMFileReader& other) const; + protected: DICOMNullFileReader(); virtual ~DICOMNullFileReader(); DICOMNullFileReader(const DICOMNullFileReader& other); DICOMNullFileReader& operator=(const DICOMNullFileReader& other); void InternalPrintConfiguration(std::ostream& os) const; private: }; } #endif diff --git a/Modules/DICOMReader/Testing/mitkDICOMReaderConfiguratorTest.cpp b/Modules/DICOMReader/Testing/mitkDICOMReaderConfiguratorTest.cpp new file mode 100644 index 0000000000..cb442bde7b --- /dev/null +++ b/Modules/DICOMReader/Testing/mitkDICOMReaderConfiguratorTest.cpp @@ -0,0 +1,66 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#include "mitkDICOMReaderConfigurator.h" +#include "mitkDICOMFileReaderSelector.h" + +#include "mitkTestingMacros.h" + +/** + \brief Verify serialization ability of DICOMReaderConfigurator. +*/ +int mitkDICOMReaderConfiguratorTest(int /*argc*/, char* /*argv*/[]) +{ + MITK_TEST_BEGIN("mitkDICOMReaderConfiguratorTest"); + + mitk::DICOMFileReaderSelector::Pointer readerSource = mitk::DICOMFileReaderSelector::New(); + readerSource->LoadBuiltIn3DConfigs(); + readerSource->LoadBuiltIn3DnTConfigs(); + + mitk::DICOMFileReaderSelector::ReaderList allReaders = readerSource->GetAllConfiguredReaders(); // this already parses XML config for this reader + for (mitk::DICOMFileReaderSelector::ReaderList::iterator rIter = allReaders.begin(); + rIter != allReaders.end(); + ++rIter) + { + mitk::DICOMFileReader::Pointer originalReader = *rIter; + + MITK_TEST_OUTPUT(<< "********************************************************************************"); + MITK_TEST_OUTPUT(<< "Testing reader '" << originalReader->GetConfigurationLabel() << "'" ); + MITK_TEST_OUTPUT(<< "********************************************************************************"); + // ask for an XML serialization of this reader, then compare the XML files. + mitk::DICOMReaderConfigurator::Pointer serializer = mitk::DICOMReaderConfigurator::New(); + std::string xmlSerialization = serializer->CreateConfigStringFromReader( originalReader.GetPointer() ); + MITK_TEST_CONDITION( !xmlSerialization.empty(), "DICOMReaderConfigurator is able to serialize reader"); + MITK_INFO << "Got serialization:"; + std::cout << xmlSerialization << std::endl; + + mitk::DICOMReaderConfigurator::Pointer creator = mitk::DICOMReaderConfigurator::New(); + try + { + mitk::DICOMFileReader::Pointer reconstructedReader = creator->CreateFromUTF8ConfigString(xmlSerialization); + MITK_TEST_CONDITION( reconstructedReader.IsNotNull(), "DICOMReaderConfigurator is able to create reader from XML"); + + MITK_TEST_CONDITION( *reconstructedReader == *originalReader, "Readers are equal before and after serialization" ) + } + catch(std::exception& e) + { + MITK_TEST_CONDITION_REQUIRED(false, "Exception from DICOMReaderConfigurator: " << e.what()); + } + } + + MITK_TEST_END(); +} + diff --git a/Modules/DICOMReader/mitkClassicDICOMSeriesReader.cpp b/Modules/DICOMReader/mitkClassicDICOMSeriesReader.cpp index 8743a784c8..786e4d9ef0 100644 --- a/Modules/DICOMReader/mitkClassicDICOMSeriesReader.cpp +++ b/Modules/DICOMReader/mitkClassicDICOMSeriesReader.cpp @@ -1,77 +1,96 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkClassicDICOMSeriesReader.h" #include "mitkDICOMTagBasedSorter.h" #include "mitkDICOMSortByTag.h" #include "mitkSortByImagePositionPatient.h" mitk::ClassicDICOMSeriesReader ::ClassicDICOMSeriesReader() :ThreeDnTDICOMSeriesReader() { mitk::DICOMTagBasedSorter::Pointer tagSorter = mitk::DICOMTagBasedSorter::New(); // all the things that split by tag in mitk::DicomSeriesReader tagSorter->AddDistinguishingTag( DICOMTag(0x0020, 0x000e) ); // Series Instance UID //tagSorter->AddDistinguishingTag( DICOMTag(0x0020, 0x0052) ); // Frame of Reference UID // a sorter... mitk::DICOMSortCriterion::ConstPointer sorting = mitk::SortByImagePositionPatient::New( // image position patient and image orientation mitk::DICOMSortByTag::New( DICOMTag(0x0020, 0x0012), // aqcuisition number mitk::DICOMSortByTag::New( DICOMTag(0x0008, 0x0032), // aqcuisition time mitk::DICOMSortByTag::New( DICOMTag(0x0018, 0x1060), // trigger time mitk::DICOMSortByTag::New( DICOMTag(0x0008, 0x0018) // SOP instance UID (last resort, not really meaningful but decides clearly) ).GetPointer() ).GetPointer() ).GetPointer() ).GetPointer() ).GetPointer(); tagSorter->SetSortCriterion( sorting ); // define above sorting for this class this->AddSortingElement( tagSorter ); this->SetFixTiltByShearing(true); this->SetToleratedOriginOffset(0.005); this->SetGroup3DandT(true); + + this->SetConfigurationLabel("2013 sorting logic"); + this->SetConfigurationDescription("Sort by Image Position, then Acquisition Number, Time, Trigger time, group by 3D+t, group tilted images"); } mitk::ClassicDICOMSeriesReader ::ClassicDICOMSeriesReader(const ClassicDICOMSeriesReader& other ) :itk::Object() ,ThreeDnTDICOMSeriesReader(other) { } mitk::ClassicDICOMSeriesReader ::~ClassicDICOMSeriesReader() { } mitk::ClassicDICOMSeriesReader& mitk::ClassicDICOMSeriesReader ::operator=(const ClassicDICOMSeriesReader& other) { if (this != &other) { ThreeDnTDICOMSeriesReader::operator=(other); } return *this; } + +bool +mitk::ClassicDICOMSeriesReader +::operator==(const DICOMFileReader& other) const +{ + if (dynamic_cast(&other)) + { + return true; + } + else + { + return false; + } +} + + diff --git a/Modules/DICOMReader/mitkClassicDICOMSeriesReader.h b/Modules/DICOMReader/mitkClassicDICOMSeriesReader.h index e550c02b6c..00540d8429 100644 --- a/Modules/DICOMReader/mitkClassicDICOMSeriesReader.h +++ b/Modules/DICOMReader/mitkClassicDICOMSeriesReader.h @@ -1,81 +1,83 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef mitkClassicDICOMSeriesReader_h #define mitkClassicDICOMSeriesReader_h #include "mitkThreeDnTDICOMSeriesReader.h" #include "DICOMReaderExports.h" namespace mitk { /** \ingroup DICOMReaderModule \brief Sorting and grouping like mitk::DicomSeriesReader until 2013. This class implements the same functionality as the legacy class DicomSeriesReader, except that is is 75 lines instead of 2500 lines. \warning Since the old class is known to have problems with some series, it is advised to use a good configuration of DICOMITKSeriesGDCMReader, which can be obtained by using DICOMFileReaderSelector. The following text documents the actual sorting logic of this reader. The class groups datasets that have different values in any of the following tags: - (0028,0010) Number of Rows - (0028,0011) Number of Columns - (0028,0030) Pixel Spacing - (0018,1164) Imager Pixel Spacing - (0020,0037) %Image Orientation (Patient) - (0018,0050) Slice Thickness - (0028,0008) Number of Frames - (0020,000e) Series Instance UID Within each of the groups, datasets are sorted by the value of the following tags (primary sorting first): - (0020,0032) %Image Position (Patient) (distance from zero along normal of (0020,0037) %Image Orientation (Patient)) - (0020,0012) Aqcuisition Number - (0008,0032) Aqcuisition Time - (0018,1060) Trigger Time - (0008,0018) SOP Instance UID (last resort, not really meaningful but decides clearly) If the series was acquired using a tilted gantry, this will be "fixed" by applying a shear transformation. If multiple images occupy the same position in space, it is assumed that this indicated a 3D+t image. */ class DICOMReader_EXPORT ClassicDICOMSeriesReader : public ThreeDnTDICOMSeriesReader { public: mitkClassMacro( ClassicDICOMSeriesReader, DICOMITKSeriesGDCMReader ); mitkCloneMacro( ClassicDICOMSeriesReader ); itkNewMacro( ClassicDICOMSeriesReader ); + + virtual bool operator==(const DICOMFileReader& other) const; protected: ClassicDICOMSeriesReader(); virtual ~ClassicDICOMSeriesReader(); ClassicDICOMSeriesReader(const ClassicDICOMSeriesReader& other); ClassicDICOMSeriesReader& operator=(const ClassicDICOMSeriesReader& other); }; } #endif diff --git a/Modules/DICOMReader/mitkDICOMDatasetSorter.h b/Modules/DICOMReader/mitkDICOMDatasetSorter.h index 43a5980ebb..fcda4551af 100644 --- a/Modules/DICOMReader/mitkDICOMDatasetSorter.h +++ b/Modules/DICOMReader/mitkDICOMDatasetSorter.h @@ -1,93 +1,95 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef mitkDICOMDatasetSorter_h #define mitkDICOMDatasetSorter_h #include "itkObjectFactory.h" #include "mitkCommon.h" #include "mitkDICOMDatasetAccess.h" namespace mitk { /** \ingroup DICOMReaderModule \brief The sorting/splitting building-block of DICOMITKSeriesGDCMReader. This class describes the interface of the sorting/splitting process described as part of DICOMITKSeriesGDCMReader::AnalyzeInputFiles() (see \ref DICOMITKSeriesGDCMReader_LoadingStrategy). The prodecure is simple: - take a list of input datasets (DICOMDatasetAccess) - sort them (to be defined by sub-classes, based on specific tags) - return the sorting result as outputs (the single input might be distributed into multiple outputs) The simplest and most generic form of sorting is implemented in sub-class DICOMTagBasedSorter. */ class DICOMReader_EXPORT DICOMDatasetSorter : public itk::LightObject { public: mitkClassMacro( DICOMDatasetSorter, itk::LightObject ) /** \brief Return the tags of interest (to facilitate scanning) */ virtual DICOMTagList GetTagsOfInterest() = 0; /// \brief Input for sorting void SetInput(DICOMDatasetList filenames); /// \brief Input for sorting const DICOMDatasetList& GetInput() const; /// \brief Sort input datasets into one or multiple outputs. virtual void Sort() = 0; /// \brief Output of the sorting process. unsigned int GetNumberOfOutputs() const; /// \brief Output of the sorting process. const DICOMDatasetList& GetOutput(unsigned int index) const; /// \brief Output of the sorting process. DICOMDatasetList& GetOutput(unsigned int index); /// \brief Print configuration details into stream. virtual void PrintConfiguration(std::ostream& os, const std::string& indent = "") const = 0; + virtual bool operator==(const DICOMDatasetSorter& other) const = 0; + protected: DICOMDatasetSorter(); virtual ~DICOMDatasetSorter(); DICOMDatasetSorter(const DICOMDatasetSorter& other); DICOMDatasetSorter& operator=(const DICOMDatasetSorter& other); void ClearOutputs(); void SetNumberOfOutputs(unsigned int numberOfOutputs); void SetOutput(unsigned int index, const DICOMDatasetList& output); private: DICOMDatasetList m_Input; std::vector< DICOMDatasetList > m_Outputs; }; } #endif diff --git a/Modules/DICOMReader/mitkDICOMFileReader.cpp b/Modules/DICOMReader/mitkDICOMFileReader.cpp index 6fc0384428..5bed6f471f 100644 --- a/Modules/DICOMReader/mitkDICOMFileReader.cpp +++ b/Modules/DICOMReader/mitkDICOMFileReader.cpp @@ -1,197 +1,197 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkDICOMFileReader.h" #include mitk::DICOMFileReader ::DICOMFileReader() :itk::Object() { } mitk::DICOMFileReader ::~DICOMFileReader() { } mitk::DICOMFileReader ::DICOMFileReader(const DICOMFileReader& other ) :itk::Object() ,m_Outputs( other.m_Outputs ) ,m_ConfigLabel( other.m_ConfigLabel ) ,m_ConfigDescription( other.m_ConfigDescription ) { } mitk::DICOMFileReader& mitk::DICOMFileReader ::operator=(const DICOMFileReader& other) { if (this != &other) { m_InputFilenames = other.m_InputFilenames; m_Outputs = other.m_Outputs; m_ConfigLabel = other.m_ConfigLabel; m_ConfigDescription = other.m_ConfigDescription; } return *this; } void mitk::DICOMFileReader ::SetConfigurationLabel(const std::string& label) { m_ConfigLabel = label; } std::string mitk::DICOMFileReader -::GetConfigurationLabel() +::GetConfigurationLabel() const { return m_ConfigLabel; } void mitk::DICOMFileReader ::SetConfigurationDescription(const std::string& desc) { m_ConfigDescription = desc; } std::string mitk::DICOMFileReader -::GetConfigurationDescription() +::GetConfigurationDescription() const { return m_ConfigDescription; } void mitk::DICOMFileReader ::SetInputFiles(StringList filenames) { m_InputFilenames = filenames; } const mitk::StringList& mitk::DICOMFileReader ::GetInputFiles() const { return m_InputFilenames; } unsigned int mitk::DICOMFileReader ::GetNumberOfOutputs() const { return m_Outputs.size(); } void mitk::DICOMFileReader ::ClearOutputs() { m_Outputs.clear(); } void mitk::DICOMFileReader ::SetNumberOfOutputs(unsigned int numberOfOutputs) { m_Outputs.resize(numberOfOutputs); } void mitk::DICOMFileReader ::SetOutput(unsigned int index, const mitk::DICOMImageBlockDescriptor& output) { if (index < m_Outputs.size()) { m_Outputs[index] = output; } else { std::stringstream ss; ss << "Index " << index << " out of range (" << m_Outputs.size() << " indices reserved)"; throw std::invalid_argument( ss.str() ); } } void mitk::DICOMFileReader ::PrintConfiguration(std::ostream& os) const { os << "---- Configuration of " << this->GetNameOfClass() <<" " << (void*)this << " ----"<< std::endl; this->InternalPrintConfiguration(os); os << "---- End of configuration ----" << std::endl; } void mitk::DICOMFileReader ::PrintOutputs(std::ostream& os, bool filenameDetails) const { os << "---- Outputs of DICOMFilereader " << (void*)this << " ----"<< std::endl; for (unsigned int o = 0; o < m_Outputs.size(); ++o) { os << "-- Output " << o << std::endl; const DICOMImageBlockDescriptor& block = m_Outputs[o]; block.Print(os, filenameDetails); } os << "---- End of output list ----" << std::endl; } const mitk::DICOMImageBlockDescriptor& mitk::DICOMFileReader ::GetOutput(unsigned int index) const { if (index < m_Outputs.size()) { return m_Outputs[index]; } else { std::stringstream ss; ss << "Index " << index << " out of range (" << m_Outputs.size() << " indices reserved)"; throw std::invalid_argument( ss.str() ); } } mitk::DICOMImageBlockDescriptor& mitk::DICOMFileReader ::InternalGetOutput(unsigned int index) { if (index < m_Outputs.size()) { return m_Outputs[index]; } else { std::stringstream ss; ss << "Index " << index << " out of range (" << m_Outputs.size() << " indices reserved)"; throw std::invalid_argument( ss.str() ); } } bool mitk::DICOMFileReader ::IsDICOM(const std::string& filename) { itk::GDCMImageIO::Pointer io = itk::GDCMImageIO::New(); return io->CanReadFile( filename.c_str() ); } diff --git a/Modules/DICOMReader/mitkDICOMFileReader.h b/Modules/DICOMReader/mitkDICOMFileReader.h index 28d15e86ed..5ea8d9550c 100644 --- a/Modules/DICOMReader/mitkDICOMFileReader.h +++ b/Modules/DICOMReader/mitkDICOMFileReader.h @@ -1,126 +1,128 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef mitkDICOMFileReader_h #define mitkDICOMFileReader_h #include "itkObjectFactory.h" #include "mitkCommon.h" #include "DICOMReaderExports.h" #include "mitkDICOMImageBlockDescriptor.h" namespace mitk { // TODO Philips3D! // TODO http://bugs.mitk.org/show_bug.cgi?id=11572 ? /** \ingroup DICOMReaderModule \brief Interface for DICOM readers that produce mitk::Images. As described in \ref DICOMReaderModule, this class structures the reader's part in the process of analyzing a set of DICOM files and selecting the most appropriate reader. The overall loading process is as follows: - Define input files: a list of absolute filenames - Analyze the potential output: see what can be made of the input files, describe with DICOMImageBlockDescriptor%s - Load pixel data: an application will usually analyze files using multiple readers and only load with a single reader Sub-classes are required to implement a number of methods that reflect above structure. See mitk::DICOMITKSeriesGDCMReader for an example. To help applications in describing different readers to the user, each reader brings a number of methods that describe its configuration/specifics by means of a short label and a (longer) description. */ class DICOMReader_EXPORT DICOMFileReader : virtual public itk::Object { public: mitkClassMacro( DICOMFileReader, itk::Object ); /// Test whether a file is DICOM at all static bool IsDICOM(const std::string& filename); /// Indicate whether this reader can handle given file virtual bool CanHandleFile(const std::string& filename) = 0; /// This input files void SetInputFiles(StringList filenames); /// This input files const StringList& GetInputFiles() const; /// Analyze input files virtual void AnalyzeInputFiles() = 0; /// Number of outputs, only meaningful after calling AnalyzeInputFiles() unsigned int GetNumberOfOutputs() const; /// Individual outputs, only meaningful after calling AnalyzeInputFiles(). \throws std::invalid_argument const DICOMImageBlockDescriptor& GetOutput(unsigned int index) const; // void AllocateOutputImages(); TODO for later implementation of slice-by-slice loading /// Load the mitk::Image%s in our outputs, the DICOMImageBlockDescriptor. To be called only after AnalyzeInputFiles(). Take care of potential exceptions! virtual bool LoadImages() = 0; /// Short label/name to describe this reader void SetConfigurationLabel(const std::string&); /// Short label/name to describe this reader - std::string GetConfigurationLabel(); + std::string GetConfigurationLabel() const; /// One-sentence description of the reader's loading "strategy" void SetConfigurationDescription(const std::string&); /// One-sentence description of the reader's loading "strategy" - std::string GetConfigurationDescription(); + std::string GetConfigurationDescription() const; /// Print configuration description to given stream, for human reader void PrintConfiguration(std::ostream& os) const; /// Print output description to given stream, for human reader void PrintOutputs(std::ostream& os, bool filenameDetails = false) const; + + virtual bool operator==(const DICOMFileReader& other) const = 0; protected: DICOMFileReader(); virtual ~DICOMFileReader(); DICOMFileReader(const DICOMFileReader& other); DICOMFileReader& operator=(const DICOMFileReader& other); void ClearOutputs(); void SetNumberOfOutputs(unsigned int numberOfOutputs); void SetOutput(unsigned int index, const DICOMImageBlockDescriptor& output); /// non-const access to the DICOMImageBlockDescriptor DICOMImageBlockDescriptor& InternalGetOutput(unsigned int index); /// Configuration description for human reader, to be implemented by sub-classes virtual void InternalPrintConfiguration(std::ostream& os) const = 0; private: StringList m_InputFilenames; std::vector< DICOMImageBlockDescriptor > m_Outputs; std::string m_ConfigLabel; std::string m_ConfigDescription; }; } #endif diff --git a/Modules/DICOMReader/mitkDICOMFileReaderSelector.cpp b/Modules/DICOMReader/mitkDICOMFileReaderSelector.cpp index 9a13cab5e5..e45b9daec5 100644 --- a/Modules/DICOMReader/mitkDICOMFileReaderSelector.cpp +++ b/Modules/DICOMReader/mitkDICOMFileReaderSelector.cpp @@ -1,244 +1,244 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkDICOMFileReaderSelector.h" #include "mitkDICOMReaderConfigurator.h" #include "mitkModuleContext.h" #include #include #include #include mitk::DICOMFileReaderSelector ::DICOMFileReaderSelector() { } mitk::DICOMFileReaderSelector ::~DICOMFileReaderSelector() { } std::list mitk::DICOMFileReaderSelector ::GetAllConfiguredReaders() const { return m_Readers; } void mitk::DICOMFileReaderSelector ::AddConfigsFromResources(const std::string& path) { std::vector configs = GetModuleContext()->GetModule()->FindResources(path, "*.xml", false); for (std::vector::iterator iter = configs.begin(); iter != configs.end(); ++iter) { ModuleResource& resource = *iter; if (resource.IsValid()) { ModuleResourceStream stream(resource); // read all into string s std::string s; stream.seekg(0, std::ios::end); s.reserve(stream.tellg()); stream.seekg(0, std::ios::beg); s.assign((std::istreambuf_iterator(stream)), std::istreambuf_iterator()); this->AddConfig(s); } } } void mitk::DICOMFileReaderSelector ::AddConfigFromResource(ModuleResource& resource) { if (resource.IsValid()) { ModuleResourceStream stream(resource); // read all into string s std::string s; stream.seekg(0, std::ios::end); s.reserve(stream.tellg()); stream.seekg(0, std::ios::beg); s.assign((std::istreambuf_iterator(stream)), std::istreambuf_iterator()); this->AddConfig(s); } } void mitk::DICOMFileReaderSelector ::AddConfigFromResource(const std::string& resourcename) { ModuleResource r = GetModuleContext()->GetModule()->GetResource(resourcename); this->AddConfigFromResource(r); } void mitk::DICOMFileReaderSelector ::AddFileReaderCanditate(DICOMFileReader::Pointer reader) { if (reader.IsNotNull()) { m_Readers.push_back( reader ); } } void mitk::DICOMFileReaderSelector ::LoadBuiltIn3DConfigs() { //this->AddConfigsFromResources("configurations/3D"); // in this order of preference... - //this->AddConfigFromResource("configurations/3D/classicreader.xml"); this->AddConfigFromResource("configurations/3D/instancenumber.xml"); this->AddConfigFromResource("configurations/3D/imageposition.xml"); this->AddConfigFromResource("configurations/3D/imageposition_byacquisition.xml"); this->AddConfigFromResource("configurations/3D/slicelocation.xml"); this->AddConfigFromResource("configurations/3D/imagetime.xml"); + this->AddConfigFromResource("configurations/3D/classicreader.xml"); } void mitk::DICOMFileReaderSelector ::LoadBuiltIn3DnTConfigs() { this->AddConfigsFromResources("configurations/3DnT"); } void mitk::DICOMFileReaderSelector ::AddConfig(const std::string& xmlDescription) { DICOMReaderConfigurator::Pointer configurator = DICOMReaderConfigurator::New(); DICOMFileReader::Pointer reader = configurator->CreateFromUTF8ConfigString(xmlDescription); if (reader.IsNotNull()) { m_Readers.push_back( reader ); m_PossibleConfigurations.push_back(xmlDescription); } else { std::stringstream ss; ss << "Could not parse reader configuration. Ignoring it."; throw std::invalid_argument( ss.str() ); } } void mitk::DICOMFileReaderSelector ::AddConfigFile(const std::string& filename) { std::ifstream file(filename.c_str()); std::string s; file.seekg(0, std::ios::end); s.reserve(file.tellg()); file.seekg(0, std::ios::beg); s.assign((std::istreambuf_iterator(file)), std::istreambuf_iterator()); this->AddConfig(s); } void mitk::DICOMFileReaderSelector ::SetInputFiles(StringList filenames) { m_InputFilenames = filenames; } const mitk::StringList& mitk::DICOMFileReaderSelector ::GetInputFiles() const { return m_InputFilenames; } mitk::DICOMFileReader::Pointer mitk::DICOMFileReaderSelector ::GetFirstReaderWithMinimumNumberOfOutputImages() { ReaderList workingCandidates; // let all readers analyze the file set unsigned int readerIndex(0); for (ReaderList::iterator rIter = m_Readers.begin(); rIter != m_Readers.end(); ++readerIndex, ++rIter) { (*rIter)->SetInputFiles( m_InputFilenames ); try { (*rIter)->AnalyzeInputFiles(); workingCandidates.push_back( *rIter ); MITK_INFO << "Reader " << readerIndex << " (" << (*rIter)->GetConfigurationLabel() << ") suggests " << (*rIter)->GetNumberOfOutputs() << " 3D blocks"; if ((*rIter)->GetNumberOfOutputs() == 1) { MITK_DEBUG << "Early out with reader #" << readerIndex << " (" << (*rIter)->GetConfigurationLabel() << "), less than 1 block is not possible"; return *rIter; } } catch (std::exception& e) { MITK_ERROR << "Reader " << readerIndex << " (" << (*rIter)->GetConfigurationLabel() << ") threw exception during file analysis, ignoring this reader. Exception: " << e.what(); } catch (...) { MITK_ERROR << "Reader " << readerIndex << " (" << (*rIter)->GetConfigurationLabel() << ") threw unknown exception during file analysis, ignoring this reader."; } } DICOMFileReader::Pointer bestReader; unsigned int minimumNumberOfOutputs = std::numeric_limits::max(); readerIndex = 0; unsigned int bestReaderIndex(0); // select the reader with the minimum number of mitk::Images as output for (ReaderList::iterator rIter = workingCandidates.begin(); rIter != workingCandidates.end(); ++readerIndex, ++rIter) { unsigned int thisReadersNumberOfOutputs = (*rIter)->GetNumberOfOutputs(); if ( thisReadersNumberOfOutputs > 0 // we don't count readers that don't actually produce output && thisReadersNumberOfOutputs < minimumNumberOfOutputs ) { minimumNumberOfOutputs = (*rIter)->GetNumberOfOutputs(); bestReader = *rIter; bestReaderIndex = readerIndex; } } MITK_DEBUG << "Decided for reader #" << bestReaderIndex << " (" << bestReader->GetConfigurationLabel() << ")"; MITK_DEBUG << m_PossibleConfigurations[bestReaderIndex]; return bestReader; } diff --git a/Modules/DICOMReader/mitkDICOMITKSeriesGDCMReader.cpp b/Modules/DICOMReader/mitkDICOMITKSeriesGDCMReader.cpp index 6b86e08739..00553c0449 100644 --- a/Modules/DICOMReader/mitkDICOMITKSeriesGDCMReader.cpp +++ b/Modules/DICOMReader/mitkDICOMITKSeriesGDCMReader.cpp @@ -1,601 +1,690 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ //#define MBILOG_ENABLE_DEBUG #include "mitkDICOMITKSeriesGDCMReader.h" #include "mitkITKDICOMSeriesReaderHelper.h" #include "mitkGantryTiltInformation.h" #include "mitkDICOMTagBasedSorter.h" #include #include #include mitk::DICOMITKSeriesGDCMReader ::DICOMITKSeriesGDCMReader(unsigned int decimalPlacesForOrientation) :DICOMFileReader() ,m_FixTiltByShearing(true) +,m_DecimalPlacesForOrientation(decimalPlacesForOrientation) { this->EnsureMandatorySortersArePresent(decimalPlacesForOrientation); } mitk::DICOMITKSeriesGDCMReader ::DICOMITKSeriesGDCMReader(const DICOMITKSeriesGDCMReader& other ) :itk::Object() ,DICOMFileReader(other) ,DICOMTagCache(other) ,m_FixTiltByShearing(false) ,m_Sorter( other.m_Sorter ) // TODO should clone the list items ,m_EquiDistantBlocksSorter( other.m_EquiDistantBlocksSorter->Clone() ) ,m_NormalDirectionConsistencySorter( other.m_NormalDirectionConsistencySorter->Clone() ) +,m_DecimalPlacesForOrientation(other.m_DecimalPlacesForOrientation) { } mitk::DICOMITKSeriesGDCMReader ::~DICOMITKSeriesGDCMReader() { } mitk::DICOMITKSeriesGDCMReader& mitk::DICOMITKSeriesGDCMReader ::operator=(const DICOMITKSeriesGDCMReader& other) { if (this != &other) { DICOMFileReader::operator=(other); this->m_FixTiltByShearing = other.m_FixTiltByShearing; this->m_Sorter = other.m_Sorter; // TODO should clone the list items this->m_EquiDistantBlocksSorter = other.m_EquiDistantBlocksSorter->Clone(); this->m_NormalDirectionConsistencySorter = other.m_NormalDirectionConsistencySorter->Clone(); + this->m_DecimalPlacesForOrientation = other.m_DecimalPlacesForOrientation; } return *this; } +bool +mitk::DICOMITKSeriesGDCMReader +::operator==(const DICOMFileReader& other) const +{ + if (const Self* otherSelf = dynamic_cast(&other)) + { + if ( this->m_FixTiltByShearing == otherSelf->m_FixTiltByShearing + && *(this->m_EquiDistantBlocksSorter) == *(otherSelf->m_EquiDistantBlocksSorter) + && (fabs(this->m_DecimalPlacesForOrientation - otherSelf->m_DecimalPlacesForOrientation) < eps) + ) + { + // test sorters for equality + if (this->m_Sorter.size() != otherSelf->m_Sorter.size()) return false; + + SorterList::const_iterator mySorterIter = this->m_Sorter.begin(); + SorterList::const_iterator oSorterIter = otherSelf->m_Sorter.begin(); + for(; mySorterIter != this->m_Sorter.end() && oSorterIter != otherSelf->m_Sorter.end(); + ++mySorterIter, ++oSorterIter) + { + if ( ! (**mySorterIter == **oSorterIter ) ) return false; // this sorter differs + } + + // nothing differs ==> all is equal + return true; + } + else + { + return false; + } + } + else + { + return false; + } +} + void mitk::DICOMITKSeriesGDCMReader ::SetFixTiltByShearing(bool on) { m_FixTiltByShearing = on; } +bool +mitk::DICOMITKSeriesGDCMReader +::GetFixTiltByShearing() const +{ + return m_FixTiltByShearing; +} + mitk::DICOMGDCMImageFrameList mitk::DICOMITKSeriesGDCMReader ::FromDICOMDatasetList(const DICOMDatasetList& input) { DICOMGDCMImageFrameList output; output.reserve(input.size()); for(DICOMDatasetList::const_iterator inputIter = input.begin(); inputIter != input.end(); ++inputIter) { DICOMGDCMImageFrameInfo* gfi = dynamic_cast(*inputIter); assert(gfi); output.push_back(gfi); } return output; } mitk::DICOMDatasetList mitk::DICOMITKSeriesGDCMReader ::ToDICOMDatasetList(const DICOMGDCMImageFrameList& input) { DICOMDatasetList output; output.reserve(input.size()); for(DICOMGDCMImageFrameList::const_iterator inputIter = input.begin(); inputIter != input.end(); ++inputIter) { DICOMDatasetAccess* da = inputIter->GetPointer(); assert(da); output.push_back(da); } return output; } mitk::DICOMImageFrameList mitk::DICOMITKSeriesGDCMReader ::ToDICOMImageFrameList(const DICOMGDCMImageFrameList& input) { DICOMImageFrameList output; output.reserve(input.size()); for(DICOMGDCMImageFrameList::const_iterator inputIter = input.begin(); inputIter != input.end(); ++inputIter) { DICOMImageFrameInfo::Pointer fi = (*inputIter)->GetFrameInfo(); assert(fi.IsNotNull()); output.push_back(fi); } return output; } void mitk::DICOMITKSeriesGDCMReader ::InternalPrintConfiguration(std::ostream& os) const { unsigned int sortIndex(1); for(SorterList::const_iterator sorterIter = m_Sorter.begin(); sorterIter != m_Sorter.end(); ++sortIndex, ++sorterIter) { os << "Sorting step " << sortIndex << ":" << std::endl; (*sorterIter)->PrintConfiguration(os, " "); } os << "Sorting step " << sortIndex << ":" << std::endl; m_EquiDistantBlocksSorter->PrintConfiguration(os, " "); } std::string mitk::DICOMITKSeriesGDCMReader ::GetActiveLocale() const { return setlocale(LC_NUMERIC, NULL); } void mitk::DICOMITKSeriesGDCMReader ::PushLocale() const { std::string currentCLocale = setlocale(LC_NUMERIC, NULL); m_ReplacedCLocales.push( currentCLocale ); setlocale(LC_NUMERIC, "C"); std::locale currentCinLocale( std::cin.getloc() ); m_ReplacedCinLocales.push( currentCinLocale ); std::locale l( "C" ); std::cin.imbue(l); } void mitk::DICOMITKSeriesGDCMReader ::PopLocale() const { if (!m_ReplacedCLocales.empty()) { setlocale(LC_NUMERIC, m_ReplacedCLocales.top().c_str()); m_ReplacedCLocales.pop(); } else { MITK_WARN << "Mismatched PopLocale on DICOMITKSeriesGDCMReader."; } if (!m_ReplacedCinLocales.empty()) { std::cin.imbue( m_ReplacedCinLocales.top() ); m_ReplacedCinLocales.pop(); } else { MITK_WARN << "Mismatched PopLocale on DICOMITKSeriesGDCMReader."; } } mitk::DICOMITKSeriesGDCMReader::SortingBlockList mitk::DICOMITKSeriesGDCMReader ::Condense3DBlocks(SortingBlockList& input) { return input; // to be implemented differently by sub-classes } void mitk::DICOMITKSeriesGDCMReader ::AnalyzeInputFiles() { itk::TimeProbesCollectorBase timer; timer.Start("Reset"); this->ClearOutputs(); m_InputFrameList.clear(); m_GDCMScanner.ClearTags(); timer.Stop("Reset"); // prepare initial sorting (== list of input files) StringList inputFilenames = this->GetInputFiles(); timer.Start("Check appropriateness of input files"); if ( inputFilenames.empty() || !this->CanHandleFile( inputFilenames.front() ) // first || !this->CanHandleFile( inputFilenames.back() ) // last || !this->CanHandleFile( inputFilenames[ inputFilenames.size() / 2] ) // roughly central file ) { // TODO a read-as-many-as-possible fallback could be implemented here MITK_DEBUG << "Reader unable to process files.."; return; } timer.Stop("Check appropriateness of input files"); // scan files for sorting-relevant tags timer.Start("Setup scanning"); for(SorterList::iterator sorterIter = m_Sorter.begin(); sorterIter != m_Sorter.end(); ++sorterIter) { assert(sorterIter->IsNotNull()); DICOMTagList tags = (*sorterIter)->GetTagsOfInterest(); for(DICOMTagList::const_iterator tagIter = tags.begin(); tagIter != tags.end(); ++tagIter) { MITK_DEBUG << "Sorting uses tag " << tagIter->GetGroup() << "," << tagIter->GetElement(); m_GDCMScanner.AddTag( gdcm::Tag(tagIter->GetGroup(), tagIter->GetElement()) ); } } // Add some of our own interest // TODO all tags that are needed in DICOMImageBlockDescriptor should be added by DICOMFileReader (this code location here should query all superclasses for tags) m_GDCMScanner.AddTag( gdcm::Tag(0x0018,0x1164) ); // pixel spacing m_GDCMScanner.AddTag( gdcm::Tag(0x0028,0x0030) ); // imager pixel spacing m_GDCMScanner.AddTag( gdcm::Tag(0x0028,0x1050) ); // window center m_GDCMScanner.AddTag( gdcm::Tag(0x0028,0x1051) ); // window width m_GDCMScanner.AddTag( gdcm::Tag(0x0008,0x0008) ); // image type m_GDCMScanner.AddTag( gdcm::Tag(0x0028,0x0004) ); // photometric interpretation m_GDCMScanner.AddTag( gdcm::Tag(0x0020,0x1041) ); // slice location m_GDCMScanner.AddTag( gdcm::Tag(0x0020,0x0013) ); // instance number m_GDCMScanner.AddTag( gdcm::Tag(0x0008,0x0016) ); // sop class UID m_GDCMScanner.AddTag( gdcm::Tag(0x0008,0x0018) ); // sop instance UID m_GDCMScanner.AddTag( gdcm::Tag(0x0020,0x0011) ); // series number m_GDCMScanner.AddTag( gdcm::Tag(0x0008,0x1030) ); // study description m_GDCMScanner.AddTag( gdcm::Tag(0x0008,0x103e) ); // series description m_GDCMScanner.AddTag( gdcm::Tag(0x0008,0x0060) ); // modality m_GDCMScanner.AddTag( gdcm::Tag(0x0020,0x0012) ); // acquisition number m_GDCMScanner.AddTag( gdcm::Tag(0x0018,0x0024) ); // sequence name m_GDCMScanner.AddTag( gdcm::Tag(0x0020,0x0037) ); // image orientation m_GDCMScanner.AddTag( gdcm::Tag(0x0020,0x0032) ); // ipp timer.Stop("Setup scanning"); timer.Start("Tag scanning"); PushLocale(); m_GDCMScanner.Scan( inputFilenames ); PopLocale(); timer.Stop("Tag scanning"); timer.Start("Setup sorting"); for (StringList::const_iterator inputIter = inputFilenames.begin(); inputIter != inputFilenames.end(); ++inputIter) { m_InputFrameList.push_back( DICOMGDCMImageFrameInfo::New( DICOMImageFrameInfo::New(*inputIter, 0), m_GDCMScanner.GetMapping(inputIter->c_str()) ) ); } m_SortingResultInProgress.clear(); m_SortingResultInProgress.push_back( m_InputFrameList ); timer.Stop("Setup sorting"); // sort and split blocks as configured timer.Start("Sorting frames"); unsigned int sorterIndex = 0; for(SorterList::iterator sorterIter = m_Sorter.begin(); sorterIter != m_Sorter.end(); ++sorterIndex, ++sorterIter) { m_SortingResultInProgress = this->InternalExecuteSortingStep(sorterIndex, *sorterIter, m_SortingResultInProgress, &timer); } // a last extra-sorting step: ensure equidistant slices m_SortingResultInProgress = this->InternalExecuteSortingStep(sorterIndex++, m_EquiDistantBlocksSorter.GetPointer(), m_SortingResultInProgress, &timer); timer.Stop("Sorting frames"); timer.Start("Condensing 3D blocks (3D+t or vector values)"); m_SortingResultInProgress = this->Condense3DBlocks( m_SortingResultInProgress ); timer.Stop("Condensing 3D blocks (3D+t or vector values)"); // provide final result as output timer.Start("Output"); unsigned int o = this->GetNumberOfOutputs(); this->SetNumberOfOutputs( o + m_SortingResultInProgress.size() ); // Condense3DBlocks may already have added outputs! for (SortingBlockList::iterator blockIter = m_SortingResultInProgress.begin(); blockIter != m_SortingResultInProgress.end(); ++o, ++blockIter) { DICOMGDCMImageFrameList& gdcmFrameInfoList = *blockIter; assert(!gdcmFrameInfoList.empty()); // reverse frames if necessary // update tilt information from absolute last sorting DICOMDatasetList datasetList = ToDICOMDatasetList( gdcmFrameInfoList ); m_NormalDirectionConsistencySorter->SetInput( datasetList ); m_NormalDirectionConsistencySorter->Sort(); DICOMGDCMImageFrameList sortedGdcmInfoFrameList = FromDICOMDatasetList( m_NormalDirectionConsistencySorter->GetOutput(0) ); const GantryTiltInformation& tiltInfo = m_NormalDirectionConsistencySorter->GetTiltInformation(); // set frame list for current block DICOMImageFrameList frameList = ToDICOMImageFrameList( sortedGdcmInfoFrameList ); assert(!frameList.empty()); DICOMImageBlockDescriptor block; block.SetTagCache( this ); // important: this must be before SetImageFrameList(), because SetImageFrameList will trigger reading of lots of interesting tags! block.SetImageFrameList( frameList ); block.SetTiltInformation( tiltInfo ); block.SetReaderImplementationLevel( this->GetReaderImplementationLevel( block.GetSOPClassUID() ) ); this->SetOutput( o, block ); } timer.Stop("Output"); #ifdef MBILOG_ENABLE_DEBUG std::cout << "---------------------------------------------------------------" << std::endl; timer.Report( std::cout ); std::cout << "---------------------------------------------------------------" << std::endl; #endif } mitk::DICOMITKSeriesGDCMReader::SortingBlockList mitk::DICOMITKSeriesGDCMReader ::InternalExecuteSortingStep( unsigned int sortingStepIndex, DICOMDatasetSorter::Pointer sorter, const SortingBlockList& input, itk::TimeProbesCollectorBase* timer) { SortingBlockList nextStepSorting; // we should not modify our input list while processing it std::stringstream ss; ss << "Sorting step " << sortingStepIndex << " '"; sorter->PrintConfiguration(ss); ss << "'"; timer->Start( ss.str().c_str() ); nextStepSorting.clear(); MITK_DEBUG << "================================================================================"; MITK_DEBUG << "DICOMITKSeriesGDCMReader: " << ss.str() << ": " << input.size() << " groups input"; unsigned int groupIndex = 0; for(SortingBlockList::const_iterator blockIter = input.begin(); blockIter != input.end(); ++groupIndex, ++blockIter) { const DICOMGDCMImageFrameList& gdcmInfoFrameList = *blockIter; DICOMDatasetList datasetList = ToDICOMDatasetList( gdcmInfoFrameList ); MITK_DEBUG << "--------------------------------------------------------------------------------"; MITK_DEBUG << "DICOMITKSeriesGDCMReader: " << ss.str() << ", dataset group " << groupIndex << " (" << datasetList.size() << " datasets): "; for (DICOMDatasetList::iterator oi = datasetList.begin(); oi != datasetList.end(); ++oi) { MITK_DEBUG << " INPUT : " << (*oi)->GetFilenameIfAvailable(); } sorter->SetInput(datasetList); sorter->Sort(); unsigned int numberOfResultingBlocks = sorter->GetNumberOfOutputs(); for (unsigned int b = 0; b < numberOfResultingBlocks; ++b) { DICOMDatasetList blockResult = sorter->GetOutput(b); for (DICOMDatasetList::iterator oi = blockResult.begin(); oi != blockResult.end(); ++oi) { MITK_DEBUG << " OUTPUT(" << b << ") :" << (*oi)->GetFilenameIfAvailable(); } DICOMGDCMImageFrameList sortedGdcmInfoFrameList = FromDICOMDatasetList(blockResult); nextStepSorting.push_back( sortedGdcmInfoFrameList ); } } timer->Stop( ss.str().c_str() ); return nextStepSorting; } mitk::ReaderImplementationLevel mitk::DICOMITKSeriesGDCMReader ::GetReaderImplementationLevel(const std::string sopClassUID) const { if (sopClassUID.empty()) { return SOPClassUnknown; } gdcm::UIDs uidKnowledge; uidKnowledge.SetFromUID( sopClassUID.c_str() ); gdcm::UIDs::TSType gdcmType = uidKnowledge; switch (gdcmType) { case gdcm::UIDs::CTImageStorage: case gdcm::UIDs::MRImageStorage: case gdcm::UIDs::PositronEmissionTomographyImageStorage: case gdcm::UIDs::ComputedRadiographyImageStorage: case gdcm::UIDs::DigitalXRayImageStorageForPresentation: case gdcm::UIDs::DigitalXRayImageStorageForProcessing: return SOPClassSupported; case gdcm::UIDs::NuclearMedicineImageStorage: return SOPClassPartlySupported; case gdcm::UIDs::SecondaryCaptureImageStorage: return SOPClassImplemented; default: return SOPClassUnsupported; } } // void AllocateOutputImages(); bool mitk::DICOMITKSeriesGDCMReader ::LoadImages() { bool success = true; unsigned int numberOfOutputs = this->GetNumberOfOutputs(); for (unsigned int o = 0; o < numberOfOutputs; ++o) { success &= this->LoadMitkImageForOutput(o); } return success; } bool mitk::DICOMITKSeriesGDCMReader ::LoadMitkImageForImageBlockDescriptor(DICOMImageBlockDescriptor& block) const { PushLocale(); const DICOMImageFrameList& frames = block.GetImageFrameList(); const GantryTiltInformation tiltInfo = block.GetTiltInformation(); bool hasTilt = tiltInfo.IsRegularGantryTilt(); ITKDICOMSeriesReaderHelper::StringContainer filenames; for (DICOMImageFrameList::const_iterator frameIter = frames.begin(); frameIter != frames.end(); ++frameIter) { filenames.push_back( (*frameIter)->Filename ); } mitk::ITKDICOMSeriesReaderHelper helper; bool success(true); try { mitk::Image::Pointer mitkImage = helper.Load( filenames, m_FixTiltByShearing && hasTilt, tiltInfo ); block.SetMitkImage( mitkImage ); } catch (std::exception& e) { success = false; MITK_ERROR << "Exception during image loading: " << e.what(); } PopLocale(); return success; } bool mitk::DICOMITKSeriesGDCMReader ::LoadMitkImageForOutput(unsigned int o) { DICOMImageBlockDescriptor& block = this->InternalGetOutput(o); return this->LoadMitkImageForImageBlockDescriptor(block); } bool mitk::DICOMITKSeriesGDCMReader ::CanHandleFile(const std::string& filename) { return ITKDICOMSeriesReaderHelper::CanHandleFile(filename); } void mitk::DICOMITKSeriesGDCMReader ::AddSortingElement(DICOMDatasetSorter* sorter, bool atFront) { assert(sorter); if (atFront) { m_Sorter.push_front( sorter ); } else { m_Sorter.push_back( sorter ); } } +mitk::DICOMITKSeriesGDCMReader::ConstSorterList +mitk::DICOMITKSeriesGDCMReader +::GetFreelyConfiguredSortingElements() const +{ + std::list result; + + unsigned int sortIndex(0); + for(SorterList::const_iterator sorterIter = m_Sorter.begin(); + sorterIter != m_Sorter.end(); + ++sortIndex, ++sorterIter) + { + if (sortIndex > 0) // ignore first element (see EnsureMandatorySortersArePresent) + { + result.push_back( (*sorterIter).GetPointer() ); + } + } + + return result; +} + void mitk::DICOMITKSeriesGDCMReader ::EnsureMandatorySortersArePresent(unsigned int decimalPlacesForOrientation) { DICOMTagBasedSorter::Pointer splitter = DICOMTagBasedSorter::New(); splitter->AddDistinguishingTag( DICOMTag(0x0028, 0x0010) ); // Number of Rows splitter->AddDistinguishingTag( DICOMTag(0x0028, 0x0011) ); // Number of Columns splitter->AddDistinguishingTag( DICOMTag(0x0028, 0x0030) ); // Pixel Spacing splitter->AddDistinguishingTag( DICOMTag(0x0018, 0x1164) ); // Imager Pixel Spacing splitter->AddDistinguishingTag( DICOMTag(0x0020, 0x0037), new mitk::DICOMTagBasedSorter::CutDecimalPlaces(decimalPlacesForOrientation) ); // Image Orientation (Patient) splitter->AddDistinguishingTag( DICOMTag(0x0018, 0x0050) ); // Slice Thickness splitter->AddDistinguishingTag( DICOMTag(0x0028, 0x0008) ); // Number of Frames this->AddSortingElement( splitter, true ); // true = at front if (m_EquiDistantBlocksSorter.IsNull()) { m_EquiDistantBlocksSorter = mitk::EquiDistantBlocksSorter::New(); } m_EquiDistantBlocksSorter->SetAcceptTilt( m_FixTiltByShearing ); if (m_NormalDirectionConsistencySorter.IsNull()) { m_NormalDirectionConsistencySorter = mitk::NormalDirectionConsistencySorter::New(); } } void mitk::DICOMITKSeriesGDCMReader ::SetToleratedOriginOffsetToAdaptive(double fractionOfInterSliceDistance) { assert( m_EquiDistantBlocksSorter.IsNotNull() ); m_EquiDistantBlocksSorter->SetToleratedOriginOffsetToAdaptive(fractionOfInterSliceDistance); } void mitk::DICOMITKSeriesGDCMReader ::SetToleratedOriginOffset(double millimeters) { assert( m_EquiDistantBlocksSorter.IsNotNull() ); m_EquiDistantBlocksSorter->SetToleratedOriginOffset(millimeters); } +double +mitk::DICOMITKSeriesGDCMReader +::GetToleratedOriginError() const +{ + assert( m_EquiDistantBlocksSorter.IsNotNull() ); + return m_EquiDistantBlocksSorter->GetToleratedOriginOffset(); +} + +bool +mitk::DICOMITKSeriesGDCMReader +::IsToleratedOriginOffsetAbsolute() const +{ + assert( m_EquiDistantBlocksSorter.IsNotNull() ); + return m_EquiDistantBlocksSorter->IsToleratedOriginOffsetAbsolute(); +} + +double +mitk::DICOMITKSeriesGDCMReader +::GetDecimalPlacesForOrientation() const +{ + return m_DecimalPlacesForOrientation; +} + std::string mitk::DICOMITKSeriesGDCMReader ::GetTagValue(DICOMImageFrameInfo* frame, const DICOMTag& tag) const { // TODO inefficient. if (m_InputFrameList.contains(frame)) return frame->GetTagValueAsString(tag); for(DICOMGDCMImageFrameList::const_iterator frameIter = m_InputFrameList.begin(); frameIter != m_InputFrameList.end(); ++frameIter) { if ( (*frameIter)->GetFrameInfo().IsNotNull() && (*((*frameIter)->GetFrameInfo()) == *frame ) ) { return (*frameIter)->GetTagValueAsString(tag); } } return ""; } diff --git a/Modules/DICOMReader/mitkDICOMITKSeriesGDCMReader.h b/Modules/DICOMReader/mitkDICOMITKSeriesGDCMReader.h index e5a2ed742d..ee3975c3af 100644 --- a/Modules/DICOMReader/mitkDICOMITKSeriesGDCMReader.h +++ b/Modules/DICOMReader/mitkDICOMITKSeriesGDCMReader.h @@ -1,337 +1,351 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef mitkDICOMITKSeriesGDCMReader_h #define mitkDICOMITKSeriesGDCMReader_h #include "mitkDICOMFileReader.h" #include "mitkDICOMDatasetSorter.h" #include "mitkDICOMGDCMImageFrameInfo.h" #include "mitkEquiDistantBlocksSorter.h" #include "mitkNormalDirectionConsistencySorter.h" #include "mitkITKDICOMSeriesReaderHelper.h" #include "DICOMReaderExports.h" #include namespace itk { class TimeProbesCollectorBase; } namespace mitk { /** \ingroup DICOMReaderModule \brief Flexible reader based on itk::ImageSeriesReader and GDCM, for single-slice modalities like CT, MR, PET, CR, etc. Implements the loading processed as structured by DICOMFileReader offers configuration of its loading strategy. Documentation sections: - \ref DICOMITKSeriesGDCMReader_LoadingStrategy - \ref DICOMITKSeriesGDCMReader_ForcedConfiguration - \ref DICOMITKSeriesGDCMReader_UserConfiguration - \ref DICOMITKSeriesGDCMReader_GantryTilt - \ref DICOMITKSeriesGDCMReader_Testing - \ref DICOMITKSeriesGDCMReader_Internals - \ref DICOMITKSeriesGDCMReader_RelatedClasses - \ref DICOMITKSeriesGDCMReader_TiltInternals - \ref DICOMITKSeriesGDCMReader_Condensing \section DICOMITKSeriesGDCMReader_LoadingStrategy Loading strategy The set of input files is processed by a number of DICOMDatasetSorter objects which may do two sort of things: 1. split a list of input frames into multiple lists, based on DICOM tags such as "Rows", "Columns", which cannot be mixed within a single mitk::Image 2. sort the frames within the input lists, based on the values of DICOM tags such as "Image Position Patient" When the DICOMITKSeriesGDCMReader is configured with DICOMDatasetSorter%s, the list of input files is processed as follows: 1. build an initial set of output groups, simply by grouping all input files. 2. for each configured DICOMDatasetSorter, process: - for each output group: 1. set this group's files as input to the sorter 2. let the sorter sort (and split) 3. integrate the sorter's output groups with our own output groups \section DICOMITKSeriesGDCMReader_ForcedConfiguration Forced Configuration In all cases, the reader will add two DICOMDatasetSorter objects that are required to load mitk::Images properly via itk::ImageSeriesReader: 1. As a \b first step, the input files will be split into groups that are not compatible because they differ in essential aspects: - (0028,0010) Number of Rows - (0028,0011) Number of Columns - (0028,0030) Pixel Spacing - (0018,1164) Imager Pixel Spacing - (0020,0037) %Image Orientation (Patient) - (0018,0050) Slice Thickness - (0028,0008) Number of Frames 2. As are two forced \b last steps: 1. There will always be an instance of EquiDistantBlocksSorter, which ensures that there is an equal distance between all the frames of an Image. This is required to achieve correct geometrical positions in the mitk::Image, i.e. it is essential to be able to make measurements in images. - whether or not the distance is required to be orthogonal to the image planes is configured by SetFixTiltByShearing(). - during this check, we need to tolerate some minor errors in documented vs. calculated image origins. The amount of tolerance can be adjusted by SetToleratedOriginOffset() and SetToleratedOriginOffsetToAdaptive(). Please see EquiDistantBlocksSorter for more details. The default should be good for most cases. 2. There is always an instance of NormalDirectionConsistencySorter, which makes the order of images go along the image normals (see NormalDirectionConsistencySorter) \section DICOMITKSeriesGDCMReader_UserConfiguration User Configuration The user of this class can add more sorting steps (similar to the one described in above section) by calling AddSortingElement(). Usually, an application will add sorting by "Image Position Patient", by "Instance Number", and by other relevant tags here. \section DICOMITKSeriesGDCMReader_GantryTilt Gantry tilt handling When CT gantry tilt is used, the gantry plane (= X-Ray source and detector ring) and the vertical plane do not align anymore. This scanner feature is used for example to reduce metal artifacs (e.g. Lee C , Evaluation of Using CT Gantry Tilt Scan on Head and Neck Cancer Patients with Dental Structure: Scans Show Less Metal Artifacts. Presented at: Radiological Society of North America 2011 Scientific Assembly and Annual Meeting; November 27- December 2, 2011 Chicago IL.). The acquired planes of such CT series do not match the expectations of a orthogonal geometry in mitk::Image: if you stack the slices, they show a small shift along the Y axis: \verbatim without tilt with tilt |||||| ////// |||||| ////// -- |||||| --------- ////// -------- table orientation |||||| ////// |||||| ////// Stacked slices: without tilt with tilt -------------- -------------- -------------- -------------- -------------- -------------- -------------- -------------- -------------- -------------- \endverbatim As such gemetries do not "work" in conjunction with mitk::Image, DICOMITKSeriesGDCMReader is able to perform a correction for such series. Whether or not such correction should be attempted is controlled by SetFixTiltByShearing(), the default being correction. For details, see "Internals" below. \section DICOMITKSeriesGDCMReader_Testing Testing A number of tests is implemented in module DICOMTesting, which is documented at \ref DICOMTesting. \section DICOMITKSeriesGDCMReader_Internals Class internals Internally, the class is based on GDCM and it depends heavily on the gdcm::Scanner class. Since the sorting elements (see DICOMDatasetSorter and DICOMSortCriterion) can access tags only via the DICOMDatasetAccess interface, BUT DICOMITKSeriesGDCMReader holds a list of more specific classes DICOMGDCMImageFrameInfo, we must convert between the two types sometimes. This explains the methods ToDICOMDatasetList(), FromDICOMDatasetList(). The intermediate result of all the sorting efforts is held in m_SortingResultInProgress, which is modified through InternalExecuteSortingStep(). \subsection DICOMITKSeriesGDCMReader_RelatedClasses Overview of related classes The following diagram gives an overview of the related classes: \image html implementeditkseriesgdcmreader.jpg \subsection DICOMITKSeriesGDCMReader_TiltInternals Details about the tilt correction The gantry tilt "correction" algorithm fixes two errors introduced by ITK's ImageSeriesReader: - the plane shift that is ignored by ITK's reader is recreated by applying a shearing transformation using itk::ResampleFilter. - the spacing is corrected (it is calculated by ITK's reader from the distance between two origins, which is NOT the slice distance in this special case) Both errors are introduced in itkImageSeriesReader.txx (ImageSeriesReader::GenerateOutputInformation(void)), lines 176 to 245 (as of ITK 3.20) For the correction, we examine two consecutive slices of a series, both described as a pair (origin/orientation): - we calculate if the first origin is on a line along the normal of the second slice - if this is not the case, the geometry will not fit a normal mitk::Image/mitk::Geometry3D - we then project the second origin into the first slice's coordinate system to quantify the shift - both is done in class GantryTiltInformation with quite some comments. The geometry of image stacks with tilted geometries is illustrated below: - green: the DICOM images as described by their tags: origin as a point with the line indicating the orientation - red: the output of ITK ImageSeriesReader: wrong, larger spacing, no tilt - blue: how much a shear must correct \image html tilt-correction.jpg \subsection DICOMITKSeriesGDCMReader_Condensing Sub-classes can condense multiple blocks into a single larger block The sorting/splitting process described above is helpful for at least two more DICOM readers, which either try to load 3D+t images or which load diffusion data. In both cases, a single pixel of the mitk::Image is made up of multiple values, in one case values over time, in the other case multiple measurements of a single point. The specialized readers for these cases (e.g. ThreeDnTDICOMSeriesReader) can reuse most of the methods in DICOMITKSeriesGDCMReader, except that they need an extra step after the usual sorting, in which they can merge already grouped 3D blocks. What blocks are merged depends on the specialized reader's understanding of these images. To allow for such merging, a method Condense3DBlocks() is called as an absolute last step of AnalyzeInputFiles(). Given this, a sub-class could implement only LoadImages() and Condense3DBlocks() instead repeating most of AnalyzeInputFiles(). */ class DICOMReader_EXPORT DICOMITKSeriesGDCMReader : public DICOMFileReader, protected DICOMTagCache { public: mitkClassMacro( DICOMITKSeriesGDCMReader, DICOMFileReader ); mitkCloneMacro( DICOMITKSeriesGDCMReader ); itkNewMacro( DICOMITKSeriesGDCMReader ); mitkNewMacro1Param( DICOMITKSeriesGDCMReader, unsigned int ); /** \brief Runs the sorting / splitting process described in \ref DICOMITKSeriesGDCMReader_LoadingStrategy. Method required by DICOMFileReader. */ virtual void AnalyzeInputFiles(); // void AllocateOutputImages(); /** \brief Loads images using itk::ImageSeriesReader, potentially applies shearing to correct gantry tilt. */ virtual bool LoadImages(); // re-implemented from super-class virtual bool CanHandleFile(const std::string& filename); /** \brief Add an element to the sorting procedure described in \ref DICOMITKSeriesGDCMReader_LoadingStrategy. */ virtual void AddSortingElement(DICOMDatasetSorter* sorter, bool atFront = false); + + typedef const std::list ConstSorterList; + ConstSorterList GetFreelyConfiguredSortingElements() const; /** \brief Controls whether to "fix" tilted acquisitions by shearing the output (see \ref DICOMITKSeriesGDCMReader_GantryTilt). */ void SetFixTiltByShearing(bool on); + bool GetFixTiltByShearing() const; + /** \brief See \ref DICOMITKSeriesGDCMReader_ForcedConfiguration. */ void SetToleratedOriginOffsetToAdaptive(double fractionOfInterSliceDistanct = 0.3); /** \brief See \ref DICOMITKSeriesGDCMReader_ForcedConfiguration. */ void SetToleratedOriginOffset(double millimeters = 0.005); + double GetToleratedOriginError() const; + bool IsToleratedOriginOffsetAbsolute() const; + + double GetDecimalPlacesForOrientation() const; + + virtual bool operator==(const DICOMFileReader& other) const; + protected: virtual void InternalPrintConfiguration(std::ostream& os) const; // From DICOMTagCache virtual std::string GetTagValue(DICOMImageFrameInfo* frame, const DICOMTag& tag) const; /// \brief Return active C locale std::string GetActiveLocale() const; /** \brief Remember current locale on stack, activate "C" locale. "C" locale is required for correct parsing of numbers by itk::ImageSeriesReader */ void PushLocale() const; /** \brief Activate last remembered locale from locale stack "C" locale is required for correct parsing of numbers by itk::ImageSeriesReader */ void PopLocale() const; DICOMITKSeriesGDCMReader(unsigned int decimalPlacesForOrientation = 5); virtual ~DICOMITKSeriesGDCMReader(); DICOMITKSeriesGDCMReader(const DICOMITKSeriesGDCMReader& other); DICOMITKSeriesGDCMReader& operator=(const DICOMITKSeriesGDCMReader& other); /// \brief See \ref DICOMITKSeriesGDCMReader_Internals DICOMDatasetList ToDICOMDatasetList(const DICOMGDCMImageFrameList& input); /// \brief See \ref DICOMITKSeriesGDCMReader_Internals DICOMGDCMImageFrameList FromDICOMDatasetList(const DICOMDatasetList& input); /// \brief See \ref DICOMITKSeriesGDCMReader_Internals DICOMImageFrameList ToDICOMImageFrameList(const DICOMGDCMImageFrameList& input); typedef std::list SortingBlockList; /** \brief "Hook" for sub-classes, see \ref DICOMITKSeriesGDCMReader_Condensing \return REMAINING blocks */ virtual SortingBlockList Condense3DBlocks(SortingBlockList& resultOf3DGrouping); /// \brief Sorting step as described in \ref DICOMITKSeriesGDCMReader_LoadingStrategy SortingBlockList InternalExecuteSortingStep( unsigned int sortingStepIndex, DICOMDatasetSorter::Pointer sorter, const SortingBlockList& input, itk::TimeProbesCollectorBase* timer); /// \brief Loads the mitk::Image by means of an itk::ImageSeriesReader virtual bool LoadMitkImageForOutput(unsigned int o); virtual bool LoadMitkImageForImageBlockDescriptor(DICOMImageBlockDescriptor& block) const; /** \brief Shear the loaded mitk::Image to "correct" a spatial error introduced by itk::ImageSeriesReader See \ref DICOMITKSeriesGDCMReader_GantryTilt for details. */ Image::Pointer FixupSpacing(Image* mitkImage, const DICOMImageBlockDescriptor& block) const; /// \brief Describe this reader's confidence for given SOP class UID ReaderImplementationLevel GetReaderImplementationLevel(const std::string sopClassUID) const; private: /// \brief Creates the required sorting steps described in \ref DICOMITKSeriesGDCMReader_ForcedConfiguration void EnsureMandatorySortersArePresent(unsigned int decimalPlacesForOrientation); protected: // NOT nice, made available to ThreeDnTDICOMSeriesReader due to lack of time bool m_FixTiltByShearing; // could be removed by ITKDICOMSeriesReader NOT flagging tilt unless requested to fix it! private: SortingBlockList m_SortingResultInProgress; typedef std::list SorterList; SorterList m_Sorter; mitk::EquiDistantBlocksSorter::Pointer m_EquiDistantBlocksSorter; protected: // NOT nice, made available to ThreeDnTDICOMSeriesReader due to lack of time mitk::NormalDirectionConsistencySorter::Pointer m_NormalDirectionConsistencySorter; private: mutable std::stack m_ReplacedCLocales; mutable std::stack m_ReplacedCinLocales; DICOMGDCMImageFrameList m_InputFrameList; gdcm::Scanner m_GDCMScanner; + + double m_DecimalPlacesForOrientation; }; } #endif diff --git a/Modules/DICOMReader/mitkDICOMReaderConfigurator.cpp b/Modules/DICOMReader/mitkDICOMReaderConfigurator.cpp index 4cacba94ce..3815c07bc4 100644 --- a/Modules/DICOMReader/mitkDICOMReaderConfigurator.cpp +++ b/Modules/DICOMReader/mitkDICOMReaderConfigurator.cpp @@ -1,365 +1,620 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkDICOMReaderConfigurator.h" -#include "mitkClassicDICOMSeriesReader.h" - -#include "mitkDICOMTagBasedSorter.h" - #include "mitkDICOMSortByTag.h" #include "mitkSortByImagePositionPatient.h" mitk::DICOMReaderConfigurator ::DICOMReaderConfigurator() { } mitk::DICOMReaderConfigurator ::~DICOMReaderConfigurator() { } mitk::DICOMFileReader::Pointer mitk::DICOMReaderConfigurator -::CreateFromConfigFile(const std::string& filename) +::CreateFromConfigFile(const std::string& filename) const { TiXmlDocument doc (filename); if (doc.LoadFile()) { return this->CreateFromTiXmlDocument( doc ); } else { MITK_ERROR << "Unable to load file at '" << filename <<"'"; return DICOMFileReader::Pointer(); } } mitk::DICOMFileReader::Pointer mitk::DICOMReaderConfigurator -::CreateFromUTF8ConfigString(const std::string& xmlContents) +::CreateFromUTF8ConfigString(const std::string& xmlContents) const { TiXmlDocument doc; doc.Parse(xmlContents.c_str(), 0, TIXML_ENCODING_UTF8); return this->CreateFromTiXmlDocument( doc ); } mitk::DICOMFileReader::Pointer mitk::DICOMReaderConfigurator -::CreateFromTiXmlDocument(TiXmlDocument& doc) +::CreateFromTiXmlDocument(TiXmlDocument& doc) const { TiXmlHandle root(doc.RootElement()); if (TiXmlElement* rootElement = root.ToElement()) { if (strcmp(rootElement->Value(), "DICOMFileReader")) // :-( no std::string methods { MITK_ERROR << "File should contain a tag at top-level! Found '" << (rootElement->Value() ? std::string(rootElement->Value()) : std::string("!nothing!")) << "' instead"; return NULL; } const char* classnameC = rootElement->Attribute("class"); if (!classnameC) { MITK_ERROR << "File should name a reader class in the class attribute: . Found nothing instead"; return NULL; } std::string classname(classnameC); double decimalPlacesForOrientation(5); bool useDecimalPlacesForOrientation(false); useDecimalPlacesForOrientation = rootElement->QueryDoubleAttribute("decimalPlacesForOrientation", &decimalPlacesForOrientation) == TIXML_SUCCESS; // attribute present and a double value + if (classname == "ClassicDICOMSeriesReader") + { + mitk::ClassicDICOMSeriesReader::Pointer reader = mitk::ClassicDICOMSeriesReader::New(); + return reader.GetPointer(); + } if (classname == "ThreeDnTDICOMSeriesReader") { mitk::ThreeDnTDICOMSeriesReader::Pointer reader; if (useDecimalPlacesForOrientation) reader = mitk::ThreeDnTDICOMSeriesReader::New(decimalPlacesForOrientation); else reader = mitk::ThreeDnTDICOMSeriesReader::New(); return ConfigureThreeDnTDICOMSeriesReader(reader, rootElement).GetPointer(); } else if (classname == "DICOMITKSeriesGDCMReader") { mitk::DICOMITKSeriesGDCMReader::Pointer reader; if (useDecimalPlacesForOrientation) reader = mitk::DICOMITKSeriesGDCMReader::New(decimalPlacesForOrientation); else reader = mitk::DICOMITKSeriesGDCMReader::New(); return ConfigureDICOMITKSeriesGDCMReader(reader, rootElement).GetPointer(); } else { MITK_ERROR << "DICOMFileReader tag names unknown class '" << classname << "'"; return NULL; } } else { MITK_ERROR << "Great confusion: no root element in XML document. Expecting a DICOMFileReader tag at top-level."; return NULL; } } #define boolStringTrue(s) \ ( s == "true" || s == "on" || s == "1" \ || s == "TRUE" || s == "ON") mitk::ThreeDnTDICOMSeriesReader::Pointer mitk::DICOMReaderConfigurator -::ConfigureThreeDnTDICOMSeriesReader(ThreeDnTDICOMSeriesReader::Pointer reader, TiXmlElement* element) +::ConfigureThreeDnTDICOMSeriesReader(ThreeDnTDICOMSeriesReader::Pointer reader, TiXmlElement* element) const { assert(element); // use all the base class configuration if (this->ConfigureDICOMITKSeriesGDCMReader( reader.GetPointer(), element ).IsNull()) { return NULL; } // add the "group3DnT" flag bool group3DnT(true); const char* group3DnTC = element->Attribute("group3DnT"); if (group3DnTC) { std::string group3DnTS(group3DnTC); group3DnT = boolStringTrue(group3DnTS); } reader->SetGroup3DandT( group3DnT ); return reader; } mitk::DICOMITKSeriesGDCMReader::Pointer mitk::DICOMReaderConfigurator -::ConfigureDICOMITKSeriesGDCMReader(DICOMITKSeriesGDCMReader::Pointer reader, TiXmlElement* element) +::ConfigureDICOMITKSeriesGDCMReader(DICOMITKSeriesGDCMReader::Pointer reader, TiXmlElement* element) const { assert(element); const char* configLabelC = element->Attribute("label"); if (configLabelC) { std::string configLabel(configLabelC); reader->SetConfigurationLabel(configLabel); } const char* configDescriptionC = element->Attribute("description"); if (configDescriptionC) { std::string configDescription(configDescriptionC); reader->SetConfigurationDescription(configDescriptionC); } // "fixTiltByShearing" flag bool fixTiltByShearing(false); const char* fixTiltByShearingC = element->Attribute("fixTiltByShearing"); if (fixTiltByShearingC) { std::string fixTiltByShearingS(fixTiltByShearingC); fixTiltByShearing = boolStringTrue(fixTiltByShearingS); } reader->SetFixTiltByShearing( fixTiltByShearing ); // "toleratedOriginError" attribute (double) - double toleratedOriginError(-0.3); + bool toleratedOriginErrorIsAbsolute(false); + const char* toleratedOriginErrorIsAbsoluteC = element->Attribute("toleratedOriginErrorIsAbsolute"); + if (toleratedOriginErrorIsAbsoluteC) + { + std::string toleratedOriginErrorIsAbsoluteS(toleratedOriginErrorIsAbsoluteC); + toleratedOriginErrorIsAbsolute = boolStringTrue(toleratedOriginErrorIsAbsoluteS); + } + + double toleratedOriginError(0.3); if (element->QueryDoubleAttribute("toleratedOriginError", &toleratedOriginError) == TIXML_SUCCESS) // attribute present and a double value { - if (toleratedOriginError >= 0.0) + if (toleratedOriginErrorIsAbsolute) { reader->SetToleratedOriginOffset( toleratedOriginError ); } else { - reader->SetToleratedOriginOffsetToAdaptive( -1.0 * toleratedOriginError ); + reader->SetToleratedOriginOffsetToAdaptive( toleratedOriginError ); } } + // DICOMTagBasedSorters are the only thing we create at this point + // TODO for-loop over all child elements of type DICOMTagBasedSorter, BUT actually a single sorter of this type is enough. + TiXmlElement* dElement = element->FirstChildElement("DICOMDatasetSorter"); + if (dElement) + { + const char* classnameC = dElement->Attribute("class"); + if (!classnameC) + { + MITK_ERROR << "File should name a DICOMDatasetSorter class in the class attribute of . Found nothing instead"; + return NULL; + } + std::string classname(classnameC); - // "distinguishing tags" + if (classname == "DICOMTagBasedSorter") + { + DICOMTagBasedSorter::Pointer tagSorter = CreateDICOMTagBasedSorter(dElement); + if (tagSorter.IsNotNull()) + { + reader->AddSortingElement( tagSorter ); + } + } + else + { + MITK_ERROR << "DICOMDatasetSorter tag names unknown class '" << classname << "'"; + return NULL; + } + } + + return reader; +} + +mitk::DICOMTagBasedSorter::Pointer +mitk::DICOMReaderConfigurator +::CreateDICOMTagBasedSorter(TiXmlElement* element) const +{ mitk::DICOMTagBasedSorter::Pointer tagSorter = mitk::DICOMTagBasedSorter::New(); TiXmlElement* dElement = element->FirstChildElement("Distinguishing"); if (dElement) { for ( TiXmlElement* tChild = dElement->FirstChildElement(); tChild != NULL; tChild = tChild->NextSiblingElement() ) { try { mitk::DICOMTag tag = tagFromXMLElement(tChild); - tagSorter->AddDistinguishingTag( tag ); - + int i(5); + if (tChild->QueryIntAttribute("cutDecimalPlaces", &i) == TIXML_SUCCESS) + { + tagSorter->AddDistinguishingTag( tag, new mitk::DICOMTagBasedSorter::CutDecimalPlaces(i) ); + } + else + { + tagSorter->AddDistinguishingTag( tag ); + } } catch(...) { return NULL; } } } // "sorting tags" TiXmlElement* sElement = element->FirstChildElement("Sorting"); - if (dElement) + if (sElement) { DICOMSortCriterion::Pointer previousCriterion; DICOMSortCriterion::Pointer currentCriterion; for ( TiXmlNode* tChildNode = sElement->LastChild(); tChildNode != NULL; tChildNode = tChildNode->PreviousSibling() ) { TiXmlElement* tChild = tChildNode->ToElement(); if (!tChild) continue; if (!strcmp(tChild->Value(), "Tag")) { try { currentCriterion = this->CreateDICOMSortByTag(tChild, previousCriterion); } catch(...) { std::stringstream ss; ss << "Could not parse element at (input line " << tChild->Row() << ", col. " << tChild->Column() << ")!"; MITK_ERROR << ss.str(); return NULL; } } else if (!strcmp(tChild->Value(), "ImagePositionPatient")) { try { currentCriterion = this->CreateSortByImagePositionPatient(tChild, previousCriterion); } catch(...) { std::stringstream ss; ss << "Could not parse element at (input line " << tChild->Row() << ", col. " << tChild->Column() << ")!"; MITK_ERROR << ss.str(); return NULL; } } else { MITK_ERROR << "File contain unknown tag <" << tChild->Value() << "> tag as child to ! Cannot interpret..."; } previousCriterion = currentCriterion; } tagSorter->SetSortCriterion( currentCriterion.GetPointer() ); } - reader->AddSortingElement( tagSorter ); - - return reader; + return tagSorter; } std::string mitk::DICOMReaderConfigurator -::requiredStringAttribute(TiXmlElement* xmlElement, const std::string& key) +::requiredStringAttribute(TiXmlElement* xmlElement, const std::string& key) const { assert(xmlElement); const char* gC = xmlElement->Attribute(key.c_str()); if (gC) { std::string gS(gC); return gS; } else { std::stringstream ss; ss << "Expected an attribute '" << key << "' at this position " "(input line " << xmlElement->Row() << ", col. " << xmlElement->Column() << ")!"; MITK_ERROR << ss.str(); throw std::invalid_argument( ss.str() ); } } unsigned int mitk::DICOMReaderConfigurator -::hexStringToUInt(const std::string& s) +::hexStringToUInt(const std::string& s) const { std::stringstream converter(s); unsigned int ui; converter >> std::hex >> ui; MITK_DEBUG << "Converted string '" << s << "' to unsigned int " << ui; return ui; } mitk::DICOMTag mitk::DICOMReaderConfigurator -::tagFromXMLElement(TiXmlElement* xmlElement) +::tagFromXMLElement(TiXmlElement* xmlElement) const { assert(xmlElement); if (strcmp(xmlElement->Value(), "Tag")) // :-( no std::string methods { std::stringstream ss; ss << "Expected a tag at this position " "(input line " << xmlElement->Row() << ", col. " << xmlElement->Column() << ")!"; MITK_ERROR << ss.str(); throw std::invalid_argument( ss.str() ); } std::string name = requiredStringAttribute(xmlElement, "name"); std::string groupS = requiredStringAttribute(xmlElement, "group"); std::string elementS = requiredStringAttribute(xmlElement, "element"); try { // convert string to int (assuming string is in hex format with leading "0x" like "0x0020") unsigned int group = hexStringToUInt(groupS); unsigned int element = hexStringToUInt(elementS); return DICOMTag(group, element); } catch(...) { std::stringstream ss; ss << "Expected group and element values in to be hexadecimal with leading 0x, e.g. '0x0020'" "(input line " << xmlElement->Row() << ", col. " << xmlElement->Column() << ")!"; MITK_ERROR << ss.str(); throw std::invalid_argument( ss.str() ); } } mitk::DICOMSortCriterion::Pointer mitk::DICOMReaderConfigurator -::CreateDICOMSortByTag(TiXmlElement* xmlElement, DICOMSortCriterion::Pointer secondaryCriterion) +::CreateDICOMSortByTag(TiXmlElement* xmlElement, DICOMSortCriterion::Pointer secondaryCriterion) const { mitk::DICOMTag tag = tagFromXMLElement(xmlElement); return DICOMSortByTag::New(tag, secondaryCriterion).GetPointer(); } mitk::DICOMSortCriterion::Pointer mitk::DICOMReaderConfigurator -::CreateSortByImagePositionPatient(TiXmlElement*, DICOMSortCriterion::Pointer secondaryCriterion) +::CreateSortByImagePositionPatient(TiXmlElement*, DICOMSortCriterion::Pointer secondaryCriterion) const { return SortByImagePositionPatient::New(secondaryCriterion).GetPointer(); } + + + +std::string +mitk::DICOMReaderConfigurator +::CreateConfigStringFromReader(DICOMFileReader::ConstPointer reader) const +{ + // check possible sub-classes from the most-specific one up to the most generic one + const DICOMFileReader* cPointer = reader; + TiXmlElement* root; + if (const ClassicDICOMSeriesReader* specificReader = dynamic_cast(cPointer)) + { + root = this->CreateConfigStringFromReader(specificReader); + } + else + if (const ThreeDnTDICOMSeriesReader* specificReader = dynamic_cast(cPointer)) + { + root = this->CreateConfigStringFromReader(specificReader); + } + else + if (const DICOMITKSeriesGDCMReader* specificReader = dynamic_cast(cPointer)) + { + root = this->CreateConfigStringFromReader(specificReader); + } + else + { + MITK_WARN << "Unknown reader class passed to DICOMReaderConfigurator::CreateConfigStringFromReader(). Cannot serialize."; + return ""; // no serialization, what a pity + } + + if (root) + { + TiXmlDocument document; + document.LinkEndChild( root ); + + TiXmlPrinter printer; + printer.SetIndent( " " ); + + document.Accept( &printer ); + std::string xmltext = printer.CStr(); + return xmltext; + } + else + { + MITK_WARN << "DICOMReaderConfigurator::CreateConfigStringFromReader() created empty serialization. Problem?"; + return ""; + } +} + +TiXmlElement* +mitk::DICOMReaderConfigurator +::CreateConfigStringFromReader(const DICOMITKSeriesGDCMReader* reader) const +{ + TiXmlElement* root = this->CreateDICOMFileReaderTag(reader); + assert(root); + + root->SetAttribute("fixTiltByShearing", toString(reader->GetFixTiltByShearing())); + root->SetDoubleAttribute("toleratedOriginError", reader->GetToleratedOriginError()); + root->SetAttribute("toleratedOriginErrorIsAbsolute", toString(reader->IsToleratedOriginOffsetAbsolute())); + root->SetDoubleAttribute("decimalPlacesForOrientation", reader->GetDecimalPlacesForOrientation()); + + // iterate DICOMDatasetSorter objects + DICOMITKSeriesGDCMReader::ConstSorterList sorterList = reader->GetFreelyConfiguredSortingElements(); + for(DICOMITKSeriesGDCMReader::ConstSorterList::const_iterator sorterIter = sorterList.begin(); + sorterIter != sorterList.end(); + ++sorterIter) + { + const DICOMDatasetSorter* sorter = *sorterIter; + if (const DICOMTagBasedSorter* specificSorter = dynamic_cast(sorter)) + { + TiXmlElement* sorterTag = this->CreateConfigStringFromDICOMDatasetSorter(specificSorter); + root->LinkEndChild(sorterTag); + } + else + { + MITK_WARN << "Unknown DICOMDatasetSorter class passed to DICOMReaderConfigurator::CreateConfigStringFromReader(). Cannot serialize."; + return NULL; + } + } + + return root; +} + +TiXmlElement* +mitk::DICOMReaderConfigurator +::CreateConfigStringFromDICOMDatasetSorter(const DICOMTagBasedSorter* sorter) const +{ + assert(sorter); + + TiXmlElement* sorterTag = new TiXmlElement("DICOMDatasetSorter"); + sorterTag->SetAttribute("class", sorter->GetNameOfClass()); + + TiXmlElement* distinguishingTagsElement = new TiXmlElement("Distinguishing"); + sorterTag->LinkEndChild(distinguishingTagsElement); + mitk::DICOMTagList distinguishingTags = sorter->GetDistinguishingTags(); + for (DICOMTagList::iterator tagIter = distinguishingTags.begin(); + tagIter != distinguishingTags.end(); + ++tagIter) + { + TiXmlElement* tag = this->CreateConfigStringFromDICOMTag(*tagIter); + distinguishingTagsElement->LinkEndChild(tag); + + const DICOMTagBasedSorter::TagValueProcessor* processor = sorter->GetTagValueProcessorForDistinguishingTag(*tagIter); + if (const DICOMTagBasedSorter::CutDecimalPlaces* specificProcessor = dynamic_cast(processor)) + { + tag->SetDoubleAttribute("cutDecimalPlaces", specificProcessor->GetPrecision()); + } + } + + TiXmlElement* sortingElement = new TiXmlElement("Sorting"); + sorterTag->LinkEndChild(sortingElement); + mitk::DICOMSortCriterion::ConstPointer sortCriterion = sorter->GetSortCriterion(); + while (sortCriterion.IsNotNull()) + { + std::string classname = sortCriterion->GetNameOfClass(); + if (classname == "SortByImagePositionPatient") + { + sortingElement->LinkEndChild( new TiXmlElement("ImagePositionPatient") ); // no parameters + } + else + if (classname == "DICOMSortByTag") + { + DICOMTagList pseudoTagList = sortCriterion->GetTagsOfInterest(); + if (pseudoTagList.size() == 1) + { + DICOMTag firstTag = pseudoTagList.front(); + + TiXmlElement* tagElement = this->CreateConfigStringFromDICOMTag(firstTag); + + sortingElement->LinkEndChild( tagElement ); + } + else + { + MITK_ERROR << "Encountered SortByTag class with MULTIPLE tag in CreateConfigStringFromDICOMDatasetSorter. Cannot serialize."; + return NULL; + } + } + else + { + MITK_ERROR << "Encountered unknown class '" << classname << "' in CreateConfigStringFromDICOMDatasetSorter. Cannot serialize."; + return NULL; + } + + sortCriterion = sortCriterion->GetSecondaryCriterion(); + } + + return sorterTag; +} + +TiXmlElement* +mitk::DICOMReaderConfigurator +::CreateConfigStringFromDICOMTag(const DICOMTag& tag) const +{ + TiXmlElement* tagElement = new TiXmlElement("Tag"); // name group element + tagElement->SetAttribute("name", tag.GetName().c_str()); + tagElement->SetAttribute("group", toHexString(tag.GetGroup())); + tagElement->SetAttribute("element", toHexString(tag.GetElement())); + return tagElement; +} + +std::string +mitk::DICOMReaderConfigurator +::toHexString(unsigned int i) const +{ + std::stringstream ss; + ss << "0x" << std::setfill ('0') << std::setw(4) << std::hex << i; + return ss.str(); +} + + +TiXmlElement* +mitk::DICOMReaderConfigurator +::CreateConfigStringFromReader(const ThreeDnTDICOMSeriesReader* reader) const +{ + TiXmlElement* root = this->CreateConfigStringFromReader(static_cast(reader)); + assert(root); + + root->SetAttribute("group3DnT", toString(reader->GetGroup3DandT())); + + return root; +} + +const char* +mitk::DICOMReaderConfigurator +::toString(bool b) const +{ + return b ? "true" : "false"; +} + +TiXmlElement* +mitk::DICOMReaderConfigurator +::CreateConfigStringFromReader(const ClassicDICOMSeriesReader* reader) const +{ + return this->CreateDICOMFileReaderTag(reader); +} + +TiXmlElement* +mitk::DICOMReaderConfigurator +::CreateDICOMFileReaderTag(const DICOMFileReader* reader) const +{ + TiXmlElement* readerTag = new TiXmlElement("DICOMFileReader"); + readerTag->SetAttribute("class", reader->GetNameOfClass()); + readerTag->SetAttribute("label", reader->GetConfigurationLabel().c_str()); + readerTag->SetAttribute("description", reader->GetConfigurationDescription().c_str()); + readerTag->SetAttribute("version", "1"); + + return readerTag; +} diff --git a/Modules/DICOMReader/mitkDICOMReaderConfigurator.h b/Modules/DICOMReader/mitkDICOMReaderConfigurator.h index 51fd27f4b0..8542c94162 100644 --- a/Modules/DICOMReader/mitkDICOMReaderConfigurator.h +++ b/Modules/DICOMReader/mitkDICOMReaderConfigurator.h @@ -1,108 +1,141 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef mitkDICOMReaderConfigurator_h #define mitkDICOMReaderConfigurator_h -#include "mitkThreeDnTDICOMSeriesReader.h" +#include "mitkClassicDICOMSeriesReader.h" +#include "mitkDICOMTagBasedSorter.h" // to put into private implementation #include "tinyxml.h" namespace mitk { /** \ingroup DICOMReaderModule \brief Too-simple factory to create DICOMFileReader%s. This class is able to instantiate and configure (where possible) DICOMFileReader%s from XML descriptions. \note This is a bad factory example, because the factory is not extensible and needs to know all the specific readers. A flexible implementation should be provided in a future version. In its current version, the XML input is meant to be structured like \verbatim \endverbatim The root-tag \i names the class to be instantiated, currently this can be one of - DICOMITKSeriesGDCMReader - ThreeDnTDICOMSeriesReader Both classes bring simple configuration flags with them and a description of how images are sorted prior to loading. Flag for DICOMITKSeriesGDCMReader:
fixTiltByShearing="true|false"
Determines whether a potential gantry tilt should be "fixed" by shearing the output image. Flag for ThreeDnTDICOMSeriesReader:
group3DnT="true|false"
Determines whether images at the same spatial position should be interpreted as 3D+t images. The tags and describe the basic loading strategy of both reader mentioned above: first images are divided into incompatible groups (), and afterwards the images within each group are sorted by means of DICOMSortCriterion, which most commonly mentions a tag. - Tag element and group are interpreted as the hexadecimal numbers + Tag element and group are interpreted as the exadecimal numbers found all around the DICOM standard. The numbers can be prepended by a "0x" if this is preferred by the programmer (but they are taken as hexadecimal in all cases). + + \section DICOMReaderConfigurator_AboutTheFuture About the future evolution of this class + + This first version is hard coded for the current state of the implementation. + + If things should evolve in a way that needs us to splitt off readers for "old" versions, + time should be taken to refactor this class. + + Basically, a serializer class should accompany each of the configurable classes. Such + serializer classes should be registered and discovered via micro-services (to support extensions). + A serializer should offer both methods to serialize a class and to desirialize it again. + + A "version" attribute at the top-level tag should be used to distinguish versions. + + Usually it should be enough to keep DE-serializers for all versions. Writers for the most + recent version should be enough. */ class DICOMReader_EXPORT DICOMReaderConfigurator : public itk::LightObject { public: mitkClassMacro( DICOMReaderConfigurator, itk::LightObject ) itkNewMacro( DICOMReaderConfigurator ) - DICOMFileReader::Pointer CreateFromConfigFile(const std::string& filename); - DICOMFileReader::Pointer CreateFromUTF8ConfigString(const std::string& xmlContents); + DICOMFileReader::Pointer CreateFromConfigFile(const std::string& filename) const; + DICOMFileReader::Pointer CreateFromUTF8ConfigString(const std::string& xmlContents) const; + + std::string CreateConfigStringFromReader(DICOMFileReader::ConstPointer reader) const; protected: DICOMReaderConfigurator(); virtual ~DICOMReaderConfigurator(); private: - DICOMFileReader::Pointer CreateFromTiXmlDocument(TiXmlDocument& doc); - DICOMTag tagFromXMLElement(TiXmlElement*); - std::string requiredStringAttribute(TiXmlElement* xmlElement, const std::string& key); - unsigned int hexStringToUInt(const std::string& s); + DICOMFileReader::Pointer CreateFromTiXmlDocument(TiXmlDocument& doc) const; + DICOMTag tagFromXMLElement(TiXmlElement*) const; + std::string requiredStringAttribute(TiXmlElement* xmlElement, const std::string& key) const; + unsigned int hexStringToUInt(const std::string& s) const; + + ThreeDnTDICOMSeriesReader::Pointer ConfigureThreeDnTDICOMSeriesReader(ThreeDnTDICOMSeriesReader::Pointer reader, TiXmlElement*) const; + DICOMITKSeriesGDCMReader::Pointer ConfigureDICOMITKSeriesGDCMReader(DICOMITKSeriesGDCMReader::Pointer reader, TiXmlElement*) const; + + DICOMSortCriterion::Pointer CreateDICOMSortByTag(TiXmlElement* xmlElement, DICOMSortCriterion::Pointer secondaryCriterion) const; + DICOMSortCriterion::Pointer CreateSortByImagePositionPatient(TiXmlElement* xmlElement, DICOMSortCriterion::Pointer secondaryCriterion) const; + + mitk::DICOMTagBasedSorter::Pointer CreateDICOMTagBasedSorter(TiXmlElement* element) const; + + TiXmlElement* CreateConfigStringFromReader(const DICOMITKSeriesGDCMReader* reader) const; + TiXmlElement* CreateConfigStringFromReader(const ThreeDnTDICOMSeriesReader* reader) const; + TiXmlElement* CreateConfigStringFromReader(const ClassicDICOMSeriesReader* reader) const; + + TiXmlElement* CreateConfigStringFromDICOMDatasetSorter(const DICOMTagBasedSorter* sorter) const; - ThreeDnTDICOMSeriesReader::Pointer ConfigureThreeDnTDICOMSeriesReader(ThreeDnTDICOMSeriesReader::Pointer reader, TiXmlElement*); - DICOMITKSeriesGDCMReader::Pointer ConfigureDICOMITKSeriesGDCMReader(DICOMITKSeriesGDCMReader::Pointer reader, TiXmlElement*); + TiXmlElement* CreateConfigStringFromDICOMTag(const DICOMTag& tag) const; - DICOMSortCriterion::Pointer CreateDICOMSortByTag(TiXmlElement* xmlElement, DICOMSortCriterion::Pointer secondaryCriterion); - DICOMSortCriterion::Pointer CreateSortByImagePositionPatient(TiXmlElement* xmlElement, DICOMSortCriterion::Pointer secondaryCriterion); + TiXmlElement* CreateDICOMFileReaderTag(const DICOMFileReader* reader) const; + const char* toString(bool) const; + std::string toHexString(unsigned int i) const; }; } // namespace #endif // mitkDICOMReaderConfigurator_h diff --git a/Modules/DICOMReader/mitkDICOMSortByTag.cpp b/Modules/DICOMReader/mitkDICOMSortByTag.cpp index 61b84349c1..7c2e8e8e9a 100644 --- a/Modules/DICOMReader/mitkDICOMSortByTag.cpp +++ b/Modules/DICOMReader/mitkDICOMSortByTag.cpp @@ -1,126 +1,146 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkDICOMSortByTag.h" mitk::DICOMSortByTag ::DICOMSortByTag(const DICOMTag& tag, DICOMSortCriterion::Pointer secondaryCriterion) :DICOMSortCriterion(secondaryCriterion) ,m_Tag(tag) { } mitk::DICOMSortByTag ::~DICOMSortByTag() { } mitk::DICOMSortByTag ::DICOMSortByTag(const DICOMSortByTag& other ) :DICOMSortCriterion(other) ,m_Tag(other.m_Tag) { } mitk::DICOMSortByTag& mitk::DICOMSortByTag ::operator=(const DICOMSortByTag& other) { if (this != &other) { DICOMSortCriterion::operator=(other); m_Tag = other.m_Tag; } return *this; } + +bool +mitk::DICOMSortByTag +::operator==(const DICOMSortCriterion& other) const +{ + if (const DICOMSortByTag* otherSelf = dynamic_cast(&other)) + { + if (!(this->m_Tag == otherSelf->m_Tag)) return false; + + if (this->m_SecondaryCriterion.IsNull() && otherSelf->m_SecondaryCriterion.IsNull()) return true; + + if (this->m_SecondaryCriterion.IsNull() || otherSelf->m_SecondaryCriterion.IsNull()) return false; + + return *(this->m_SecondaryCriterion) == *(otherSelf->m_SecondaryCriterion); + } + else + { + return false; + } +} void mitk::DICOMSortByTag ::Print(std::ostream& os) const { m_Tag.Print(os); } mitk::DICOMTagList mitk::DICOMSortByTag ::GetTagsOfInterest() const { DICOMTagList list; list.push_back(m_Tag); return list; } bool mitk::DICOMSortByTag ::IsLeftBeforeRight(const mitk::DICOMDatasetAccess* left, const mitk::DICOMDatasetAccess* right) const { return this->NumericCompare(left, right, m_Tag); } bool mitk::DICOMSortByTag ::StringCompare(const mitk::DICOMDatasetAccess* left, const mitk::DICOMDatasetAccess* right, const DICOMTag& tag) const { assert(left); assert(right); std::string leftString = left->GetTagValueAsString(tag); std::string rightString = right->GetTagValueAsString(tag); if (leftString != rightString) { return leftString.compare(rightString) < 0; } else { return this->NextLevelIsLeftBeforeRight(left, right); } } bool mitk::DICOMSortByTag ::NumericCompare(const mitk::DICOMDatasetAccess* left, const mitk::DICOMDatasetAccess* right, const DICOMTag& tag) const { assert(left); assert(right); std::string leftString = left->GetTagValueAsString(tag); std::string rightString = right->GetTagValueAsString(tag); std::istringstream lefti(leftString); std::istringstream righti(rightString); double leftDouble(0); double rightDouble(0); if ( (lefti >> leftDouble) && (righti >> rightDouble) && lefti.eof() && righti.eof() ) { if (leftDouble != rightDouble) // can we decide? { return leftDouble < rightDouble; } else // ask secondary criterion { return this->NextLevelIsLeftBeforeRight(left, right); } } else // no numerical conversion.. { return this->StringCompare(left,right, tag); // fallback to string compare } } diff --git a/Modules/DICOMReader/mitkDICOMSortByTag.h b/Modules/DICOMReader/mitkDICOMSortByTag.h index 33a228fa2e..984a115a4a 100644 --- a/Modules/DICOMReader/mitkDICOMSortByTag.h +++ b/Modules/DICOMReader/mitkDICOMSortByTag.h @@ -1,68 +1,70 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef mitkDICOMSortByTag_h #define mitkDICOMSortByTag_h #include "mitkDICOMSortCriterion.h" namespace mitk { /** \ingroup DICOMReaderModule \brief Compare two datasets by the value of a single tag (for use in DICOMTagBasedSorter). The class will compare the tag values by 1. numerical value if possible (i.e. both datasets have a value that is numerical) 2. alphabetical order otherwise If the comparison results in equalness, it is refered to the secondary criterion, see DICOMSortByTag::NextLevelIsLeftBeforeRight(). */ class DICOMReader_EXPORT DICOMSortByTag : public DICOMSortCriterion { public: mitkClassMacro( DICOMSortByTag, DICOMSortCriterion ); mitkNewMacro1Param( DICOMSortByTag, const DICOMTag& ); mitkNewMacro2Param( DICOMSortByTag, const DICOMTag&, DICOMSortCriterion::Pointer ); virtual DICOMTagList GetTagsOfInterest() const; virtual bool IsLeftBeforeRight(const mitk::DICOMDatasetAccess* left, const mitk::DICOMDatasetAccess* right) const; virtual void Print(std::ostream& os) const; + + virtual bool operator==(const DICOMSortCriterion& other) const; protected: DICOMSortByTag( const DICOMTag& tag, DICOMSortCriterion::Pointer secondaryCriterion = NULL ); virtual ~DICOMSortByTag(); DICOMSortByTag(const DICOMSortByTag& other); DICOMSortByTag& operator=(const DICOMSortByTag& other); bool StringCompare(const mitk::DICOMDatasetAccess* left, const mitk::DICOMDatasetAccess* right, const DICOMTag& tag) const; bool NumericCompare(const mitk::DICOMDatasetAccess* left, const mitk::DICOMDatasetAccess* right, const DICOMTag& tag) const; private: DICOMTag m_Tag; }; } #endif diff --git a/Modules/DICOMReader/mitkDICOMSortCriterion.h b/Modules/DICOMReader/mitkDICOMSortCriterion.h index 8921bd9290..079f7be8f0 100644 --- a/Modules/DICOMReader/mitkDICOMSortCriterion.h +++ b/Modules/DICOMReader/mitkDICOMSortCriterion.h @@ -1,76 +1,78 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef mitkDICOMSortCriterion_h #define mitkDICOMSortCriterion_h #include "itkObjectFactory.h" #include "mitkCommon.h" #include "mitkDICOMDatasetAccess.h" namespace mitk { /** \ingroup DICOMReaderModule \brief A tag based sorting criterion for use in DICOMTagBasedSorter. This class is used within std::sort (see DICOMTagBasedSorter::Sort()) and has to answer a simple question by implementing IsLeftBeforeRight(). Each time IsLeftBeforeRight() is called, the method should return whether the left dataset should be sorted before the right dataset. Because there are identical tags values quite oftenly, a DICOMSortCriterion will always hold a secondary DICOMSortCriterion. In cases of equal tag values, the decision is refered to the secondary criterion. */ class DICOMReader_EXPORT DICOMSortCriterion : public itk::LightObject { public: mitkClassMacro( DICOMSortCriterion, itk::LightObject ); /// \brief Tags used for comparison (includes seconary criteria). DICOMTagList GetAllTagsOfInterest() const; /// \brief Tags used for comparison. virtual DICOMTagList GetTagsOfInterest() const = 0; /// \brief Answer the sorting question. virtual bool IsLeftBeforeRight(const mitk::DICOMDatasetAccess* left, const mitk::DICOMDatasetAccess* right) const = 0; /// \brief The fallback criterion. DICOMSortCriterion::ConstPointer GetSecondaryCriterion() const; /// brief describe this class in given stream. virtual void Print(std::ostream& os) const = 0; + virtual bool operator==(const DICOMSortCriterion& other) const = 0; + protected: DICOMSortCriterion( DICOMSortCriterion::Pointer secondaryCriterion ); virtual ~DICOMSortCriterion(); bool NextLevelIsLeftBeforeRight(const mitk::DICOMDatasetAccess* left, const mitk::DICOMDatasetAccess* right) const; DICOMSortCriterion(const DICOMSortCriterion& other); DICOMSortCriterion& operator=(const DICOMSortCriterion& other); DICOMSortCriterion::Pointer m_SecondaryCriterion; }; } #endif diff --git a/Modules/DICOMReader/mitkDICOMTagBasedSorter.cpp b/Modules/DICOMReader/mitkDICOMTagBasedSorter.cpp index b5668d09b9..3243907c0f 100644 --- a/Modules/DICOMReader/mitkDICOMTagBasedSorter.cpp +++ b/Modules/DICOMReader/mitkDICOMTagBasedSorter.cpp @@ -1,287 +1,337 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkDICOMTagBasedSorter.h" #include mitk::DICOMTagBasedSorter::CutDecimalPlaces ::CutDecimalPlaces(unsigned int precision) :m_Precision(precision) { } std::string mitk::DICOMTagBasedSorter::CutDecimalPlaces ::operator()(const std::string& input) const { // be a bit tolerant for tags such as image orientation orienatation, let only the first few digits matter (http://bugs.mitk.org/show_bug.cgi?id=12263) // iterate all fields, convert each to a number, cut this number as configured, then return a concatenated string with all cut-off numbers static std::ostringstream resultString; resultString.str(std::string()); resultString.clear(); resultString.setf(std::ios::fixed, std::ios::floatfield); resultString.precision(m_Precision); static std::stringstream ss(input); ss.str(input); ss.clear(); static std::string item; while (std::getline(ss, item, '\\')) { static std::istringstream converter(item); converter.str(item); converter.clear(); static double number(0); if (converter >> number && converter.eof()) { // converted to double resultString << number; } else { // did not convert to double resultString << item; // just paste the unmodified string } if (!ss.eof()) { resultString << "\\"; } } return resultString.str(); } +unsigned int +mitk::DICOMTagBasedSorter::CutDecimalPlaces +::GetPrecision() const +{ + return m_Precision; +} + mitk::DICOMTagBasedSorter ::DICOMTagBasedSorter() :DICOMDatasetSorter() { } mitk::DICOMTagBasedSorter ::~DICOMTagBasedSorter() { for(TagValueProcessorMap::iterator ti = m_TagValueProcessor.begin(); ti != m_TagValueProcessor.end(); ++ti) { delete ti->second; } } mitk::DICOMTagBasedSorter ::DICOMTagBasedSorter(const DICOMTagBasedSorter& other ) :DICOMDatasetSorter(other) { } mitk::DICOMTagBasedSorter& mitk::DICOMTagBasedSorter ::operator=(const DICOMTagBasedSorter& other) { if (this != &other) { DICOMDatasetSorter::operator=(other); } return *this; } +bool +mitk::DICOMTagBasedSorter +::operator==(const DICOMDatasetSorter& other) const +{ + if (const DICOMTagBasedSorter* otherSelf = dynamic_cast(&other)) + { + return true; // TODO + } + else + { + return false; + } +} + void mitk::DICOMTagBasedSorter ::PrintConfiguration(std::ostream& os, const std::string& indent) const { os << indent << "Tag based sorting:" << std::endl; for (DICOMTagList::const_iterator tagIter = m_DistinguishingTags.begin(); tagIter != m_DistinguishingTags.end(); ++tagIter) { os << indent << " Split on "; tagIter->Print(os); os << std::endl; } DICOMSortCriterion::ConstPointer crit = m_SortCriterion.GetPointer(); while (crit.IsNotNull()) { os << indent << " Sort by "; crit->Print(os); os << std::endl; crit = crit->GetSecondaryCriterion(); } } mitk::DICOMTagList mitk::DICOMTagBasedSorter ::GetTagsOfInterest() { DICOMTagList allTags = m_DistinguishingTags; DICOMTagList sortingRelevantTags = m_SortCriterion->GetAllTagsOfInterest(); allTags.insert( allTags.end(), sortingRelevantTags.begin(), sortingRelevantTags.end() ); // append return allTags; } +mitk::DICOMTagList +mitk::DICOMTagBasedSorter +::GetDistinguishingTags() const +{ + return m_DistinguishingTags; +} + +const mitk::DICOMTagBasedSorter::TagValueProcessor* +mitk::DICOMTagBasedSorter +::GetTagValueProcessorForDistinguishingTag(const DICOMTag& tag) const +{ + TagValueProcessorMap::const_iterator loc = m_TagValueProcessor.find(tag); + if (loc != m_TagValueProcessor.end()) + { + return loc->second; + } + else + { + return NULL; + } +} + void mitk::DICOMTagBasedSorter ::AddDistinguishingTag( const DICOMTag& tag, TagValueProcessor* tagValueProcessor ) { m_DistinguishingTags.push_back(tag); m_TagValueProcessor[tag] = tagValueProcessor; } void mitk::DICOMTagBasedSorter ::SetSortCriterion( DICOMSortCriterion::ConstPointer criterion ) { m_SortCriterion = criterion; } +mitk::DICOMSortCriterion::ConstPointer +mitk::DICOMTagBasedSorter +::GetSortCriterion() const +{ + return m_SortCriterion; +} + void mitk::DICOMTagBasedSorter ::Sort() { // 1. split // 2. sort each group GroupIDToListType groups = this->SplitInputGroups(); GroupIDToListType& sortedGroups = this->SortGroups( groups ); // 3. define output this->SetNumberOfOutputs(sortedGroups.size()); unsigned int outputIndex(0); for (GroupIDToListType::iterator groupIter = sortedGroups.begin(); groupIter != sortedGroups.end(); ++outputIndex, ++groupIter) { this->SetOutput(outputIndex, groupIter->second); } } std::string mitk::DICOMTagBasedSorter ::BuildGroupID( DICOMDatasetAccess* dataset ) { // just concatenate all tag values assert(dataset); std::stringstream groupID; groupID << "g"; for (DICOMTagList::iterator tagIter = m_DistinguishingTags.begin(); tagIter != m_DistinguishingTags.end(); ++tagIter) { groupID << tagIter->GetGroup() << tagIter->GetElement(); // make group/element part of the id to cover empty tags std::string rawTagValue = dataset->GetTagValueAsString(*tagIter); std::string processedTagValue; if ( m_TagValueProcessor[*tagIter] != NULL ) { processedTagValue = (*m_TagValueProcessor[*tagIter])(rawTagValue); } else { processedTagValue = rawTagValue; } groupID << processedTagValue; } // shorten ID? return groupID.str(); } mitk::DICOMTagBasedSorter::GroupIDToListType mitk::DICOMTagBasedSorter ::SplitInputGroups() { DICOMDatasetList input = GetInput(); // copy GroupIDToListType listForGroupID; for (DICOMDatasetList::iterator dsIter = input.begin(); dsIter != input.end(); ++dsIter) { DICOMDatasetAccess* dataset = *dsIter; assert(dataset); std::string groupID = this->BuildGroupID( dataset ); MITK_DEBUG << "Group ID for for " << dataset->GetFilenameIfAvailable() << ": " << groupID; listForGroupID[groupID].push_back(dataset); } MITK_DEBUG << "After tag based splitting: " << listForGroupID.size() << " groups"; return listForGroupID; } mitk::DICOMTagBasedSorter::GroupIDToListType& mitk::DICOMTagBasedSorter ::SortGroups(GroupIDToListType& groups) { if (m_SortCriterion.IsNotNull()) { // for each output // sort by all configured tags, use secondary tags when equal or empty // make configurable: // - sorting order (ascending, descending) // - sort numerically // - ... ? unsigned int groupIndex(0); for (GroupIDToListType::iterator gIter = groups.begin(); gIter != groups.end(); ++groupIndex, ++gIter) { DICOMDatasetList& dsList = gIter->second; MITK_DEBUG << " --------------------------------------------------------------------------------"; MITK_DEBUG << " DICOMTagBasedSorter before sorting group : " << groupIndex; for (DICOMDatasetList::iterator oi = dsList.begin(); oi != dsList.end(); ++oi) { MITK_DEBUG << " INPUT : " << (*oi)->GetFilenameIfAvailable(); } std::sort( dsList.begin(), dsList.end(), ParameterizedDatasetSort( m_SortCriterion ) ); MITK_DEBUG << " --------------------------------------------------------------------------------"; MITK_DEBUG << " DICOMTagBasedSorter after sorting group : " << groupIndex; for (DICOMDatasetList::iterator oi = dsList.begin(); oi != dsList.end(); ++oi) { MITK_DEBUG << " OUTPUT : " << (*oi)->GetFilenameIfAvailable(); } MITK_DEBUG << " --------------------------------------------------------------------------------"; } } return groups; } mitk::DICOMTagBasedSorter::ParameterizedDatasetSort ::ParameterizedDatasetSort(DICOMSortCriterion::ConstPointer criterion) :m_SortCriterion(criterion) { } bool mitk::DICOMTagBasedSorter::ParameterizedDatasetSort ::operator() (const mitk::DICOMDatasetAccess* left, const mitk::DICOMDatasetAccess* right) { assert(left); assert(right); assert(m_SortCriterion.IsNotNull()); return m_SortCriterion->IsLeftBeforeRight(left, right); } diff --git a/Modules/DICOMReader/mitkDICOMTagBasedSorter.h b/Modules/DICOMReader/mitkDICOMTagBasedSorter.h index f054843ab6..176e0d49c0 100644 --- a/Modules/DICOMReader/mitkDICOMTagBasedSorter.h +++ b/Modules/DICOMReader/mitkDICOMTagBasedSorter.h @@ -1,148 +1,154 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef mitkDICOMTagBasedSorter_h #define mitkDICOMTagBasedSorter_h #include "mitkDICOMDatasetSorter.h" #include "mitkDICOMSortCriterion.h" namespace mitk { /** \ingroup DICOMReaderModule \brief Sort DICOM datasets based on configurable tags. This class implements sorting of input DICOM datasets into multiple outputs as described in \ref DICOMITKSeriesGDCMReader_LoadingStrategy. The logic of sorting and splitting is most simple and most generic: 1. Datasets will be put into different groups, if they differ in their value of specific tags (defined by AddDistinguishingTag()) - there might be multiple distinguishing tags defined - tag values might be processed before comparison by means of TagValueProcessor (e.g. round to a number of decimal places) 2. Each of the groups will be sorted by comparing their tag values using multiple DICOMSortCriterion - DICOMSortCriterion might evaluate a single tag (e.g. Instance Number) or multiple values (as in SortByImagePositionPatient) - only a single DICOMSortCriterion is defined for DICOMTagBasedSorter, because each DICOMSortCriterion holds a "secondary sort criterion", i.e. an application can define multiple tags for sorting by chaining \link DICOMSortCriterion DICOMSortCriteria \endlink - applications should make sure that sorting is always defined (to avoid problems with standard containers), e.g. by adding a comparison of filenames or instance UIDs as a last sorting fallback. */ class DICOMReader_EXPORT DICOMTagBasedSorter : public DICOMDatasetSorter { public: /** \brief Processes tag values before they are compared. These classes could do some kind of normalization such as rounding, lower case formatting, etc. */ class DICOMReader_EXPORT TagValueProcessor { public: /// \brief Implements the "processing". virtual std::string operator()(const std::string&) const = 0; }; /** \brief Cuts a number after configured number of decimal places. An instance of this class can be used to avoid errors when comparing minimally different image orientations. */ class DICOMReader_EXPORT CutDecimalPlaces : public TagValueProcessor { public: CutDecimalPlaces(unsigned int precision); + unsigned int GetPrecision() const; virtual std::string operator()(const std::string&) const; private: unsigned int m_Precision; }; mitkClassMacro( DICOMTagBasedSorter, DICOMDatasetSorter ) itkNewMacro( DICOMTagBasedSorter ) /** \brief Datasets that differ in given tag's value will be sorted into separate outputs. */ void AddDistinguishingTag( const DICOMTag&, TagValueProcessor* tagValueProcessor = NULL ); + DICOMTagList GetDistinguishingTags() const; + const TagValueProcessor* GetTagValueProcessorForDistinguishingTag(const DICOMTag&) const; /** \brief Define the sorting criterion (which holds seconardy criteria) */ void SetSortCriterion( DICOMSortCriterion::ConstPointer criterion ); + DICOMSortCriterion::ConstPointer GetSortCriterion() const; /** \brief A list of all the tags needed for processing (facilitates scanning). */ virtual DICOMTagList GetTagsOfInterest(); /** \brief Actually sort as described in the Detailed Description. */ virtual void Sort(); /** \brief Print configuration details into given stream. */ virtual void PrintConfiguration(std::ostream& os, const std::string& indent = "") const; + + virtual bool operator==(const DICOMDatasetSorter& other) const; protected: /** \brief Helper struct to feed into std::sort, configured via DICOMSortCriterion. */ struct ParameterizedDatasetSort { ParameterizedDatasetSort(DICOMSortCriterion::ConstPointer); bool operator() (const mitk::DICOMDatasetAccess* left, const mitk::DICOMDatasetAccess* right); DICOMSortCriterion::ConstPointer m_SortCriterion; }; DICOMTagBasedSorter(); virtual ~DICOMTagBasedSorter(); DICOMTagBasedSorter(const DICOMTagBasedSorter& other); DICOMTagBasedSorter& operator=(const DICOMTagBasedSorter& other); /** \brief Helper for SplitInputGroups(). */ std::string BuildGroupID( DICOMDatasetAccess* dataset ); typedef std::map GroupIDToListType; /** \brief Implements the "distiguishing tags". To sort datasets into different groups, a long string will be built for each dataset. The string concatenates all tags and their respective values. Datasets that match in all values will end up with the same string. */ GroupIDToListType SplitInputGroups(); /** \brief Implements the sorting step. Relatively simple implementation thanks to std::sort and a parameterization via DICOMSortCriterion. */ GroupIDToListType& SortGroups(GroupIDToListType& groups); DICOMTagList m_DistinguishingTags; typedef std::map TagValueProcessorMap; TagValueProcessorMap m_TagValueProcessor; DICOMSortCriterion::ConstPointer m_SortCriterion; }; } #endif diff --git a/Modules/DICOMReader/mitkEquiDistantBlocksSorter.cpp b/Modules/DICOMReader/mitkEquiDistantBlocksSorter.cpp index 100c4cb946..5c7846997b 100644 --- a/Modules/DICOMReader/mitkEquiDistantBlocksSorter.cpp +++ b/Modules/DICOMReader/mitkEquiDistantBlocksSorter.cpp @@ -1,537 +1,572 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ //#define MBILOG_ENABLE_DEBUG #include "mitkEquiDistantBlocksSorter.h" mitk::EquiDistantBlocksSorter::SliceGroupingAnalysisResult ::SliceGroupingAnalysisResult() { } mitk::DICOMDatasetList mitk::EquiDistantBlocksSorter::SliceGroupingAnalysisResult ::GetBlockDatasets() { return m_GroupedFiles; } mitk::DICOMDatasetList mitk::EquiDistantBlocksSorter::SliceGroupingAnalysisResult ::GetUnsortedDatasets() { return m_UnsortedFiles; } bool mitk::EquiDistantBlocksSorter::SliceGroupingAnalysisResult ::ContainsGantryTilt() { return m_TiltInfo.IsRegularGantryTilt(); } void mitk::EquiDistantBlocksSorter::SliceGroupingAnalysisResult ::AddFileToSortedBlock(DICOMDatasetAccess* dataset) { m_GroupedFiles.push_back( dataset ); } void mitk::EquiDistantBlocksSorter::SliceGroupingAnalysisResult ::AddFileToUnsortedBlock(DICOMDatasetAccess* dataset) { m_UnsortedFiles.push_back( dataset ); } void mitk::EquiDistantBlocksSorter::SliceGroupingAnalysisResult ::AddFilesToUnsortedBlock(const DICOMDatasetList& datasets) { m_UnsortedFiles.insert( m_UnsortedFiles.end(), datasets.begin(), datasets.end() ); } void mitk::EquiDistantBlocksSorter::SliceGroupingAnalysisResult ::SetFirstFilenameOfBlock(const std::string& filename) { m_FirstFilenameOfBlock = filename; } std::string mitk::EquiDistantBlocksSorter::SliceGroupingAnalysisResult ::GetFirstFilenameOfBlock() const { return m_FirstFilenameOfBlock; } void mitk::EquiDistantBlocksSorter::SliceGroupingAnalysisResult ::SetLastFilenameOfBlock(const std::string& filename) { m_LastFilenameOfBlock = filename; } std::string mitk::EquiDistantBlocksSorter::SliceGroupingAnalysisResult ::GetLastFilenameOfBlock() const { return m_LastFilenameOfBlock; } void mitk::EquiDistantBlocksSorter::SliceGroupingAnalysisResult ::FlagGantryTilt(const GantryTiltInformation& tiltInfo) { m_TiltInfo = tiltInfo; } const mitk::GantryTiltInformation& mitk::EquiDistantBlocksSorter::SliceGroupingAnalysisResult ::GetTiltInfo() const { return m_TiltInfo; } void mitk::EquiDistantBlocksSorter::SliceGroupingAnalysisResult ::UndoPrematureGrouping() { assert( !m_GroupedFiles.empty() ); m_UnsortedFiles.insert( m_UnsortedFiles.begin(), m_GroupedFiles.back() ); m_GroupedFiles.pop_back(); m_TiltInfo = GantryTiltInformation(); } // ------------------------ end helper class mitk::EquiDistantBlocksSorter ::EquiDistantBlocksSorter() :DICOMDatasetSorter() ,m_AcceptTilt(false) -,m_ToleratedOriginOffset(-1.0) // convention: negative values mean "adaptive" +,m_ToleratedOriginOffset(0.3) +,m_ToleratedOriginOffsetIsAbsolute(false) { } mitk::EquiDistantBlocksSorter ::EquiDistantBlocksSorter(const EquiDistantBlocksSorter& other ) :DICOMDatasetSorter(other) ,m_AcceptTilt(other.m_AcceptTilt) ,m_ToleratedOriginOffset(other.m_ToleratedOriginOffset) +,m_ToleratedOriginOffsetIsAbsolute(other.m_ToleratedOriginOffsetIsAbsolute) { } mitk::EquiDistantBlocksSorter ::~EquiDistantBlocksSorter() { } +bool +mitk::EquiDistantBlocksSorter +::operator==(const DICOMDatasetSorter& other) const +{ + if (const EquiDistantBlocksSorter* otherSelf = dynamic_cast(&other)) + { + return this->m_AcceptTilt == otherSelf->m_AcceptTilt + && this->m_ToleratedOriginOffsetIsAbsolute == otherSelf->m_ToleratedOriginOffsetIsAbsolute + && (fabs(this->m_ToleratedOriginOffset - otherSelf->m_ToleratedOriginOffset) < eps); + } + else + { + return false; + } +} + void mitk::EquiDistantBlocksSorter ::PrintConfiguration(std::ostream& os, const std::string& indent) const { std::stringstream ts; - if (m_ToleratedOriginOffset < 0.0) + if (!m_ToleratedOriginOffsetIsAbsolute) { ts << "adaptive"; } else { ts << m_ToleratedOriginOffset << "mm"; } os << indent << "Sort into blocks of equidistant, well-aligned (tolerance " << ts.str() << ") slices " << (m_AcceptTilt ? "(accepting a gantry tilt)" : "") << std::endl; } void mitk::EquiDistantBlocksSorter ::SetAcceptTilt(bool accept) { m_AcceptTilt = accept; } mitk::EquiDistantBlocksSorter& mitk::EquiDistantBlocksSorter ::operator=(const EquiDistantBlocksSorter& other) { if (this != &other) { DICOMDatasetSorter::operator=(other); m_AcceptTilt = other.m_AcceptTilt; m_ToleratedOriginOffset = other.m_ToleratedOriginOffset; + m_ToleratedOriginOffsetIsAbsolute = other.m_ToleratedOriginOffsetIsAbsolute; } return *this; } mitk::DICOMTagList mitk::EquiDistantBlocksSorter ::GetTagsOfInterest() { DICOMTagList tags; tags.push_back( DICOMTag(0x0020, 0x0032) ); // ImagePositionPatient tags.push_back( DICOMTag(0x0020, 0x0037) ); // ImageOrientationPatient tags.push_back( DICOMTag(0x0018, 0x1120) ); // GantryTilt return tags; } void mitk::EquiDistantBlocksSorter ::Sort() { DICOMDatasetList remainingInput = GetInput(); // copy typedef std::list OutputListType; OutputListType outputs; m_SliceGroupingResults.clear(); while (!remainingInput.empty()) // repeat until all files are grouped somehow { SliceGroupingAnalysisResult regularBlock = this->AnalyzeFileForITKImageSeriesReaderSpacingAssumption( remainingInput, m_AcceptTilt ); DICOMDatasetList inBlock = regularBlock.GetBlockDatasets(); DICOMDatasetList laterBlock = regularBlock.GetUnsortedDatasets(); MITK_DEBUG << "Result: sorted 3D group with " << inBlock.size() << " files"; for (DICOMDatasetList::const_iterator diter = inBlock.begin(); diter != inBlock.end(); ++diter) MITK_DEBUG << " IN " << (*diter)->GetFilenameIfAvailable(); for (DICOMDatasetList::const_iterator diter = laterBlock.begin(); diter != laterBlock.end(); ++diter) MITK_DEBUG << " OUT " << (*diter)->GetFilenameIfAvailable(); outputs.push_back( regularBlock.GetBlockDatasets() ); m_SliceGroupingResults.push_back( regularBlock ); remainingInput = regularBlock.GetUnsortedDatasets(); } unsigned int numberOfOutputs = outputs.size(); this->SetNumberOfOutputs(numberOfOutputs); unsigned int outputIndex(0); for (OutputListType::iterator oIter = outputs.begin(); oIter != outputs.end(); ++outputIndex, ++oIter) { this->SetOutput(outputIndex, *oIter); } } void mitk::EquiDistantBlocksSorter ::SetToleratedOriginOffsetToAdaptive(double fractionOfInterSliceDistance) { - m_ToleratedOriginOffset = -fractionOfInterSliceDistance; // convention: negative values mean "adaptive" - if (m_ToleratedOriginOffset > 0.0) + m_ToleratedOriginOffset = fractionOfInterSliceDistance; + m_ToleratedOriginOffsetIsAbsolute = false; + + if (m_ToleratedOriginOffset < 0.0) { MITK_WARN << "Call SetToleratedOriginOffsetToAdaptive() only with positive numbers between 0.0 and 1.0, read documentation!"; } - if (m_ToleratedOriginOffset < -0.5) + if (m_ToleratedOriginOffset > 0.5) { MITK_WARN << "EquiDistantBlocksSorter is now accepting large errors, take care of measurements, they could appear at unprecise locations!"; } } void mitk::EquiDistantBlocksSorter ::SetToleratedOriginOffset(double millimeters) { m_ToleratedOriginOffset = millimeters; + m_ToleratedOriginOffsetIsAbsolute = true; if (m_ToleratedOriginOffset < 0.0) { - MITK_WARN << "Call SetToleratedOriginOffsetToAdaptive() instead of passing negative numbers to SetToleratedOriginOffsetToAdaptive()!"; + MITK_WARN << "Negative tolerance set to SetToleratedOriginOffset()!"; } } +double +mitk::EquiDistantBlocksSorter +::GetToleratedOriginOffset() const +{ + return m_ToleratedOriginOffset; +} + +bool +mitk::EquiDistantBlocksSorter +::IsToleratedOriginOffsetAbsolute() const +{ + return m_ToleratedOriginOffsetIsAbsolute; +} + std::string mitk::EquiDistantBlocksSorter ::ConstCharStarToString(const char* s) { return s ? std::string(s) : std::string(); } mitk::EquiDistantBlocksSorter::SliceGroupingAnalysisResult mitk::EquiDistantBlocksSorter ::AnalyzeFileForITKImageSeriesReaderSpacingAssumption( const DICOMDatasetList& datasets, bool groupImagesWithGantryTilt) { // result.first = files that fit ITK's assumption // result.second = files that do not fit, should be run through AnalyzeFileForITKImageSeriesReaderSpacingAssumption() again SliceGroupingAnalysisResult result; // we const_cast here, because I could not use a map.at(), which would make the code much more readable const DICOMTag tagImagePositionPatient = DICOMTag(0x0020,0x0032); // Image Position (Patient) const DICOMTag tagImageOrientation = DICOMTag(0x0020, 0x0037); // Image Orientation const DICOMTag tagGantryTilt = DICOMTag(0x0018, 0x1120); // gantry tilt Vector3D fromFirstToSecondOrigin; fromFirstToSecondOrigin.Fill(0.0); bool fromFirstToSecondOriginInitialized(false); Point3D thisOrigin; thisOrigin.Fill(0.0f); Point3D lastOrigin; lastOrigin.Fill(0.0f); Point3D lastDifferentOrigin; lastDifferentOrigin.Fill(0.0f); bool lastOriginInitialized(false); MITK_DEBUG << "--------------------------------------------------------------------------------"; MITK_DEBUG << "Analyzing " << datasets.size() << " files for z-spacing assumption of ITK's ImageSeriesReader (group tilted: " << groupImagesWithGantryTilt << ")"; unsigned int fileIndex(0); - bool adaptiveErrorTolerance = m_ToleratedOriginOffset < 0.0; // convention: negative values: adaptive/percentage of slice distance ; positive: absolute in millimeters double toleratedOriginError(0.005); // default: max. 1/10mm error when measurement crosses 20 slices in z direction (too strict? we don't know better) for (DICOMDatasetList::const_iterator dsIter = datasets.begin(); dsIter != datasets.end(); ++dsIter, ++fileIndex) { bool fileFitsIntoPattern(false); std::string thisOriginString; // Read tag value into point3D. PLEASE replace this by appropriate GDCM code if you figure out how to do that thisOriginString = (*dsIter)->GetTagValueAsString( tagImagePositionPatient ); if (thisOriginString.empty()) { // don't let such files be in a common group. Everything without position information will be loaded as a single slice: // with standard DICOM files this can happen to: CR, DX, SC MITK_DEBUG << " ==> Sort away " << *dsIter << " for later analysis (no position information)"; // we already have one occupying this position if ( result.GetBlockDatasets().empty() ) // nothing WITH position information yet { // ==> this is a group of its own, stop processing, come back later result.AddFileToSortedBlock( *dsIter ); DICOMDatasetList remainingFiles; remainingFiles.insert( remainingFiles.end(), dsIter+1, datasets.end() ); result.AddFilesToUnsortedBlock( remainingFiles ); fileFitsIntoPattern = false; break; // no files anymore } else { // ==> this does not match, consider later result.AddFileToUnsortedBlock( *dsIter ); // sort away for further analysis fileFitsIntoPattern = false; continue; // next file } } bool ignoredConversionError(-42); // hard to get here, no graceful way to react thisOrigin = DICOMStringToPoint3D( thisOriginString, ignoredConversionError ); MITK_DEBUG << " " << fileIndex << " " << (*dsIter)->GetFilenameIfAvailable() << " at " /* << thisOriginString */ << "(" << thisOrigin[0] << "," << thisOrigin[1] << "," << thisOrigin[2] << ")"; if ( lastOriginInitialized && (thisOrigin == lastOrigin) ) { MITK_DEBUG << " ==> Sort away " << *dsIter << " for separate time step"; // we already have one occupying this position result.AddFileToUnsortedBlock( *dsIter ); // sort away for further analysis fileFitsIntoPattern = false; } else { if (!fromFirstToSecondOriginInitialized && lastOriginInitialized) // calculate vector as soon as possible when we get a new position { fromFirstToSecondOrigin = thisOrigin - lastDifferentOrigin; fromFirstToSecondOriginInitialized = true; // classic mode without tolerance! - if (adaptiveErrorTolerance) + if (!m_ToleratedOriginOffsetIsAbsolute) { MITK_DEBUG << "Distance of two slices: " << fromFirstToSecondOrigin.GetNorm() << "mm"; toleratedOriginError = fromFirstToSecondOrigin.GetNorm() * 0.3; // a third of the slice distance // (less than half, which would mean that a slice is displayed where another slice should actually be) } else { toleratedOriginError = m_ToleratedOriginOffset; } MITK_DEBUG << "Accepting errors in actual versus expected origin up to " << toleratedOriginError << "mm"; // Here we calculate if this slice and the previous one are well aligned, // i.e. we test if the previous origin is on a line through the current // origin, directed into the normal direction of the current slice. // If this is NOT the case, then we have a data set with a TILTED GANTRY geometry, // which cannot be simply loaded into a single mitk::Image at the moment. // For this case, we flag this finding in the result and DicomSeriesReader // can correct for that later. Vector3D right; right.Fill(0.0); Vector3D up; right.Fill(0.0); // might be down as well, but it is just a name at this point std::string orientationValue = (*dsIter)->GetTagValueAsString( tagImageOrientation ); DICOMStringToOrientationVectors( orientationValue, right, up, ignoredConversionError ); GantryTiltInformation tiltInfo( lastDifferentOrigin, thisOrigin, right, up, 1 ); if ( tiltInfo.IsSheared() ) { /* optimistic approach, accepting gantry tilt: save file for later, check all further files */ // at this point we have TWO slices analyzed! if they are the only two files, we still split, because there is no third to verify our tilting assumption. // later with a third being available, we must check if the initial tilting vector is still valid. if yes, continue. // if NO, we need to split the already sorted part (result.first) and the currently analyzed file (*dsIter) // tell apart gantry tilt from overall skewedness // sort out irregularly sheared slices, that IS NOT tilting if ( groupImagesWithGantryTilt && tiltInfo.IsRegularGantryTilt() ) { // check if this is at least roughly the same angle as recorded in DICOM tags double angle = 0.0; std::string tiltStr = (*dsIter)->GetTagValueAsString( tagGantryTilt ); std::istringstream i(tiltStr); if (i >> angle) { MITK_DEBUG << "Comparing recorded tilt angle " << angle << " against calculated value " << tiltInfo.GetTiltAngleInDegrees(); // TODO we probably want the signs correct, too (that depends: this is just a rough check, nothing serious) if ( fabs(angle) - tiltInfo.GetTiltAngleInDegrees() > 0.25) { result.AddFileToUnsortedBlock( *dsIter ); // sort away for further analysis fileFitsIntoPattern = false; } else // tilt angle from header is less than 0.25 degrees different from what we calculated, assume this is fine { assert(!datasets.empty()); result.FlagGantryTilt(tiltInfo); result.AddFileToSortedBlock( *dsIter ); // this file is good for current block result.SetFirstFilenameOfBlock( datasets.front()->GetFilenameIfAvailable() ); result.SetLastFilenameOfBlock( datasets.back()->GetFilenameIfAvailable() ); fileFitsIntoPattern = true; } } else // we cannot check the calculated tilt angle against the one from the dicom header (so we assume we are right) { assert(!datasets.empty()); result.FlagGantryTilt(tiltInfo); result.AddFileToSortedBlock( *dsIter ); // this file is good for current block result.SetFirstFilenameOfBlock( datasets.front()->GetFilenameIfAvailable() ); result.SetLastFilenameOfBlock( datasets.back()->GetFilenameIfAvailable() ); fileFitsIntoPattern = true; } } else // caller does not want tilt compensation OR shearing is more complicated than tilt { result.AddFileToUnsortedBlock( *dsIter ); // sort away for further analysis fileFitsIntoPattern = false; } } else // not sheared { result.AddFileToSortedBlock( *dsIter ); // this file is good for current block fileFitsIntoPattern = true; } } else if (fromFirstToSecondOriginInitialized) // we already know the offset between slices { Point3D assumedOrigin = lastDifferentOrigin + fromFirstToSecondOrigin; Vector3D originError = assumedOrigin - thisOrigin; double norm = originError.GetNorm(); if (norm > toleratedOriginError) { MITK_DEBUG << " File does not fit into the inter-slice distance pattern (diff = " << norm << ", allowed " << toleratedOriginError << ")."; MITK_DEBUG << " Expected position (" << assumedOrigin[0] << "," << assumedOrigin[1] << "," << assumedOrigin[2] << "), got position (" << thisOrigin[0] << "," << thisOrigin[1] << "," << thisOrigin[2] << ")"; MITK_DEBUG << " ==> Sort away " << *dsIter << " for later analysis"; // At this point we know we deviated from the expectation of ITK's ImageSeriesReader // We split the input file list at this point, i.e. all files up to this one (excluding it) // are returned as group 1, the remaining files (including the faulty one) are group 2 /* Optimistic approach: check if any of the remaining slices fits in */ result.AddFileToUnsortedBlock( *dsIter ); // sort away for further analysis fileFitsIntoPattern = false; } else { result.AddFileToSortedBlock( *dsIter ); // this file is good for current block fileFitsIntoPattern = true; } } else // this should be the very first slice { result.AddFileToSortedBlock( *dsIter ); // this file is good for current block fileFitsIntoPattern = true; } } // record current origin for reference in later iterations if ( !lastOriginInitialized || ( fileFitsIntoPattern && (thisOrigin != lastOrigin) ) ) { lastDifferentOrigin = thisOrigin; } lastOrigin = thisOrigin; lastOriginInitialized = true; } if ( result.ContainsGantryTilt() ) { // check here how many files were grouped. // IF it was only two files AND we assume tiltedness (e.g. save "distance") // THEN we would want to also split the two previous files (simple) because // we don't have any reason to assume they belong together if ( result.GetBlockDatasets().size() == 2 ) { result.UndoPrematureGrouping(); } } // update tilt info to get maximum precision // earlier, tilt was only calculated from first and second slice. // now that we know the whole range, we can re-calculate using the very first and last slice if ( result.ContainsGantryTilt() && result.GetBlockDatasets().size() > 1 ) { try { DICOMDatasetList datasets = result.GetBlockDatasets(); DICOMDatasetAccess* firstDataset = datasets.front(); DICOMDatasetAccess* lastDataset = datasets.back(); unsigned int numberOfSlicesApart = datasets.size() - 1; std::string orientationString = firstDataset->GetTagValueAsString( tagImageOrientation ); std::string firstOriginString = firstDataset->GetTagValueAsString( tagImagePositionPatient ); std::string lastOriginString = lastDataset->GetTagValueAsString( tagImagePositionPatient ); result.FlagGantryTilt( GantryTiltInformation::MakeFromTagValues( firstOriginString, lastOriginString, orientationString, numberOfSlicesApart )); } catch (...) { // just do not flag anything, we are ok } } return result; } diff --git a/Modules/DICOMReader/mitkEquiDistantBlocksSorter.h b/Modules/DICOMReader/mitkEquiDistantBlocksSorter.h index 66d056f64b..92a4790ae0 100644 --- a/Modules/DICOMReader/mitkEquiDistantBlocksSorter.h +++ b/Modules/DICOMReader/mitkEquiDistantBlocksSorter.h @@ -1,204 +1,210 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef mitkEquiDistantBlocksSorter_h #define mitkEquiDistantBlocksSorter_h #include "mitkDICOMDatasetSorter.h" #include "mitkDICOMSortCriterion.h" #include "mitkGantryTiltInformation.h" #include "mitkVector.h" #include namespace mitk { /** \ingroup DICOMReaderModule \brief Split inputs into blocks of equidistant slices (for use in DICOMITKSeriesGDCMReader). Since inter-slice distance is not recorded in DICOM tags, we must ensure that blocks are made up of slices that have equal distances between neighboring slices. This is especially necessary because itk::ImageSeriesReader is later used for the actual loading, and this class expects (and does nocht verify) equal inter-slice distance (see \ref DICOMITKSeriesGDCMReader_ForcedConfiguration). To achieve such grouping, the inter-slice distance is calculated from the first two different slice positions of a block. Following slices are added to a block as long as they can be added by adding the calculated inter-slice distance to the last slice of the block. Slices that do not fit into the expected distance pattern, are set aside for further analysis. This grouping is done until each file has been assigned to a group. Slices that share a position in space are also sorted into separate blocks during this step. So the result of this step is a set of blocks that contain only slices with equal z spacing and uniqe slices at each position. During sorting, the origins (documented in tag image position patient) are compared against expected origins (from former origin plus moving direction). As there will be minor differences in numbers (from both calculations and unprecise tag values), we must be a bit tolerant here. The default behavior is to expect that an origin is not further away from the expected position than 30% of the inter-slice distance. To support a legacy behavior of a former loader (DicomSeriesReader), this default can be restricted to a constant number of millimeters by calling SetToleratedOriginOffset(mm). Detailed implementation in AnalyzeFileForITKImageSeriesReaderSpacingAssumption(). */ class DICOMReader_EXPORT EquiDistantBlocksSorter : public DICOMDatasetSorter { public: mitkClassMacro( EquiDistantBlocksSorter, DICOMDatasetSorter ) itkNewMacro( EquiDistantBlocksSorter ) virtual DICOMTagList GetTagsOfInterest(); /** \brief Delegates work to AnalyzeFileForITKImageSeriesReaderSpacingAssumption(). AnalyzeFileForITKImageSeriesReaderSpacingAssumption() is called until it does not create multiple blocks anymore. */ virtual void Sort(); /** \brief Whether or not to accept images from a tilted acquisition in a single output group. */ void SetAcceptTilt(bool accept); /** \brief See class description and SetToleratedOriginOffset(). */ void SetToleratedOriginOffsetToAdaptive(double fractionOfInterSliceDistanct = 0.3); /** \brief See class description and SetToleratedOriginOffsetToAdaptive(). Default value of 0.005 is calculated so that we get a maximum of 1/10mm error when having a measurement crosses 20 slices in z direction (too strict? we don't know better..). */ void SetToleratedOriginOffset(double millimeters = 0.005); + double GetToleratedOriginOffset() const; + bool IsToleratedOriginOffsetAbsolute() const; + virtual void PrintConfiguration(std::ostream& os, const std::string& indent = "") const; + virtual bool operator==(const DICOMDatasetSorter& other) const; + protected: /** \brief Return type of AnalyzeFileForITKImageSeriesReaderSpacingAssumption(). Class contains the grouping result of method AnalyzeFileForITKImageSeriesReaderSpacingAssumption(), which takes as input a number of images, which are all equally oriented and spatially sorted along their normal direction. The result contains of two blocks: a first one is the grouping result, all of those images can be loaded into one image block because they have an equal origin-to-origin distance without any gaps in-between. */ class SliceGroupingAnalysisResult { public: SliceGroupingAnalysisResult(); /** \brief Grouping result, all same origin-to-origin distance w/o gaps. */ DICOMDatasetList GetBlockDatasets(); void SetFirstFilenameOfBlock(const std::string& filename); std::string GetFirstFilenameOfBlock() const; void SetLastFilenameOfBlock(const std::string& filename); std::string GetLastFilenameOfBlock() const; /** \brief Remaining files, which could not be grouped. */ DICOMDatasetList GetUnsortedDatasets(); /** \brief Wheter or not the grouped result contain a gantry tilt. */ bool ContainsGantryTilt(); /** \brief Detailed description of gantry tilt. */ const GantryTiltInformation& GetTiltInfo() const; /** \brief Meant for internal use by AnalyzeFileForITKImageSeriesReaderSpacingAssumption only. */ void AddFileToSortedBlock(DICOMDatasetAccess* dataset); /** \brief Meant for internal use by AnalyzeFileForITKImageSeriesReaderSpacingAssumption only. */ void AddFileToUnsortedBlock(DICOMDatasetAccess* dataset); void AddFilesToUnsortedBlock(const DICOMDatasetList& datasets); /** \brief Meant for internal use by AnalyzeFileForITKImageSeriesReaderSpacingAssumption only. \todo Could make sense to enhance this with an instance of GantryTiltInformation to store the whole result! */ void FlagGantryTilt(const GantryTiltInformation& tiltInfo); /** \brief Only meaningful for use by AnalyzeFileForITKImageSeriesReaderSpacingAssumption. */ void UndoPrematureGrouping(); protected: DICOMDatasetList m_GroupedFiles; DICOMDatasetList m_UnsortedFiles; GantryTiltInformation m_TiltInfo; std::string m_FirstFilenameOfBlock; std::string m_LastFilenameOfBlock; }; /** \brief Ensure an equal z-spacing for a group of files. Takes as input a number of images, which are all equally oriented and spatially sorted along their normal direction. Internally used by GetSeries. Returns two lists: the first one contins slices of equal inter-slice spacing. The second list contains remaining files, which need to be run through AnalyzeFileForITKImageSeriesReaderSpacingAssumption again. Relevant code that is matched here is in itkImageSeriesReader.txx (ImageSeriesReader::GenerateOutputInformation(void)), lines 176 to 245 (as of ITK 3.20) */ SliceGroupingAnalysisResult AnalyzeFileForITKImageSeriesReaderSpacingAssumption(const DICOMDatasetList& files, bool groupsOfSimilarImages); /** \brief Safely convert const char* to std::string. */ std::string ConstCharStarToString(const char* s); EquiDistantBlocksSorter(); virtual ~EquiDistantBlocksSorter(); EquiDistantBlocksSorter(const EquiDistantBlocksSorter& other); EquiDistantBlocksSorter& operator=(const EquiDistantBlocksSorter& other); bool m_AcceptTilt; typedef std::vector ResultsList; ResultsList m_SliceGroupingResults; double m_ToleratedOriginOffset; + bool m_ToleratedOriginOffsetIsAbsolute; }; } #endif diff --git a/Modules/DICOMReader/mitkNormalDirectionConsistencySorter.cpp b/Modules/DICOMReader/mitkNormalDirectionConsistencySorter.cpp index 40666d8a96..d152d03a6b 100644 --- a/Modules/DICOMReader/mitkNormalDirectionConsistencySorter.cpp +++ b/Modules/DICOMReader/mitkNormalDirectionConsistencySorter.cpp @@ -1,165 +1,173 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ //#define MBILOG_ENABLE_DEBUG #include "mitkNormalDirectionConsistencySorter.h" #include mitk::NormalDirectionConsistencySorter ::NormalDirectionConsistencySorter() :DICOMDatasetSorter() { } mitk::NormalDirectionConsistencySorter ::NormalDirectionConsistencySorter(const NormalDirectionConsistencySorter& other ) :DICOMDatasetSorter(other) { } mitk::NormalDirectionConsistencySorter ::~NormalDirectionConsistencySorter() { } void mitk::NormalDirectionConsistencySorter ::PrintConfiguration(std::ostream& os, const std::string& indent) const { os << indent << "NormalDirectionConsistencySorter" << std::endl; } mitk::NormalDirectionConsistencySorter& mitk::NormalDirectionConsistencySorter ::operator=(const NormalDirectionConsistencySorter& other) { if (this != &other) { DICOMDatasetSorter::operator=(other); } return *this; } +bool +mitk::NormalDirectionConsistencySorter +::operator==(const DICOMDatasetSorter& other) const +{ + return dynamic_cast(&other) != NULL; +} + + mitk::DICOMTagList mitk::NormalDirectionConsistencySorter ::GetTagsOfInterest() { DICOMTagList tags; tags.push_back( DICOMTag(0x0020, 0x0032) ); // ImagePositionPatient tags.push_back( DICOMTag(0x0020, 0x0037) ); // ImageOrientationPatient return tags; } void mitk::NormalDirectionConsistencySorter ::Sort() { DICOMDatasetList datasets = GetInput(); if (datasets.size() > 1) { // at some point in the code, there is the expectation that // the direction of the slice normals is the same as the direction between // first and last slice origin. We need to make this sure here, because // we want to feed the files into itk::ImageSeriesReader with the consistent // setting of ReverseOrderOff. static const DICOMTag tagImagePositionPatient = DICOMTag(0x0020,0x0032); // Image Position (Patient) static const DICOMTag tagImageOrientation = DICOMTag(0x0020, 0x0037); // Image Orientation DICOMDatasetAccess* firstDS = datasets.front(); DICOMDatasetAccess* lastDS = datasets.back(); // make sure here that the direction from slice to slice is the direction of // image normals... std::string imageOrientationString = firstDS->GetTagValueAsString( tagImageOrientation ); std::string imagePositionPatientFirst = firstDS->GetTagValueAsString( tagImagePositionPatient ); std::string imagePositionPatientLast = lastDS->GetTagValueAsString( tagImagePositionPatient ); static Vector3D right; right.Fill(0.0); static Vector3D up; up.Fill(0.0); static bool hasOrientation(false); DICOMStringToOrientationVectors( imageOrientationString, right, up, hasOrientation ); static Point3D firstOrigin; firstOrigin.Fill(0.0f); static bool firstHasOrigin(false); firstOrigin = DICOMStringToPoint3D( imagePositionPatientFirst, firstHasOrigin ); static Point3D lastOrigin; lastOrigin.Fill(0.0f); static bool lastHasOrigin(false); lastOrigin = DICOMStringToPoint3D( imagePositionPatientLast, lastHasOrigin ); static Vector3D normal; normal[0] = right[1] * up[2] - right[2] * up[1]; normal[1] = right[2] * up[0] - right[0] * up[2]; normal[2] = right[0] * up[1] - right[1] * up[0]; normal.Normalize(); static Vector3D directionOfSlices; directionOfSlices = lastOrigin - firstOrigin; directionOfSlices.Normalize(); static double projection = 0.0; projection = 0.0; projection = normal * directionOfSlices; MITK_DEBUG << "Making sense of \norientation '" << imageOrientationString << "'\nfirst position '" << imagePositionPatientFirst << "'\nlast position '" << imagePositionPatientLast << "'"; MITK_DEBUG << "Normal: " << normal; MITK_DEBUG << "Direction of slices: " << directionOfSlices; MITK_DEBUG << "Projection of direction onto slice normal: " << projection; if ( projection < 0.0 ) { MITK_DEBUG << "Need to reverse filenames"; std::reverse( datasets.begin(), datasets.end() ); m_TiltInfo = GantryTiltInformation::MakeFromTagValues( imagePositionPatientLast, imagePositionPatientFirst, imageOrientationString, datasets.size() - 1 ); } else { m_TiltInfo = GantryTiltInformation::MakeFromTagValues( imagePositionPatientFirst, imagePositionPatientLast, imageOrientationString, datasets.size() - 1 ); } } else // just ONE dataset, do not forget to reset tilt information { m_TiltInfo = GantryTiltInformation(); // empty info } this->SetNumberOfOutputs(1); this->SetOutput(0, datasets); } mitk::GantryTiltInformation mitk::NormalDirectionConsistencySorter ::GetTiltInformation() const { return m_TiltInfo; } diff --git a/Modules/DICOMReader/mitkNormalDirectionConsistencySorter.h b/Modules/DICOMReader/mitkNormalDirectionConsistencySorter.h index b0dbfb478f..8fec03b966 100644 --- a/Modules/DICOMReader/mitkNormalDirectionConsistencySorter.h +++ b/Modules/DICOMReader/mitkNormalDirectionConsistencySorter.h @@ -1,72 +1,74 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef mitkNormalDirectionConsistencySorter_h #define mitkNormalDirectionConsistencySorter_h #include "mitkDICOMDatasetSorter.h" #include "mitkGantryTiltInformation.h" namespace mitk { /** \ingroup DICOMReaderModule \brief Makes sure that the order of files is along the image plane normals. When loading with ImageSeriesReader and initializing an mitk::Image with the result we need to make sure that the order of inputs for the ImageSeriesReader is along the normal of the images. I.e. The direction of the normal needs to be the same direction as the vector from the first to the last image origin. Since this class is used as a last sorting step before loading, it will also calculate (and return) an updated GantryTiltInformation object. \note This class might be a workaround for another bug in MITK, but until this issue is completely understood, the workaround fixes the problem of images that appear upside-down. */ class DICOMReader_EXPORT NormalDirectionConsistencySorter : public DICOMDatasetSorter { public: mitkClassMacro( NormalDirectionConsistencySorter, DICOMDatasetSorter ) itkNewMacro( NormalDirectionConsistencySorter ) virtual DICOMTagList GetTagsOfInterest(); /// See class description. virtual void Sort(); /// See class description and DICOMITKSeriesGDCMReader. GantryTiltInformation GetTiltInformation() const; virtual void PrintConfiguration(std::ostream& os, const std::string& indent = "") const; + virtual bool operator==(const DICOMDatasetSorter& other) const; + protected: NormalDirectionConsistencySorter(); virtual ~NormalDirectionConsistencySorter(); NormalDirectionConsistencySorter(const NormalDirectionConsistencySorter& other); NormalDirectionConsistencySorter& operator=(const NormalDirectionConsistencySorter& other); GantryTiltInformation m_TiltInfo; }; } #endif diff --git a/Modules/DICOMReader/mitkSortByImagePositionPatient.cpp b/Modules/DICOMReader/mitkSortByImagePositionPatient.cpp index bea8ff52cf..da30f6be08 100644 --- a/Modules/DICOMReader/mitkSortByImagePositionPatient.cpp +++ b/Modules/DICOMReader/mitkSortByImagePositionPatient.cpp @@ -1,135 +1,142 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkSortByImagePositionPatient.h" #include "mitkDICOMTag.h" mitk::SortByImagePositionPatient ::SortByImagePositionPatient(DICOMSortCriterion::Pointer secondaryCriterion) :DICOMSortCriterion(secondaryCriterion) { } mitk::SortByImagePositionPatient ::~SortByImagePositionPatient() { } mitk::SortByImagePositionPatient ::SortByImagePositionPatient(const SortByImagePositionPatient& other ) :DICOMSortCriterion(other) { } mitk::SortByImagePositionPatient& mitk::SortByImagePositionPatient ::operator=(const SortByImagePositionPatient& other) { if (this != &other) { DICOMSortCriterion::operator=(other); } return *this; } + +bool +mitk::SortByImagePositionPatient +::operator==(const DICOMSortCriterion& other) const +{ + return dynamic_cast(&other) != NULL; // same class +} void mitk::SortByImagePositionPatient ::Print(std::ostream& os) const { os << "(0020,0032) Image Position (Patient) along normal of (0020,0037) Image Orientation (Patient)"; } mitk::DICOMTagList mitk::SortByImagePositionPatient ::GetTagsOfInterest() const { DICOMTagList tags; tags.push_back( DICOMTag(0x0020, 0x0032) ); // ImagePositionPatient tags.push_back( DICOMTag(0x0020, 0x0037) ); // ImageOrientationPatient return tags; } bool mitk::SortByImagePositionPatient ::IsLeftBeforeRight(const mitk::DICOMDatasetAccess* left, const mitk::DICOMDatasetAccess* right) const { // sort by distance to world origin, assuming (almost) equal orientation static const DICOMTag tagImagePositionPatient = DICOMTag(0x0020,0x0032); // Image Position (Patient) static const DICOMTag tagImageOrientation = DICOMTag(0x0020, 0x0037); // Image Orientation static Vector3D leftRight; leftRight.Fill(0.0); static Vector3D leftUp; leftUp.Fill(0.0); static bool leftHasOrientation(false); DICOMStringToOrientationVectors( left->GetTagValueAsString( tagImageOrientation ), leftRight, leftUp, leftHasOrientation ); static Vector3D rightRight; rightRight.Fill(0.0); static Vector3D rightUp; rightUp.Fill(0.0); static bool rightHasOrientation(false); DICOMStringToOrientationVectors( right->GetTagValueAsString( tagImageOrientation ), rightRight, rightUp, rightHasOrientation ); static Point3D leftOrigin; leftOrigin.Fill(0.0f); static bool leftHasOrigin(false); leftOrigin = DICOMStringToPoint3D( left->GetTagValueAsString( tagImagePositionPatient ), leftHasOrigin ); static Point3D rightOrigin; rightOrigin.Fill(0.0f); static bool rightHasOrigin(false); rightOrigin = DICOMStringToPoint3D( right->GetTagValueAsString( tagImagePositionPatient ), rightHasOrigin ); // we tolerate very small differences in image orientation, since we got to know about // acquisitions where these values change across a single series (7th decimal digit) // (http://bugs.mitk.org/show_bug.cgi?id=12263) // still, we want to check if our assumption of 'almost equal' orientations is valid for (unsigned int dim = 0; dim < 3; ++dim) { if ( fabs(leftRight[dim] - rightRight[dim]) > 0.0001 || fabs(leftUp[dim] - rightUp[dim]) > 0.0001) { MITK_ERROR << "Dicom images have different orientations."; throw std::logic_error("Dicom images have different orientations. Call GetSeries() first to separate images."); } } static Vector3D normal; normal[0] = leftRight[1] * leftUp[2] - leftRight[2] * leftUp[1]; normal[1] = leftRight[2] * leftUp[0] - leftRight[0] * leftUp[2]; normal[2] = leftRight[0] * leftUp[1] - leftRight[1] * leftUp[0]; static double leftDistance = 0.0; static double rightDistance = 0.0; leftDistance = 0.0; rightDistance = 0.0; // this computes the distance from world origin (0,0,0) ALONG THE NORMAL of the image planes for (unsigned int dim = 0; dim < 3; ++dim) { leftDistance += normal[dim] * leftOrigin[dim]; rightDistance += normal[dim] * rightOrigin[dim]; } // if we can sort by just comparing the distance, we do exactly that if ( fabs(leftDistance - rightDistance) >= mitk::eps) { // default: compare position return leftDistance < rightDistance; } else { return this->NextLevelIsLeftBeforeRight(left, right); } } diff --git a/Modules/DICOMReader/mitkSortByImagePositionPatient.h b/Modules/DICOMReader/mitkSortByImagePositionPatient.h index 950196a184..bc9fdad546 100644 --- a/Modules/DICOMReader/mitkSortByImagePositionPatient.h +++ b/Modules/DICOMReader/mitkSortByImagePositionPatient.h @@ -1,62 +1,64 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef mitkSortByImagePositionPatient_h #define mitkSortByImagePositionPatient_h #include "mitkDICOMSortCriterion.h" #include "mitkVector.h" namespace mitk { /** \ingroup DICOMReaderModule \brief Sort by distance of image origin along image normal (for use in DICOMTagBasedSorter). To compare two datasets, their distance to the world origin is calculated. This distance is calculated along the image normals because we do not know the image orientation in advance, to any of the three coordinates could be identical for all datasets. \note This class assumes that the datasets have identical orientations! */ class DICOMReader_EXPORT SortByImagePositionPatient : public DICOMSortCriterion { public: mitkClassMacro( SortByImagePositionPatient, DICOMSortCriterion ); mitkNewMacro1Param( SortByImagePositionPatient, DICOMSortCriterion::Pointer ); virtual DICOMTagList GetTagsOfInterest() const; virtual bool IsLeftBeforeRight(const mitk::DICOMDatasetAccess* left, const mitk::DICOMDatasetAccess* right) const; virtual void Print(std::ostream& os) const; + + virtual bool operator==(const DICOMSortCriterion& other) const; protected: SortByImagePositionPatient( DICOMSortCriterion::Pointer secondaryCriterion = NULL ); virtual ~SortByImagePositionPatient(); SortByImagePositionPatient(const SortByImagePositionPatient& other); SortByImagePositionPatient& operator=(const SortByImagePositionPatient& other); private: }; } #endif diff --git a/Modules/DICOMReader/mitkThreeDnTDICOMSeriesReader.cpp b/Modules/DICOMReader/mitkThreeDnTDICOMSeriesReader.cpp index 119e07f8ee..c57f552a97 100644 --- a/Modules/DICOMReader/mitkThreeDnTDICOMSeriesReader.cpp +++ b/Modules/DICOMReader/mitkThreeDnTDICOMSeriesReader.cpp @@ -1,242 +1,265 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkThreeDnTDICOMSeriesReader.h" #include "mitkITKDICOMSeriesReaderHelper.h" mitk::ThreeDnTDICOMSeriesReader ::ThreeDnTDICOMSeriesReader(unsigned int decimalPlacesForOrientation) :DICOMITKSeriesGDCMReader(decimalPlacesForOrientation) ,m_Group3DandT(true) { } mitk::ThreeDnTDICOMSeriesReader ::ThreeDnTDICOMSeriesReader(const ThreeDnTDICOMSeriesReader& other ) :itk::Object() ,DICOMITKSeriesGDCMReader(other) ,m_Group3DandT(true) { } mitk::ThreeDnTDICOMSeriesReader ::~ThreeDnTDICOMSeriesReader() { } mitk::ThreeDnTDICOMSeriesReader& mitk::ThreeDnTDICOMSeriesReader ::operator=(const ThreeDnTDICOMSeriesReader& other) { if (this != &other) { DICOMITKSeriesGDCMReader::operator=(other); this->m_Group3DandT = other.m_Group3DandT; } return *this; } +bool +mitk::ThreeDnTDICOMSeriesReader +::operator==(const DICOMFileReader& other) const +{ + if (const Self* otherSelf = dynamic_cast(&other)) + { + return + DICOMITKSeriesGDCMReader::operator==(other) + && this->m_Group3DandT == otherSelf->m_Group3DandT; + } + else + { + return false; + } +} + void mitk::ThreeDnTDICOMSeriesReader ::SetGroup3DandT(bool on) { m_Group3DandT = on; } +bool +mitk::ThreeDnTDICOMSeriesReader +::GetGroup3DandT() const +{ + return m_Group3DandT; +} + mitk::DICOMITKSeriesGDCMReader::SortingBlockList mitk::ThreeDnTDICOMSeriesReader ::Condense3DBlocks(SortingBlockList& resultOf3DGrouping) { if (!m_Group3DandT) { return resultOf3DGrouping; // don't work if nobody asks us to } SortingBlockList remainingBlocks = resultOf3DGrouping; SortingBlockList non3DnTBlocks; SortingBlockList true3DnTBlocks; std::vector true3DnTBlocksTimeStepCount; // we should describe our need for this tag as needed via a function // (however, we currently know that the superclass will always need this tag) const DICOMTag tagImagePositionPatient(0x0020, 0x0032); while (!remainingBlocks.empty()) { // new block to fill up DICOMGDCMImageFrameList& firstBlock = remainingBlocks.front(); DICOMGDCMImageFrameList current3DnTBlock = firstBlock; int current3DnTBlockNumberOfTimeSteps = 1; // get block characteristics of first block unsigned int currentBlockNumberOfSlices = firstBlock.size(); std::string currentBlockFirstOrigin = firstBlock.front()->GetTagValueAsString( tagImagePositionPatient ); std::string currentBlockLastOrigin = firstBlock.back()->GetTagValueAsString( tagImagePositionPatient ); remainingBlocks.pop_front(); // compare all other blocks against the first one for (SortingBlockList::iterator otherBlockIter = remainingBlocks.begin(); otherBlockIter != remainingBlocks.end(); /*++otherBlockIter*/) // <-- inside loop { // get block characteristics from first block DICOMGDCMImageFrameList& otherBlock = *otherBlockIter; unsigned int otherBlockNumberOfSlices = otherBlock.size(); std::string otherBlockFirstOrigin = otherBlock.front()->GetTagValueAsString( tagImagePositionPatient ); std::string otherBlockLastOrigin = otherBlock.back()->GetTagValueAsString( tagImagePositionPatient ); // add matching blocks to current3DnTBlock // keep other blocks for later if ( otherBlockNumberOfSlices == currentBlockNumberOfSlices && otherBlockFirstOrigin == currentBlockFirstOrigin && otherBlockLastOrigin == currentBlockLastOrigin ) { // matching block ++current3DnTBlockNumberOfTimeSteps; current3DnTBlock.insert( current3DnTBlock.end(), otherBlock.begin(), otherBlock.end() ); // append // remove this block from remainingBlocks otherBlockIter = remainingBlocks.erase(otherBlockIter); // make sure iterator otherBlockIter is valid afterwards } else { ++otherBlockIter; } } // in any case, we now now all about the first block of our list ... // ... and we wither call it 3D o 3D+t if (current3DnTBlockNumberOfTimeSteps > 1) { true3DnTBlocks.push_back(current3DnTBlock); true3DnTBlocksTimeStepCount.push_back(current3DnTBlockNumberOfTimeSteps); } else { non3DnTBlocks.push_back(current3DnTBlock); } } // create output for real 3D+t blocks (other outputs will be created by superclass) // set 3D+t flag on output block this->SetNumberOfOutputs( true3DnTBlocks.size() ); unsigned int o = 0; for (SortingBlockList::iterator blockIter = true3DnTBlocks.begin(); blockIter != true3DnTBlocks.end(); ++o, ++blockIter) { // bad copy&paste code from DICOMITKSeriesGDCMReader, should be handled in a better way DICOMGDCMImageFrameList& gdcmFrameInfoList = *blockIter; assert(!gdcmFrameInfoList.empty()); // reverse frames if necessary // update tilt information from absolute last sorting DICOMDatasetList datasetList = ToDICOMDatasetList( gdcmFrameInfoList ); m_NormalDirectionConsistencySorter->SetInput( datasetList ); m_NormalDirectionConsistencySorter->Sort(); DICOMGDCMImageFrameList sortedGdcmInfoFrameList = FromDICOMDatasetList( m_NormalDirectionConsistencySorter->GetOutput(0) ); const GantryTiltInformation& tiltInfo = m_NormalDirectionConsistencySorter->GetTiltInformation(); // set frame list for current block DICOMImageFrameList frameList = ToDICOMImageFrameList( sortedGdcmInfoFrameList ); assert(!frameList.empty()); DICOMImageBlockDescriptor block; block.SetTagCache( this ); // important: this must be before SetImageFrameList(), because SetImageFrameList will trigger reading of lots of interesting tags! block.SetImageFrameList( frameList ); block.SetTiltInformation( tiltInfo ); block.SetFlag("3D+t", true); block.SetIntProperty("timesteps", true3DnTBlocksTimeStepCount[o]); MITK_DEBUG << "Found " << true3DnTBlocksTimeStepCount[o] << " timesteps"; this->SetOutput( o, block ); } return non3DnTBlocks; } bool mitk::ThreeDnTDICOMSeriesReader ::LoadImages() { bool success = true; unsigned int numberOfOutputs = this->GetNumberOfOutputs(); for (unsigned int o = 0; o < numberOfOutputs; ++o) { DICOMImageBlockDescriptor& block = this->InternalGetOutput(o); if (block.GetFlag("3D+t", false)) { success &= this->LoadMitkImageForOutput(o); } else { success &= DICOMITKSeriesGDCMReader::LoadMitkImageForOutput(o); // let superclass handle non-3D+t } } return success; } bool mitk::ThreeDnTDICOMSeriesReader ::LoadMitkImageForImageBlockDescriptor(DICOMImageBlockDescriptor& block) const { PushLocale(); const DICOMImageFrameList& frames = block.GetImageFrameList(); const GantryTiltInformation tiltInfo = block.GetTiltInformation(); bool hasTilt = tiltInfo.IsRegularGantryTilt(); int numberOfTimesteps = block.GetIntProperty("timesteps", 1); if (numberOfTimesteps == 1) { return DICOMITKSeriesGDCMReader::LoadMitkImageForImageBlockDescriptor(block); } int numberOfFramesPerTimestep = frames.size() / numberOfTimesteps; assert( int(double((double)frames.size() / (double)numberOfTimesteps )) == numberOfFramesPerTimestep ); // this should hold ITKDICOMSeriesReaderHelper::StringContainerList filenamesPerTimestep; for (int timeStep = 0; timeStepFilename ); } filenamesPerTimestep.push_back( filenamesOfThisTimeStep ); } mitk::ITKDICOMSeriesReaderHelper helper; mitk::Image::Pointer mitkImage = helper.Load3DnT( filenamesPerTimestep, m_FixTiltByShearing && hasTilt, tiltInfo ); block.SetMitkImage( mitkImage ); PopLocale(); return true; } diff --git a/Modules/DICOMReader/mitkThreeDnTDICOMSeriesReader.h b/Modules/DICOMReader/mitkThreeDnTDICOMSeriesReader.h index d8cab3b715..d977c9814a 100644 --- a/Modules/DICOMReader/mitkThreeDnTDICOMSeriesReader.h +++ b/Modules/DICOMReader/mitkThreeDnTDICOMSeriesReader.h @@ -1,84 +1,86 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef mitkThreeDnTDICOMSeriesReader_h #define mitkThreeDnTDICOMSeriesReader_h #include "mitkDICOMITKSeriesGDCMReader.h" #include "DICOMReaderExports.h" namespace mitk { /** \ingroup DICOMReader \brief Extends DICOMITKSeriesGDCMReader by sorting/grouping into 3D+t image blocks. This class reuses the DICOMITKSeriesGDCMReader class and adds the option of grouping 3D blocks at the same spatial position into a single block, which is loaded as a 3D+t mitk::Image (compare to \ref DICOMITKSeriesGDCMReader_Condensing). To group two output blocks into a single 3D+t block, this class tests a number of requirements that the two blocks must fulfill: - the origin of the first slice must be identical - the origin of the last slice must be identical - the number of slices must be identical The output blocks described by DICOMImageBlockDescriptor will contains the following properties: - \b "3D+t": true if the image is 3D+t - \b "timesteps": number of timesteps of an image (only defined if "3D+t" is true) */ class DICOMReader_EXPORT ThreeDnTDICOMSeriesReader : public DICOMITKSeriesGDCMReader { public: mitkClassMacro( ThreeDnTDICOMSeriesReader, DICOMITKSeriesGDCMReader ); mitkCloneMacro( ThreeDnTDICOMSeriesReader ); itkNewMacro( ThreeDnTDICOMSeriesReader ); mitkNewMacro1Param( ThreeDnTDICOMSeriesReader, unsigned int ); /// \brief Control whether 3D+t grouping shall actually be attempted. void SetGroup3DandT(bool on); + bool GetGroup3DandT() const; // void AllocateOutputImages(); /// \brief Load via multiple calls to itk::ImageSeriesReader. virtual bool LoadImages(); + virtual bool operator==(const DICOMFileReader& other) const; protected: ThreeDnTDICOMSeriesReader(unsigned int decimalPlacesForOrientation = 5); virtual ~ThreeDnTDICOMSeriesReader(); ThreeDnTDICOMSeriesReader(const ThreeDnTDICOMSeriesReader& other); ThreeDnTDICOMSeriesReader& operator=(const ThreeDnTDICOMSeriesReader& other); /** \brief Analyze the groups produced by DICOMITKSeriesGDCMReader for 3D+t properties. This method tests whether some blocks are at the same spatial position and groups them into single blocks. */ virtual SortingBlockList Condense3DBlocks(SortingBlockList&); bool LoadMitkImageForImageBlockDescriptor(DICOMImageBlockDescriptor& block) const; bool m_Group3DandT; }; } #endif