diff --git a/Modules/DICOMReader/files.cmake b/Modules/DICOMReader/files.cmake index b2e1395c9b..c64c804da2 100644 --- a/Modules/DICOMReader/files.cmake +++ b/Modules/DICOMReader/files.cmake @@ -1,53 +1,54 @@ file(GLOB_RECURSE H_FILES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/include/*") set(CPP_FILES mitkBaseDICOMReaderService.cpp mitkDICOMFileReader.cpp mitkDICOMTagScanner.cpp mitkDICOMGDCMTagScanner.cpp mitkDICOMDCMTKTagScanner.cpp mitkDICOMImageBlockDescriptor.cpp mitkDICOMITKSeriesGDCMReader.cpp mitkDICOMDatasetSorter.cpp mitkDICOMTagBasedSorter.cpp mitkDICOMGDCMImageFrameInfo.cpp mitkDICOMImageFrameInfo.cpp mitkDICOMIOHelper.cpp mitkDICOMGenericImageFrameInfo.cpp mitkDICOMDatasetAccessingImageFrameInfo.cpp mitkDICOMSortCriterion.cpp mitkDICOMSortByTag.cpp mitkITKDICOMSeriesReaderHelper.cpp mitkEquiDistantBlocksSorter.cpp mitkNormalDirectionConsistencySorter.cpp mitkSortByImagePositionPatient.cpp mitkGantryTiltInformation.cpp mitkClassicDICOMSeriesReader.cpp mitkThreeDnTDICOMSeriesReader.cpp mitkDICOMTag.cpp mitkDICOMTagsOfInterestHelper.cpp mitkDICOMTagCache.cpp mitkDICOMGDCMTagCache.cpp mitkDICOMGenericTagCache.cpp mitkDICOMEnums.cpp mitkDICOMReaderConfigurator.cpp mitkDICOMFileReaderSelector.cpp mitkIDICOMTagsOfInterest.cpp mitkDICOMTagPath.cpp mitkDICOMProperty.cpp mitkDICOMFilesHelper.cpp ) set(RESOURCE_FILES configurations/3D/classicreader.xml configurations/3D/imageposition.xml configurations/3D/imageposition_byacquisition.xml configurations/3D/instancenumber.xml configurations/3D/instancenumber_soft.xml configurations/3D/slicelocation.xml configurations/3D/simpleinstancenumber_soft.xml configurations/3DnT/classicreader.xml + configurations/3DnT/imageposition.xml configurations/3DnT/imageposition_byacquisition.xml configurations/3DnT/imageposition_bytriggertime.xml ) diff --git a/Modules/DICOMReader/include/mitkDICOMITKSeriesGDCMReader.h b/Modules/DICOMReader/include/mitkDICOMITKSeriesGDCMReader.h index 4732f8f129..38aeb15eeb 100644 --- a/Modules/DICOMReader/include/mitkDICOMITKSeriesGDCMReader.h +++ b/Modules/DICOMReader/include/mitkDICOMITKSeriesGDCMReader.h @@ -1,369 +1,381 @@ /*=================================================================== 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 #include "itkMutexLock.h" #include "mitkDICOMFileReader.h" #include "mitkDICOMDatasetSorter.h" #include "mitkDICOMGDCMImageFrameInfo.h" #include "mitkEquiDistantBlocksSorter.h" #include "mitkNormalDirectionConsistencySorter.h" #include "MitkDICOMReaderExports.h" namespace itk { class TimeProbesCollectorBase; } namespace mitk { /** \ingroup DICOMReaderModule \brief Flexible reader based on itk::ImageSeriesReader and GDCM, for single-slice modalities like CT, MR, PET, CR, etc. Implements the loading processed as structured by DICOMFileReader offers configuration of its loading strategy. Documentation sections: - \ref DICOMITKSeriesGDCMReader_LoadingStrategy - \ref DICOMITKSeriesGDCMReader_ForcedConfiguration - \ref DICOMITKSeriesGDCMReader_UserConfiguration - \ref DICOMITKSeriesGDCMReader_GantryTilt - \ref DICOMITKSeriesGDCMReader_Testing - \ref DICOMITKSeriesGDCMReader_Internals - \ref DICOMITKSeriesGDCMReader_RelatedClasses - \ref DICOMITKSeriesGDCMReader_TiltInternals - \ref DICOMITKSeriesGDCMReader_Condensing \section DICOMITKSeriesGDCMReader_LoadingStrategy Loading strategy The set of input files is processed by a number of DICOMDatasetSorter objects which may do two sort of things: 1. split a list of input frames into multiple lists, based on DICOM tags such as "Rows", "Columns", which cannot be mixed within a single mitk::Image 2. sort the frames within the input lists, based on the values of DICOM tags such as "Image Position Patient" When the DICOMITKSeriesGDCMReader is configured with DICOMDatasetSorter%s, the list of input files is processed as follows: 1. build an initial set of output groups, simply by grouping all input files. 2. for each configured DICOMDatasetSorter, process: - for each output group: 1. set this group's files as input to the sorter 2. let the sorter sort (and split) 3. integrate the sorter's output groups with our own output groups \section DICOMITKSeriesGDCMReader_ForcedConfiguration Forced Configuration In all cases, the reader will add two DICOMDatasetSorter objects that are required to load mitk::Images properly via itk::ImageSeriesReader: 1. As a \b first step, the input files will be split into groups that are not compatible because they differ in essential aspects: - (0028,0010) Number of Rows - (0028,0011) Number of Columns - (0028,0030) Pixel Spacing - (0018,1164) Imager Pixel Spacing - (0020,0037) %Image Orientation (Patient) - (0018,0050) Slice Thickness - (0028,0008) Number of Frames 2. As are two forced \b last steps: 1. There will always be an instance of EquiDistantBlocksSorter, which ensures that there is an equal distance between all the frames of an Image. This is required to achieve correct geometrical positions in the mitk::Image, i.e. it is essential to be able to make measurements in images. - whether or not the distance is required to be orthogonal to the image planes is configured by SetFixTiltByShearing(). - during this check, we need to tolerate some minor errors in documented vs. calculated image origins. The amount of tolerance can be adjusted by SetToleratedOriginOffset() and SetToleratedOriginOffsetToAdaptive(). Please see EquiDistantBlocksSorter for more details. The default should be good for most cases. 2. There is always an instance of NormalDirectionConsistencySorter, which makes the order of images go along the image normals (see NormalDirectionConsistencySorter) \section DICOMITKSeriesGDCMReader_UserConfiguration User Configuration The user of this class can add more sorting steps (similar to the one described in above section) by calling AddSortingElement(). Usually, an application will add sorting by "Image Position Patient", by "Instance Number", and by other relevant tags here. \section DICOMITKSeriesGDCMReader_GantryTilt Gantry tilt handling When CT gantry tilt is used, the gantry plane (= X-Ray source and detector ring) and the vertical plane do not align anymore. This scanner feature is used for example to reduce metal artifacs (e.g. Lee C , Evaluation of Using CT Gantry Tilt Scan on Head and Neck Cancer Patients with Dental Structure: Scans Show Less Metal Artifacts. Presented at: Radiological Society of North America 2011 Scientific Assembly and Annual Meeting; November 27- December 2, 2011 Chicago IL.). The acquired planes of such CT series do not match the expectations of a orthogonal geometry in mitk::Image: if you stack the slices, they show a small shift along the Y axis: \verbatim without tilt with tilt |||||| ////// |||||| ////// -- |||||| --------- ////// -------- table orientation |||||| ////// |||||| ////// Stacked slices: without tilt with tilt -------------- -------------- -------------- -------------- -------------- -------------- -------------- -------------- -------------- -------------- \endverbatim As such gemetries do not "work" in conjunction with mitk::Image, DICOMITKSeriesGDCMReader is able to perform a correction for such series. Whether or not such correction should be attempted is controlled by SetFixTiltByShearing(), the default being correction. For details, see "Internals" below. \section DICOMITKSeriesGDCMReader_Testing Testing A number of tests is implemented in module DICOMTesting, which is documented at \ref DICOMTesting. \section DICOMITKSeriesGDCMReader_Internals Class internals Internally, the class is based on GDCM and it depends heavily on the gdcm::Scanner class. Since the sorting elements (see DICOMDatasetSorter and DICOMSortCriterion) can access tags only via the DICOMDatasetAccess interface, BUT DICOMITKSeriesGDCMReader holds a list of more specific classes DICOMGDCMImageFrameInfo, we must convert between the two types sometimes. This explains the methods ToDICOMDatasetList(), FromDICOMDatasetList(). The intermediate result of all the sorting efforts is held in m_SortingResultInProgress, which is modified through InternalExecuteSortingStep(). \subsection DICOMITKSeriesGDCMReader_RelatedClasses Overview of related classes The following diagram gives an overview of the related classes: \image html implementeditkseriesgdcmreader.jpg \subsection DICOMITKSeriesGDCMReader_TiltInternals Details about the tilt correction The gantry tilt "correction" algorithm fixes two errors introduced by ITK's ImageSeriesReader: - the plane shift that is ignored by ITK's reader is recreated by applying a shearing transformation using itk::ResampleFilter. - the spacing is corrected (it is calculated by ITK's reader from the distance between two origins, which is NOT the slice distance in this special case) Both errors are introduced in itkImageSeriesReader.txx (ImageSeriesReader::GenerateOutputInformation(void)), lines 176 to 245 (as of ITK 3.20) For the correction, we examine two consecutive slices of a series, both described as a pair (origin/orientation): - we calculate if the first origin is on a line along the normal of the second slice - if this is not the case, the geometry will not fit a normal mitk::Image/mitk::Geometry3D - we then project the second origin into the first slice's coordinate system to quantify the shift - both is done in class GantryTiltInformation with quite some comments. The geometry of image stacks with tilted geometries is illustrated below: - green: the DICOM images as described by their tags: origin as a point with the line indicating the orientation - red: the output of ITK ImageSeriesReader: wrong, larger spacing, no tilt - blue: how much a shear must correct \image html tilt-correction.jpg \subsection DICOMITKSeriesGDCMReader_Condensing Sub-classes can condense multiple blocks into a single larger block The sorting/splitting process described above is helpful for at least two more DICOM readers, which either try to load 3D+t images or which load diffusion data. In both cases, a single pixel of the mitk::Image is made up of multiple values, in one case values over time, in the other case multiple measurements of a single point. The specialized readers for these cases (e.g. ThreeDnTDICOMSeriesReader) can reuse most of the methods in DICOMITKSeriesGDCMReader, except that they need an extra step after the usual sorting, in which they can merge already grouped 3D blocks. What blocks are merged depends on the specialized reader's understanding of these images. To allow for such merging, a method Condense3DBlocks() is called as an absolute last step of AnalyzeInputFiles(). Given this, a sub-class could implement only LoadImages() and Condense3DBlocks() instead repeating most of AnalyzeInputFiles(). */ class MITKDICOMREADER_EXPORT DICOMITKSeriesGDCMReader : public DICOMFileReader { public: mitkClassMacro( DICOMITKSeriesGDCMReader, DICOMFileReader ); mitkCloneMacro( DICOMITKSeriesGDCMReader ); itkFactorylessNewMacro( DICOMITKSeriesGDCMReader ); mitkNewMacro1Param( DICOMITKSeriesGDCMReader, unsigned int ); mitkNewMacro2Param( DICOMITKSeriesGDCMReader, unsigned int, bool ); /** \brief Runs the sorting / splitting process described in \ref DICOMITKSeriesGDCMReader_LoadingStrategy. Method required by DICOMFileReader. */ void AnalyzeInputFiles() override; // void AllocateOutputImages(); /** \brief Loads images using itk::ImageSeriesReader, potentially applies shearing to correct gantry tilt. */ bool LoadImages() override; // re-implemented from super-class bool CanHandleFile(const std::string& filename) override; /** \brief Add an element to the sorting procedure described in \ref DICOMITKSeriesGDCMReader_LoadingStrategy. */ virtual void AddSortingElement(DICOMDatasetSorter* sorter, bool atFront = false); typedef const std::list ConstSorterList; ConstSorterList GetFreelyConfiguredSortingElements() const; /** \brief Controls whether to "fix" tilted acquisitions by shearing the output (see \ref DICOMITKSeriesGDCMReader_GantryTilt). */ void SetFixTiltByShearing(bool on); bool GetFixTiltByShearing() const; /** \brief Controls whether groups of only two images are accepted when ensuring consecutive slices via EquiDistantBlocksSorter. */ void SetAcceptTwoSlicesGroups(bool accept) const; bool GetAcceptTwoSlicesGroups() const; /** \brief See \ref DICOMITKSeriesGDCMReader_ForcedConfiguration. */ void SetToleratedOriginOffsetToAdaptive(double fractionOfInterSliceDistanct = 0.3) const; /** \brief See \ref DICOMITKSeriesGDCMReader_ForcedConfiguration. */ void SetToleratedOriginOffset(double millimeters = 0.005) const; /** \brief Ignore all dicom tags that are non-essential for simple 3D volume import. */ void SetSimpleVolumeReading(bool read) { m_SimpleVolumeReading = read; }; /** \brief Ignore all dicom tags that are non-essential for simple 3D volume import. */ bool GetSimpleVolumeReading() { return m_SimpleVolumeReading; }; double GetToleratedOriginError() const; bool IsToleratedOriginOffsetAbsolute() const; double GetDecimalPlacesForOrientation() const; bool operator==(const DICOMFileReader& other) const override; DICOMTagPathList GetTagsOfInterest() const override; static int GetDefaultDecimalPlacesForOrientation() { return m_DefaultDecimalPlacesForOrientation; } + static bool GetDefaultSimpleVolumeImport() + { + return m_DefaultSimpleVolumeImport; + } + + static bool GetDefaultFixTiltByShearing() + { + return m_DefaultFixTiltByShearing; + } + protected: void InternalPrintConfiguration(std::ostream& os) const override; /// \brief Return active C locale static std::string GetActiveLocale(); /** \brief Remember current locale on stack, activate "C" locale. "C" locale is required for correct parsing of numbers by itk::ImageSeriesReader */ void PushLocale() const; /** \brief Activate last remembered locale from locale stack "C" locale is required for correct parsing of numbers by itk::ImageSeriesReader */ void PopLocale() const; const static int m_DefaultDecimalPlacesForOrientation = 5; + const static bool m_DefaultSimpleVolumeImport = false; + const static bool m_DefaultFixTiltByShearing = true; - DICOMITKSeriesGDCMReader(unsigned int decimalPlacesForOrientation = m_DefaultDecimalPlacesForOrientation, bool simpleVolumeImport = false); + DICOMITKSeriesGDCMReader(unsigned int decimalPlacesForOrientation = m_DefaultDecimalPlacesForOrientation, bool simpleVolumeImport = m_DefaultSimpleVolumeImport); ~DICOMITKSeriesGDCMReader() override; DICOMITKSeriesGDCMReader(const DICOMITKSeriesGDCMReader& other); DICOMITKSeriesGDCMReader& operator=(const DICOMITKSeriesGDCMReader& other); typedef std::vector SortingBlockList; /** \brief "Hook" for sub-classes, see \ref DICOMITKSeriesGDCMReader_Condensing \return REMAINING blocks */ virtual SortingBlockList Condense3DBlocks(SortingBlockList& resultOf3DGrouping); virtual DICOMTagCache::Pointer GetTagCache() const; void SetTagCache( const DICOMTagCache::Pointer& ) override; /// \brief Sorting step as described in \ref DICOMITKSeriesGDCMReader_LoadingStrategy static SortingBlockList InternalExecuteSortingStep( unsigned int sortingStepIndex, const DICOMDatasetSorter::Pointer& sorter, const SortingBlockList& input); /// \brief Loads the mitk::Image by means of an itk::ImageSeriesReader virtual bool LoadMitkImageForOutput(unsigned int o); virtual bool LoadMitkImageForImageBlockDescriptor(DICOMImageBlockDescriptor& block) const; /// \brief Describe this reader's confidence for given SOP class UID static ReaderImplementationLevel GetReaderImplementationLevel(const std::string sopClassUID); private: /// \brief Creates the required sorting steps described in \ref DICOMITKSeriesGDCMReader_ForcedConfiguration void EnsureMandatorySortersArePresent(unsigned int decimalPlacesForOrientation, bool simpleVolumeImport = false); 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! bool m_SimpleVolumeReading; private: SortingBlockList m_SortingResultInProgress; typedef std::list SorterList; SorterList m_Sorter; protected: // NOT nice, made available to ThreeDnTDICOMSeriesReader and ClassicDICOMSeriesReader due to lack of time mitk::EquiDistantBlocksSorter::Pointer m_EquiDistantBlocksSorter; mitk::NormalDirectionConsistencySorter::Pointer m_NormalDirectionConsistencySorter; private: static itk::MutexLock::Pointer s_LocaleMutex; mutable std::stack m_ReplacedCLocales; mutable std::stack m_ReplacedCinLocales; double m_DecimalPlacesForOrientation; DICOMTagCache::Pointer m_TagCache; bool m_ExternalCache; }; } #endif diff --git a/Modules/DICOMReader/include/mitkDICOMReaderConfigurator.h b/Modules/DICOMReader/include/mitkDICOMReaderConfigurator.h index 1eec866995..475cbcf4d7 100644 --- a/Modules/DICOMReader/include/mitkDICOMReaderConfigurator.h +++ b/Modules/DICOMReader/include/mitkDICOMReaderConfigurator.h @@ -1,143 +1,146 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef mitkDICOMReaderConfigurator_h #define mitkDICOMReaderConfigurator_h #include "mitkClassicDICOMSeriesReader.h" #include "mitkDICOMTagBasedSorter.h" // to put into private implementation #include "tinyxml.h" namespace mitk { /** \ingroup DICOMReaderModule \brief Too-simple factory to create DICOMFileReader%s. This class is able to instantiate and configure (where possible) DICOMFileReader%s from XML descriptions. \note This is a bad factory example, because the factory is not extensible and needs to know all the specific readers. A flexible implementation should be provided in a future version. In its current version, the XML input is meant to be structured like \verbatim \endverbatim The root-tag \i names the class to be instantiated, currently this can be one of - DICOMITKSeriesGDCMReader - ThreeDnTDICOMSeriesReader Both classes bring simple configuration flags with them and a description of how images are sorted prior to loading. Flag for DICOMITKSeriesGDCMReader:
fixTiltByShearing="true|false"
Determines whether a potential gantry tilt should be "fixed" by shearing the output image. Flag for ThreeDnTDICOMSeriesReader:
group3DnT="true|false"
Determines whether images at the same spatial position should be interpreted as 3D+t images. The tags and describe the basic loading strategy of both reader mentioned above: first images are divided into incompatible groups (), and afterwards the images within each group are sorted by means of DICOMSortCriterion, which most commonly mentions a tag. Tag element and group are interpreted as the exadecimal numbers found all around the DICOM standard. The numbers can be prepended by a "0x" if this is preferred by the programmer (but they are taken as hexadecimal in all cases). \section DICOMReaderConfigurator_AboutTheFuture About the future evolution of this class This first version is hard coded for the current state of the implementation. If things should evolve in a way that needs us to splitt off readers for "old" versions, time should be taken to refactor this class. Basically, a serializer class should accompany each of the configurable classes. Such serializer classes should be registered and discovered via micro-services (to support extensions). A serializer should offer both methods to serialize a class and to desirialize it again. A "version" attribute at the top-level tag should be used to distinguish versions. Usually it should be enough to keep DE-serializers for all versions. Writers for the most recent version should be enough. */ class MITKDICOMREADER_EXPORT DICOMReaderConfigurator : public itk::LightObject { public: mitkClassMacroItkParent( DICOMReaderConfigurator, itk::LightObject ) itkNewMacro( DICOMReaderConfigurator ) DICOMFileReader::Pointer CreateFromConfigFile(const std::string& filename) const; DICOMFileReader::Pointer CreateFromUTF8ConfigString(const std::string& xmlContents) const; std::string CreateConfigStringFromReader(DICOMFileReader::ConstPointer reader) const; protected: DICOMReaderConfigurator(); ~DICOMReaderConfigurator() override; private: DICOMFileReader::Pointer CreateFromTiXmlDocument(TiXmlDocument& doc) const; DICOMTag tagFromXMLElement(TiXmlElement*) const; std::string requiredStringAttribute(TiXmlElement* xmlElement, const std::string& key) const; unsigned int hexStringToUInt(const std::string& s) const; ThreeDnTDICOMSeriesReader::Pointer ConfigureThreeDnTDICOMSeriesReader(ThreeDnTDICOMSeriesReader::Pointer reader, TiXmlElement*) const; DICOMITKSeriesGDCMReader::Pointer ConfigureDICOMITKSeriesGDCMReader(DICOMITKSeriesGDCMReader::Pointer reader, TiXmlElement*) const; void ConfigureCommonPropertiesOfDICOMITKSeriesGDCMReader(DICOMITKSeriesGDCMReader::Pointer reader, TiXmlElement* element) const; void ConfigureCommonPropertiesOfThreeDnTDICOMSeriesReader(ThreeDnTDICOMSeriesReader::Pointer reader, TiXmlElement* element) const; DICOMSortCriterion::Pointer CreateDICOMSortByTag(TiXmlElement* xmlElement, DICOMSortCriterion::Pointer secondaryCriterion) const; DICOMSortCriterion::Pointer CreateSortByImagePositionPatient(TiXmlElement* xmlElement, DICOMSortCriterion::Pointer secondaryCriterion) const; mitk::DICOMTagBasedSorter::Pointer CreateDICOMTagBasedSorter(TiXmlElement* element) const; TiXmlElement* CreateConfigStringFromReader(const DICOMITKSeriesGDCMReader* reader) const; TiXmlElement* CreateConfigStringFromReader(const ThreeDnTDICOMSeriesReader* reader) const; TiXmlElement* CreateConfigStringFromReader(const ClassicDICOMSeriesReader* reader) const; TiXmlElement* CreateConfigStringFromDICOMDatasetSorter(const DICOMTagBasedSorter* sorter) const; TiXmlElement* CreateConfigStringFromDICOMTag(const DICOMTag& tag) const; TiXmlElement* CreateDICOMFileReaderTag(const DICOMFileReader* reader) const; const char* toString(bool) const; std::string toHexString(unsigned int i) const; + + /** Helper that queries an boolean xml attribute. If the attribute does not exist, the passed default value is used.*/ + bool QueryBooleanAttribute(const TiXmlElement* element, const char* attributeName, bool defaultValue) const; }; } // namespace #endif // mitkDICOMReaderConfigurator_h diff --git a/Modules/DICOMReader/include/mitkDICOMTagBasedSorter.h b/Modules/DICOMReader/include/mitkDICOMTagBasedSorter.h index d04fb5c608..5f516fe519 100644 --- a/Modules/DICOMReader/include/mitkDICOMTagBasedSorter.h +++ b/Modules/DICOMReader/include/mitkDICOMTagBasedSorter.h @@ -1,192 +1,205 @@ /*=================================================================== 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 MITKDICOMREADER_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 MITKDICOMREADER_EXPORT TagValueProcessor { public: /// \brief Implements the "processing". virtual std::string operator()(const std::string&) const = 0; virtual TagValueProcessor* Clone() const = 0; virtual ~TagValueProcessor() {} }; /** \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 MITKDICOMREADER_EXPORT CutDecimalPlaces : public TagValueProcessor { public: CutDecimalPlaces(unsigned int precision); CutDecimalPlaces(const CutDecimalPlaces& other); unsigned int GetPrecision() const; std::string operator()(const std::string&) const override; TagValueProcessor* Clone() const override; 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 = nullptr ); 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). */ DICOMTagList GetTagsOfInterest() override; /** \brief Whether or not groups should be checked for consecutive tag values. When this flag is set (default in constructor=off), the sorter will not only sort in a way that the values of a configured tag are ascending BUT in addition the sorter will enforce a constant numerical distance between values. Having this flag is useful for handling of series with missing slices, e.g. Instance Numbers 1 2 3 5 6 7 8. With the flag set to true, the sorter would split this group into two, because the initial distance of 1 is not kept between Instance Numbers 3 and 5. A special case of this behavior can be configured by SetExpectDistanceOne(). When this additional flag is set to true, the sorter will expect distance 1 exactly. This can help if the second slice is missing already. Without this additional flag, we would "learn" about a wrong distance of 2 (or similar) and then sort completely wrong. */ void SetStrictSorting(bool strict); bool GetStrictSorting() const; /** \brief Flag for a special case in "strict sorting". Please see documentation of SetStrictSorting(). \sa SetStrictSorting */ void SetExpectDistanceOne(bool strict); bool GetExpectDistanceOne() const; /** \brief Actually sort as described in the Detailed Description. */ void Sort() override; /** \brief Print configuration details into given stream. */ void PrintConfiguration(std::ostream& os, const std::string& indent = "") const override; bool operator==(const DICOMDatasetSorter& other) const override; + static bool GetDefaultStrictSorting() + { + return m_DefaultStrictSorting; + } + + static bool GetDefaultExpectDistanceOne() + { + return m_DefaultExpectDistanceOne; + } + 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(); ~DICOMTagBasedSorter() override; 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; bool m_ExpectDistanceOne; + + const static bool m_DefaultStrictSorting = false; + const static bool m_DefaultExpectDistanceOne = false; }; } #endif diff --git a/Modules/DICOMReader/include/mitkThreeDnTDICOMSeriesReader.h b/Modules/DICOMReader/include/mitkThreeDnTDICOMSeriesReader.h index 1ac6834713..94f7d2a57f 100644 --- a/Modules/DICOMReader/include/mitkThreeDnTDICOMSeriesReader.h +++ b/Modules/DICOMReader/include/mitkThreeDnTDICOMSeriesReader.h @@ -1,86 +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 mitkThreeDnTDICOMSeriesReader_h #define mitkThreeDnTDICOMSeriesReader_h #include "mitkDICOMITKSeriesGDCMReader.h" #include "MitkDICOMReaderExports.h" namespace mitk { /** \ingroup DICOMReader \brief Extends DICOMITKSeriesGDCMReader by sorting/grouping into 3D+t image blocks. This class reuses the DICOMITKSeriesGDCMReader class and adds the option of grouping 3D blocks at the same spatial position into a single block, which is loaded as a 3D+t mitk::Image (compare to \ref DICOMITKSeriesGDCMReader_Condensing). To group two output blocks into a single 3D+t block, this class tests a number of requirements that the two blocks must fulfill: - the origin of the first slice must be identical - the origin of the last slice must be identical - the number of slices must be identical The output blocks described by DICOMImageBlockDescriptor will contains the following properties: - \b "3D+t": true if the image is 3D+t - \b "timesteps": number of timesteps of an image (only defined if "3D+t" is true) */ class MITKDICOMREADER_EXPORT ThreeDnTDICOMSeriesReader : public DICOMITKSeriesGDCMReader { public: mitkClassMacro( ThreeDnTDICOMSeriesReader, DICOMITKSeriesGDCMReader ); mitkCloneMacro( ThreeDnTDICOMSeriesReader ); itkNewMacro( ThreeDnTDICOMSeriesReader ); mitkNewMacro1Param( ThreeDnTDICOMSeriesReader, unsigned int ); /// \brief Control whether 3D+t grouping shall actually be attempted. void SetGroup3DandT(bool on); bool GetGroup3DandT() const; // void AllocateOutputImages(); /// \brief Load via multiple calls to itk::ImageSeriesReader. bool LoadImages() override; bool operator==(const DICOMFileReader& other) const override; + static bool GetDefaultGroup3DandT() + { + return m_DefaultGroup3DandT; + } + protected: - ThreeDnTDICOMSeriesReader(unsigned int decimalPlacesForOrientation = 5); + ThreeDnTDICOMSeriesReader(unsigned int decimalPlacesForOrientation = Superclass::m_DefaultDecimalPlacesForOrientation); ~ThreeDnTDICOMSeriesReader() override; ThreeDnTDICOMSeriesReader(const ThreeDnTDICOMSeriesReader& other); ThreeDnTDICOMSeriesReader& operator=(const ThreeDnTDICOMSeriesReader& other); /** \brief Analyze the groups produced by DICOMITKSeriesGDCMReader for 3D+t properties. This method tests whether some blocks are at the same spatial position and groups them into single blocks. */ SortingBlockList Condense3DBlocks(SortingBlockList&) override; bool LoadMitkImageForImageBlockDescriptor(DICOMImageBlockDescriptor& block) const override; bool m_Group3DandT; + + const static bool m_DefaultGroup3DandT = true; }; } #endif diff --git a/Modules/DICOMReader/resource/configurations/3D/classicreader.xml b/Modules/DICOMReader/resource/configurations/3D/classicreader.xml index 2765df8827..7ed3332f6a 100644 --- a/Modules/DICOMReader/resource/configurations/3D/classicreader.xml +++ b/Modules/DICOMReader/resource/configurations/3D/classicreader.xml @@ -1,10 +1,10 @@ diff --git a/Modules/DICOMReader/resource/configurations/3D/imageposition.xml b/Modules/DICOMReader/resource/configurations/3D/imageposition.xml index 2f12235624..c00d4cd8e2 100644 --- a/Modules/DICOMReader/resource/configurations/3D/imageposition.xml +++ b/Modules/DICOMReader/resource/configurations/3D/imageposition.xml @@ -1,25 +1,25 @@ diff --git a/Modules/DICOMReader/resource/configurations/3D/imageposition_byacquisition.xml b/Modules/DICOMReader/resource/configurations/3D/imageposition_byacquisition.xml index 0ae9f9995e..54b9f6377a 100644 --- a/Modules/DICOMReader/resource/configurations/3D/imageposition_byacquisition.xml +++ b/Modules/DICOMReader/resource/configurations/3D/imageposition_byacquisition.xml @@ -1,26 +1,26 @@ diff --git a/Modules/DICOMReader/resource/configurations/3D/imagetime.xml b/Modules/DICOMReader/resource/configurations/3D/imagetime.xml index ded67081e6..83cdb1c1b0 100644 --- a/Modules/DICOMReader/resource/configurations/3D/imagetime.xml +++ b/Modules/DICOMReader/resource/configurations/3D/imagetime.xml @@ -1,24 +1,24 @@ - + diff --git a/Modules/DICOMReader/resource/configurations/3D/instancenumber.xml b/Modules/DICOMReader/resource/configurations/3D/instancenumber.xml index 4734b3bafb..f83b56e3c8 100644 --- a/Modules/DICOMReader/resource/configurations/3D/instancenumber.xml +++ b/Modules/DICOMReader/resource/configurations/3D/instancenumber.xml @@ -1,24 +1,24 @@ diff --git a/Modules/DICOMReader/resource/configurations/3D/instancenumber_soft.xml b/Modules/DICOMReader/resource/configurations/3D/instancenumber_soft.xml index 4e6d50f26f..1c2c660700 100644 --- a/Modules/DICOMReader/resource/configurations/3D/instancenumber_soft.xml +++ b/Modules/DICOMReader/resource/configurations/3D/instancenumber_soft.xml @@ -1,24 +1,24 @@ diff --git a/Modules/DICOMReader/resource/configurations/3D/simpleinstancenumber_soft.xml b/Modules/DICOMReader/resource/configurations/3D/simpleinstancenumber_soft.xml index 32c6396cfa..79c636e78b 100644 --- a/Modules/DICOMReader/resource/configurations/3D/simpleinstancenumber_soft.xml +++ b/Modules/DICOMReader/resource/configurations/3D/simpleinstancenumber_soft.xml @@ -1,25 +1,25 @@ diff --git a/Modules/DICOMReader/resource/configurations/3D/slicelocation.xml b/Modules/DICOMReader/resource/configurations/3D/slicelocation.xml index 3949171ad6..967bb5af57 100644 --- a/Modules/DICOMReader/resource/configurations/3D/slicelocation.xml +++ b/Modules/DICOMReader/resource/configurations/3D/slicelocation.xml @@ -1,24 +1,24 @@ diff --git a/Modules/DICOMReader/resource/configurations/3DnT/classicreader.xml b/Modules/DICOMReader/resource/configurations/3DnT/classicreader.xml index 0f382e3a0d..dfc9c4bbb4 100644 --- a/Modules/DICOMReader/resource/configurations/3DnT/classicreader.xml +++ b/Modules/DICOMReader/resource/configurations/3DnT/classicreader.xml @@ -1,21 +1,10 @@ - - - - - - - - - - - - - + fixTiltByShearing="true" + > +
\ No newline at end of file diff --git a/Modules/DICOMReader/resource/configurations/3DnT/classicreader.xml b/Modules/DICOMReader/resource/configurations/3DnT/imageposition.xml similarity index 76% copy from Modules/DICOMReader/resource/configurations/3DnT/classicreader.xml copy to Modules/DICOMReader/resource/configurations/3DnT/imageposition.xml index 0f382e3a0d..f8e1601952 100644 --- a/Modules/DICOMReader/resource/configurations/3DnT/classicreader.xml +++ b/Modules/DICOMReader/resource/configurations/3DnT/imageposition.xml @@ -1,21 +1,21 @@ - + diff --git a/Modules/DICOMReader/resource/configurations/3DnT/imageposition_byacquisition.xml b/Modules/DICOMReader/resource/configurations/3DnT/imageposition_byacquisition.xml index bff9b2cbc6..1a356abfb1 100644 --- a/Modules/DICOMReader/resource/configurations/3DnT/imageposition_byacquisition.xml +++ b/Modules/DICOMReader/resource/configurations/3DnT/imageposition_byacquisition.xml @@ -1,19 +1,19 @@ - + \ No newline at end of file diff --git a/Modules/DICOMReader/resource/configurations/3DnT/imageposition_bytriggertime.xml b/Modules/DICOMReader/resource/configurations/3DnT/imageposition_bytriggertime.xml index 0b052ceb8f..f8f636255c 100644 --- a/Modules/DICOMReader/resource/configurations/3DnT/imageposition_bytriggertime.xml +++ b/Modules/DICOMReader/resource/configurations/3DnT/imageposition_bytriggertime.xml @@ -1,19 +1,19 @@ - + \ No newline at end of file diff --git a/Modules/DICOMReader/src/mitkDICOMITKSeriesGDCMReader.cpp b/Modules/DICOMReader/src/mitkDICOMITKSeriesGDCMReader.cpp index 518207e729..857610edfc 100644 --- a/Modules/DICOMReader/src/mitkDICOMITKSeriesGDCMReader.cpp +++ b/Modules/DICOMReader/src/mitkDICOMITKSeriesGDCMReader.cpp @@ -1,622 +1,622 @@ /*=================================================================== 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 #include #include "mitkDICOMITKSeriesGDCMReader.h" #include "mitkITKDICOMSeriesReaderHelper.h" #include "mitkGantryTiltInformation.h" #include "mitkDICOMTagBasedSorter.h" #include "mitkDICOMGDCMTagScanner.h" itk::MutexLock::Pointer mitk::DICOMITKSeriesGDCMReader::s_LocaleMutex = itk::MutexLock::New(); mitk::DICOMITKSeriesGDCMReader::DICOMITKSeriesGDCMReader( unsigned int decimalPlacesForOrientation, bool simpleVolumeImport ) : DICOMFileReader() -, m_FixTiltByShearing( true ) +, m_FixTiltByShearing(m_DefaultFixTiltByShearing) , m_SimpleVolumeReading( simpleVolumeImport ) , m_DecimalPlacesForOrientation( decimalPlacesForOrientation ) , m_ExternalCache(false) { this->EnsureMandatorySortersArePresent( decimalPlacesForOrientation, simpleVolumeImport ); } mitk::DICOMITKSeriesGDCMReader::DICOMITKSeriesGDCMReader( const DICOMITKSeriesGDCMReader& other ) : DICOMFileReader( other ) -, m_FixTiltByShearing( false ) +, m_FixTiltByShearing( other.m_FixTiltByShearing) , 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 ) , m_ExternalCache(other.m_ExternalCache) { } 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 auto* 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; auto mySorterIter = this->m_Sorter.cbegin(); auto oSorterIter = otherSelf->m_Sorter.cbegin(); for ( ; mySorterIter != this->m_Sorter.cend() && oSorterIter != otherSelf->m_Sorter.cend(); ++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 ) { this->Modified(); m_FixTiltByShearing = on; } bool mitk::DICOMITKSeriesGDCMReader::GetFixTiltByShearing() const { return m_FixTiltByShearing; } void mitk::DICOMITKSeriesGDCMReader::SetAcceptTwoSlicesGroups( bool accept ) const { this->Modified(); m_EquiDistantBlocksSorter->SetAcceptTwoSlicesGroups( accept ); } bool mitk::DICOMITKSeriesGDCMReader::GetAcceptTwoSlicesGroups() const { return m_EquiDistantBlocksSorter->GetAcceptTwoSlicesGroups(); } void mitk::DICOMITKSeriesGDCMReader::InternalPrintConfiguration( std::ostream& os ) const { unsigned int sortIndex( 1 ); for ( auto sorterIter = m_Sorter.cbegin(); sorterIter != m_Sorter.cend(); ++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() { return setlocale( LC_NUMERIC, nullptr ); } void mitk::DICOMITKSeriesGDCMReader::PushLocale() const { s_LocaleMutex->Lock(); std::string currentCLocale = setlocale( LC_NUMERIC, nullptr ); 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 ); s_LocaleMutex->Unlock(); } void mitk::DICOMITKSeriesGDCMReader::PopLocale() const { s_LocaleMutex->Lock(); 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."; } s_LocaleMutex->Unlock(); } 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) const 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() || ( m_TagCache->GetMTime()GetMTime() && !m_ExternalCache )) { timeStart( "Tag scanning" ); DICOMGDCMTagScanner::Pointer filescanner = DICOMGDCMTagScanner::New(); filescanner->SetInputFiles( inputFilenames ); filescanner->AddTagPaths( this->GetTagsOfInterest() ); PushLocale(); filescanner->Scan(); PopLocale(); m_TagCache = filescanner->GetScanCache(); // keep alive and make accessible to sub-classes timeStop("Tag scanning"); } else { // ensure that the tag cache contains our required tags AND files and has scanned! } m_SortingResultInProgress.clear(); m_SortingResultInProgress.push_back(m_TagCache->GetFrameInfoList()); // sort and split blocks as configured timeStart( "Sorting frames" ); unsigned int sorterIndex = 0; for ( auto sorterIter = m_Sorter.cbegin(); sorterIter != m_Sorter.cend(); ++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() ); } if ( !m_SimpleVolumeReading ) { // 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 ( auto blockIter = m_SortingResultInProgress.cbegin(); blockIter != m_SortingResultInProgress.cend(); ++o, ++blockIter ) { const DICOMDatasetAccessingImageFrameList& gdcmFrameInfoList = *blockIter; assert( !gdcmFrameInfoList.empty() ); // reverse frames if necessary // update tilt information from absolute last sorting const DICOMDatasetList datasetList = ConvertToDICOMDatasetList( gdcmFrameInfoList ); m_NormalDirectionConsistencySorter->SetInput( datasetList ); m_NormalDirectionConsistencySorter->Sort(); const DICOMDatasetAccessingImageFrameList sortedGdcmInfoFrameList = ConvertToDICOMDatasetAccessingImageFrameList( m_NormalDirectionConsistencySorter->GetOutput( 0 ) ); const GantryTiltInformation& tiltInfo = m_NormalDirectionConsistencySorter->GetTiltInformation(); // set frame list for current block const DICOMImageFrameList frameList = ConvertToDICOMImageFrameList( 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.SetAdditionalTagsOfInterest( GetAdditionalTagsOfInterest() ); block.SetTagLookupTableToPropertyFunctor( GetTagLookupTableToPropertyFunctor() ); 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, const 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 ( auto blockIter = input.cbegin(); blockIter != input.cend(); ++groupIndex, ++blockIter ) { const DICOMDatasetAccessingImageFrameList& gdcmInfoFrameList = *blockIter; const DICOMDatasetList datasetList = ConvertToDICOMDatasetList( gdcmInfoFrameList ); #if defined( MBILOG_ENABLE_DEBUG ) MITK_DEBUG << "--------------------------------------------------------------------------------"; MITK_DEBUG << "DICOMITKSeriesGDCMReader: " << ss.str() << ", dataset group " << groupIndex << " (" << datasetList.size() << " datasets): "; for ( auto oi = datasetList.cbegin(); oi != datasetList.cend(); ++oi ) { MITK_DEBUG << " INPUT : " << ( *oi )->GetFilenameIfAvailable(); } #endif sorter->SetInput( datasetList ); sorter->Sort(); unsigned int numberOfResultingBlocks = sorter->GetNumberOfOutputs(); for ( unsigned int b = 0; b < numberOfResultingBlocks; ++b ) { const DICOMDatasetList blockResult = sorter->GetOutput( b ); for ( auto oi = blockResult.cbegin(); oi != blockResult.cend(); ++oi ) { MITK_DEBUG << " OUTPUT(" << b << ") :" << ( *oi )->GetFilenameIfAvailable(); } DICOMDatasetAccessingImageFrameList sortedGdcmInfoFrameList = ConvertToDICOMDatasetAccessingImageFrameList( blockResult ); nextStepSorting.push_back( sortedGdcmInfoFrameList ); } } return nextStepSorting; } mitk::ReaderImplementationLevel mitk::DICOMITKSeriesGDCMReader::GetReaderImplementationLevel( const std::string sopClassUID ) { 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; filenames.reserve( frames.size() ); for ( auto frameIter = frames.cbegin(); frameIter != frames.cend(); ++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 ( const 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 ); } this->Modified(); } mitk::DICOMITKSeriesGDCMReader::ConstSorterList mitk::DICOMITKSeriesGDCMReader::GetFreelyConfiguredSortingElements() const { std::list result; unsigned int sortIndex( 0 ); for ( auto 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, bool simpleVolumeImport ) { 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 if ( !simpleVolumeImport ) { std::cout << "Simple volume reading: ignoring number of frames" << std::endl; 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 ) const { assert( m_EquiDistantBlocksSorter.IsNotNull() ); m_EquiDistantBlocksSorter->SetToleratedOriginOffsetToAdaptive( fractionOfInterSliceDistance ); this->Modified(); } void mitk::DICOMITKSeriesGDCMReader::SetToleratedOriginOffset( double millimeters ) const { assert( m_EquiDistantBlocksSorter.IsNotNull() ); m_EquiDistantBlocksSorter->SetToleratedOriginOffset( millimeters ); this->Modified(); } 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( const DICOMTagCache::Pointer& tagCache ) { m_TagCache = tagCache; m_ExternalCache = tagCache.IsNotNull(); } mitk::DICOMTagPathList mitk::DICOMITKSeriesGDCMReader::GetTagsOfInterest() const { DICOMTagPathList completeList; // check all configured sorters for ( auto sorterIter = m_Sorter.cbegin(); sorterIter != m_Sorter.cend(); ++sorterIter ) { assert( sorterIter->IsNotNull() ); const DICOMTagList tags = ( *sorterIter )->GetTagsOfInterest(); completeList.insert( completeList.end(), tags.cbegin(), tags.cend() ); } // check our own forced sorters DICOMTagList tags = m_EquiDistantBlocksSorter->GetTagsOfInterest(); completeList.insert( completeList.end(), tags.cbegin(), tags.cend() ); tags = m_NormalDirectionConsistencySorter->GetTagsOfInterest(); completeList.insert( completeList.end(), tags.cbegin(), tags.cend() ); // add the tags for DICOMImageBlockDescriptor tags = DICOMImageBlockDescriptor::GetTagsOfInterest(); completeList.insert( completeList.end(), tags.cbegin(), tags.cend() ); const AdditionalTagsMapType tagList = GetAdditionalTagsOfInterest(); for ( auto iter = tagList.cbegin(); iter != tagList.cend(); ++iter ) { completeList.push_back( iter->first ) ; } return completeList; } diff --git a/Modules/DICOMReader/src/mitkDICOMReaderConfigurator.cpp b/Modules/DICOMReader/src/mitkDICOMReaderConfigurator.cpp index 9190c0ad8f..b2ccb80959 100644 --- a/Modules/DICOMReader/src/mitkDICOMReaderConfigurator.cpp +++ b/Modules/DICOMReader/src/mitkDICOMReaderConfigurator.cpp @@ -1,693 +1,677 @@ /*=================================================================== 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 "mitkDICOMSortByTag.h" #include "mitkSortByImagePositionPatient.h" + mitk::DICOMReaderConfigurator ::DICOMReaderConfigurator() { } mitk::DICOMReaderConfigurator ::~DICOMReaderConfigurator() { } mitk::DICOMFileReader::Pointer mitk::DICOMReaderConfigurator ::CreateFromConfigFile(const std::string& filename) const { TiXmlDocument doc (filename); if (doc.LoadFile()) { return this->CreateFromTiXmlDocument( doc ); } else { MITK_ERROR << "Unable to load file at '" << filename <<"'"; return DICOMFileReader::Pointer(); } } mitk::DICOMFileReader::Pointer mitk::DICOMReaderConfigurator ::CreateFromUTF8ConfigString(const std::string& xmlContents) const { TiXmlDocument doc; doc.Parse(xmlContents.c_str(), nullptr, TIXML_ENCODING_UTF8); return this->CreateFromTiXmlDocument( doc ); } mitk::DICOMFileReader::Pointer mitk::DICOMReaderConfigurator ::CreateFromTiXmlDocument(TiXmlDocument& doc) const { TiXmlHandle root(doc.RootElement()); if (TiXmlElement* rootElement = root.ToElement()) { if (strcmp(rootElement->Value(), "DICOMFileReader")) // :-( no std::string methods { MITK_ERROR << "File should contain a tag at top-level! Found '" << (rootElement->Value() ? std::string(rootElement->Value()) : std::string("!nothing!")) << "' instead"; return nullptr; } const char* classnameC = rootElement->Attribute("class"); if (!classnameC) { MITK_ERROR << "File should name a reader class in the class attribute: . Found nothing instead"; return nullptr; } int version(1); if ( rootElement->QueryIntAttribute("version", &version) == TIXML_SUCCESS) { - if (version != 1) + if (version == 1) + { + MITK_WARN << "Warning the given configuration is for DICOMFileReaders of version 1. " + << "This old version may be interpreted differently. Reason: " + << "The default values for the following xml settings have been changed: " + << "FixTiltByShearing (false -> true); StrictSorting (true -> false); ExpectDistanceOne (true -> false)."; + } + else if (version >2) { - MITK_WARN << "This reader is only capable of creating DICOMFileReaders of version 1. " - << "Will not continue, because given configuration is meant for version " << version << "."; + MITK_WARN << "This reader is only capable of creating DICOMFileReaders of version 1 and 2. " + << "Will not continue, because given configuration is meant for version " << version << "."; return nullptr; } } else { MITK_ERROR << "File should name the version of the reader class in the version attribute: ." << " Found nothing instead, assuming version 1!"; version = 1; } std::string classname(classnameC); - double decimalPlacesForOrientation(5); + double decimalPlacesForOrientation(mitk::DICOMITKSeriesGDCMReader::GetDefaultDecimalPlacesForOrientation()); bool useDecimalPlacesForOrientation(false); useDecimalPlacesForOrientation = rootElement->QueryDoubleAttribute("decimalPlacesForOrientation", &decimalPlacesForOrientation) == TIXML_SUCCESS; // attribute present and a double value if (classname == "ClassicDICOMSeriesReader") { mitk::ClassicDICOMSeriesReader::Pointer reader = mitk::ClassicDICOMSeriesReader::New(); this->ConfigureCommonPropertiesOfDICOMITKSeriesGDCMReader(reader.GetPointer(), rootElement); this->ConfigureCommonPropertiesOfThreeDnTDICOMSeriesReader(reader.GetPointer(), rootElement); return reader.GetPointer(); } if (classname == "ThreeDnTDICOMSeriesReader") { mitk::ThreeDnTDICOMSeriesReader::Pointer reader; if (useDecimalPlacesForOrientation) reader = mitk::ThreeDnTDICOMSeriesReader::New(decimalPlacesForOrientation); else reader = mitk::ThreeDnTDICOMSeriesReader::New(); return ConfigureThreeDnTDICOMSeriesReader(reader, rootElement).GetPointer(); } else if (classname == "DICOMITKSeriesGDCMReader") { - const char* simpleVolumeImportC = rootElement->Attribute("simpleVolumeImport"); - bool simpleVolumeImport = simpleVolumeImportC ? true : false; + bool simpleVolumeImport = QueryBooleanAttribute(rootElement, "simpleVolumeImport", mitk::DICOMITKSeriesGDCMReader::GetDefaultSimpleVolumeImport()); mitk::DICOMITKSeriesGDCMReader::Pointer reader; if (useDecimalPlacesForOrientation) reader = mitk::DICOMITKSeriesGDCMReader::New( decimalPlacesForOrientation, simpleVolumeImport ); else reader = mitk::DICOMITKSeriesGDCMReader::New( mitk::DICOMITKSeriesGDCMReader::GetDefaultDecimalPlacesForOrientation(), simpleVolumeImport ); // simple volume import that ignores number of frames and inter slice distance return ConfigureDICOMITKSeriesGDCMReader(reader, rootElement).GetPointer(); } else { MITK_ERROR << "DICOMFileReader tag names unknown class '" << classname << "'"; return nullptr; } } else { MITK_ERROR << "Great confusion: no root element in XML document. Expecting a DICOMFileReader tag at top-level."; return nullptr; } } #define boolStringTrue(s) \ ( s == "true" || s == "on" || s == "1" \ || s == "TRUE" || s == "ON") +bool +mitk::DICOMReaderConfigurator +::QueryBooleanAttribute(const TiXmlElement* element, const char* attributeName, bool defaultValue) const +{ + bool value(defaultValue); + const char* valueC = element->Attribute(attributeName); + if (valueC) + { + std::string valueS(valueC); + value = boolStringTrue(valueS); + } + return value; +} + void mitk::DICOMReaderConfigurator ::ConfigureCommonPropertiesOfThreeDnTDICOMSeriesReader(ThreeDnTDICOMSeriesReader::Pointer reader, TiXmlElement* element) const { // add the "group3DnT" flag - bool group3DnT(true); - const char* group3DnTC = element->Attribute("group3DnT"); - if (group3DnTC) - { - std::string group3DnTS(group3DnTC); - group3DnT = boolStringTrue(group3DnTS); - } + bool group3DnT = QueryBooleanAttribute(element, "group3DnT", ThreeDnTDICOMSeriesReader::GetDefaultGroup3DandT()); reader->SetGroup3DandT( group3DnT ); } mitk::ThreeDnTDICOMSeriesReader::Pointer mitk::DICOMReaderConfigurator ::ConfigureThreeDnTDICOMSeriesReader(ThreeDnTDICOMSeriesReader::Pointer reader, TiXmlElement* element) const { assert(element); // use all the base class configuration if (this->ConfigureDICOMITKSeriesGDCMReader( reader.GetPointer(), element ).IsNull()) { return nullptr; } this->ConfigureCommonPropertiesOfThreeDnTDICOMSeriesReader(reader,element); return reader; } void mitk::DICOMReaderConfigurator ::ConfigureCommonPropertiesOfDICOMITKSeriesGDCMReader(DICOMITKSeriesGDCMReader::Pointer reader, TiXmlElement* element) const { assert(element); const char* configLabelC = element->Attribute("label"); if (configLabelC) { std::string configLabel(configLabelC); reader->SetConfigurationLabel(configLabel); } const char* configDescriptionC = element->Attribute("description"); if (configDescriptionC) { reader->SetConfigurationDescription(configDescriptionC); } // "fixTiltByShearing" flag - bool fixTiltByShearing(false); - const char* fixTiltByShearingC = element->Attribute("fixTiltByShearing"); - if (fixTiltByShearingC) - { - std::string fixTiltByShearingS(fixTiltByShearingC); - fixTiltByShearing = boolStringTrue(fixTiltByShearingS); - } + bool fixTiltByShearing = QueryBooleanAttribute(element, "fixTiltByShearing", DICOMITKSeriesGDCMReader::GetDefaultFixTiltByShearing()); reader->SetFixTiltByShearing( fixTiltByShearing ); - } mitk::DICOMITKSeriesGDCMReader::Pointer mitk::DICOMReaderConfigurator ::ConfigureDICOMITKSeriesGDCMReader(DICOMITKSeriesGDCMReader::Pointer reader, TiXmlElement* element) const { assert(element); this->ConfigureCommonPropertiesOfDICOMITKSeriesGDCMReader(reader, element); // "acceptTwoSlicesGroups" flag - bool acceptTwoSlicesGroups(true); - const char* acceptTwoSlicesGroupsC = element->Attribute("acceptTwoSlicesGroups"); - if (acceptTwoSlicesGroupsC) - { - std::string acceptTwoSlicesGroupsS(acceptTwoSlicesGroupsC); - acceptTwoSlicesGroups = boolStringTrue(acceptTwoSlicesGroupsS); - } + bool acceptTwoSlicesGroups = QueryBooleanAttribute(element, "acceptTwoSlicesGroups", true); reader->SetAcceptTwoSlicesGroups( acceptTwoSlicesGroups ); // "toleratedOriginError" attribute (double) - bool toleratedOriginErrorIsAbsolute(false); - const char* toleratedOriginErrorIsAbsoluteC = element->Attribute("toleratedOriginErrorIsAbsolute"); - if (toleratedOriginErrorIsAbsoluteC) - { - std::string toleratedOriginErrorIsAbsoluteS(toleratedOriginErrorIsAbsoluteC); - toleratedOriginErrorIsAbsolute = boolStringTrue(toleratedOriginErrorIsAbsoluteS); - } + bool toleratedOriginErrorIsAbsolute = QueryBooleanAttribute(element, "toleratedOriginErrorIsAbsolute", false); double toleratedOriginError(0.3); if (element->QueryDoubleAttribute("toleratedOriginError", &toleratedOriginError) == TIXML_SUCCESS) // attribute present and a double value { if (toleratedOriginErrorIsAbsolute) { reader->SetToleratedOriginOffset( toleratedOriginError ); } else { reader->SetToleratedOriginOffsetToAdaptive( toleratedOriginError ); } } // DICOMTagBasedSorters are the only thing we create at this point // TODO for-loop over all child elements of type DICOMTagBasedSorter, BUT actually a single sorter of this type is enough. TiXmlElement* dElement = element->FirstChildElement("DICOMDatasetSorter"); if (dElement) { const char* classnameC = dElement->Attribute("class"); if (!classnameC) { MITK_ERROR << "File should name a DICOMDatasetSorter class in the class attribute of . Found nothing instead"; return nullptr; } std::string classname(classnameC); if (classname == "DICOMTagBasedSorter") { DICOMTagBasedSorter::Pointer tagSorter = CreateDICOMTagBasedSorter(dElement); if (tagSorter.IsNotNull()) { reader->AddSortingElement( tagSorter ); } } else { MITK_ERROR << "DICOMDatasetSorter tag names unknown class '" << classname << "'"; return nullptr; } } return reader; } mitk::DICOMTagBasedSorter::Pointer mitk::DICOMReaderConfigurator ::CreateDICOMTagBasedSorter(TiXmlElement* element) const { mitk::DICOMTagBasedSorter::Pointer tagSorter = mitk::DICOMTagBasedSorter::New(); // "strictSorting" parameter! - bool strictSorting(true); - const char* strictSortingC = element->Attribute("strictSorting"); - if (strictSortingC) - { - std::string strictSortingS(strictSortingC); - strictSorting = boolStringTrue(strictSortingS); - } + bool strictSorting = QueryBooleanAttribute(element, "strictSorting", mitk::DICOMTagBasedSorter::GetDefaultStrictSorting()); tagSorter->SetStrictSorting(strictSorting); // "strictSorting" parameter! - bool expectDistanceOne(true); - const char* expectDistanceOneC = element->Attribute("expectDistanceOne"); - if (expectDistanceOneC) - { - std::string expectDistanceOneS(expectDistanceOneC); - expectDistanceOne = boolStringTrue(expectDistanceOneS); - } + bool expectDistanceOne = QueryBooleanAttribute(element, "expectDistanceOne", mitk::DICOMTagBasedSorter::GetDefaultExpectDistanceOne()); tagSorter->SetExpectDistanceOne(expectDistanceOne); TiXmlElement* dElement = element->FirstChildElement("Distinguishing"); if (dElement) { for ( TiXmlElement* tChild = dElement->FirstChildElement(); tChild != nullptr; tChild = tChild->NextSiblingElement() ) { try { mitk::DICOMTag tag = tagFromXMLElement(tChild); int i(5); if (tChild->QueryIntAttribute("cutDecimalPlaces", &i) == TIXML_SUCCESS) { tagSorter->AddDistinguishingTag( tag, new mitk::DICOMTagBasedSorter::CutDecimalPlaces(i) ); } else { tagSorter->AddDistinguishingTag( tag ); } } catch(...) { return nullptr; } } } // "sorting tags" TiXmlElement* sElement = element->FirstChildElement("Sorting"); if (sElement) { DICOMSortCriterion::Pointer previousCriterion; DICOMSortCriterion::Pointer currentCriterion; for ( TiXmlNode* tChildNode = sElement->LastChild(); tChildNode != nullptr; 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 nullptr; } } 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 nullptr; } } else { MITK_ERROR << "File contain unknown tag <" << tChild->Value() << "> tag as child to ! Cannot interpret..."; } previousCriterion = currentCriterion; } tagSorter->SetSortCriterion( currentCriterion.GetPointer() ); } return tagSorter; } std::string mitk::DICOMReaderConfigurator ::requiredStringAttribute(TiXmlElement* xmlElement, const std::string& key) const { assert(xmlElement); const char* gC = xmlElement->Attribute(key.c_str()); if (gC) { std::string gS(gC); return gS; } else { std::stringstream ss; ss << "Expected an attribute '" << key << "' at this position " "(input line " << xmlElement->Row() << ", col. " << xmlElement->Column() << ")!"; MITK_ERROR << ss.str(); throw std::invalid_argument( ss.str() ); } } unsigned int mitk::DICOMReaderConfigurator ::hexStringToUInt(const std::string& s) const { std::stringstream converter(s); unsigned int ui; converter >> std::hex >> ui; MITK_DEBUG << "Converted string '" << s << "' to unsigned int " << ui; return ui; } mitk::DICOMTag mitk::DICOMReaderConfigurator ::tagFromXMLElement(TiXmlElement* xmlElement) const { assert(xmlElement); if (strcmp(xmlElement->Value(), "Tag")) // :-( no std::string methods { std::stringstream ss; ss << "Expected a tag at this position " "(input line " << xmlElement->Row() << ", col. " << xmlElement->Column() << ")!"; MITK_ERROR << ss.str(); throw std::invalid_argument( ss.str() ); } std::string groupS = requiredStringAttribute(xmlElement, "group"); std::string elementS = requiredStringAttribute(xmlElement, "element"); try { // convert string to int (assuming string is in hex format with leading "0x" like "0x0020") unsigned int group = hexStringToUInt(groupS); unsigned int element = hexStringToUInt(elementS); return DICOMTag(group, element); } catch(...) { std::stringstream ss; ss << "Expected group and element values in to be hexadecimal with leading 0x, e.g. '0x0020'" "(input line " << xmlElement->Row() << ", col. " << xmlElement->Column() << ")!"; MITK_ERROR << ss.str(); throw std::invalid_argument( ss.str() ); } } mitk::DICOMSortCriterion::Pointer mitk::DICOMReaderConfigurator ::CreateDICOMSortByTag(TiXmlElement* xmlElement, DICOMSortCriterion::Pointer secondaryCriterion) const { mitk::DICOMTag tag = tagFromXMLElement(xmlElement); return DICOMSortByTag::New(tag, secondaryCriterion).GetPointer(); } mitk::DICOMSortCriterion::Pointer mitk::DICOMReaderConfigurator ::CreateSortByImagePositionPatient(TiXmlElement*, DICOMSortCriterion::Pointer secondaryCriterion) const { return SortByImagePositionPatient::New(secondaryCriterion).GetPointer(); } std::string mitk::DICOMReaderConfigurator ::CreateConfigStringFromReader(DICOMFileReader::ConstPointer reader) const { // check possible sub-classes from the most-specific one up to the most generic one const DICOMFileReader* cPointer = reader; TiXmlElement* root; if (const auto* specificReader = dynamic_cast(cPointer)) { root = this->CreateConfigStringFromReader(specificReader); } else if (const auto* specificReader = dynamic_cast(cPointer)) { root = this->CreateConfigStringFromReader(specificReader); } else if (const auto* specificReader = dynamic_cast(cPointer)) { root = this->CreateConfigStringFromReader(specificReader); } else { MITK_WARN << "Unknown reader class passed to DICOMReaderConfigurator::CreateConfigStringFromReader(). Cannot serialize."; return ""; // no serialization, what a pity } if (root) { TiXmlDocument document; document.LinkEndChild( root ); TiXmlPrinter printer; printer.SetIndent( " " ); document.Accept( &printer ); std::string xmltext = printer.CStr(); return xmltext; } else { MITK_WARN << "DICOMReaderConfigurator::CreateConfigStringFromReader() created empty serialization. Problem?"; return ""; } } TiXmlElement* mitk::DICOMReaderConfigurator ::CreateConfigStringFromReader(const DICOMITKSeriesGDCMReader* reader) const { TiXmlElement* root = this->CreateDICOMFileReaderTag(reader); assert(root); root->SetAttribute("fixTiltByShearing", toString(reader->GetFixTiltByShearing())); root->SetAttribute("acceptTwoSlicesGroups", toString(reader->GetAcceptTwoSlicesGroups())); root->SetDoubleAttribute("toleratedOriginError", reader->GetToleratedOriginError()); root->SetAttribute("toleratedOriginErrorIsAbsolute", toString(reader->IsToleratedOriginOffsetAbsolute())); root->SetDoubleAttribute("decimalPlacesForOrientation", reader->GetDecimalPlacesForOrientation()); // iterate DICOMDatasetSorter objects DICOMITKSeriesGDCMReader::ConstSorterList sorterList = reader->GetFreelyConfiguredSortingElements(); for(auto sorterIter = sorterList.begin(); sorterIter != sorterList.end(); ++sorterIter) { const DICOMDatasetSorter* sorter = *sorterIter; if (const auto* specificSorter = dynamic_cast(sorter)) { TiXmlElement* sorterTag = this->CreateConfigStringFromDICOMDatasetSorter(specificSorter); root->LinkEndChild(sorterTag); } else { MITK_WARN << "Unknown DICOMDatasetSorter class passed to DICOMReaderConfigurator::CreateConfigStringFromReader(). Cannot serialize."; return nullptr; } } return root; } TiXmlElement* mitk::DICOMReaderConfigurator ::CreateConfigStringFromDICOMDatasetSorter(const DICOMTagBasedSorter* sorter) const { assert(sorter); auto sorterTag = new TiXmlElement("DICOMDatasetSorter"); sorterTag->SetAttribute("class", sorter->GetNameOfClass()); sorterTag->SetAttribute("strictSorting", toString(sorter->GetStrictSorting())); sorterTag->SetAttribute("expectDistanceOne", toString(sorter->GetExpectDistanceOne())); auto distinguishingTagsElement = new TiXmlElement("Distinguishing"); sorterTag->LinkEndChild(distinguishingTagsElement); mitk::DICOMTagList distinguishingTags = sorter->GetDistinguishingTags(); for (auto tagIter = distinguishingTags.begin(); tagIter != distinguishingTags.end(); ++tagIter) { TiXmlElement* tag = this->CreateConfigStringFromDICOMTag(*tagIter); distinguishingTagsElement->LinkEndChild(tag); const DICOMTagBasedSorter::TagValueProcessor* processor = sorter->GetTagValueProcessorForDistinguishingTag(*tagIter); if (const auto* specificProcessor = dynamic_cast(processor)) { tag->SetDoubleAttribute("cutDecimalPlaces", specificProcessor->GetPrecision()); } } auto sortingElement = new TiXmlElement("Sorting"); sorterTag->LinkEndChild(sortingElement); mitk::DICOMSortCriterion::ConstPointer sortCriterion = sorter->GetSortCriterion(); while (sortCriterion.IsNotNull()) { std::string classname = sortCriterion->GetNameOfClass(); if (classname == "SortByImagePositionPatient") { sortingElement->LinkEndChild( new TiXmlElement("ImagePositionPatient") ); // no parameters } else if (classname == "DICOMSortByTag") { DICOMTagList pseudoTagList = sortCriterion->GetTagsOfInterest(); if (pseudoTagList.size() == 1) { DICOMTag firstTag = pseudoTagList.front(); TiXmlElement* tagElement = this->CreateConfigStringFromDICOMTag(firstTag); sortingElement->LinkEndChild( tagElement ); } else { MITK_ERROR << "Encountered SortByTag class with MULTIPLE tag in CreateConfigStringFromDICOMDatasetSorter. Cannot serialize."; return nullptr; } } else { MITK_ERROR << "Encountered unknown class '" << classname << "' in CreateConfigStringFromDICOMDatasetSorter. Cannot serialize."; return nullptr; } sortCriterion = sortCriterion->GetSecondaryCriterion(); } return sorterTag; } TiXmlElement* mitk::DICOMReaderConfigurator ::CreateConfigStringFromDICOMTag(const DICOMTag& tag) const { auto tagElement = new TiXmlElement("Tag"); // name group element tagElement->SetAttribute("name", tag.GetName().c_str()); tagElement->SetAttribute("group", toHexString(tag.GetGroup())); tagElement->SetAttribute("element", toHexString(tag.GetElement())); return tagElement; } std::string mitk::DICOMReaderConfigurator ::toHexString(unsigned int i) const { std::stringstream ss; ss << "0x" << std::setfill ('0') << std::setw(4) << std::hex << i; return ss.str(); } TiXmlElement* mitk::DICOMReaderConfigurator ::CreateConfigStringFromReader(const ThreeDnTDICOMSeriesReader* reader) const { TiXmlElement* root = this->CreateConfigStringFromReader(static_cast(reader)); assert(root); root->SetAttribute("group3DnT", toString(reader->GetGroup3DandT())); return root; } const char* mitk::DICOMReaderConfigurator ::toString(bool b) const { return b ? "true" : "false"; } TiXmlElement* mitk::DICOMReaderConfigurator ::CreateConfigStringFromReader(const ClassicDICOMSeriesReader* reader) const { return this->CreateDICOMFileReaderTag(reader); } TiXmlElement* mitk::DICOMReaderConfigurator ::CreateDICOMFileReaderTag(const DICOMFileReader* reader) const { auto readerTag = new TiXmlElement("DICOMFileReader"); readerTag->SetAttribute("class", reader->GetNameOfClass()); readerTag->SetAttribute("label", reader->GetConfigurationLabel().c_str()); readerTag->SetAttribute("description", reader->GetConfigurationDescription().c_str()); readerTag->SetAttribute("version", "1"); return readerTag; } diff --git a/Modules/DICOMReader/src/mitkDICOMTagBasedSorter.cpp b/Modules/DICOMReader/src/mitkDICOMTagBasedSorter.cpp index 7cacc334e3..08512c2a75 100644 --- a/Modules/DICOMReader/src/mitkDICOMTagBasedSorter.cpp +++ b/Modules/DICOMReader/src/mitkDICOMTagBasedSorter.cpp @@ -1,596 +1,596 @@ /*=================================================================== 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 std::ostringstream resultString; resultString.str(std::string()); resultString.clear(); resultString.setf(std::ios::fixed, std::ios::floatfield); resultString.precision(m_Precision); std::stringstream ss(input); ss.str(input); ss.clear(); std::string item; double number(0); std::istringstream converter(item); while (std::getline(ss, item, '\\')) { converter.str(item); converter.clear(); 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(false) -,m_ExpectDistanceOne(false) +,m_StrictSorting(m_DefaultStrictSorting) +,m_ExpectDistanceOne(m_DefaultExpectDistanceOne) { } mitk::DICOMTagBasedSorter ::~DICOMTagBasedSorter() { for(auto ti = m_TagValueProcessor.cbegin(); ti != m_TagValueProcessor.cend(); ++ti) { delete ti->second; } } mitk::DICOMTagBasedSorter ::DICOMTagBasedSorter(const DICOMTagBasedSorter& other ) :DICOMDatasetSorter(other) ,m_DistinguishingTags( other.m_DistinguishingTags ) ,m_SortCriterion( other.m_SortCriterion ) ,m_StrictSorting( other.m_StrictSorting ) ,m_ExpectDistanceOne( other.m_ExpectDistanceOne ) { for(auto ti = other.m_TagValueProcessor.cbegin(); ti != other.m_TagValueProcessor.cend(); ++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; m_ExpectDistanceOne = other.m_ExpectDistanceOne; for(auto ti = other.m_TagValueProcessor.cbegin(); ti != other.m_TagValueProcessor.cend(); ++ti) { m_TagValueProcessor[ti->first] = ti->second->Clone(); } } return *this; } bool mitk::DICOMTagBasedSorter ::operator==(const DICOMDatasetSorter& other) const { if (const auto* otherSelf = dynamic_cast(&other)) { if (this->m_StrictSorting != otherSelf->m_StrictSorting) return false; if (this->m_ExpectDistanceOne != otherSelf->m_ExpectDistanceOne) return false; bool allTagsPresentAndEqual(true); if (this->m_DistinguishingTags.size() != otherSelf->m_DistinguishingTags.size()) return false; for (auto myTag = this->m_DistinguishingTags.cbegin(); myTag != this->m_DistinguishingTags.cend(); ++myTag) { allTagsPresentAndEqual &= (std::find( otherSelf->m_DistinguishingTags.cbegin(), otherSelf->m_DistinguishingTags.cend(), *myTag ) != otherSelf->m_DistinguishingTags.cend()); // 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 " << "(strict=" << (m_StrictSorting?"true":"false") << ", expectDistanceOne=" << (m_ExpectDistanceOne?"true":"false") << "):" << std::endl; for (auto 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; } void mitk::DICOMTagBasedSorter ::SetExpectDistanceOne(bool strict) { m_ExpectDistanceOne = strict; } bool mitk::DICOMTagBasedSorter ::GetExpectDistanceOne() const { return m_ExpectDistanceOne; } mitk::DICOMTagList mitk::DICOMTagBasedSorter ::GetTagsOfInterest() { DICOMTagList allTags = m_DistinguishingTags; const DICOMTagList sortingRelevantTags = m_SortCriterion->GetAllTagsOfInterest(); allTags.insert( allTags.end(), sortingRelevantTags.cbegin(), sortingRelevantTags.cend() ); // append return allTags; } mitk::DICOMTagList mitk::DICOMTagBasedSorter ::GetDistinguishingTags() const { return m_DistinguishingTags; } const mitk::DICOMTagBasedSorter::TagValueProcessor* mitk::DICOMTagBasedSorter ::GetTagValueProcessorForDistinguishingTag(const DICOMTag& tag) const { auto loc = m_TagValueProcessor.find(tag); if (loc != m_TagValueProcessor.cend()) { return loc->second; } else { return nullptr; } } 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 (auto groupIter = sortedGroups.cbegin(); groupIter != sortedGroups.cend(); ++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 (auto tagIter = m_DistinguishingTags.cbegin(); tagIter != m_DistinguishingTags.cend(); ++tagIter) { groupID << tagIter->GetGroup() << tagIter->GetElement(); // make group/element part of the id to cover empty tags DICOMDatasetFinding rawTagValue = dataset->GetTagValueAsString(*tagIter); std::string processedTagValue; if ( m_TagValueProcessor[*tagIter] != nullptr && rawTagValue.isValid) { processedTagValue = (*m_TagValueProcessor[*tagIter])(rawTagValue.value); } else { processedTagValue = rawTagValue.value; } groupID << processedTagValue; } // shorten ID? return groupID.str(); } mitk::DICOMTagBasedSorter::GroupIDToListType mitk::DICOMTagBasedSorter ::SplitInputGroups() { DICOMDatasetList input = GetInput(); // copy GroupIDToListType listForGroupID; for (auto dsIter = input.cbegin(); dsIter != input.cend(); ++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 (auto gIter = groups.begin(); gIter != groups.end(); ++groupIndex, ++gIter) { DICOMDatasetList& dsList = gIter->second; #ifdef MBILOG_ENABLE_DEBUG MITK_DEBUG << " --------------------------------------------------------------------------------"; MITK_DEBUG << " DICOMTagBasedSorter before sorting group : " << groupIndex; for (auto oi = dsList.begin(); oi != dsList.cend(); ++oi) { MITK_DEBUG << " INPUT : " << (*oi)->GetFilenameIfAvailable(); } #endif // #ifdef MBILOG_ENABLE_DEBUG std::sort( dsList.begin(), dsList.end(), ParameterizedDatasetSort( m_SortCriterion ) ); #ifdef MBILOG_ENABLE_DEBUG MITK_DEBUG << " --------------------------------------------------------------------------------"; MITK_DEBUG << " DICOMTagBasedSorter after sorting group : " << groupIndex; for (auto oi = dsList.cbegin(); oi != dsList.cend(); ++oi) { MITK_DEBUG << " OUTPUT : " << (*oi)->GetFilenameIfAvailable(); } MITK_DEBUG << " --------------------------------------------------------------------------------"; #endif // MBILOG_ENABLE_DEBUG } GroupIDToListType consecutiveGroups; if (m_StrictSorting) { // Step 2: create new groups by enforcing consecutive order within each group unsigned int groupIndex(0); for (auto gIter = groups.begin(); gIter != groups.end(); ++gIter) { std::stringstream groupKey; groupKey << std::setfill('0') << std::setw(6) << groupIndex++; DICOMDatasetList& dsList = gIter->second; DICOMDatasetAccess* previousDS(nullptr); unsigned int dsIndex(0); double constantDistance(0.0); bool constantDistanceInitialized(false); for (auto dataset = dsList.cbegin(); dataset != dsList.cend(); ++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! const double currentDistance = m_SortCriterion->NumericDistance(previousDS, *dataset); if (constantDistanceInitialized) { if (fabs(currentDistance - constantDistance) < fabs(constantDistance * 0.01)) // ok, deviation of up to 1% of distance is tolerated { // nothing to do, just ok MITK_DEBUG << "Checking currentDistance==" << currentDistance << ": small enough"; } //else if (currentDistance < mitk::eps) // close enough to 0 else { MITK_DEBUG << "Split consecutive group at index " << dsIndex << " (current distance " << currentDistance << ", constant distance " << constantDistance << ")"; // 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 // addition to the above: when sorting by imagepositions, a distance other than 1 between the first two slices is // not unusual, actually expected... then we should not split if (m_ExpectDistanceOne) { if ((currentDistance - (int)currentDistance == 0.0) && fabs(currentDistance) != 1.0) // exact comparison. An integer should not be expressed as 1.000000000000000000000000001! { MITK_DEBUG << "Split consecutive group at index " << dsIndex << " (special case: expected distance 1 exactly)"; groupKey.str(std::string()); groupKey.clear(); groupKey << std::setfill('0') << std::setw(6) << groupIndex++; } } MITK_DEBUG << "Initialize strict distance to currentDistance=" << currentDistance; 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 (auto gIter = consecutiveGroups.cbegin(); gIter != consecutiveGroups.cend(); ++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 (auto firstSlice = firstSlices.cbegin(); firstSlice != firstSlices.cend(); ++firstSlice) { for (auto gIter = consecutiveGroups.cbegin(); gIter != consecutiveGroups.cend(); ++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; } #ifdef MBILOG_ENABLE_DEBUG unsigned int groupIndex( 0 ); for ( auto gIter = groups.begin(); gIter != groups.end(); ++groupIndex, ++gIter ) { DICOMDatasetList& dsList = gIter->second; MITK_DEBUG << " --------------------------------------------------------------------------------"; MITK_DEBUG << " DICOMTagBasedSorter after sorting group : " << groupIndex; for ( auto oi = dsList.begin(); oi != dsList.end(); ++oi ) { MITK_DEBUG << " OUTPUT : " << ( *oi )->GetFilenameIfAvailable(); } MITK_DEBUG << " --------------------------------------------------------------------------------"; } #endif // MBILOG_ENABLE_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/src/mitkThreeDnTDICOMSeriesReader.cpp b/Modules/DICOMReader/src/mitkThreeDnTDICOMSeriesReader.cpp index 00008c9dcd..44db220ff1 100644 --- a/Modules/DICOMReader/src/mitkThreeDnTDICOMSeriesReader.cpp +++ b/Modules/DICOMReader/src/mitkThreeDnTDICOMSeriesReader.cpp @@ -1,264 +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) +,m_Group3DandT(m_DefaultGroup3DandT) { } mitk::ThreeDnTDICOMSeriesReader ::ThreeDnTDICOMSeriesReader(const ThreeDnTDICOMSeriesReader& other ) :DICOMITKSeriesGDCMReader(other) -,m_Group3DandT(true) +,m_Group3DandT(m_DefaultGroup3DandT) { } 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 auto* 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 const DICOMDatasetAccessingImageFrameList& firstBlock = remainingBlocks.front(); DICOMDatasetAccessingImageFrameList current3DnTBlock = firstBlock; int current3DnTBlockNumberOfTimeSteps = 1; // get block characteristics of first block const unsigned int currentBlockNumberOfSlices = firstBlock.size(); const std::string currentBlockFirstOrigin = firstBlock.front()->GetTagValueAsString( tagImagePositionPatient ).value; const std::string currentBlockLastOrigin = firstBlock.back()->GetTagValueAsString( tagImagePositionPatient ).value; remainingBlocks.erase( remainingBlocks.begin() ); // compare all other blocks against the first one for (auto otherBlockIter = remainingBlocks.begin(); otherBlockIter != remainingBlocks.cend(); /*++otherBlockIter*/) // <-- inside loop { // get block characteristics from first block const DICOMDatasetAccessingImageFrameList otherBlock = *otherBlockIter; const unsigned int otherBlockNumberOfSlices = otherBlock.size(); const std::string otherBlockFirstOrigin = otherBlock.front()->GetTagValueAsString( tagImagePositionPatient ).value; const std::string otherBlockLastOrigin = otherBlock.back()->GetTagValueAsString( tagImagePositionPatient ).value; // 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 (auto blockIter = true3DnTBlocks.cbegin(); blockIter != true3DnTBlocks.cend(); ++o, ++blockIter) { // bad copy&paste code from DICOMITKSeriesGDCMReader, should be handled in a better way DICOMDatasetAccessingImageFrameList gdcmFrameInfoList = *blockIter; assert(!gdcmFrameInfoList.empty()); // reverse frames if necessary // update tilt information from absolute last sorting const DICOMDatasetList datasetList = ConvertToDICOMDatasetList( gdcmFrameInfoList ); m_NormalDirectionConsistencySorter->SetInput( datasetList ); m_NormalDirectionConsistencySorter->Sort(); const DICOMDatasetAccessingImageFrameList sortedGdcmInfoFrameList = ConvertToDICOMDatasetAccessingImageFrameList( m_NormalDirectionConsistencySorter->GetOutput(0) ); const GantryTiltInformation& tiltInfo = m_NormalDirectionConsistencySorter->GetTiltInformation(); // set frame list for current block const DICOMImageFrameList frameList = ConvertToDICOMImageFrameList( 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.SetAdditionalTagsOfInterest(GetAdditionalTagsOfInterest()); block.SetTagLookupTableToPropertyFunctor(GetTagLookupTableToPropertyFunctor()); 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) { const 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(); const bool hasTilt = tiltInfo.IsRegularGantryTilt(); const int numberOfTimesteps = block.GetNumberOfTimeSteps(); if (numberOfTimesteps == 1) { return DICOMITKSeriesGDCMReader::LoadMitkImageForImageBlockDescriptor(block); } const int numberOfFramesPerTimestep = block.GetNumberOfFramesPerTimeStep(); ITKDICOMSeriesReaderHelper::StringContainerList filenamesPerTimestep; for (int timeStep = 0; timeStepFilename ); } filenamesPerTimestep.push_back( filenamesOfThisTimeStep ); } mitk::ITKDICOMSeriesReaderHelper helper; mitk::Image::Pointer mitkImage = helper.Load3DnT( filenamesPerTimestep, m_FixTiltByShearing && hasTilt, tiltInfo ); block.SetMitkImage( mitkImage ); PopLocale(); return true; } diff --git a/Modules/DICOMReaderServices/include/mitkClassicDICOMSeriesReaderService.h b/Modules/DICOMReaderServices/include/mitkClassicDICOMSeriesReaderService.h index 0bdd3726a4..1b9e728856 100644 --- a/Modules/DICOMReaderServices/include/mitkClassicDICOMSeriesReaderService.h +++ b/Modules/DICOMReaderServices/include/mitkClassicDICOMSeriesReaderService.h @@ -1,45 +1,44 @@ /*=================================================================== 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 MITKCLASSICDICOMSERIESREADERSERVICE_H #define MITKCLASSICDICOMSERIESREADERSERVICE_H #include namespace mitk { /** - Service wrapper that auto selects (using the mitk::DICOMFileReaderSelector) the best DICOMFileReader from + Service wrapper that directly returns an instance of ClassicDICOMSeriesReader class from the DICOMReader module. */ class ClassicDICOMSeriesReaderService : public BaseDICOMReaderService { public: ClassicDICOMSeriesReaderService(); protected: - /** Returns the reader instance that should be used. The descission may be based - * one the passed relevant file list.*/ + /** Returns a ClassicDICOMSeriesReader instance.*/ mitk::DICOMFileReader::Pointer GetReader(const mitk::StringList& relevantFiles) const override; private: ClassicDICOMSeriesReaderService* Clone() const override; }; } #endif // MITKCLASSICDICOMSERIESREADERSERVICE_H