diff --git a/Modules/DICOMReader/Testing/mitkDICOMNullFileReader.cpp b/Modules/DICOMReader/Testing/mitkDICOMNullFileReader.cpp index 949cd0f728..8a6891b938 100644 --- a/Modules/DICOMReader/Testing/mitkDICOMNullFileReader.cpp +++ b/Modules/DICOMReader/Testing/mitkDICOMNullFileReader.cpp @@ -1,88 +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. ===================================================================*/ #include "mitkDICOMNullFileReader.h" mitk::DICOMNullFileReader ::DICOMNullFileReader() :DICOMFileReader() { } mitk::DICOMNullFileReader ::~DICOMNullFileReader() { } mitk::DICOMNullFileReader ::DICOMNullFileReader(const DICOMNullFileReader& other ) :DICOMFileReader(other) { } mitk::DICOMNullFileReader& mitk::DICOMNullFileReader ::operator=(const DICOMNullFileReader& other) { if (this != &other) { DICOMFileReader::operator=(other); } return *this; } +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 708a59e21f..77e671c995 100644 --- a/Modules/DICOMReader/Testing/mitkDICOMNullFileReader.h +++ b/Modules/DICOMReader/Testing/mitkDICOMNullFileReader.h @@ -1,53 +1,55 @@ /*=================================================================== 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); 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/mitkDICOMDatasetSorter.h b/Modules/DICOMReader/mitkDICOMDatasetSorter.h index f0772d99a3..512b0de030 100644 --- a/Modules/DICOMReader/mitkDICOMDatasetSorter.h +++ b/Modules/DICOMReader/mitkDICOMDatasetSorter.h @@ -1,65 +1,67 @@ /*=================================================================== 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 { class DICOMReader_EXPORT DICOMDatasetSorter : public itk::LightObject { public: mitkClassMacro( DICOMDatasetSorter, itk::LightObject ) virtual DICOMTagList GetTagsOfInterest() = 0; void SetInput(DICOMDatasetList filenames); const DICOMDatasetList& GetInput() const; virtual void Sort() = 0; unsigned int GetNumberOfOutputs() const; const DICOMDatasetList& GetOutput(unsigned int index) const; DICOMDatasetList& GetOutput(unsigned int index); + virtual void PrintConfiguration(std::ostream& os, const std::string& indent = "") 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 b7ae23911a..064894e072 100644 --- a/Modules/DICOMReader/mitkDICOMFileReader.cpp +++ b/Modules/DICOMReader/mitkDICOMFileReader.cpp @@ -1,155 +1,165 @@ /*=================================================================== 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::LightObject() { } mitk::DICOMFileReader ::~DICOMFileReader() { } mitk::DICOMFileReader ::DICOMFileReader(const DICOMFileReader& other ) :itk::LightObject() ,m_Outputs( other.m_Outputs ) { } mitk::DICOMFileReader& mitk::DICOMFileReader ::operator=(const DICOMFileReader& other) { if (this != &other) { m_InputFilenames = other.m_InputFilenames; m_Outputs = other.m_Outputs; } return *this; } 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 -::PrintOutputs(std::ostream& os, bool filenameDetails) +::PrintConfiguration(std::ostream& os) const { - os << "---- Outputs of DICOMFilereader " << (void*)this << "----"<< std::endl; + 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 e3c230d0f8..38febb268d 100644 --- a/Modules/DICOMReader/mitkDICOMFileReader.h +++ b/Modules/DICOMReader/mitkDICOMFileReader.h @@ -1,83 +1,85 @@ /*=================================================================== 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 ? class DICOMReader_EXPORT DICOMFileReader : public itk::LightObject { public: enum LoadingConfidence { NoSupport = 0, FullSupport = 1, PartialSupport = 2, }; mitkClassMacro( DICOMFileReader, itk::LightObject ) void SetInputFiles(StringList filenames); const StringList& GetInputFiles() const; virtual void AnalyzeInputFiles() = 0; unsigned int GetNumberOfOutputs() const; const DICOMImageBlockDescriptor& GetOutput(unsigned int index) const; // void AllocateOutputImages(); virtual bool LoadImages() = 0; virtual bool CanHandleFile(const std::string& filename) = 0; - void PrintOutputs(std::ostream& os, bool filenameDetails = false); + void PrintConfiguration(std::ostream& os) const; + void PrintOutputs(std::ostream& os, bool filenameDetails = false) const; static bool IsDICOM(const std::string& filename); 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); DICOMImageBlockDescriptor& InternalGetOutput(unsigned int index); + virtual void InternalPrintConfiguration(std::ostream& os) const = 0; private: StringList m_InputFilenames; std::vector< DICOMImageBlockDescriptor > m_Outputs; }; } #endif diff --git a/Modules/DICOMReader/mitkDICOMFilenameSorter.cpp b/Modules/DICOMReader/mitkDICOMFilenameSorter.cpp index 23850e66a9..f4f83cbef5 100644 --- a/Modules/DICOMReader/mitkDICOMFilenameSorter.cpp +++ b/Modules/DICOMReader/mitkDICOMFilenameSorter.cpp @@ -1,73 +1,80 @@ /*=================================================================== 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; } 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/mitkDICOMFilenameSorter.h b/Modules/DICOMReader/mitkDICOMFilenameSorter.h index d80cff0c13..0945c9dc0d 100644 --- a/Modules/DICOMReader/mitkDICOMFilenameSorter.h +++ b/Modules/DICOMReader/mitkDICOMFilenameSorter.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 mitkDICOMFilenameSorter_h #define mitkDICOMFilenameSorter_h #include "mitkDICOMDatasetSorter.h" namespace mitk { /** \brief sort files based on filename (last resort). */ class DICOMReader_EXPORT 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; + 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/mitkDICOMITKSeriesGDCMReader.cpp b/Modules/DICOMReader/mitkDICOMITKSeriesGDCMReader.cpp index 7e915e22b3..fbc4436c78 100644 --- a/Modules/DICOMReader/mitkDICOMITKSeriesGDCMReader.cpp +++ b/Modules/DICOMReader/mitkDICOMITKSeriesGDCMReader.cpp @@ -1,514 +1,544 @@ /*=================================================================== 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 "mitkDICOMITKSeriesGDCMReader.h" #include "mitkITKDICOMSeriesReaderHelper.h" #include "mitkGantryTiltInformation.h" #include "mitkDICOMTagBasedSorter.h" #include #include #include mitk::DICOMITKSeriesGDCMReader ::DICOMITKSeriesGDCMReader() :DICOMFileReader() ,m_FixTiltByShearing(true) { + this->EnsureMandatorySortersArePresent(); } mitk::DICOMITKSeriesGDCMReader ::DICOMITKSeriesGDCMReader(const DICOMITKSeriesGDCMReader& other ) :DICOMFileReader(other) ,m_FixTiltByShearing(false) { + this->EnsureMandatorySortersArePresent(); } 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(); } return *this; } void mitk::DICOMITKSeriesGDCMReader ::SetFixTiltByShearing(bool on) { m_FixTiltByShearing = on; } mitk::DICOMGDCMImageFrameList mitk::DICOMITKSeriesGDCMReader -::FromDICOMDatasetList(DICOMDatasetList& input) +::FromDICOMDatasetList(const DICOMDatasetList& input) { DICOMGDCMImageFrameList output; output.reserve(input.size()); - for(DICOMDatasetList::iterator inputIter = input.begin(); + 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(DICOMGDCMImageFrameList& input) +::ToDICOMDatasetList(const DICOMGDCMImageFrameList& input) { DICOMDatasetList output; output.reserve(input.size()); - for(DICOMGDCMImageFrameList::iterator inputIter = input.begin(); + 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(DICOMGDCMImageFrameList& input) +::ToDICOMImageFrameList(const DICOMGDCMImageFrameList& input) { DICOMImageFrameList output; output.reserve(input.size()); - for(DICOMGDCMImageFrameList::iterator inputIter = input.begin(); + 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() { 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() { 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() { - // at this point, make sure we have a sorting element at the end that splits geometrically separate blocks - this->EnsureMandatorySortersArePresent(); - 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(); // 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(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(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"); - SortingBlockList nextStepSorting; // we should not modify our input list while processing it unsigned int sorterIndex = 0; for(SorterList::iterator sorterIter = m_Sorter.begin(); sorterIter != m_Sorter.end(); ++sorterIndex, ++sorterIter) { - std::stringstream ss; ss << "Sorting step " << sorterIndex; - timer.Start( ss.str().c_str() ); - nextStepSorting.clear(); - DICOMDatasetSorter::Pointer& sorter = *sorterIter; - - MITK_DEBUG << "================================================================================"; - MITK_DEBUG << "DICOMITKSeriesGDCMReader: " << ss.str() << ": " << m_SortingResultInProgress.size() << " groups input"; - unsigned int groupIndex = 0; - - for(SortingBlockList::iterator blockIter = m_SortingResultInProgress.begin(); - blockIter != m_SortingResultInProgress.end(); - ++groupIndex, ++blockIter) - { - 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(); - } + m_SortingResultInProgress = this->InternalExecuteSortingStep(sorterIndex, *sorterIter, m_SortingResultInProgress, &timer); + } - DICOMGDCMImageFrameList sortedGdcmInfoFrameList = FromDICOMDatasetList(blockResult); - nextStepSorting.push_back( sortedGdcmInfoFrameList ); - } - } + // a last extra-sorting step: ensure equidistant slices + m_SortingResultInProgress = this->InternalExecuteSortingStep(sorterIndex, m_EquiDistantBlocksSorter.GetPointer(), m_SortingResultInProgress, &timer); - m_SortingResultInProgress = nextStepSorting; - timer.Stop( ss.str().c_str() ); - } 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; DICOMImageFrameList frameList = ToDICOMImageFrameList( gdcmFrameInfoList ); assert(!gdcmFrameInfoList.empty()); 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 ); const GantryTiltInformation& tiltInfo = m_EquiDistantBlocksSorter->GetTiltInformation( (gdcmFrameInfoList.front())->GetFilenameIfAvailable() ); block.SetFlag("gantryTilt", tiltInfo.IsRegularGantryTilt()); block.SetTiltInformation( tiltInfo ); static const DICOMTag tagPixelSpacing(0x0028,0x0030); static const DICOMTag tagImagerPixelSpacing(0x0018,0x1164); std::string pixelSpacingString = (gdcmFrameInfoList.front())->GetTagValueAsString( tagPixelSpacing ); std::string imagerPixelSpacingString = gdcmFrameInfoList.front()->GetTagValueAsString( tagImagerPixelSpacing ); block.SetPixelSpacingInformation(pixelSpacingString, imagerPixelSpacingString); static const DICOMTag tagSOPClassUID(0x0008,0x0016); std::string sopClassUID = (gdcmFrameInfoList.front())->GetTagValueAsString( tagSOPClassUID ); block.SetSOPClassUID(sopClassUID); block.SetReaderImplementationLevel( this->GetReaderImplementationLevel(sopClassUID) ); 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; + 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 uid) const { if (uid.empty()) { return SOPClassUnknown; } gdcm::UIDs uidKnowledge; uidKnowledge.SetFromUID( uid.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 ::LoadMitkImageForOutput(unsigned int o) { PushLocale(); DICOMImageBlockDescriptor& block = this->InternalGetOutput(o); const DICOMImageFrameList& frames = block.GetImageFrameList(); const GantryTiltInformation tiltInfo = block.GetTiltInformation(); bool hasTilt = block.GetFlag("gantryTilt", false); if (hasTilt) { MITK_DEBUG << "When loading image " << o << ": got tilt info:"; //tiltInfo.Print(std::cout); } else { MITK_DEBUG << "When loading image " << o << ": has NO info."; } ITKDICOMSeriesReaderHelper::StringContainer filenames; for (DICOMImageFrameList::const_iterator frameIter = frames.begin(); frameIter != frames.end(); ++frameIter) { filenames.push_back( (*frameIter)->Filename ); } mitk::ITKDICOMSeriesReaderHelper helper; mitk::Image::Pointer mitkImage = helper.Load( filenames, m_FixTiltByShearing && hasTilt, tiltInfo ); // TODO preloaded images, caching..? block.SetMitkImage( mitkImage ); PopLocale(); return true; } bool mitk::DICOMITKSeriesGDCMReader ::CanHandleFile(const std::string& itkNotUsed(filename)) { return true; // can handle all // peek into file, check DCM // nice-to-have: check multi-framedness } void mitk::DICOMITKSeriesGDCMReader ::AddSortingElement(DICOMDatasetSorter* sorter, bool atFront) { assert(sorter); if (atFront) { m_Sorter.push_front( sorter ); } else { m_Sorter.push_back( sorter ); } } void mitk::DICOMITKSeriesGDCMReader ::EnsureMandatorySortersArePresent() { - // TODO do just once! + // TODO do just once! Do it in c'tor and handle this step extra! 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(5) ); // Image Orientation (Patient) // TODO: configurable! 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 ); - - this->AddSortingElement( m_EquiDistantBlocksSorter ); } std::string mitk::DICOMITKSeriesGDCMReader ::GetTagValue(DICOMImageFrameInfo* frame, const DICOMTag& tag) const { 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 bd8f7882b9..935d3d26d9 100644 --- a/Modules/DICOMReader/mitkDICOMITKSeriesGDCMReader.h +++ b/Modules/DICOMReader/mitkDICOMITKSeriesGDCMReader.h @@ -1,116 +1,129 @@ /*=================================================================== 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 "DICOMReaderExports.h" #include // TODO tests seem to pass if reader creates NO output at all! // TODO preloaded volumes?? could be solved in a different way.. +namespace itk +{ + class TimeProbesCollectorBase; +} + namespace mitk { class DICOMReader_EXPORT DICOMITKSeriesGDCMReader : public DICOMFileReader, protected DICOMTagCache { public: mitkClassMacro( DICOMITKSeriesGDCMReader, DICOMFileReader ); mitkCloneMacro( DICOMITKSeriesGDCMReader ); itkNewMacro( DICOMITKSeriesGDCMReader ); virtual void AnalyzeInputFiles(); // void AllocateOutputImages(); virtual bool LoadImages(); virtual bool CanHandleFile(const std::string& filename); virtual void AddSortingElement(DICOMDatasetSorter* sorter, bool atFront = false); void SetFixTiltByShearing(bool on); protected: + virtual void InternalPrintConfiguration(std::ostream& os) const; + virtual std::string GetTagValue(DICOMImageFrameInfo* frame, const DICOMTag& tag) const; std::string GetActiveLocale() const; void PushLocale(); void PopLocale(); DICOMITKSeriesGDCMReader(); virtual ~DICOMITKSeriesGDCMReader(); DICOMITKSeriesGDCMReader(const DICOMITKSeriesGDCMReader& other); DICOMITKSeriesGDCMReader& operator=(const DICOMITKSeriesGDCMReader& other); - DICOMDatasetList ToDICOMDatasetList(DICOMGDCMImageFrameList& input); - DICOMGDCMImageFrameList FromDICOMDatasetList(DICOMDatasetList& input); - DICOMImageFrameList ToDICOMImageFrameList(DICOMGDCMImageFrameList& input); + DICOMDatasetList ToDICOMDatasetList(const DICOMGDCMImageFrameList& input); + DICOMGDCMImageFrameList FromDICOMDatasetList(const DICOMDatasetList& input); + DICOMImageFrameList ToDICOMImageFrameList(const DICOMGDCMImageFrameList& input); typedef std::list SortingBlockList; /// \return REMAINING blocks virtual SortingBlockList Condense3DBlocks(SortingBlockList& resultOf3DGrouping); + SortingBlockList InternalExecuteSortingStep( + unsigned int sortingStepIndex, + DICOMDatasetSorter::Pointer sorter, + const SortingBlockList& input, + itk::TimeProbesCollectorBase* timer); + void EnsureMandatorySortersArePresent(); virtual bool LoadMitkImageForOutput(unsigned int o); Image::Pointer FixupSpacing(Image* mitkImage, const DICOMImageBlockDescriptor& block) const; ReaderImplementationLevel GetReaderImplementationLevel(const std::string uid) const; private: 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: bool m_3DBlocksWereCondensed; SortingBlockList m_SortingResultInProgress; typedef std::list SorterList; SorterList m_Sorter; protected: // NOT nice, made available to ThreeDnTDICOMSeriesReader due to lack of time mitk::EquiDistantBlocksSorter::Pointer m_EquiDistantBlocksSorter; private: std::stack m_ReplacedCLocales; std::stack m_ReplacedCinLocales; DICOMGDCMImageFrameList m_InputFrameList; gdcm::Scanner m_GDCMScanner; }; } #endif diff --git a/Modules/DICOMReader/mitkDICOMReaderConfigurator.cpp b/Modules/DICOMReader/mitkDICOMReaderConfigurator.cpp index 1f9ef381c6..7647c6faa7 100644 --- a/Modules/DICOMReader/mitkDICOMReaderConfigurator.cpp +++ b/Modules/DICOMReader/mitkDICOMReaderConfigurator.cpp @@ -1,265 +1,319 @@ /*=================================================================== 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) { 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) { TiXmlDocument doc; doc.Parse(xmlContents.c_str(), 0, TIXML_ENCODING_UTF8); return this->CreateFromTiXmlDocument( doc ); } mitk::DICOMFileReader::Pointer mitk::DICOMReaderConfigurator ::CreateFromTiXmlDocument(TiXmlDocument& doc) { 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); if (classname == "ThreeDnTDICOMSeriesReader") { mitk::ThreeDnTDICOMSeriesReader::Pointer reader = mitk::ThreeDnTDICOMSeriesReader::New(); return ConfigureThreeDnTDICOMSeriesReader(reader, rootElement).GetPointer(); } else if (classname == "DICOMITKSeriesGDCMReader") { mitk::DICOMITKSeriesGDCMReader::Pointer reader = mitk::DICOMITKSeriesGDCMReader::New(); return ConfigureDICOMITKSeriesGDCMReader(reader, rootElement).GetPointer(); } else { MITK_ERROR << "DICOMFileReader tag names unknown class '" << classname << "'"; return NULL; } } - - /* - mitk::DICOMTagBasedSorter::Pointer tagSorter = mitk::DICOMTagBasedSorter::New(); - - TiXmlHandle hroot (0); - hroot = TiXmlHandle (doc.FirstChildElement()); - - MITK_INFO<< "Loading dicom reader configuration... "<Value(); - - if (hroot.FirstChild("DistinguishingTags").Element() == NULL) - return tagSorter; - - TiXmlElement* distTag = hroot.FirstChild("DistinguishingTags").FirstChild().Element(); - - - for( ;distTag; distTag=distTag->NextSiblingElement()) - { - tagSorter->AddDistinguishingTag(this->getTagFromXMLElement(distTag)); - } - - // Sorting tags are loaded in reverse order - if (hroot.FirstChild("SortingTags").Element() == NULL) - return tagSorter; - - TiXmlNode* sortTag = hroot.FirstChild("SortingTags").ToNode()->LastChild(); - - mitk::DICOMSortCriterion::Pointer sortingCrit; - for( ;sortTag; sortTag=sortTag->PreviousSibling()) - { - if (sortingCrit.IsNull()) - sortingCrit = mitk::DICOMSortByTag::New(this->getTagFromXMLElement(sortTag->ToElement())).GetPointer(); - else - sortingCrit = mitk::DICOMSortByTag::New(this->getTagFromXMLElement(sortTag->ToElement()), sortingCrit).GetPointer(); - } - tagSorter->SetSortCriterion(sortingCrit.GetPointer()); - - return tagSorter; - */ - return mitk::ClassicDICOMSeriesReader::New().GetPointer(); + 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) { assert(element); // use all the base class configuration if (this->ConfigureDICOMITKSeriesGDCMReader( reader.GetPointer(), element ).IsNull()) { - - // 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; + return NULL; } - else + + // add the "group3DnT" flag + bool group3DnT(true); + const char* group3DnTC = element->Attribute("group3DnT"); + if (group3DnTC) { - return NULL; + std::string group3DnTS(group3DnTC); + group3DnT = boolStringTrue(group3DnTS); } + + reader->SetGroup3DandT( group3DnT ); + + return reader; } mitk::DICOMITKSeriesGDCMReader::Pointer mitk::DICOMReaderConfigurator ::ConfigureDICOMITKSeriesGDCMReader(DICOMITKSeriesGDCMReader::Pointer reader, TiXmlElement* element) { assert(element); // "fixTiltByShearing" flag bool fixTiltByShearing(false); const char* fixTiltByShearingC = element->Attribute("fixTiltByShearing"); if (fixTiltByShearingC) { std::string fixTiltByShearingS(fixTiltByShearingC); fixTiltByShearing = boolStringTrue(fixTiltByShearingS); } reader->SetFixTiltByShearing( fixTiltByShearing ); // "distinguishing tags" mitk::DICOMTagBasedSorter::Pointer tagSorter = mitk::DICOMTagBasedSorter::New(); - TiXmlElement* dElement = element->FirstChildElement("DistinguishingTags"); + 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 ); } catch(...) { return NULL; } } } - // "sorting" + // "sorting tags" + TiXmlElement* sElement = element->FirstChildElement("Sorting"); + if (dElement) + { + 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() ); // TODO get ConstPointer declarations right here + } reader->AddSortingElement( tagSorter ); return reader; } std::string mitk::DICOMReaderConfigurator ::requiredStringAttribute(TiXmlElement* xmlElement, const std::string& key) { 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) +{ + 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) { 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"); - // convert string to int (assuming string is in hex format with leading "0x" like "0x0020") - unsigned int group(0); - unsigned int element(0); + 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) +{ + mitk::DICOMTag tag = tagFromXMLElement(xmlElement); + return DICOMSortByTag::New(tag, secondaryCriterion).GetPointer(); +} - return DICOMTag(group, element); +mitk::DICOMSortCriterion::Pointer +mitk::DICOMReaderConfigurator +::CreateSortByImagePositionPatient(TiXmlElement*, DICOMSortCriterion::Pointer secondaryCriterion) +{ + return SortByImagePositionPatient::New(secondaryCriterion).GetPointer(); } diff --git a/Modules/DICOMReader/mitkDICOMReaderConfigurator.h b/Modules/DICOMReader/mitkDICOMReaderConfigurator.h index 4d5fd85c8b..bccec1f782 100644 --- a/Modules/DICOMReader/mitkDICOMReaderConfigurator.h +++ b/Modules/DICOMReader/mitkDICOMReaderConfigurator.h @@ -1,55 +1,59 @@ /*=================================================================== 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" // to put into private implementation #include "tinyxml.h" namespace mitk { 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); 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); ThreeDnTDICOMSeriesReader::Pointer ConfigureThreeDnTDICOMSeriesReader(ThreeDnTDICOMSeriesReader::Pointer reader, TiXmlElement*); DICOMITKSeriesGDCMReader::Pointer ConfigureDICOMITKSeriesGDCMReader(DICOMITKSeriesGDCMReader::Pointer reader, TiXmlElement*); + + DICOMSortCriterion::Pointer CreateDICOMSortByTag(TiXmlElement* xmlElement, DICOMSortCriterion::Pointer secondaryCriterion); + DICOMSortCriterion::Pointer CreateSortByImagePositionPatient(TiXmlElement* xmlElement, DICOMSortCriterion::Pointer secondaryCriterion); }; } // namespace #endif // mitkDICOMReaderConfigurator_h diff --git a/Modules/DICOMReader/mitkDICOMSortByTag.cpp b/Modules/DICOMReader/mitkDICOMSortByTag.cpp index 5117e7390c..8cacc7fef5 100644 --- a/Modules/DICOMReader/mitkDICOMSortByTag.cpp +++ b/Modules/DICOMReader/mitkDICOMSortByTag.cpp @@ -1,117 +1,125 @@ /*=================================================================== 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; } +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) ) { 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 8ca6c48596..fbbcc579c0 100644 --- a/Modules/DICOMReader/mitkDICOMSortByTag.h +++ b/Modules/DICOMReader/mitkDICOMSortByTag.h @@ -1,54 +1,56 @@ /*=================================================================== 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 { 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; + 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.cpp b/Modules/DICOMReader/mitkDICOMSortCriterion.cpp index bbeebb07f0..7086f4305b 100644 --- a/Modules/DICOMReader/mitkDICOMSortCriterion.cpp +++ b/Modules/DICOMReader/mitkDICOMSortCriterion.cpp @@ -1,79 +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. ===================================================================*/ #include "mitkDICOMSortCriterion.h" mitk::DICOMSortCriterion ::DICOMSortCriterion(DICOMSortCriterion::Pointer secondaryCriterion) :itk::LightObject() ,m_SecondaryCriterion(secondaryCriterion) { } mitk::DICOMSortCriterion ::~DICOMSortCriterion() { } mitk::DICOMSortCriterion ::DICOMSortCriterion(const DICOMSortCriterion& other ) :itk::LightObject() ,m_SecondaryCriterion(other.m_SecondaryCriterion) { } mitk::DICOMSortCriterion& mitk::DICOMSortCriterion ::operator=(const DICOMSortCriterion& other) { if (this != &other) { m_SecondaryCriterion = other.m_SecondaryCriterion; } return *this; } +mitk::DICOMSortCriterion::ConstPointer +mitk::DICOMSortCriterion +::GetSecondaryCriterion() const +{ + return m_SecondaryCriterion.GetPointer(); +} + mitk::DICOMTagList mitk::DICOMSortCriterion ::GetAllTagsOfInterest() const { DICOMTagList allTags; // iterate all secondary criteria, return all tags const DICOMSortCriterion* criterionToCheck = this; while (criterionToCheck) { DICOMTagList newElements = criterionToCheck->GetTagsOfInterest(); allTags.insert( allTags.end(), newElements.begin(), newElements.end() ); // append criterionToCheck = criterionToCheck->m_SecondaryCriterion.GetPointer(); } return allTags; } bool mitk::DICOMSortCriterion ::NextLevelIsLeftBeforeRight(const mitk::DICOMDatasetAccess* left, const mitk::DICOMDatasetAccess* right) const { if (m_SecondaryCriterion.IsNotNull()) { return m_SecondaryCriterion->IsLeftBeforeRight(left, right); } else { return (void*)left < (void*)right; } } diff --git a/Modules/DICOMReader/mitkDICOMSortCriterion.h b/Modules/DICOMReader/mitkDICOMSortCriterion.h index 061ac3b542..f88179967b 100644 --- a/Modules/DICOMReader/mitkDICOMSortCriterion.h +++ b/Modules/DICOMReader/mitkDICOMSortCriterion.h @@ -1,54 +1,58 @@ /*=================================================================== 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 { class DICOMReader_EXPORT DICOMSortCriterion : public itk::LightObject { public: mitkClassMacro( DICOMSortCriterion, itk::LightObject ); DICOMTagList GetAllTagsOfInterest() const; virtual DICOMTagList GetTagsOfInterest() const = 0; virtual bool IsLeftBeforeRight(const mitk::DICOMDatasetAccess* left, const mitk::DICOMDatasetAccess* right) const = 0; + DICOMSortCriterion::ConstPointer GetSecondaryCriterion() const; + + virtual void Print(std::ostream& os) 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/mitkDICOMTag.cpp b/Modules/DICOMReader/mitkDICOMTag.cpp index 3c9538d432..4a54e4d58e 100644 --- a/Modules/DICOMReader/mitkDICOMTag.cpp +++ b/Modules/DICOMReader/mitkDICOMTag.cpp @@ -1,166 +1,207 @@ /*=================================================================== 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 "mitkDICOMTag.h" +#include +#include + +#include "mitkLogMacros.h" + mitk::DICOMTag ::DICOMTag(unsigned int group, unsigned int element) :m_Group(group) ,m_Element(element) { } mitk::DICOMTag ::DICOMTag(const DICOMTag& other) :m_Group(other.m_Group) ,m_Element(other.m_Element) { } bool mitk::DICOMTag ::operator==(const DICOMTag& other) const { return m_Group == other.m_Group && m_Element == other.m_Element ; } mitk::DICOMTag& mitk::DICOMTag ::operator=(const DICOMTag& other) { if (this != &other) { m_Group = other.m_Group; m_Element = other.m_Element; } return *this; } unsigned int mitk::DICOMTag ::GetGroup() const { return m_Group; } unsigned int mitk::DICOMTag ::GetElement() const { return m_Element; } bool mitk::DICOMTag ::operator<(const DICOMTag& other) const { + // TODO check this comparison! return this->m_Group * 0x3000 + this->m_Element < other.m_Group * 0x3000 + other.m_Element; } +std::string +mitk::DICOMTag +::GetName() const +{ + gdcm::Tag t(m_Group, m_Element); + + const gdcm::Global& g = gdcm::Global::GetInstance(); // sum of all knowledge ! + const gdcm::Dicts& dicts = g.GetDicts(); + const gdcm::Dict& pub = dicts.GetPublicDict(); // Part 6 + + const gdcm::DictEntry& entry = pub.GetDictEntry(t); + std::string name = entry.GetName(); + if (name.empty()) + { + name = "Unknown Tag"; + } + + return name; +} + +std::string +mitk::DICOMTag +::toHexString(unsigned int i) const +{ + std::stringstream ss; + ss << std::setfill ('0') << std::setw(4) << std::hex << i; + return ss.str(); +} + +void +mitk::DICOMTag +::Print(std::ostream& os) const +{ + os << "(" << toHexString(m_Group) << "," << toHexString(m_Element) << ") " << this->GetName(); +} void mitk::DICOMStringToOrientationVectors(const std::string& s, Vector3D& right, Vector3D& up, bool& successful) { successful = true; std::istringstream orientationReader(s); std::string coordinate; unsigned int dim(0); while( std::getline( orientationReader, coordinate, '\\' ) && dim < 6 ) { if (dim<3) { right[dim++] = atof(coordinate.c_str()); } else { up[dim++ - 3] = atof(coordinate.c_str()); } } if (dim && dim != 6) { successful = false; } else if (dim == 0) { // fill with defaults right.Fill(0.0); right[0] = 1.0; up.Fill(0.0); up[1] = 1.0; successful = false; } } bool mitk::DICOMStringToSpacing(const std::string& s, ScalarType& spacingX, ScalarType& spacingY) { bool successful = false; std::istringstream spacingReader(s); std::string spacing; if ( std::getline( spacingReader, spacing, '\\' ) ) { spacingY = atof( spacing.c_str() ); if ( std::getline( spacingReader, spacing, '\\' ) ) { spacingX = atof( spacing.c_str() ); successful = true; } } return successful; } mitk::Point3D mitk::DICOMStringToPoint3D(const std::string& s, bool& successful) { Point3D p; successful = true; std::istringstream originReader(s); std::string coordinate; unsigned int dim(0); while( std::getline( originReader, coordinate, '\\' ) && dim < 3) { p[dim++]= atof(coordinate.c_str()); } if (dim && dim != 3) { successful = false; } else if (dim == 0) { successful = false; p.Fill(0.0); // assume default (0,0,0) } return p; } diff --git a/Modules/DICOMReader/mitkDICOMTag.h b/Modules/DICOMReader/mitkDICOMTag.h index c827e272b6..c9b5365d1e 100644 --- a/Modules/DICOMReader/mitkDICOMTag.h +++ b/Modules/DICOMReader/mitkDICOMTag.h @@ -1,73 +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 mitkTag_h #define mitkTag_h #include "mitkVector.h" #include "DICOMReaderExports.h" namespace mitk { class DICOMReader_EXPORT DICOMTag { public: DICOMTag(unsigned int group, unsigned int element); DICOMTag(const DICOMTag& other); DICOMTag& operator=(const DICOMTag& other); bool operator==(const DICOMTag& other) const; bool operator<(const DICOMTag& other) const; unsigned int GetGroup() const; unsigned int GetElement() const; + std::string GetName() const; + void Print(std::ostream& os) const; + protected: + std::string toHexString(unsigned int i) const; + unsigned int m_Group; unsigned int m_Element; }; typedef std::vector DICOMTagList; /** \brief Convert DICOM string describing a point two Vector3D. DICOM tags like ImageOrientationPatient contain two vectors as float numbers separated by backslashes: \verbatim 42.7131\13.77\0.7\137.76\0.3 \endverbatim */ void DICOMStringToOrientationVectors(const std::string& s, Vector3D& right, Vector3D& up, bool& successful); bool DICOMStringToSpacing(const std::string& s, ScalarType& spacingX, ScalarType& spacingY); /** \brief Convert DICOM string describing a point to Point3D. DICOM tags like ImagePositionPatient contain a position as float numbers separated by backslashes: \verbatim 42.7131\13.77\0.7 \endverbatim */ mitk::Point3D DICOMStringToPoint3D(const std::string& s, bool& successful); } #endif diff --git a/Modules/DICOMReader/mitkDICOMTagBasedSorter.cpp b/Modules/DICOMReader/mitkDICOMTagBasedSorter.cpp index f615cb876f..5d791bcce0 100644 --- a/Modules/DICOMReader/mitkDICOMTagBasedSorter.cpp +++ b/Modules/DICOMReader/mitkDICOMTagBasedSorter.cpp @@ -1,244 +1,269 @@ /*=================================================================== 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) bool conversionError(false); Vector3D right; right.Fill(0.0); Vector3D up; right.Fill(0.0); DICOMStringToOrientationVectors( input, right, up, conversionError ); std::ostringstream ss; ss.setf(std::ios::fixed, std::ios::floatfield); ss.precision(m_Precision); ss << right[0] << "\\" << right[1] << "\\" << right[2] << "\\" << up[0] << "\\" << up[1] << "\\" << up[2]; return ss.str(); } 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; } +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; } 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; } 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 efec719b9b..864fb43144 100644 --- a/Modules/DICOMReader/mitkDICOMTagBasedSorter.h +++ b/Modules/DICOMReader/mitkDICOMTagBasedSorter.h @@ -1,91 +1,93 @@ /*=================================================================== 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 { /** \brief sort files based on filename (last resort). */ class DICOMReader_EXPORT DICOMTagBasedSorter : public DICOMDatasetSorter { public: class TagValueProcessor { public: virtual std::string operator()(const std::string&) const = 0; }; class CutDecimalPlaces : public TagValueProcessor { public: CutDecimalPlaces(unsigned int precision); virtual std::string operator()(const std::string&) const; private: unsigned int m_Precision; }; mitkClassMacro( DICOMTagBasedSorter, DICOMDatasetSorter ) itkNewMacro( DICOMTagBasedSorter ) void AddDistinguishingTag( const DICOMTag&, TagValueProcessor* tagValueProcessor = NULL ); void SetSortCriterion( DICOMSortCriterion::ConstPointer criterion ); virtual DICOMTagList GetTagsOfInterest(); virtual void Sort(); + virtual void PrintConfiguration(std::ostream& os, const std::string& indent = "") const; + protected: struct ParameterizedDatasetSort { ParameterizedDatasetSort(DICOMSortCriterion::ConstPointer); bool operator() (const mitk::DICOMDatasetAccess* left, const mitk::DICOMDatasetAccess* right); bool StringCompare(const mitk::DICOMDatasetAccess* left, const mitk::DICOMDatasetAccess* right); bool NumericCompare(const mitk::DICOMDatasetAccess* left, const mitk::DICOMDatasetAccess* right); DICOMSortCriterion::ConstPointer m_SortCriterion; }; DICOMTagBasedSorter(); virtual ~DICOMTagBasedSorter(); DICOMTagBasedSorter(const DICOMTagBasedSorter& other); DICOMTagBasedSorter& operator=(const DICOMTagBasedSorter& other); std::string BuildGroupID( DICOMDatasetAccess* dataset ); typedef std::map GroupIDToListType; GroupIDToListType SplitInputGroups(); 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 0979f8472d..3e67bda2c5 100644 --- a/Modules/DICOMReader/mitkEquiDistantBlocksSorter.cpp +++ b/Modules/DICOMReader/mitkEquiDistantBlocksSorter.cpp @@ -1,484 +1,492 @@ /*=================================================================== 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 "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 ::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) { } mitk::EquiDistantBlocksSorter ::EquiDistantBlocksSorter(const EquiDistantBlocksSorter& other ) :DICOMDatasetSorter(other) ,m_AcceptTilt(false) { } mitk::EquiDistantBlocksSorter ::~EquiDistantBlocksSorter() { } +void +mitk::EquiDistantBlocksSorter +::PrintConfiguration(std::ostream& os, const std::string& indent) const +{ + os << indent << "Sort into blocks of equidistant, well-aligned 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); } 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); } } const mitk::GantryTiltInformation mitk::EquiDistantBlocksSorter ::GetTiltInformation(const std::string& filename) { for (ResultsList::iterator ri = m_SliceGroupingResults.begin(); ri != m_SliceGroupingResults.end(); ++ri) { SliceGroupingAnalysisResult& result = *ri; if (filename == result.GetFirstFilenameOfBlock()) { return result.GetTiltInfo(); } } return GantryTiltInformation(); // empty } 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); 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; // 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() ) // mitk::eps is too small; 1/1000 of a mm should be enough to detect tilt { /* 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() ); 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() ); 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(); double toleratedError(0.005); // max. 1/10mm error when measurement crosses 20 slices in z direction if (norm > toleratedError) { MITK_DEBUG << " File does not fit into the inter-slice distance pattern (diff = " << norm << ", allowed " << toleratedError << ")."; 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 ) { DICOMDatasetList datasets = result.GetBlockDatasets(); DICOMDatasetAccess* firstDataset = datasets.front(); DICOMDatasetAccess* lastDataset = datasets.back(); unsigned int numberOfSlicesApart = datasets.size() - 1; 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 = firstDataset->GetTagValueAsString( tagImageOrientation ); bool orientationConversion(false); DICOMStringToOrientationVectors( orientationValue, right, up, orientationConversion ); if (orientationConversion) { std::string firstOriginString = firstDataset->GetTagValueAsString( tagImagePositionPatient ); std::string lastOriginString = lastDataset->GetTagValueAsString( tagImagePositionPatient ); if (!firstOriginString.empty() && !lastOriginString.empty()) { bool firstOriginConversion(false); bool lastOriginConversion(false); Point3D firstOrigin = DICOMStringToPoint3D( firstOriginString, firstOriginConversion ); Point3D lastOrigin = DICOMStringToPoint3D( lastOriginString, lastOriginConversion ); if (firstOriginConversion && lastOriginConversion) { GantryTiltInformation updatedTiltInfo( firstOrigin, lastOrigin, right, up, numberOfSlicesApart ); result.FlagGantryTilt(updatedTiltInfo); } } } } return result; } diff --git a/Modules/DICOMReader/mitkEquiDistantBlocksSorter.h b/Modules/DICOMReader/mitkEquiDistantBlocksSorter.h index cc6b2a6ba9..4c21f5e255 100644 --- a/Modules/DICOMReader/mitkEquiDistantBlocksSorter.h +++ b/Modules/DICOMReader/mitkEquiDistantBlocksSorter.h @@ -1,155 +1,157 @@ /*=================================================================== 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 { /** \brief Split inputs into blocks of equidant slices. This kind of splitting is used as a check before loading a DICOM series with ITK ImageSeriesReader. */ class DICOMReader_EXPORT EquiDistantBlocksSorter : public DICOMDatasetSorter { public: mitkClassMacro( EquiDistantBlocksSorter, DICOMDatasetSorter ) itkNewMacro( EquiDistantBlocksSorter ) virtual DICOMTagList GetTagsOfInterest(); virtual void Sort(); void SetAcceptTilt(bool accept); const GantryTiltInformation GetTiltInformation(const std::string& filename); + virtual void PrintConfiguration(std::ostream& os, const std::string& indent = "") const; + protected: /** \brief Return type of DicomSeriesReader::AnalyzeFileForITKImageSeriesReaderSpacingAssumption. Class contains the grouping result of method DicomSeriesReader::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; /** \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; }; /** \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; }; } #endif diff --git a/Modules/DICOMReader/mitkSortByImagePositionPatient.cpp b/Modules/DICOMReader/mitkSortByImagePositionPatient.cpp index 5b24cd2911..bea8ff52cf 100644 --- a/Modules/DICOMReader/mitkSortByImagePositionPatient.cpp +++ b/Modules/DICOMReader/mitkSortByImagePositionPatient.cpp @@ -1,128 +1,135 @@ /*=================================================================== 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; } +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 510bb56ea3..88869a5658 100644 --- a/Modules/DICOMReader/mitkSortByImagePositionPatient.h +++ b/Modules/DICOMReader/mitkSortByImagePositionPatient.h @@ -1,50 +1,52 @@ /*=================================================================== 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 { 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; + protected: SortByImagePositionPatient( DICOMSortCriterion::Pointer secondaryCriterion = NULL ); virtual ~SortByImagePositionPatient(); SortByImagePositionPatient(const SortByImagePositionPatient& other); SortByImagePositionPatient& operator=(const SortByImagePositionPatient& other); private: }; } #endif