diff --git a/Modules/DICOMReader/TODOs.txt b/Modules/DICOMReader/TODOs.txt index b62e34cfeb..b947c3ce66 100644 --- a/Modules/DICOMReader/TODOs.txt +++ b/Modules/DICOMReader/TODOs.txt @@ -1,64 +1,64 @@ Important [x] Make sure that ..Selector does tag scanning only once -[x] Check Gantry Tilt TODO/Bug in old reader +[x] Check Gantry Tilt ToDo/Bug in old reader [x] - and in new reader, how do we WANT to handle this? - old and new code both did not correctly check the stored value against the calculated value - it SEEMS like tilts below 45 degrees were never checked against the tag, and bigger values are probably unrealistic(?) - so, nothing wrong. It just seems that we calculated a DIFFERENT angle than the one recorded - in the new reader we remove the check. We are right as far as our shearing is affected. [x] Keep OLD DICOMTesting module, in order to test code until it is removed - restored this module as DCMTesting module [x] ONLY load a pre-sorted list - Should work now by: - get the list of images, construct FrameInfo list (order must be known from prior sorting) - prepare a tagcache object that provides tag information (from some kind of database, or run AnalyzeInputFiles() on file reader) - create DICOMImageBlockDescriptor with frame list and tag cache - Call SetFixTiltByShearing() and TiltInfo object as appropriate... - call DICOMITKSeriesGDCMReader::LoadMitkImageForImageBlockDescriptor(block) [x] Option to just provide properties to a pre-loaded mitk::Image - see TestDICOMLoading::DecorateVerifyCachedImage() [x] Performance of mitkdump / DICOMReaderSelector (TagCache?) - the note in GDCM..Reader::GetTagValue() is correct. This piece of code is taking lots of time. Approx. half of the time is tag scanning, the other half is construction of block describing properties, which accesses GetTagValue. [x] - maybe we can evaluate these properties in a lazy way (only when asked for). Solution: Yes, this helps, implemented. [x] Gantry tilt broken during implementation - Was a wrong implementation of NormalDirectionConsistencySorter [x] Accepted image origin error during EquiDistantBlocksSorter must be configurable. To support legacy behavior of the reader, the tolerance must be fixable to a constant value. For newer readers, it should be adapted to the actual slice distance. [x] Sorting by "Image Time" seems undeterministic (clarkson test data) [x] - Numeric conversion of "1.2.3.4" yields 1.2 on Windows, seems to fail on Linux(?) - need to verify that the whole string (except whitespace at the begin/end) was converted Solution: GetTagValue as String does a right-trim plus we check after conversion! Works. [x] Implement Configurator::GetBuiltIn3DReaders() (don't ignore "classic reader") [x] Complete MITK properties of images: level/window and color type (MONOCHROME1/2) [x] Check input images fo DICOMness and non-multi-frameness [x] Use CanHandleFile() in selection! - implicitly now, the reader checks itself and produces no output if it cannot handle the files [x] Check ToDo in mitkDICOMTag.cpp - operator< for DICOMTag has been re-implemented in a readable and safer way. [x] Configurable precision for tag value comparisons [x] Images are upside-down in some cases - error was hidden assumption somewhere: filenames for ImageSeriesReader need to be in an order that goes along the image normals, not in the opposite direction Questionable [ ] Fallback to filename instead of instance UID? Nice-to-have [ ] Multi-Frame images (DCMTK) [ ] ... [x] Add the tags used by DICOMImageBlockDescriptor dynamically instead of hard-coded (look for ToDo around m_GDCMScanner.AddTag() in mitkDICOMITKSeriesGDCMReader.cpp) [ ] Let DICOMImageBlockDescriptor describe attributes common and different for all blocks of a series [ ] Let DICOMImageBlockDescriptor check image properties when mitk::Image is set (pre-loaded case) diff --git a/Modules/DICOMReader/Testing/mitkDICOMNullFileReader.cpp b/Modules/DICOMReader/Testing/mitkDICOMNullFileReader.cpp index a2c1f30b44..53c6831844 100644 --- a/Modules/DICOMReader/Testing/mitkDICOMNullFileReader.cpp +++ b/Modules/DICOMReader/Testing/mitkDICOMNullFileReader.cpp @@ -1,102 +1,101 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkDICOMNullFileReader.h" mitk::DICOMNullFileReader ::DICOMNullFileReader() :DICOMFileReader() { } mitk::DICOMNullFileReader ::~DICOMNullFileReader() { } mitk::DICOMNullFileReader ::DICOMNullFileReader(const DICOMNullFileReader& other ) -:itk::Object() -,DICOMFileReader(other) +:DICOMFileReader(other) { } mitk::DICOMNullFileReader& mitk::DICOMNullFileReader ::operator=(const DICOMNullFileReader& other) { if (this != &other) { DICOMFileReader::operator=(other); } return *this; } bool mitk::DICOMNullFileReader ::operator==(const DICOMFileReader& other) const { return dynamic_cast(&other) != NULL; // same class, we don't have members } void mitk::DICOMNullFileReader ::InternalPrintConfiguration(std::ostream& os) const { os << "Nothing to configure" << std::endl; } void mitk::DICOMNullFileReader ::AnalyzeInputFiles() { this->ClearOutputs(); StringList inputFilenames = this->GetInputFiles(); this->SetNumberOfOutputs( inputFilenames.size() ); //generates one output for each input unsigned int o = 0; for (StringList::const_iterator inputIter = inputFilenames.begin(); inputIter != inputFilenames.end(); ++o, ++inputIter) { DICOMImageBlockDescriptor block; DICOMImageFrameList outputFrames; outputFrames.push_back( DICOMImageFrameInfo::New(*inputIter) ); block.SetImageFrameList( outputFrames ); this->SetOutput( o, block ); } } // void AllocateOutputImages(); bool mitk::DICOMNullFileReader ::LoadImages() { // does nothing return true; } bool mitk::DICOMNullFileReader ::CanHandleFile(const std::string& itkNotUsed(filename)) { return true; // can handle all } diff --git a/Modules/DICOMReader/mitkClassicDICOMSeriesReader.cpp b/Modules/DICOMReader/mitkClassicDICOMSeriesReader.cpp index 1215181723..012f953827 100644 --- a/Modules/DICOMReader/mitkClassicDICOMSeriesReader.cpp +++ b/Modules/DICOMReader/mitkClassicDICOMSeriesReader.cpp @@ -1,99 +1,98 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkClassicDICOMSeriesReader.h" #include "mitkDICOMTagBasedSorter.h" #include "mitkDICOMSortByTag.h" #include "mitkSortByImagePositionPatient.h" mitk::ClassicDICOMSeriesReader ::ClassicDICOMSeriesReader() :ThreeDnTDICOMSeriesReader() { mitk::DICOMTagBasedSorter::Pointer tagSorter = mitk::DICOMTagBasedSorter::New(); // all the things that split by tag in mitk::DicomSeriesReader tagSorter->AddDistinguishingTag( DICOMTag(0x0020, 0x000e) ); // Series Instance UID //tagSorter->AddDistinguishingTag( DICOMTag(0x0020, 0x0052) ); // Frame of Reference UID // a sorter... mitk::DICOMSortCriterion::ConstPointer sorting = mitk::SortByImagePositionPatient::New( // image position patient and image orientation mitk::DICOMSortByTag::New( DICOMTag(0x0020, 0x0012), // aqcuisition number mitk::DICOMSortByTag::New( DICOMTag(0x0008, 0x0032), // aqcuisition time mitk::DICOMSortByTag::New( DICOMTag(0x0018, 0x1060), // trigger time mitk::DICOMSortByTag::New( DICOMTag(0x0008, 0x0018) // SOP instance UID (last resort, not really meaningful but decides clearly) ).GetPointer() ).GetPointer() ).GetPointer() ).GetPointer() ).GetPointer(); tagSorter->SetSortCriterion( sorting ); tagSorter->SetStrictSorting(false); // nothing did enforce constant distances before, there was just the EquiDistantBlocksSorter logic // define above sorting for this class this->AddSortingElement( tagSorter ); this->SetFixTiltByShearing(true); // that was configurable, default was true this->SetToleratedOriginOffset(0.005); // was hard-coded this->SetGroup3DandT(true); // that was configurable, default was true m_EquiDistantBlocksSorter->SetAcceptTwoSlicesGroups(false); // old reader did not accept that this->SetConfigurationLabel("2013 sorting logic"); this->SetConfigurationDescription("Sort by Image Position, then Acquisition Number, Time, Trigger time, group by 3D+t, group tilted images"); } mitk::ClassicDICOMSeriesReader ::ClassicDICOMSeriesReader(const ClassicDICOMSeriesReader& other ) -:itk::Object() -,ThreeDnTDICOMSeriesReader(other) +:ThreeDnTDICOMSeriesReader(other) { } mitk::ClassicDICOMSeriesReader ::~ClassicDICOMSeriesReader() { } mitk::ClassicDICOMSeriesReader& mitk::ClassicDICOMSeriesReader ::operator=(const ClassicDICOMSeriesReader& other) { if (this != &other) { ThreeDnTDICOMSeriesReader::operator=(other); } return *this; } bool mitk::ClassicDICOMSeriesReader ::operator==(const DICOMFileReader& other) const { if (dynamic_cast(&other)) { return true; } else { return false; } } diff --git a/Modules/DICOMReader/mitkDICOMFileReader.cpp b/Modules/DICOMReader/mitkDICOMFileReader.cpp index 5bed6f471f..49ae5824f2 100644 --- a/Modules/DICOMReader/mitkDICOMFileReader.cpp +++ b/Modules/DICOMReader/mitkDICOMFileReader.cpp @@ -1,197 +1,198 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkDICOMFileReader.h" #include mitk::DICOMFileReader ::DICOMFileReader() :itk::Object() { } mitk::DICOMFileReader ::~DICOMFileReader() { } mitk::DICOMFileReader ::DICOMFileReader(const DICOMFileReader& other ) :itk::Object() +,m_InputFilenames( other.m_InputFilenames ) ,m_Outputs( other.m_Outputs ) ,m_ConfigLabel( other.m_ConfigLabel ) ,m_ConfigDescription( other.m_ConfigDescription ) { } mitk::DICOMFileReader& mitk::DICOMFileReader ::operator=(const DICOMFileReader& other) { if (this != &other) { m_InputFilenames = other.m_InputFilenames; m_Outputs = other.m_Outputs; m_ConfigLabel = other.m_ConfigLabel; m_ConfigDescription = other.m_ConfigDescription; } return *this; } void mitk::DICOMFileReader ::SetConfigurationLabel(const std::string& label) { m_ConfigLabel = label; } std::string mitk::DICOMFileReader ::GetConfigurationLabel() const { return m_ConfigLabel; } void mitk::DICOMFileReader ::SetConfigurationDescription(const std::string& desc) { m_ConfigDescription = desc; } std::string mitk::DICOMFileReader ::GetConfigurationDescription() const { return m_ConfigDescription; } void mitk::DICOMFileReader ::SetInputFiles(StringList filenames) { m_InputFilenames = filenames; } const mitk::StringList& mitk::DICOMFileReader ::GetInputFiles() const { return m_InputFilenames; } unsigned int mitk::DICOMFileReader ::GetNumberOfOutputs() const { return m_Outputs.size(); } void mitk::DICOMFileReader ::ClearOutputs() { m_Outputs.clear(); } void mitk::DICOMFileReader ::SetNumberOfOutputs(unsigned int numberOfOutputs) { m_Outputs.resize(numberOfOutputs); } void mitk::DICOMFileReader ::SetOutput(unsigned int index, const mitk::DICOMImageBlockDescriptor& output) { if (index < m_Outputs.size()) { m_Outputs[index] = output; } else { std::stringstream ss; ss << "Index " << index << " out of range (" << m_Outputs.size() << " indices reserved)"; throw std::invalid_argument( ss.str() ); } } void mitk::DICOMFileReader ::PrintConfiguration(std::ostream& os) const { os << "---- Configuration of " << this->GetNameOfClass() <<" " << (void*)this << " ----"<< std::endl; this->InternalPrintConfiguration(os); os << "---- End of configuration ----" << std::endl; } void mitk::DICOMFileReader ::PrintOutputs(std::ostream& os, bool filenameDetails) const { os << "---- Outputs of DICOMFilereader " << (void*)this << " ----"<< std::endl; for (unsigned int o = 0; o < m_Outputs.size(); ++o) { os << "-- Output " << o << std::endl; const DICOMImageBlockDescriptor& block = m_Outputs[o]; block.Print(os, filenameDetails); } os << "---- End of output list ----" << std::endl; } const mitk::DICOMImageBlockDescriptor& mitk::DICOMFileReader ::GetOutput(unsigned int index) const { if (index < m_Outputs.size()) { return m_Outputs[index]; } else { std::stringstream ss; ss << "Index " << index << " out of range (" << m_Outputs.size() << " indices reserved)"; throw std::invalid_argument( ss.str() ); } } mitk::DICOMImageBlockDescriptor& mitk::DICOMFileReader ::InternalGetOutput(unsigned int index) { if (index < m_Outputs.size()) { return m_Outputs[index]; } else { std::stringstream ss; ss << "Index " << index << " out of range (" << m_Outputs.size() << " indices reserved)"; throw std::invalid_argument( ss.str() ); } } bool mitk::DICOMFileReader ::IsDICOM(const std::string& filename) { itk::GDCMImageIO::Pointer io = itk::GDCMImageIO::New(); return io->CanReadFile( filename.c_str() ); } diff --git a/Modules/DICOMReader/mitkDICOMFileReader.h b/Modules/DICOMReader/mitkDICOMFileReader.h index cbd3064f19..e942cd0b1e 100644 --- a/Modules/DICOMReader/mitkDICOMFileReader.h +++ b/Modules/DICOMReader/mitkDICOMFileReader.h @@ -1,135 +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. ===================================================================*/ #ifndef mitkDICOMFileReader_h #define mitkDICOMFileReader_h #include "itkObjectFactory.h" #include "mitkCommon.h" #include "DICOMReaderExports.h" #include "mitkDICOMTagCache.h" #include "mitkDICOMImageBlockDescriptor.h" namespace mitk { // TODO Philips3D! // TODO http://bugs.mitk.org/show_bug.cgi?id=11572 ? /** \ingroup DICOMReaderModule \brief Interface for DICOM readers that produce mitk::Images. As described in \ref DICOMReaderModule, this class structures the reader's part in the process of analyzing a set of DICOM files and selecting the most appropriate reader. The overall loading process is as follows: - Define input files: a list of absolute filenames - Analyze the potential output: see what can be made of the input files, describe with DICOMImageBlockDescriptor%s - Load pixel data: an application will usually analyze files using multiple readers and only load with a single reader Sub-classes are required to implement a number of methods that reflect above structure. See mitk::DICOMITKSeriesGDCMReader for an example. To help applications in describing different readers to the user, each reader brings a number of methods that describe its configuration/specifics by means of a short label and a (longer) description. */ -class DICOMReader_EXPORT DICOMFileReader : virtual public itk::Object +class DICOMReader_EXPORT DICOMFileReader : public itk::Object { public: mitkClassMacro( DICOMFileReader, itk::Object ); /// Test whether a file is DICOM at all static bool IsDICOM(const std::string& filename); /// Indicate whether this reader can handle given file virtual bool CanHandleFile(const std::string& filename) = 0; /// This input files void SetInputFiles(StringList filenames); /// This input files const StringList& GetInputFiles() const; /// Analyze input files virtual void AnalyzeInputFiles() = 0; /// Number of outputs, only meaningful after calling AnalyzeInputFiles() unsigned int GetNumberOfOutputs() const; /// Individual outputs, only meaningful after calling AnalyzeInputFiles(). \throws std::invalid_argument const DICOMImageBlockDescriptor& GetOutput(unsigned int index) const; // void AllocateOutputImages(); TODO for later implementation of slice-by-slice loading /// Load the mitk::Image%s in our outputs, the DICOMImageBlockDescriptor. To be called only after AnalyzeInputFiles(). Take care of potential exceptions! virtual bool LoadImages() = 0; - // TODO seems reasonable virtual DICOMTagList GetTagsOfInterest() const = 0; - // TODO need to document + + /// A way to provide external knowledge about files and tag values is appreciated. virtual void SetTagCache(DICOMTagCache::Pointer) = 0; /// Short label/name to describe this reader void SetConfigurationLabel(const std::string&); /// Short label/name to describe this reader std::string GetConfigurationLabel() const; /// One-sentence description of the reader's loading "strategy" void SetConfigurationDescription(const std::string&); /// One-sentence description of the reader's loading "strategy" std::string GetConfigurationDescription() const; /// Print configuration description to given stream, for human reader void PrintConfiguration(std::ostream& os) const; /// Print output description to given stream, for human reader void PrintOutputs(std::ostream& os, bool filenameDetails = false) const; virtual bool operator==(const DICOMFileReader& other) const = 0; protected: DICOMFileReader(); virtual ~DICOMFileReader(); DICOMFileReader(const DICOMFileReader& other); DICOMFileReader& operator=(const DICOMFileReader& other); void ClearOutputs(); void SetNumberOfOutputs(unsigned int numberOfOutputs); void SetOutput(unsigned int index, const DICOMImageBlockDescriptor& output); /// non-const access to the DICOMImageBlockDescriptor DICOMImageBlockDescriptor& InternalGetOutput(unsigned int index); /// Configuration description for human reader, to be implemented by sub-classes virtual void InternalPrintConfiguration(std::ostream& os) const = 0; private: StringList m_InputFilenames; std::vector< DICOMImageBlockDescriptor > m_Outputs; std::string m_ConfigLabel; std::string m_ConfigDescription; }; } #endif diff --git a/Modules/DICOMReader/mitkDICOMGDCMTagScanner.cpp b/Modules/DICOMReader/mitkDICOMGDCMTagScanner.cpp index c66d2c70cc..73bf7ae0e6 100644 --- a/Modules/DICOMReader/mitkDICOMGDCMTagScanner.cpp +++ b/Modules/DICOMReader/mitkDICOMGDCMTagScanner.cpp @@ -1,137 +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 "mitkDICOMGDCMTagScanner.h" mitk::DICOMGDCMTagScanner ::DICOMGDCMTagScanner() -:itk::Object() { } mitk::DICOMGDCMTagScanner ::DICOMGDCMTagScanner(const DICOMGDCMTagScanner& other) -:itk::Object() -,DICOMTagCache(other) +:DICOMTagCache(other) { } mitk::DICOMGDCMTagScanner ::~DICOMGDCMTagScanner() { } std::string mitk::DICOMGDCMTagScanner ::GetTagValue(DICOMImageFrameInfo* frame, const DICOMTag& tag) const { assert(frame); for(DICOMGDCMImageFrameList::const_iterator frameIter = m_ScanResult.begin(); frameIter != m_ScanResult.end(); ++frameIter) { if ( (*frameIter)->GetFrameInfo().IsNotNull() && (*((*frameIter)->GetFrameInfo()) == *frame ) ) { return (*frameIter)->GetTagValueAsString(tag); } } if ( m_ScannedTags.find(tag) != m_ScannedTags.end() ) { if ( std::find( m_InputFilenames.begin(), m_InputFilenames.end(), frame->Filename ) != m_InputFilenames.end() ) { // precondition of gdcm::Scanner::GetValue() fulfilled return m_GDCMScanner.GetValue( frame->Filename.c_str(), gdcm::Tag( tag.GetGroup(), tag.GetElement() ) ); } else { // callers are required to tell us about the filenames they are interested in // this is a helpful reminder for them to inform us std::stringstream errorstring; errorstring << "Invalid call to DICOMGDCMTagScanner::GetTagValue( " << "'" << frame->Filename << "', frame " << frame->FrameNo << " ). Filename was never mentioned before!"; MITK_ERROR << errorstring.str(); throw std::invalid_argument(errorstring.str()); } } else { // callers are required to tell us about the tags they are interested in // this is a helpful reminder for them to inform us std::stringstream errorstring; errorstring << "Invalid call to DICOMGDCMTagScanner::GetTagValue( "; tag.Print(errorstring); errorstring << " ). Tag was never mentioned before!"; MITK_ERROR << errorstring.str(); throw std::invalid_argument(errorstring.str()); } } void mitk::DICOMGDCMTagScanner ::AddTag(const DICOMTag& tag) { m_ScannedTags.insert(tag); m_GDCMScanner.AddTag( gdcm::Tag(tag.GetGroup(), tag.GetElement()) ); // also a set, duplicate calls to AddTag don't hurt } void mitk::DICOMGDCMTagScanner ::AddTags(const DICOMTagList& tags) { for(DICOMTagList::const_iterator tagIter = tags.begin(); tagIter != tags.end(); ++tagIter) { this->AddTag(*tagIter); } } void mitk::DICOMGDCMTagScanner ::SetInputFiles(const StringList& filenames) { m_InputFilenames = filenames; } void mitk::DICOMGDCMTagScanner ::Scan() { // TODO integrate push/pop locale?? m_GDCMScanner.Scan( m_InputFilenames ); m_ScanResult.clear(); for (StringList::const_iterator inputIter = m_InputFilenames.begin(); inputIter != m_InputFilenames.end(); ++inputIter) { m_ScanResult.push_back( DICOMGDCMImageFrameInfo::New( DICOMImageFrameInfo::New(*inputIter, 0), m_GDCMScanner.GetMapping(inputIter->c_str()) ) ); } } mitk::DICOMGDCMImageFrameList mitk::DICOMGDCMTagScanner ::GetFrameInfoList() const { return m_ScanResult; } diff --git a/Modules/DICOMReader/mitkDICOMITKSeriesGDCMReader.cpp b/Modules/DICOMReader/mitkDICOMITKSeriesGDCMReader.cpp index b3032c0aaf..00eab84d98 100644 --- a/Modules/DICOMReader/mitkDICOMITKSeriesGDCMReader.cpp +++ b/Modules/DICOMReader/mitkDICOMITKSeriesGDCMReader.cpp @@ -1,721 +1,728 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ //#define MBILOG_ENABLE_DEBUG //#define ENABLE_TIMING #include "mitkDICOMITKSeriesGDCMReader.h" #include "mitkITKDICOMSeriesReaderHelper.h" #include "mitkGantryTiltInformation.h" #include "mitkDICOMTagBasedSorter.h" #include "mitkDICOMGDCMTagScanner.h" #include #include mitk::DICOMITKSeriesGDCMReader ::DICOMITKSeriesGDCMReader(unsigned int decimalPlacesForOrientation) :DICOMFileReader() ,m_FixTiltByShearing(true) ,m_DecimalPlacesForOrientation(decimalPlacesForOrientation) { this->EnsureMandatorySortersArePresent(decimalPlacesForOrientation); } mitk::DICOMITKSeriesGDCMReader ::DICOMITKSeriesGDCMReader(const DICOMITKSeriesGDCMReader& other ) -:itk::Object() -,DICOMFileReader(other) +:DICOMFileReader(other) ,m_FixTiltByShearing(false) -,m_Sorter( other.m_Sorter ) // TODO should clone the list items +,m_SortingResultInProgress( other.m_SortingResultInProgress ) +,m_Sorter( other.m_Sorter ) ,m_EquiDistantBlocksSorter( other.m_EquiDistantBlocksSorter->Clone() ) ,m_NormalDirectionConsistencySorter( other.m_NormalDirectionConsistencySorter->Clone() ) +,m_ReplacedCLocales( other.m_ReplacedCLocales ) +,m_ReplacedCinLocales( other.m_ReplacedCinLocales ) ,m_DecimalPlacesForOrientation(other.m_DecimalPlacesForOrientation) +,m_TagCache( other.m_TagCache ) { } 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_SortingResultInProgress = other.m_SortingResultInProgress; this->m_Sorter = other.m_Sorter; // TODO should clone the list items this->m_EquiDistantBlocksSorter = other.m_EquiDistantBlocksSorter->Clone(); this->m_NormalDirectionConsistencySorter = other.m_NormalDirectionConsistencySorter->Clone(); + this->m_ReplacedCLocales = other.m_ReplacedCLocales; + this->m_ReplacedCinLocales = other.m_ReplacedCinLocales; this->m_DecimalPlacesForOrientation = other.m_DecimalPlacesForOrientation; + this->m_TagCache = other.m_TagCache; } return *this; } bool mitk::DICOMITKSeriesGDCMReader ::operator==(const DICOMFileReader& other) const { if (const Self* otherSelf = dynamic_cast(&other)) { if ( this->m_FixTiltByShearing == otherSelf->m_FixTiltByShearing && *(this->m_EquiDistantBlocksSorter) == *(otherSelf->m_EquiDistantBlocksSorter) && (fabs(this->m_DecimalPlacesForOrientation - otherSelf->m_DecimalPlacesForOrientation) < eps) ) { // test sorters for equality if (this->m_Sorter.size() != otherSelf->m_Sorter.size()) return false; SorterList::const_iterator mySorterIter = this->m_Sorter.begin(); SorterList::const_iterator oSorterIter = otherSelf->m_Sorter.begin(); for(; mySorterIter != this->m_Sorter.end() && oSorterIter != otherSelf->m_Sorter.end(); ++mySorterIter, ++oSorterIter) { if ( ! (**mySorterIter == **oSorterIter ) ) return false; // this sorter differs } // nothing differs ==> all is equal return true; } else { return false; } } else { return false; } } void mitk::DICOMITKSeriesGDCMReader ::SetFixTiltByShearing(bool on) { m_FixTiltByShearing = on; } bool mitk::DICOMITKSeriesGDCMReader ::GetFixTiltByShearing() const { return m_FixTiltByShearing; } void mitk::DICOMITKSeriesGDCMReader ::SetAcceptTwoSlicesGroups(bool accept) { m_EquiDistantBlocksSorter->SetAcceptTwoSlicesGroups(accept); } bool mitk::DICOMITKSeriesGDCMReader ::GetAcceptTwoSlicesGroups() const { return m_EquiDistantBlocksSorter->GetAcceptTwoSlicesGroups(); } mitk::DICOMGDCMImageFrameList mitk::DICOMITKSeriesGDCMReader ::FromDICOMDatasetList(const DICOMDatasetList& input) { DICOMGDCMImageFrameList output; output.reserve(input.size()); for(DICOMDatasetList::const_iterator inputIter = input.begin(); inputIter != input.end(); ++inputIter) { DICOMGDCMImageFrameInfo* gfi = dynamic_cast(*inputIter); assert(gfi); output.push_back(gfi); } return output; } mitk::DICOMDatasetList mitk::DICOMITKSeriesGDCMReader ::ToDICOMDatasetList(const DICOMGDCMImageFrameList& input) { DICOMDatasetList output; output.reserve(input.size()); for(DICOMGDCMImageFrameList::const_iterator inputIter = input.begin(); inputIter != input.end(); ++inputIter) { DICOMDatasetAccess* da = inputIter->GetPointer(); assert(da); output.push_back(da); } return output; } mitk::DICOMImageFrameList mitk::DICOMITKSeriesGDCMReader ::ToDICOMImageFrameList(const DICOMGDCMImageFrameList& input) { DICOMImageFrameList output; output.reserve(input.size()); for(DICOMGDCMImageFrameList::const_iterator inputIter = input.begin(); inputIter != input.end(); ++inputIter) { DICOMImageFrameInfo::Pointer fi = (*inputIter)->GetFrameInfo(); assert(fi.IsNotNull()); output.push_back(fi); } return output; } void mitk::DICOMITKSeriesGDCMReader ::InternalPrintConfiguration(std::ostream& os) const { unsigned int sortIndex(1); for(SorterList::const_iterator sorterIter = m_Sorter.begin(); sorterIter != m_Sorter.end(); ++sortIndex, ++sorterIter) { os << "Sorting step " << sortIndex << ":" << std::endl; (*sorterIter)->PrintConfiguration(os, " "); } os << "Sorting step " << sortIndex << ":" << std::endl; m_EquiDistantBlocksSorter->PrintConfiguration(os, " "); } std::string mitk::DICOMITKSeriesGDCMReader ::GetActiveLocale() const { return setlocale(LC_NUMERIC, NULL); } void mitk::DICOMITKSeriesGDCMReader ::PushLocale() const { std::string currentCLocale = setlocale(LC_NUMERIC, NULL); m_ReplacedCLocales.push( currentCLocale ); setlocale(LC_NUMERIC, "C"); std::locale currentCinLocale( std::cin.getloc() ); m_ReplacedCinLocales.push( currentCinLocale ); std::locale l( "C" ); std::cin.imbue(l); } void mitk::DICOMITKSeriesGDCMReader ::PopLocale() const { if (!m_ReplacedCLocales.empty()) { setlocale(LC_NUMERIC, m_ReplacedCLocales.top().c_str()); m_ReplacedCLocales.pop(); } else { MITK_WARN << "Mismatched PopLocale on DICOMITKSeriesGDCMReader."; } if (!m_ReplacedCinLocales.empty()) { std::cin.imbue( m_ReplacedCinLocales.top() ); m_ReplacedCinLocales.pop(); } else { MITK_WARN << "Mismatched PopLocale on DICOMITKSeriesGDCMReader."; } } mitk::DICOMITKSeriesGDCMReader::SortingBlockList mitk::DICOMITKSeriesGDCMReader ::Condense3DBlocks(SortingBlockList& input) { return input; // to be implemented differently by sub-classes } #if defined(MBILOG_ENABLE_DEBUG) || defined (ENABLE_TIMING) #define timeStart(part) timer.Start(part); #define timeStop(part) timer.Stop(part); #else #define timeStart(part) #define timeStop(part) #endif void mitk::DICOMITKSeriesGDCMReader ::AnalyzeInputFiles() { itk::TimeProbesCollectorBase timer; timeStart("Reset"); this->ClearOutputs(); timeStop("Reset"); // prepare initial sorting (== list of input files) StringList inputFilenames = this->GetInputFiles(); timeStart("Check input for DCM"); if ( inputFilenames.empty() || !this->CanHandleFile( inputFilenames.front() ) // first || !this->CanHandleFile( inputFilenames.back() ) // last || !this->CanHandleFile( inputFilenames[ inputFilenames.size() / 2] ) // roughly central file ) { // TODO a read-as-many-as-possible fallback could be implemented here MITK_DEBUG << "Reader unable to process files.."; return; } timeStop("Check input for DCM"); // scan files for sorting-relevant tags if (m_TagCache.IsNull()) { timeStart("Tag scanning"); DICOMGDCMTagScanner::Pointer filescanner = DICOMGDCMTagScanner::New(); m_TagCache = filescanner.GetPointer(); // keep alive and make accessible to sub-classes filescanner->SetInputFiles(inputFilenames); filescanner->AddTags( this->GetTagsOfInterest() ); PushLocale(); filescanner->Scan(); PopLocale(); timeStop("Tag scanning"); } else { // ensure that the tag cache contains our required tags AND files and has scanned! } m_SortingResultInProgress.clear(); // TODO We should remove the following cast // DICOMImageFrameInfo would need to inherit DICOMDatasetAccess! // - then the DICOMGDCMTagScanner class could create a DICOMGDCMImageFrameList internally // - and return it as a DICOMImageFrameList // - like this, DICOMITKSeriesGDCMReader could use the DICOMImageFrameInfoList to feed its sorters // - problem: // - DICOMImageFrameInfo is also part of DICOMImageBlockDescriptor, which is meant // to describe the scanner output, even after the reader (and its scanner) is deleted. // - if DICOMImageFrameInfo now inherits DICOMDatasetAccess, it would also need to implement // GetTagValueAsString(). // - so this could all work if we implement a default response in DICOMImageFrameInfo::GetTagValueAsString() (like in GetFilenameIfAvailable) // and overwrite it in DICOMGDCMImageFrameInfo, which also knows about a specific GDCM scanner result // (which again COULD (no need to?) be hidden as a point to a DICOMGDCMTagScanner class) // if ( DICOMGDCMTagScanner* tagCache = dynamic_cast(m_TagCache.GetPointer()) ) { m_SortingResultInProgress.push_back( tagCache->GetFrameInfoList() ); } else { throw std::logic_error("Bad implementation error: DICOMITKSeriesGDCMReader now unable to find dataset/tag information for its input."); } // sort and split blocks as configured timeStart("Sorting frames"); unsigned int sorterIndex = 0; for(SorterList::iterator sorterIter = m_Sorter.begin(); sorterIter != m_Sorter.end(); ++sorterIndex, ++sorterIter) { std::stringstream ss; ss << "Sorting step " << sorterIndex; timeStart( ss.str().c_str() ); m_SortingResultInProgress = this->InternalExecuteSortingStep(sorterIndex, *sorterIter, m_SortingResultInProgress); timeStop( ss.str().c_str() ); } // a last extra-sorting step: ensure equidistant slices timeStart( "EquiDistantBlocksSorter" ); m_SortingResultInProgress = this->InternalExecuteSortingStep(sorterIndex++, m_EquiDistantBlocksSorter.GetPointer(), m_SortingResultInProgress); timeStop( "EquiDistantBlocksSorter" ); timeStop("Sorting frames"); timeStart("Condensing 3D blocks"); m_SortingResultInProgress = this->Condense3DBlocks( m_SortingResultInProgress ); timeStop("Condensing 3D blocks"); // provide final result as output timeStart("Output"); unsigned int o = this->GetNumberOfOutputs(); this->SetNumberOfOutputs( o + m_SortingResultInProgress.size() ); // Condense3DBlocks may already have added outputs! for (SortingBlockList::iterator blockIter = m_SortingResultInProgress.begin(); blockIter != m_SortingResultInProgress.end(); ++o, ++blockIter) { DICOMGDCMImageFrameList& gdcmFrameInfoList = *blockIter; assert(!gdcmFrameInfoList.empty()); // reverse frames if necessary // update tilt information from absolute last sorting DICOMDatasetList datasetList = ToDICOMDatasetList( gdcmFrameInfoList ); m_NormalDirectionConsistencySorter->SetInput( datasetList ); m_NormalDirectionConsistencySorter->Sort(); DICOMGDCMImageFrameList sortedGdcmInfoFrameList = FromDICOMDatasetList( m_NormalDirectionConsistencySorter->GetOutput(0) ); const GantryTiltInformation& tiltInfo = m_NormalDirectionConsistencySorter->GetTiltInformation(); // set frame list for current block DICOMImageFrameList frameList = ToDICOMImageFrameList( sortedGdcmInfoFrameList ); assert(!frameList.empty()); DICOMImageBlockDescriptor block; block.SetTagCache( this->GetTagCache() ); // important: this must be before SetImageFrameList(), because SetImageFrameList will trigger reading of lots of interesting tags! block.SetImageFrameList( frameList ); block.SetTiltInformation( tiltInfo ); block.SetReaderImplementationLevel( this->GetReaderImplementationLevel( block.GetSOPClassUID() ) ); this->SetOutput( o, block ); } timeStop("Output"); #if defined(MBILOG_ENABLE_DEBUG) || defined (ENABLE_TIMING) 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) { SortingBlockList nextStepSorting; // we should not modify our input list while processing it std::stringstream ss; ss << "Sorting step " << sortingStepIndex << " '"; #if defined(MBILOG_ENABLE_DEBUG) sorter->PrintConfiguration(ss); #endif ss << "'"; 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 ); } } return nextStepSorting; } mitk::ReaderImplementationLevel mitk::DICOMITKSeriesGDCMReader ::GetReaderImplementationLevel(const std::string sopClassUID) const { if (sopClassUID.empty()) { return SOPClassUnknown; } gdcm::UIDs uidKnowledge; uidKnowledge.SetFromUID( sopClassUID.c_str() ); gdcm::UIDs::TSType gdcmType = uidKnowledge; switch (gdcmType) { case gdcm::UIDs::CTImageStorage: case gdcm::UIDs::MRImageStorage: case gdcm::UIDs::PositronEmissionTomographyImageStorage: case gdcm::UIDs::ComputedRadiographyImageStorage: case gdcm::UIDs::DigitalXRayImageStorageForPresentation: case gdcm::UIDs::DigitalXRayImageStorageForProcessing: return SOPClassSupported; case gdcm::UIDs::NuclearMedicineImageStorage: return SOPClassPartlySupported; case gdcm::UIDs::SecondaryCaptureImageStorage: return SOPClassImplemented; default: return SOPClassUnsupported; } } // void AllocateOutputImages(); bool mitk::DICOMITKSeriesGDCMReader ::LoadImages() { bool success = true; unsigned int numberOfOutputs = this->GetNumberOfOutputs(); for (unsigned int o = 0; o < numberOfOutputs; ++o) { success &= this->LoadMitkImageForOutput(o); } return success; } bool mitk::DICOMITKSeriesGDCMReader ::LoadMitkImageForImageBlockDescriptor(DICOMImageBlockDescriptor& block) const { PushLocale(); const DICOMImageFrameList& frames = block.GetImageFrameList(); const GantryTiltInformation tiltInfo = block.GetTiltInformation(); bool hasTilt = tiltInfo.IsRegularGantryTilt(); ITKDICOMSeriesReaderHelper::StringContainer filenames; for (DICOMImageFrameList::const_iterator frameIter = frames.begin(); frameIter != frames.end(); ++frameIter) { filenames.push_back( (*frameIter)->Filename ); } mitk::ITKDICOMSeriesReaderHelper helper; bool success(true); try { mitk::Image::Pointer mitkImage = helper.Load( filenames, m_FixTiltByShearing && hasTilt, tiltInfo ); block.SetMitkImage( mitkImage ); } catch (std::exception& e) { success = false; MITK_ERROR << "Exception during image loading: " << e.what(); } PopLocale(); return success; } bool mitk::DICOMITKSeriesGDCMReader ::LoadMitkImageForOutput(unsigned int o) { DICOMImageBlockDescriptor& block = this->InternalGetOutput(o); return this->LoadMitkImageForImageBlockDescriptor(block); } bool mitk::DICOMITKSeriesGDCMReader ::CanHandleFile(const std::string& filename) { return ITKDICOMSeriesReaderHelper::CanHandleFile(filename); } void mitk::DICOMITKSeriesGDCMReader ::AddSortingElement(DICOMDatasetSorter* sorter, bool atFront) { assert(sorter); if (atFront) { m_Sorter.push_front( sorter ); } else { m_Sorter.push_back( sorter ); } } mitk::DICOMITKSeriesGDCMReader::ConstSorterList mitk::DICOMITKSeriesGDCMReader ::GetFreelyConfiguredSortingElements() const { std::list result; unsigned int sortIndex(0); for(SorterList::const_iterator sorterIter = m_Sorter.begin(); sorterIter != m_Sorter.end(); ++sortIndex, ++sorterIter) { if (sortIndex > 0) // ignore first element (see EnsureMandatorySortersArePresent) { result.push_back( (*sorterIter).GetPointer() ); } } return result; } void mitk::DICOMITKSeriesGDCMReader ::EnsureMandatorySortersArePresent(unsigned int decimalPlacesForOrientation) { DICOMTagBasedSorter::Pointer splitter = DICOMTagBasedSorter::New(); splitter->AddDistinguishingTag( DICOMTag(0x0028, 0x0010) ); // Number of Rows splitter->AddDistinguishingTag( DICOMTag(0x0028, 0x0011) ); // Number of Columns splitter->AddDistinguishingTag( DICOMTag(0x0028, 0x0030) ); // Pixel Spacing splitter->AddDistinguishingTag( DICOMTag(0x0018, 0x1164) ); // Imager Pixel Spacing splitter->AddDistinguishingTag( DICOMTag(0x0020, 0x0037), new mitk::DICOMTagBasedSorter::CutDecimalPlaces(decimalPlacesForOrientation) ); // Image Orientation (Patient) splitter->AddDistinguishingTag( DICOMTag(0x0018, 0x0050) ); // Slice Thickness splitter->AddDistinguishingTag( DICOMTag(0x0028, 0x0008) ); // Number of Frames this->AddSortingElement( splitter, true ); // true = at front if (m_EquiDistantBlocksSorter.IsNull()) { m_EquiDistantBlocksSorter = mitk::EquiDistantBlocksSorter::New(); } m_EquiDistantBlocksSorter->SetAcceptTilt( m_FixTiltByShearing ); if (m_NormalDirectionConsistencySorter.IsNull()) { m_NormalDirectionConsistencySorter = mitk::NormalDirectionConsistencySorter::New(); } } void mitk::DICOMITKSeriesGDCMReader ::SetToleratedOriginOffsetToAdaptive(double fractionOfInterSliceDistance) { assert( m_EquiDistantBlocksSorter.IsNotNull() ); m_EquiDistantBlocksSorter->SetToleratedOriginOffsetToAdaptive(fractionOfInterSliceDistance); } void mitk::DICOMITKSeriesGDCMReader ::SetToleratedOriginOffset(double millimeters) { assert( m_EquiDistantBlocksSorter.IsNotNull() ); m_EquiDistantBlocksSorter->SetToleratedOriginOffset(millimeters); } double mitk::DICOMITKSeriesGDCMReader ::GetToleratedOriginError() const { assert( m_EquiDistantBlocksSorter.IsNotNull() ); return m_EquiDistantBlocksSorter->GetToleratedOriginOffset(); } bool mitk::DICOMITKSeriesGDCMReader ::IsToleratedOriginOffsetAbsolute() const { assert( m_EquiDistantBlocksSorter.IsNotNull() ); return m_EquiDistantBlocksSorter->IsToleratedOriginOffsetAbsolute(); } double mitk::DICOMITKSeriesGDCMReader ::GetDecimalPlacesForOrientation() const { return m_DecimalPlacesForOrientation; } mitk::DICOMTagCache::Pointer mitk::DICOMITKSeriesGDCMReader ::GetTagCache() const { return m_TagCache; } void mitk::DICOMITKSeriesGDCMReader ::SetTagCache(DICOMTagCache::Pointer tagCache) { m_TagCache = tagCache; } mitk::DICOMTagList mitk::DICOMITKSeriesGDCMReader ::GetTagsOfInterest() const { DICOMTagList completeList; // check all configured sorters for(SorterList::const_iterator sorterIter = m_Sorter.begin(); sorterIter != m_Sorter.end(); ++sorterIter) { assert(sorterIter->IsNotNull()); DICOMTagList tags = (*sorterIter)->GetTagsOfInterest(); completeList.insert( completeList.end(), tags.begin(), tags.end() ); } // check our own forced sorters DICOMTagList tags = m_EquiDistantBlocksSorter->GetTagsOfInterest(); completeList.insert( completeList.end(), tags.begin(), tags.end() ); tags = m_NormalDirectionConsistencySorter->GetTagsOfInterest(); completeList.insert( completeList.end(), tags.begin(), tags.end() ); // add the tags for DICOMImageBlockDescriptor tags = DICOMImageBlockDescriptor::GetTagsOfInterest(); completeList.insert( completeList.end(), tags.begin(), tags.end() ); return completeList; } diff --git a/Modules/DICOMReader/mitkDICOMTagBasedSorter.cpp b/Modules/DICOMReader/mitkDICOMTagBasedSorter.cpp index df1007768d..185814d2e6 100644 --- a/Modules/DICOMReader/mitkDICOMTagBasedSorter.cpp +++ b/Modules/DICOMReader/mitkDICOMTagBasedSorter.cpp @@ -1,509 +1,540 @@ /*=================================================================== 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 #include mitk::DICOMTagBasedSorter::CutDecimalPlaces ::CutDecimalPlaces(unsigned int precision) :m_Precision(precision) { } +mitk::DICOMTagBasedSorter::CutDecimalPlaces +::CutDecimalPlaces(const CutDecimalPlaces& other) +:m_Precision(other.m_Precision) +{ +} + std::string mitk::DICOMTagBasedSorter::CutDecimalPlaces ::operator()(const std::string& input) const { // be a bit tolerant for tags such as image orientation orienatation, let only the first few digits matter (http://bugs.mitk.org/show_bug.cgi?id=12263) // iterate all fields, convert each to a number, cut this number as configured, then return a concatenated string with all cut-off numbers static std::ostringstream resultString; resultString.str(std::string()); resultString.clear(); resultString.setf(std::ios::fixed, std::ios::floatfield); resultString.precision(m_Precision); static std::stringstream ss(input); ss.str(input); ss.clear(); static std::string item; while (std::getline(ss, item, '\\')) { static std::istringstream converter(item); converter.str(item); converter.clear(); static double number(0); if (converter >> number && converter.eof()) { // converted to double resultString << number; } else { // did not convert to double resultString << item; // just paste the unmodified string } if (!ss.eof()) { resultString << "\\"; } } return resultString.str(); } +mitk::DICOMTagBasedSorter::TagValueProcessor* +mitk::DICOMTagBasedSorter::CutDecimalPlaces +::Clone() const +{ + return new CutDecimalPlaces(*this); +} + + unsigned int mitk::DICOMTagBasedSorter::CutDecimalPlaces ::GetPrecision() const { return m_Precision; } mitk::DICOMTagBasedSorter ::DICOMTagBasedSorter() :DICOMDatasetSorter() ,m_StrictSorting(true) { } 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) -,m_StrictSorting(other.m_StrictSorting) +,m_DistinguishingTags( other.m_DistinguishingTags ) +,m_SortCriterion( other.m_SortCriterion ) +,m_StrictSorting( other.m_StrictSorting ) { + for(TagValueProcessorMap::const_iterator ti = other.m_TagValueProcessor.begin(); + ti != other.m_TagValueProcessor.end(); + ++ti) + { + m_TagValueProcessor[ti->first] = ti->second->Clone(); + } } mitk::DICOMTagBasedSorter& mitk::DICOMTagBasedSorter ::operator=(const DICOMTagBasedSorter& other) { if (this != &other) { DICOMDatasetSorter::operator=(other); + m_DistinguishingTags = other.m_DistinguishingTags; + m_SortCriterion = other.m_SortCriterion; m_StrictSorting = other.m_StrictSorting; + + for(TagValueProcessorMap::const_iterator ti = other.m_TagValueProcessor.begin(); + ti != other.m_TagValueProcessor.end(); + ++ti) + { + m_TagValueProcessor[ti->first] = ti->second->Clone(); + } } return *this; } bool mitk::DICOMTagBasedSorter ::operator==(const DICOMDatasetSorter& other) const { if (const DICOMTagBasedSorter* otherSelf = dynamic_cast(&other)) { if (this->m_StrictSorting != otherSelf->m_StrictSorting) return false; bool allTagsPresentAndEqual(true); if (this->m_DistinguishingTags.size() != otherSelf->m_DistinguishingTags.size()) return false; for (DICOMTagList::const_iterator myTag = this->m_DistinguishingTags.begin(); myTag != this->m_DistinguishingTags.end(); ++myTag) { allTagsPresentAndEqual &= (std::find( otherSelf->m_DistinguishingTags.begin(), otherSelf->m_DistinguishingTags.end(), *myTag ) != otherSelf->m_DistinguishingTags.end()); // other contains this tags // since size is equal, we don't need to check the inverse } if (!allTagsPresentAndEqual) return false; if (this->m_SortCriterion.IsNotNull() && otherSelf->m_SortCriterion.IsNotNull()) { return *(this->m_SortCriterion) == *(otherSelf->m_SortCriterion); } else { return this->m_SortCriterion.IsNull() && otherSelf->m_SortCriterion.IsNull(); } } else { return false; } } void mitk::DICOMTagBasedSorter ::PrintConfiguration(std::ostream& os, const std::string& indent) const { os << indent << "Tag based sorting:" << std::endl; for (DICOMTagList::const_iterator tagIter = m_DistinguishingTags.begin(); tagIter != m_DistinguishingTags.end(); ++tagIter) { os << indent << " Split on "; tagIter->Print(os); os << std::endl; } DICOMSortCriterion::ConstPointer crit = m_SortCriterion.GetPointer(); while (crit.IsNotNull()) { os << indent << " Sort by "; crit->Print(os); os << std::endl; crit = crit->GetSecondaryCriterion(); } } void mitk::DICOMTagBasedSorter ::SetStrictSorting(bool strict) { m_StrictSorting = strict; } bool mitk::DICOMTagBasedSorter ::GetStrictSorting() const { return m_StrictSorting; } mitk::DICOMTagList mitk::DICOMTagBasedSorter ::GetTagsOfInterest() { DICOMTagList allTags = m_DistinguishingTags; DICOMTagList sortingRelevantTags = m_SortCriterion->GetAllTagsOfInterest(); allTags.insert( allTags.end(), sortingRelevantTags.begin(), sortingRelevantTags.end() ); // append return allTags; } mitk::DICOMTagList mitk::DICOMTagBasedSorter ::GetDistinguishingTags() const { return m_DistinguishingTags; } const mitk::DICOMTagBasedSorter::TagValueProcessor* mitk::DICOMTagBasedSorter ::GetTagValueProcessorForDistinguishingTag(const DICOMTag& tag) const { TagValueProcessorMap::const_iterator loc = m_TagValueProcessor.find(tag); if (loc != m_TagValueProcessor.end()) { return loc->second; } else { return NULL; } } void mitk::DICOMTagBasedSorter ::AddDistinguishingTag( const DICOMTag& tag, TagValueProcessor* tagValueProcessor ) { m_DistinguishingTags.push_back(tag); m_TagValueProcessor[tag] = tagValueProcessor; } void mitk::DICOMTagBasedSorter ::SetSortCriterion( DICOMSortCriterion::ConstPointer criterion ) { m_SortCriterion = criterion; } mitk::DICOMSortCriterion::ConstPointer mitk::DICOMTagBasedSorter ::GetSortCriterion() const { return m_SortCriterion; } void mitk::DICOMTagBasedSorter ::Sort() { // 1. split // 2. sort each group GroupIDToListType groups = this->SplitInputGroups(); GroupIDToListType& sortedGroups = this->SortGroups( groups ); // 3. define output this->SetNumberOfOutputs(sortedGroups.size()); unsigned int outputIndex(0); for (GroupIDToListType::iterator groupIter = sortedGroups.begin(); groupIter != sortedGroups.end(); ++outputIndex, ++groupIter) { this->SetOutput(outputIndex, groupIter->second); } } std::string mitk::DICOMTagBasedSorter ::BuildGroupID( DICOMDatasetAccess* dataset ) { // just concatenate all tag values assert(dataset); std::stringstream groupID; groupID << "g"; for (DICOMTagList::iterator tagIter = m_DistinguishingTags.begin(); tagIter != m_DistinguishingTags.end(); ++tagIter) { groupID << tagIter->GetGroup() << tagIter->GetElement(); // make group/element part of the id to cover empty tags std::string rawTagValue = dataset->GetTagValueAsString(*tagIter); std::string processedTagValue; if ( m_TagValueProcessor[*tagIter] != NULL ) { processedTagValue = (*m_TagValueProcessor[*tagIter])(rawTagValue); } else { processedTagValue = rawTagValue; } groupID << processedTagValue; } // shorten ID? return groupID.str(); } mitk::DICOMTagBasedSorter::GroupIDToListType mitk::DICOMTagBasedSorter ::SplitInputGroups() { DICOMDatasetList input = GetInput(); // copy GroupIDToListType listForGroupID; for (DICOMDatasetList::iterator dsIter = input.begin(); dsIter != input.end(); ++dsIter) { DICOMDatasetAccess* dataset = *dsIter; assert(dataset); std::string groupID = this->BuildGroupID( dataset ); MITK_DEBUG << "Group ID for for " << dataset->GetFilenameIfAvailable() << ": " << groupID; listForGroupID[groupID].push_back(dataset); } MITK_DEBUG << "After tag based splitting: " << listForGroupID.size() << " groups"; return listForGroupID; } mitk::DICOMTagBasedSorter::GroupIDToListType& mitk::DICOMTagBasedSorter ::SortGroups(GroupIDToListType& groups) { if (m_SortCriterion.IsNotNull()) { /* Three steps here: 1. sort within each group - this may result in orders such as 1 2 3 4 6 7 8 10 12 13 14 2. create new groups by enforcing consecutive order within each group - resorts above example like 1 2 3 4 ; 6 7 8 ; 10 ; 12 13 14 3. sort all of the groups (not WITHIN each group) by their first frame - if earlier "distinguish" steps created groups like 6 7 8 ; 1 2 3 4 ; 10, then this step would sort them like 1 2 3 4 ; 6 7 8 ; 10 */ // Step 1: sort within the groups // 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 << " --------------------------------------------------------------------------------"; } GroupIDToListType consecutiveGroups; if (m_StrictSorting) { // Step 2: create new groups by enforcing consecutive order within each group unsigned int groupIndex(0); for (GroupIDToListType::iterator gIter = groups.begin(); gIter != groups.end(); ++gIter) { std::stringstream groupKey; groupKey << std::setfill('0') << std::setw(6) << groupIndex++; DICOMDatasetList& dsList = gIter->second; DICOMDatasetAccess* previousDS(NULL); unsigned int dsIndex(0); double constantDistance(0.0); bool constantDistanceInitialized(false); for (DICOMDatasetList::iterator dataset = dsList.begin(); dataset != dsList.end(); ++dsIndex, ++dataset) { if (dsIndex >0) // ignore the first dataset, we cannot check any distances yet.. { // for the second and every following dataset: // let the sorting criterion calculate a "distance" // if the distance is not 1, split off a new group! double currentDistance = m_SortCriterion->NumericDistance(previousDS, *dataset); if (constantDistanceInitialized) { if (fabs(currentDistance - constantDistance) < constantDistance * 0.01) // ok, deviation of up to 1% of distance is tolerated { // nothing to do, just ok } else if (currentDistance < mitk::eps) // close enough to 0 { // no numeric comparison possible? // rare case(?), just accept } else { // split! this is done by simply creating a new group (key) groupKey.str(std::string()); groupKey.clear(); groupKey << std::setfill('0') << std::setw(6) << groupIndex++; } } else { // second slice: learn about the expected distance! // heuristic: if distance is an integer, we check for a special case: // if the distance is integer and not 1/-1, then we assume // a missing slice right after the first slice // ==> split off slices // in all other cases: second dataset at this position, no need to split already, we are still learning about the images if ((currentDistance - (int)currentDistance == 0.0) && fabs(currentDistance) != 1.0) // exact comparison. An integer should not be expressed as 1.000000000000000000000000001! { groupKey.str(std::string()); groupKey.clear(); groupKey << std::setfill('0') << std::setw(6) << groupIndex++; } constantDistance = currentDistance; constantDistanceInitialized = true; } } consecutiveGroups[groupKey.str()].push_back(*dataset); previousDS = *dataset; } } } else { consecutiveGroups = groups; } // Step 3: sort all of the groups (not WITHIN each group) by their first frame /* build a list-1 of datasets with the first dataset one of each group sort this list-1 build a new result list-2: - iterate list-1, for each dataset - find the group that contains this dataset - add this group as the next element to list-2 return list-2 as the sorted output */ DICOMDatasetList firstSlices; for (GroupIDToListType::iterator gIter = consecutiveGroups.begin(); gIter != consecutiveGroups.end(); ++gIter) { assert(!gIter->second.empty()); firstSlices.push_back(gIter->second.front()); } std::sort( firstSlices.begin(), firstSlices.end(), ParameterizedDatasetSort( m_SortCriterion ) ); GroupIDToListType sortedResultBlocks; unsigned int groupKeyValue(0); for (DICOMDatasetList::iterator firstSlice = firstSlices.begin(); firstSlice != firstSlices.end(); ++firstSlice) { for (GroupIDToListType::iterator gIter = consecutiveGroups.begin(); gIter != consecutiveGroups.end(); ++groupKeyValue, ++gIter) { if (gIter->second.front() == *firstSlice) { std::stringstream groupKey; groupKey << std::setfill('0') << std::setw(6) << groupKeyValue; // try more than 999,999 groups and you are doomed (your application already is) sortedResultBlocks[groupKey.str()] = gIter->second; } } } groups = sortedResultBlocks; } 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 32722b48e1..603f2b51a9 100644 --- a/Modules/DICOMReader/mitkDICOMTagBasedSorter.h +++ b/Modules/DICOMReader/mitkDICOMTagBasedSorter.h @@ -1,162 +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. ===================================================================*/ #ifndef mitkDICOMTagBasedSorter_h #define mitkDICOMTagBasedSorter_h #include "mitkDICOMDatasetSorter.h" #include "mitkDICOMSortCriterion.h" namespace mitk { /** \ingroup DICOMReaderModule \brief Sort DICOM datasets based on configurable tags. This class implements sorting of input DICOM datasets into multiple outputs as described in \ref DICOMITKSeriesGDCMReader_LoadingStrategy. The logic of sorting and splitting is most simple and most generic: 1. Datasets will be put into different groups, if they differ in their value of specific tags (defined by AddDistinguishingTag()) - there might be multiple distinguishing tags defined - tag values might be processed before comparison by means of TagValueProcessor (e.g. round to a number of decimal places) 2. Each of the groups will be sorted by comparing their tag values using multiple DICOMSortCriterion - DICOMSortCriterion might evaluate a single tag (e.g. Instance Number) or multiple values (as in SortByImagePositionPatient) - only a single DICOMSortCriterion is defined for DICOMTagBasedSorter, because each DICOMSortCriterion holds a "secondary sort criterion", i.e. an application can define multiple tags for sorting by chaining \link DICOMSortCriterion DICOMSortCriteria \endlink - applications should make sure that sorting is always defined (to avoid problems with standard containers), e.g. by adding a comparison of filenames or instance UIDs as a last sorting fallback. */ class DICOMReader_EXPORT DICOMTagBasedSorter : public DICOMDatasetSorter { public: /** \brief Processes tag values before they are compared. These classes could do some kind of normalization such as rounding, lower case formatting, etc. */ class DICOMReader_EXPORT TagValueProcessor { public: /// \brief Implements the "processing". virtual std::string operator()(const std::string&) const = 0; + virtual TagValueProcessor* Clone() const = 0; }; /** \brief Cuts a number after configured number of decimal places. An instance of this class can be used to avoid errors when comparing minimally different image orientations. */ class DICOMReader_EXPORT CutDecimalPlaces : public TagValueProcessor { public: CutDecimalPlaces(unsigned int precision); + CutDecimalPlaces(const CutDecimalPlaces& other); unsigned int GetPrecision() const; virtual std::string operator()(const std::string&) const; + virtual TagValueProcessor* Clone() const; private: unsigned int m_Precision; }; mitkClassMacro( DICOMTagBasedSorter, DICOMDatasetSorter ) itkNewMacro( DICOMTagBasedSorter ) /** \brief Datasets that differ in given tag's value will be sorted into separate outputs. */ void AddDistinguishingTag( const DICOMTag&, TagValueProcessor* tagValueProcessor = NULL ); DICOMTagList GetDistinguishingTags() const; const TagValueProcessor* GetTagValueProcessorForDistinguishingTag(const DICOMTag&) const; /** \brief Define the sorting criterion (which holds seconardy criteria) */ void SetSortCriterion( DICOMSortCriterion::ConstPointer criterion ); DICOMSortCriterion::ConstPointer GetSortCriterion() const; /** \brief A list of all the tags needed for processing (facilitates scanning). */ virtual DICOMTagList GetTagsOfInterest(); /** \brief Whether or not groups should be checked for consecutive tag values. */ void SetStrictSorting(bool strict); bool GetStrictSorting() const; /** \brief Actually sort as described in the Detailed Description. */ virtual void Sort(); /** \brief Print configuration details into given stream. */ virtual void PrintConfiguration(std::ostream& os, const std::string& indent = "") const; virtual bool operator==(const DICOMDatasetSorter& other) const; protected: /** \brief Helper struct to feed into std::sort, configured via DICOMSortCriterion. */ struct ParameterizedDatasetSort { ParameterizedDatasetSort(DICOMSortCriterion::ConstPointer); bool operator() (const mitk::DICOMDatasetAccess* left, const mitk::DICOMDatasetAccess* right); DICOMSortCriterion::ConstPointer m_SortCriterion; }; DICOMTagBasedSorter(); virtual ~DICOMTagBasedSorter(); DICOMTagBasedSorter(const DICOMTagBasedSorter& other); DICOMTagBasedSorter& operator=(const DICOMTagBasedSorter& other); /** \brief Helper for SplitInputGroups(). */ std::string BuildGroupID( DICOMDatasetAccess* dataset ); typedef std::map GroupIDToListType; /** \brief Implements the "distiguishing tags". To sort datasets into different groups, a long string will be built for each dataset. The string concatenates all tags and their respective values. Datasets that match in all values will end up with the same string. */ GroupIDToListType SplitInputGroups(); /** \brief Implements the sorting step. Relatively simple implementation thanks to std::sort and a parameterization via DICOMSortCriterion. */ GroupIDToListType& SortGroups(GroupIDToListType& groups); DICOMTagList m_DistinguishingTags; typedef std::map TagValueProcessorMap; TagValueProcessorMap m_TagValueProcessor; DICOMSortCriterion::ConstPointer m_SortCriterion; bool m_StrictSorting; }; } #endif diff --git a/Modules/DICOMReader/mitkDICOMTagCache.h b/Modules/DICOMReader/mitkDICOMTagCache.h index c37e1bab3c..bd4a7802f7 100644 --- a/Modules/DICOMReader/mitkDICOMTagCache.h +++ b/Modules/DICOMReader/mitkDICOMTagCache.h @@ -1,52 +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 mitkDICOMTagCache_h #define mitkDICOMTagCache_h #include "itkObjectFactory.h" #include "mitkCommon.h" #include "mitkDICOMTag.h" #include "DICOMReaderExports.h" namespace mitk { class DICOMImageFrameInfo; /** \ingroup DICOMReaderModule \brief ... */ - class DICOMReader_EXPORT DICOMTagCache : virtual public itk::Object + class DICOMReader_EXPORT DICOMTagCache : public itk::Object { public: mitkClassMacro( DICOMTagCache, itk::Object ); virtual std::string GetTagValue(DICOMImageFrameInfo* frame, const DICOMTag& tag) const = 0; protected: DICOMTagCache(); DICOMTagCache(const DICOMTagCache&); virtual ~DICOMTagCache(); }; } #endif diff --git a/Modules/DICOMReader/mitkThreeDnTDICOMSeriesReader.cpp b/Modules/DICOMReader/mitkThreeDnTDICOMSeriesReader.cpp index d63e0c01c6..24c6fdfff6 100644 --- a/Modules/DICOMReader/mitkThreeDnTDICOMSeriesReader.cpp +++ b/Modules/DICOMReader/mitkThreeDnTDICOMSeriesReader.cpp @@ -1,265 +1,264 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkThreeDnTDICOMSeriesReader.h" #include "mitkITKDICOMSeriesReaderHelper.h" mitk::ThreeDnTDICOMSeriesReader ::ThreeDnTDICOMSeriesReader(unsigned int decimalPlacesForOrientation) :DICOMITKSeriesGDCMReader(decimalPlacesForOrientation) ,m_Group3DandT(true) { } mitk::ThreeDnTDICOMSeriesReader ::ThreeDnTDICOMSeriesReader(const ThreeDnTDICOMSeriesReader& other ) -:itk::Object() -,DICOMITKSeriesGDCMReader(other) +:DICOMITKSeriesGDCMReader(other) ,m_Group3DandT(true) { } mitk::ThreeDnTDICOMSeriesReader ::~ThreeDnTDICOMSeriesReader() { } mitk::ThreeDnTDICOMSeriesReader& mitk::ThreeDnTDICOMSeriesReader ::operator=(const ThreeDnTDICOMSeriesReader& other) { if (this != &other) { DICOMITKSeriesGDCMReader::operator=(other); this->m_Group3DandT = other.m_Group3DandT; } return *this; } bool mitk::ThreeDnTDICOMSeriesReader ::operator==(const DICOMFileReader& other) const { if (const Self* otherSelf = dynamic_cast(&other)) { return DICOMITKSeriesGDCMReader::operator==(other) && this->m_Group3DandT == otherSelf->m_Group3DandT; } else { return false; } } void mitk::ThreeDnTDICOMSeriesReader ::SetGroup3DandT(bool on) { m_Group3DandT = on; } bool mitk::ThreeDnTDICOMSeriesReader ::GetGroup3DandT() const { return m_Group3DandT; } mitk::DICOMITKSeriesGDCMReader::SortingBlockList mitk::ThreeDnTDICOMSeriesReader ::Condense3DBlocks(SortingBlockList& resultOf3DGrouping) { if (!m_Group3DandT) { return resultOf3DGrouping; // don't work if nobody asks us to } SortingBlockList remainingBlocks = resultOf3DGrouping; SortingBlockList non3DnTBlocks; SortingBlockList true3DnTBlocks; std::vector true3DnTBlocksTimeStepCount; // we should describe our need for this tag as needed via a function // (however, we currently know that the superclass will always need this tag) const DICOMTag tagImagePositionPatient(0x0020, 0x0032); while (!remainingBlocks.empty()) { // new block to fill up DICOMGDCMImageFrameList& firstBlock = remainingBlocks.front(); DICOMGDCMImageFrameList current3DnTBlock = firstBlock; int current3DnTBlockNumberOfTimeSteps = 1; // get block characteristics of first block unsigned int currentBlockNumberOfSlices = firstBlock.size(); std::string currentBlockFirstOrigin = firstBlock.front()->GetTagValueAsString( tagImagePositionPatient ); std::string currentBlockLastOrigin = firstBlock.back()->GetTagValueAsString( tagImagePositionPatient ); remainingBlocks.pop_front(); // compare all other blocks against the first one for (SortingBlockList::iterator otherBlockIter = remainingBlocks.begin(); otherBlockIter != remainingBlocks.end(); /*++otherBlockIter*/) // <-- inside loop { // get block characteristics from first block DICOMGDCMImageFrameList& otherBlock = *otherBlockIter; unsigned int otherBlockNumberOfSlices = otherBlock.size(); std::string otherBlockFirstOrigin = otherBlock.front()->GetTagValueAsString( tagImagePositionPatient ); std::string otherBlockLastOrigin = otherBlock.back()->GetTagValueAsString( tagImagePositionPatient ); // add matching blocks to current3DnTBlock // keep other blocks for later if ( otherBlockNumberOfSlices == currentBlockNumberOfSlices && otherBlockFirstOrigin == currentBlockFirstOrigin && otherBlockLastOrigin == currentBlockLastOrigin ) { // matching block ++current3DnTBlockNumberOfTimeSteps; current3DnTBlock.insert( current3DnTBlock.end(), otherBlock.begin(), otherBlock.end() ); // append // remove this block from remainingBlocks otherBlockIter = remainingBlocks.erase(otherBlockIter); // make sure iterator otherBlockIter is valid afterwards } else { ++otherBlockIter; } } // in any case, we now now all about the first block of our list ... // ... and we wither call it 3D o 3D+t if (current3DnTBlockNumberOfTimeSteps > 1) { true3DnTBlocks.push_back(current3DnTBlock); true3DnTBlocksTimeStepCount.push_back(current3DnTBlockNumberOfTimeSteps); } else { non3DnTBlocks.push_back(current3DnTBlock); } } // create output for real 3D+t blocks (other outputs will be created by superclass) // set 3D+t flag on output block this->SetNumberOfOutputs( true3DnTBlocks.size() ); unsigned int o = 0; for (SortingBlockList::iterator blockIter = true3DnTBlocks.begin(); blockIter != true3DnTBlocks.end(); ++o, ++blockIter) { // bad copy&paste code from DICOMITKSeriesGDCMReader, should be handled in a better way DICOMGDCMImageFrameList& gdcmFrameInfoList = *blockIter; assert(!gdcmFrameInfoList.empty()); // reverse frames if necessary // update tilt information from absolute last sorting DICOMDatasetList datasetList = ToDICOMDatasetList( gdcmFrameInfoList ); m_NormalDirectionConsistencySorter->SetInput( datasetList ); m_NormalDirectionConsistencySorter->Sort(); DICOMGDCMImageFrameList sortedGdcmInfoFrameList = FromDICOMDatasetList( m_NormalDirectionConsistencySorter->GetOutput(0) ); const GantryTiltInformation& tiltInfo = m_NormalDirectionConsistencySorter->GetTiltInformation(); // set frame list for current block DICOMImageFrameList frameList = ToDICOMImageFrameList( sortedGdcmInfoFrameList ); assert(!frameList.empty()); DICOMImageBlockDescriptor block; block.SetTagCache( this->GetTagCache() ); // important: this must be before SetImageFrameList(), because SetImageFrameList will trigger reading of lots of interesting tags! block.SetImageFrameList( frameList ); block.SetTiltInformation( tiltInfo ); block.SetFlag("3D+t", true); block.SetIntProperty("timesteps", true3DnTBlocksTimeStepCount[o]); MITK_DEBUG << "Found " << true3DnTBlocksTimeStepCount[o] << " timesteps"; this->SetOutput( o, block ); } return non3DnTBlocks; } bool mitk::ThreeDnTDICOMSeriesReader ::LoadImages() { bool success = true; unsigned int numberOfOutputs = this->GetNumberOfOutputs(); for (unsigned int o = 0; o < numberOfOutputs; ++o) { DICOMImageBlockDescriptor& block = this->InternalGetOutput(o); if (block.GetFlag("3D+t", false)) { success &= this->LoadMitkImageForOutput(o); } else { success &= DICOMITKSeriesGDCMReader::LoadMitkImageForOutput(o); // let superclass handle non-3D+t } } return success; } bool mitk::ThreeDnTDICOMSeriesReader ::LoadMitkImageForImageBlockDescriptor(DICOMImageBlockDescriptor& block) const { PushLocale(); const DICOMImageFrameList& frames = block.GetImageFrameList(); const GantryTiltInformation tiltInfo = block.GetTiltInformation(); bool hasTilt = tiltInfo.IsRegularGantryTilt(); int numberOfTimesteps = block.GetIntProperty("timesteps", 1); if (numberOfTimesteps == 1) { return DICOMITKSeriesGDCMReader::LoadMitkImageForImageBlockDescriptor(block); } int numberOfFramesPerTimestep = frames.size() / numberOfTimesteps; assert( int(double((double)frames.size() / (double)numberOfTimesteps )) == numberOfFramesPerTimestep ); // this should hold ITKDICOMSeriesReaderHelper::StringContainerList filenamesPerTimestep; for (int timeStep = 0; timeStepFilename ); } filenamesPerTimestep.push_back( filenamesOfThisTimeStep ); } mitk::ITKDICOMSeriesReaderHelper helper; mitk::Image::Pointer mitkImage = helper.Load3DnT( filenamesPerTimestep, m_FixTiltByShearing && hasTilt, tiltInfo ); block.SetMitkImage( mitkImage ); PopLocale(); return true; }