diff --git a/Modules/DICOMReader/Testing/mitkDICOMITKSeriesGDCMReaderBasicsTest.cpp b/Modules/DICOMReader/Testing/mitkDICOMITKSeriesGDCMReaderBasicsTest.cpp index b137157b93..faaebe11ee 100644 --- a/Modules/DICOMReader/Testing/mitkDICOMITKSeriesGDCMReaderBasicsTest.cpp +++ b/Modules/DICOMReader/Testing/mitkDICOMITKSeriesGDCMReaderBasicsTest.cpp @@ -1,88 +1,88 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkDICOMITKSeriesGDCMReader.h" #include "mitkDICOMFileReaderTestHelper.h" #include "mitkDICOMFilenameSorter.h" #include "mitkDICOMTagBasedSorter.h" #include "mitkDICOMSortByTag.h" #include "mitkTestingMacros.h" using mitk::DICOMTag; int mitkDICOMITKSeriesGDCMReaderBasicsTest(int argc, char* argv[]) { MITK_TEST_BEGIN("mitkDICOMITKSeriesGDCMReaderBasicsTest"); mitk::DICOMITKSeriesGDCMReader::Pointer gdcmReader = mitk::DICOMITKSeriesGDCMReader::New(); MITK_TEST_CONDITION_REQUIRED(gdcmReader.IsNotNull(), "DICOMITKSeriesGDCMReader can be instantiated."); mitk::DICOMFileReaderTestHelper::SetTestInputFilenames( argc,argv ); // check the Set/GetInput function mitk::DICOMFileReaderTestHelper::TestInputFilenames( gdcmReader ); // check that output is a good reproduction of input (no duplicates, no new elements) mitk::DICOMFileReaderTestHelper::TestOutputsContainInputs( gdcmReader ); // repeat test with filename based sorter in-between mitk::DICOMFilenameSorter::Pointer filenameSorter = mitk::DICOMFilenameSorter::New(); gdcmReader->AddSortingElement( filenameSorter ); mitk::DICOMFileReaderTestHelper::TestOutputsContainInputs( gdcmReader ); // repeat test with some more realistic sorting gdcmReader = mitk::DICOMITKSeriesGDCMReader::New(); // this also tests destruction mitk::DICOMTagBasedSorter::Pointer tagSorter = mitk::DICOMTagBasedSorter::New(); // all the things that split by tag in DicomSeriesReader tagSorter->AddDistinguishingTag( DICOMTag(0x0028, 0x0010) ); // Number of Rows tagSorter->AddDistinguishingTag( DICOMTag(0x0028, 0x0011) ); // Number of Columns tagSorter->AddDistinguishingTag( DICOMTag(0x0028, 0x0030) ); // Pixel Spacing tagSorter->AddDistinguishingTag( DICOMTag(0x0018, 0x1164) ); // Imager Pixel Spacing - tagSorter->AddDistinguishingTag( DICOMTag(0x0020, 0x0037) ); // Image Orientation (Patient) // TODO add tolerance parameter (l. 1572 of original code) + tagSorter->AddDistinguishingTag( DICOMTag(0x0020, 0x0037), new mitk::DICOMTagBasedSorter::CutDecimalPlaces(5) ); // Image Orientation (Patient) // TODO handle as real vectors! cluster with configurable errors! tagSorter->AddDistinguishingTag( DICOMTag(0x0020, 0x000e) ); // Series Instance UID tagSorter->AddDistinguishingTag( DICOMTag(0x0018, 0x0050) ); // Slice Thickness tagSorter->AddDistinguishingTag( DICOMTag(0x0028, 0x0008) ); // Number of Frames tagSorter->AddDistinguishingTag( DICOMTag(0x0020, 0x0052) ); // Frame of Reference UID // a sorter... // TODO ugly syntax, improve.. mitk::DICOMSortCriterion::ConstPointer sorting = mitk::DICOMSortByTag::New( DICOMTag(0x0020, 0x0013), // instance number mitk::DICOMSortByTag::New( DICOMTag(0x0020, 0x0012), // aqcuisition number mitk::DICOMSortByTag::New( DICOMTag(0x0008, 0x0032), // aqcuisition time mitk::DICOMSortByTag::New( DICOMTag(0x0018, 0x1060), // trigger time mitk::DICOMSortByTag::New( DICOMTag(0x0008, 0x0018) // SOP instance UID (last resort, not really meaningful but decides clearly) ).GetPointer() ).GetPointer() ).GetPointer() ).GetPointer() ).GetPointer(); tagSorter->SetSortCriterion( sorting ); gdcmReader->AddSortingElement( tagSorter ); mitk::DICOMFileReaderTestHelper::TestOutputsContainInputs( gdcmReader ); gdcmReader->PrintOutputs(std::cout, true); // really load images mitk::DICOMFileReaderTestHelper::TestMitkImagesAreLoaded( gdcmReader ); MITK_TEST_END(); } diff --git a/Modules/DICOMReader/mitkClassicDICOMSeriesReader.cpp b/Modules/DICOMReader/mitkClassicDICOMSeriesReader.cpp index 5e8e7681a5..408c5ba673 100644 --- a/Modules/DICOMReader/mitkClassicDICOMSeriesReader.cpp +++ b/Modules/DICOMReader/mitkClassicDICOMSeriesReader.cpp @@ -1,80 +1,80 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkClassicDICOMSeriesReader.h" #include "mitkDICOMTagBasedSorter.h" #include "mitkDICOMSortByTag.h" #include "mitkSortByImagePositionPatient.h" mitk::ClassicDICOMSeriesReader ::ClassicDICOMSeriesReader() :DICOMITKSeriesGDCMReader() { mitk::DICOMTagBasedSorter::Pointer tagSorter = mitk::DICOMTagBasedSorter::New(); // all the things that split by tag in mitk::DicomSeriesReader tagSorter->AddDistinguishingTag( DICOMTag(0x0028, 0x0010) ); // Number of Rows tagSorter->AddDistinguishingTag( DICOMTag(0x0028, 0x0011) ); // Number of Columns tagSorter->AddDistinguishingTag( DICOMTag(0x0028, 0x0030) ); // Pixel Spacing tagSorter->AddDistinguishingTag( DICOMTag(0x0018, 0x1164) ); // Imager Pixel Spacing - tagSorter->AddDistinguishingTag( DICOMTag(0x0020, 0x0037) ); // Image Orientation (Patient) + tagSorter->AddDistinguishingTag( DICOMTag(0x0020, 0x0037), new mitk::DICOMTagBasedSorter::CutDecimalPlaces(5) ); // Image Orientation (Patient) tagSorter->AddDistinguishingTag( DICOMTag(0x0020, 0x000e) ); // Series Instance UID tagSorter->AddDistinguishingTag( DICOMTag(0x0018, 0x0050) ); // Slice Thickness tagSorter->AddDistinguishingTag( DICOMTag(0x0028, 0x0008) ); // Number of Frames - tagSorter->AddDistinguishingTag( DICOMTag(0x0020, 0x0052) ); // Frame of Reference UID + //tagSorter->AddDistinguishingTag( DICOMTag(0x0020, 0x0052) ); // Frame of Reference UID // a sorter... // TODO ugly syntax, improve.. mitk::DICOMSortCriterion::ConstPointer sorting = mitk::SortByImagePositionPatient::New( // image position patient and image orientation mitk::DICOMSortByTag::New( DICOMTag(0x0020, 0x0012), // aqcuisition number mitk::DICOMSortByTag::New( DICOMTag(0x0008, 0x0032), // aqcuisition time mitk::DICOMSortByTag::New( DICOMTag(0x0018, 0x1060), // trigger time mitk::DICOMSortByTag::New( DICOMTag(0x0008, 0x0018) // SOP instance UID (last resort, not really meaningful but decides clearly) ).GetPointer() ).GetPointer() ).GetPointer() ).GetPointer() ).GetPointer(); tagSorter->SetSortCriterion( sorting ); // define above sorting for this class this->AddSortingElement( tagSorter ); } mitk::ClassicDICOMSeriesReader ::ClassicDICOMSeriesReader(const ClassicDICOMSeriesReader& other ) :DICOMITKSeriesGDCMReader(other) { } mitk::ClassicDICOMSeriesReader ::~ClassicDICOMSeriesReader() { } mitk::ClassicDICOMSeriesReader& mitk::ClassicDICOMSeriesReader ::operator=(const ClassicDICOMSeriesReader& other) { if (this != &other) { DICOMITKSeriesGDCMReader::operator=(other); } return *this; } diff --git a/Modules/DICOMReader/mitkDICOMDatasetAccess.h b/Modules/DICOMReader/mitkDICOMDatasetAccess.h index e503a4cbf1..4f6742b2b8 100644 --- a/Modules/DICOMReader/mitkDICOMDatasetAccess.h +++ b/Modules/DICOMReader/mitkDICOMDatasetAccess.h @@ -1,40 +1,42 @@ /*=================================================================== 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 mitkDICOMDatasetAccess_h #define mitkDICOMDatasetAccess_h #include "mitkDICOMTag.h" #include "DICOMReaderExports.h" namespace mitk { class DICOMReader_EXPORT DICOMDatasetAccess { public: virtual std::string GetFilenameIfAvailable() const = 0; virtual std::string GetTagValueAsString(const mitk::DICOMTag& tag) const = 0; + + virtual ~DICOMDatasetAccess() {}; }; typedef std::vector DICOMDatasetList; } #endif diff --git a/Modules/DICOMReader/mitkDICOMGDCMImageFrameInfo.cpp b/Modules/DICOMReader/mitkDICOMGDCMImageFrameInfo.cpp index 384c2fb1ad..235d7f771a 100644 --- a/Modules/DICOMReader/mitkDICOMGDCMImageFrameInfo.cpp +++ b/Modules/DICOMReader/mitkDICOMGDCMImageFrameInfo.cpp @@ -1,100 +1,104 @@ /*=================================================================== 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 "mitkDICOMGDCMImageFrameInfo.h" mitk::DICOMGDCMImageFrameInfo ::DICOMGDCMImageFrameInfo(const std::string& filename, unsigned int frameNo) :itk::LightObject() ,m_FrameInfo( DICOMImageFrameInfo::New(filename, frameNo) ) ,m_TagForValue(gdcm::Scanner::TagToValue()) // empty { } mitk::DICOMGDCMImageFrameInfo ::DICOMGDCMImageFrameInfo(DICOMImageFrameInfo::Pointer frameinfo) :itk::LightObject() ,m_FrameInfo(frameinfo) ,m_TagForValue(gdcm::Scanner::TagToValue()) // empty { } mitk::DICOMGDCMImageFrameInfo ::DICOMGDCMImageFrameInfo(DICOMImageFrameInfo::Pointer frameinfo, gdcm::Scanner::TagToValue const& tagToValueMapping) :itk::LightObject() ,m_FrameInfo(frameinfo) ,m_TagForValue(tagToValueMapping) { } +mitk::DICOMGDCMImageFrameInfo:: +~DICOMGDCMImageFrameInfo() +{ +} std::string mitk::DICOMGDCMImageFrameInfo ::GetTagValueAsString(const DICOMTag& tag) const { gdcm::Scanner::TagToValue::const_iterator mappedValue = m_TagForValue.find( gdcm::Tag(tag.GetGroup(), tag.GetElement()) ); if (mappedValue != m_TagForValue.end()) { return mappedValue->second; } else { const DICOMTag tagImagePositionPatient = DICOMTag(0x0020,0x0032); // Image Position (Patient) const DICOMTag tagImageOrientation = DICOMTag(0x0020, 0x0037); // Image Orientation if (tag == tagImagePositionPatient) { return std::string("0\\0\\0"); } else if (tag == tagImageOrientation) { return std::string("1\\0\\0\\0\\1\\0"); } else { return std::string(""); } } } std::string mitk::DICOMGDCMImageFrameInfo ::GetFilenameIfAvailable() const { if (m_FrameInfo.IsNotNull()) { return m_FrameInfo->Filename; } else { return ""; } } mitk::DICOMImageFrameInfo::Pointer mitk::DICOMGDCMImageFrameInfo ::GetFrameInfo() const { return m_FrameInfo; } void mitk::DICOMGDCMImageFrameInfo ::SetFrameInfo(DICOMImageFrameInfo::Pointer frameinfo) { m_FrameInfo = frameinfo; } diff --git a/Modules/DICOMReader/mitkDICOMGDCMImageFrameInfo.h b/Modules/DICOMReader/mitkDICOMGDCMImageFrameInfo.h index 6bf50e5f82..f508ccd9b9 100644 --- a/Modules/DICOMReader/mitkDICOMGDCMImageFrameInfo.h +++ b/Modules/DICOMReader/mitkDICOMGDCMImageFrameInfo.h @@ -1,59 +1,61 @@ /*=================================================================== 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 mitkDICOMGDCMImageFrameInfo_h #define mitkDICOMGDCMImageFrameInfo_h #include "mitkDICOMImageFrameInfo.h" #include "mitkDICOMDatasetAccess.h" #include "gdcmScanner.h" namespace mitk { class DICOMReader_EXPORT DICOMGDCMImageFrameInfo : public itk::LightObject, public DICOMDatasetAccess { public: mitkClassMacro(DICOMGDCMImageFrameInfo, itk::LightObject); itkNewMacro( DICOMGDCMImageFrameInfo ); mitkNewMacro1Param( DICOMGDCMImageFrameInfo, const std::string&); mitkNewMacro2Param( DICOMGDCMImageFrameInfo, const std::string&, unsigned int ); mitkNewMacro1Param( DICOMGDCMImageFrameInfo, DICOMImageFrameInfo::Pointer); mitkNewMacro2Param( DICOMGDCMImageFrameInfo, DICOMImageFrameInfo::Pointer, gdcm::Scanner::TagToValue const&); + virtual ~DICOMGDCMImageFrameInfo(); + virtual std::string GetTagValueAsString(const DICOMTag&) const; std::string GetFilenameIfAvailable() const; DICOMImageFrameInfo::Pointer GetFrameInfo() const; void SetFrameInfo(DICOMImageFrameInfo::Pointer frameinfo); protected: DICOMImageFrameInfo::Pointer m_FrameInfo; DICOMGDCMImageFrameInfo(DICOMImageFrameInfo::Pointer frameinfo); DICOMGDCMImageFrameInfo(DICOMImageFrameInfo::Pointer frameinfo, gdcm::Scanner::TagToValue const& tagToValueMapping); DICOMGDCMImageFrameInfo(const std::string& filename = "", unsigned int frameNo = 0); gdcm::Scanner::TagToValue const& m_TagForValue; }; typedef std::vector DICOMGDCMImageFrameList; } #endif diff --git a/Modules/DICOMReader/mitkDICOMImageBlockDescriptor.cpp b/Modules/DICOMReader/mitkDICOMImageBlockDescriptor.cpp index de1c012bd8..39f3ecf02a 100644 --- a/Modules/DICOMReader/mitkDICOMImageBlockDescriptor.cpp +++ b/Modules/DICOMReader/mitkDICOMImageBlockDescriptor.cpp @@ -1,236 +1,211 @@ /*=================================================================== 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 "mitkDICOMImageBlockDescriptor.h" mitk::DICOMImageBlockDescriptor ::DICOMImageBlockDescriptor() :m_PixelsInterpolated(false) ,m_PixelSpacingInterpretation() ,m_PixelSpacing("") ,m_ImagerPixelSpacing("") { } mitk::DICOMImageBlockDescriptor ::~DICOMImageBlockDescriptor() { } mitk::DICOMImageBlockDescriptor ::DICOMImageBlockDescriptor(const DICOMImageBlockDescriptor& other) :m_ImageFrameList( other.m_ImageFrameList ) ,m_MitkImage( other.m_MitkImage ) ,m_SliceIsLoaded( other.m_SliceIsLoaded ) ,m_PixelsInterpolated( other.m_PixelsInterpolated ) ,m_PixelSpacingInterpretation( other.m_PixelSpacingInterpretation ) ,m_PixelSpacing( other.m_PixelSpacing ) ,m_ImagerPixelSpacing( other.m_ImagerPixelSpacing ) { if (m_MitkImage) { m_MitkImage = m_MitkImage->Clone(); } } mitk::DICOMImageBlockDescriptor& mitk::DICOMImageBlockDescriptor ::operator=(const DICOMImageBlockDescriptor& other) { if (this != &other) { m_ImageFrameList = other.m_ImageFrameList; m_MitkImage = other.m_MitkImage; m_SliceIsLoaded = other.m_SliceIsLoaded; m_PixelsInterpolated = other.m_PixelsInterpolated; m_PixelSpacingInterpretation = other.m_PixelSpacingInterpretation; m_PixelSpacing = other.m_PixelSpacing; m_ImagerPixelSpacing = other.m_ImagerPixelSpacing; if (m_MitkImage) { m_MitkImage = m_MitkImage->Clone(); } } return *this; } void mitk::DICOMImageBlockDescriptor ::SetImageFrameList(const DICOMImageFrameList& framelist) { m_ImageFrameList = framelist; m_SliceIsLoaded.resize(framelist.size()); m_SliceIsLoaded.assign(framelist.size(), false); } const mitk::DICOMImageFrameList& mitk::DICOMImageBlockDescriptor ::GetImageFrameList() const { return m_ImageFrameList; } void mitk::DICOMImageBlockDescriptor ::SetMitkImage(Image::Pointer image) { if (m_MitkImage != image) { m_MitkImage = image; } } mitk::Image::Pointer mitk::DICOMImageBlockDescriptor ::GetMitkImage() const { return m_MitkImage; } void mitk::DICOMImageBlockDescriptor ::SetSliceIsLoaded(unsigned int index, bool isLoaded) { if (index < m_SliceIsLoaded.size()) { m_SliceIsLoaded[index] = isLoaded; } else { std::stringstream ss; ss << "Index " << index << " out of range (" << m_SliceIsLoaded.size() << " indices reserved)"; throw std::invalid_argument( ss.str() ); } } bool mitk::DICOMImageBlockDescriptor ::IsSliceLoaded(unsigned int index) const { if (index < m_SliceIsLoaded.size()) { return m_SliceIsLoaded[index]; } else { std::stringstream ss; ss << "Index " << index << " out of range (" << m_SliceIsLoaded.size() << " indices reserved)"; throw std::invalid_argument( ss.str() ); } } bool mitk::DICOMImageBlockDescriptor ::AllSlicesAreLoaded() const { bool allLoaded = true; for (BoolList::const_iterator iter = m_SliceIsLoaded.begin(); iter != m_SliceIsLoaded.end(); ++iter) { allLoaded &= *iter; } return allLoaded; } void mitk::DICOMImageBlockDescriptor ::SetPixelsInterpolated(bool pixelsAreInterpolated) { if (m_PixelsInterpolated != pixelsAreInterpolated) { m_PixelsInterpolated = pixelsAreInterpolated; } } bool mitk::DICOMImageBlockDescriptor ::GetPixelsInterpolated() const { return m_PixelsInterpolated; } void mitk::DICOMImageBlockDescriptor ::SetPixelSpacingInterpretation( PixelSpacingInterpretation interpretation ) { if (m_PixelSpacingInterpretation != interpretation) { m_PixelSpacingInterpretation = interpretation; } } mitk::PixelSpacingInterpretation mitk::DICOMImageBlockDescriptor ::GetPixelSpacingInterpretation() const { return m_PixelSpacingInterpretation; } void mitk::DICOMImageBlockDescriptor ::SetPixelSpacingInformation(const std::string& pixelSpacing, const std::string& imagerPixelSpacing) { m_PixelSpacing = pixelSpacing; m_ImagerPixelSpacing = imagerPixelSpacing; } void mitk::DICOMImageBlockDescriptor ::GetDesiredMITKImagePixelSpacing( float& spacingX, float& spacingY) const { // preference for "in patient" pixel spacing if ( !DICOMStringToSpacing( m_PixelSpacing, spacingX, spacingY ) ) { // fallback to "on detector" spacing if ( !DICOMStringToSpacing( m_ImagerPixelSpacing, spacingX, spacingY ) ) { // last resort: invent something spacingX = spacingY = 1.0; } } } -bool -mitk::DICOMImageBlockDescriptor -::DICOMStringToSpacing(const std::string& s, float& spacingX, float& spacingY) const -{ - bool successful = false; - - MITK_INFO << "Convert '"<m_Group * 0x3000 + this->m_Element < + other.m_Group * 0x3000 + other.m_Element; +} + + +void +mitk::DICOMStringToOrientationVectors(const std::string& s, Vector3D& right, Vector3D& up, bool& successful) +{ + successful = true; + + std::istringstream orientationReader(s); + std::string coordinate; + unsigned int dim(0); + while( std::getline( orientationReader, coordinate, '\\' ) && dim < 6 ) + { + if (dim<3) + { + right[dim++] = atof(coordinate.c_str()); + } + else + { + up[dim++ - 3] = atof(coordinate.c_str()); + } + } + + if (dim && dim != 6) + { + successful = false; + } + else if (dim == 0) + { + // fill with defaults + right.Fill(0.0); + right[0] = 1.0; + + up.Fill(0.0); + up[1] = 1.0; + + successful = false; + } +} + +bool +mitk::DICOMStringToSpacing(const std::string& s, float& spacingX, float& spacingY) +{ + bool successful = false; + + std::istringstream spacingReader(s); + std::string spacing; + if ( std::getline( spacingReader, spacing, '\\' ) ) + { + spacingY = atof( spacing.c_str() ); + + if ( std::getline( spacingReader, spacing, '\\' ) ) + { + spacingX = atof( spacing.c_str() ); + + successful = true; + } + } + + return successful; +} + +mitk::Point3D +mitk::DICOMStringToPoint3D(const std::string& s, bool& successful) +{ + Point3D p; + successful = true; + + std::istringstream originReader(s); + std::string coordinate; + unsigned int dim(0); + while( std::getline( originReader, coordinate, '\\' ) && dim < 3) + { + p[dim++]= atof(coordinate.c_str()); + } + + if (dim && dim != 3) + { + successful = false; + } + else if (dim == 0) + { + successful = false; + p.Fill(0.0); // assume default (0,0,0) + } + + return p; +} diff --git a/Modules/DICOMReader/mitkDICOMTag.h b/Modules/DICOMReader/mitkDICOMTag.h index 8e7dbd0bb0..417c08aea6 100644 --- a/Modules/DICOMReader/mitkDICOMTag.h +++ b/Modules/DICOMReader/mitkDICOMTag.h @@ -1,49 +1,73 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef mitkTag_h #define mitkTag_h -#include +#include "mitkVector.h" #include "DICOMReaderExports.h" namespace mitk { class DICOMReader_EXPORT DICOMTag { public: DICOMTag(unsigned int group, unsigned int element); DICOMTag(const DICOMTag& other); DICOMTag& operator=(const DICOMTag& other); bool operator==(const DICOMTag& other) const; + bool operator<(const DICOMTag& other) const; unsigned int GetGroup() const; unsigned int GetElement() const; protected: unsigned int m_Group; unsigned int m_Element; }; typedef std::vector DICOMTagList; +/** + \brief Convert DICOM string describing a point two Vector3D. + + DICOM tags like ImageOrientationPatient contain two vectors as float numbers separated by backslashes: + \verbatim + 42.7131\13.77\0.7\137.76\0.3 + \endverbatim + */ +void DICOMStringToOrientationVectors(const std::string& s, Vector3D& right, Vector3D& up, bool& successful); + +bool DICOMStringToSpacing(const std::string& s, float& spacingX, float& spacingY); + + +/** + \brief Convert DICOM string describing a point to Point3D. + + DICOM tags like ImagePositionPatient contain a position as float numbers separated by backslashes: + \verbatim + 42.7131\13.77\0.7 + \endverbatim + */ +mitk::Point3D DICOMStringToPoint3D(const std::string& s, bool& successful); + } #endif diff --git a/Modules/DICOMReader/mitkDICOMTagBasedSorter.cpp b/Modules/DICOMReader/mitkDICOMTagBasedSorter.cpp index 74676eadde..f615cb876f 100644 --- a/Modules/DICOMReader/mitkDICOMTagBasedSorter.cpp +++ b/Modules/DICOMReader/mitkDICOMTagBasedSorter.cpp @@ -1,175 +1,244 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkDICOMTagBasedSorter.h" #include +mitk::DICOMTagBasedSorter::CutDecimalPlaces +::CutDecimalPlaces(unsigned int precision) +:m_Precision(precision) +{ +} + +std::string +mitk::DICOMTagBasedSorter::CutDecimalPlaces +::operator()(const std::string& input) const +{ + // be a bit tolerant for tags such as image orientation orienatation, let only the first few digits matter (http://bugs.mitk.org/show_bug.cgi?id=12263) + + bool conversionError(false); + Vector3D right; right.Fill(0.0); + Vector3D up; right.Fill(0.0); + DICOMStringToOrientationVectors( input, right, up, conversionError ); + + std::ostringstream ss; + ss.setf(std::ios::fixed, std::ios::floatfield); + ss.precision(m_Precision); + ss << right[0] << "\\" + << right[1] << "\\" + << right[2] << "\\" + << up[0] << "\\" + << up[1] << "\\" + << up[2]; + + return ss.str(); +} + mitk::DICOMTagBasedSorter ::DICOMTagBasedSorter() :DICOMDatasetSorter() { } mitk::DICOMTagBasedSorter ::~DICOMTagBasedSorter() { + for(TagValueProcessorMap::iterator ti = m_TagValueProcessor.begin(); + ti != m_TagValueProcessor.end(); + ++ti) + { + delete ti->second; + } } mitk::DICOMTagBasedSorter ::DICOMTagBasedSorter(const DICOMTagBasedSorter& other ) :DICOMDatasetSorter(other) { } mitk::DICOMTagBasedSorter& mitk::DICOMTagBasedSorter ::operator=(const DICOMTagBasedSorter& other) { if (this != &other) { DICOMDatasetSorter::operator=(other); } return *this; } mitk::DICOMTagList mitk::DICOMTagBasedSorter ::GetTagsOfInterest() { DICOMTagList allTags = m_DistinguishingTags; DICOMTagList sortingRelevantTags = m_SortCriterion->GetAllTagsOfInterest(); allTags.insert( allTags.end(), sortingRelevantTags.begin(), sortingRelevantTags.end() ); // append return allTags; } void mitk::DICOMTagBasedSorter -::AddDistinguishingTag( const DICOMTag& tag ) +::AddDistinguishingTag( const DICOMTag& tag, TagValueProcessor* tagValueProcessor ) { m_DistinguishingTags.push_back(tag); + m_TagValueProcessor[tag] = tagValueProcessor; } void mitk::DICOMTagBasedSorter ::SetSortCriterion( DICOMSortCriterion::ConstPointer criterion ) { m_SortCriterion = criterion; } void mitk::DICOMTagBasedSorter ::Sort() { // 1. split // 2. sort each group GroupIDToListType groups = this->SplitInputGroups(); GroupIDToListType& sortedGroups = this->SortGroups( groups ); // 3. define output this->SetNumberOfOutputs(sortedGroups.size()); unsigned int outputIndex(0); for (GroupIDToListType::iterator groupIter = sortedGroups.begin(); groupIter != sortedGroups.end(); ++outputIndex, ++groupIter) { this->SetOutput(outputIndex, groupIter->second); } } std::string mitk::DICOMTagBasedSorter ::BuildGroupID( DICOMDatasetAccess* dataset ) { // just concatenate all tag values assert(dataset); std::stringstream groupID; groupID << "g"; for (DICOMTagList::iterator tagIter = m_DistinguishingTags.begin(); tagIter != m_DistinguishingTags.end(); ++tagIter) { groupID << tagIter->GetGroup() << tagIter->GetElement(); // make group/element part of the id to cover empty tags - groupID << dataset->GetTagValueAsString(*tagIter); + std::string rawTagValue = dataset->GetTagValueAsString(*tagIter); + std::string processedTagValue; + if ( m_TagValueProcessor[*tagIter] != NULL ) + { + processedTagValue = (*m_TagValueProcessor[*tagIter])(rawTagValue); + } + else + { + processedTagValue = rawTagValue; + } + groupID << processedTagValue; } // shorten ID? return groupID.str(); } mitk::DICOMTagBasedSorter::GroupIDToListType mitk::DICOMTagBasedSorter ::SplitInputGroups() { DICOMDatasetList input = GetInput(); // copy GroupIDToListType listForGroupID; for (DICOMDatasetList::iterator dsIter = input.begin(); dsIter != input.end(); ++dsIter) { DICOMDatasetAccess* dataset = *dsIter; assert(dataset); std::string groupID = this->BuildGroupID( dataset ); MITK_DEBUG << "Group ID for for " << dataset->GetFilenameIfAvailable() << ": " << groupID; listForGroupID[groupID].push_back(dataset); } + MITK_DEBUG << "After tag based splitting: " << listForGroupID.size() << " groups"; + return listForGroupID; } mitk::DICOMTagBasedSorter::GroupIDToListType& mitk::DICOMTagBasedSorter ::SortGroups(GroupIDToListType& groups) { if (m_SortCriterion.IsNotNull()) { // for each output // sort by all configured tags, use secondary tags when equal or empty // make configurable: // - sorting order (ascending, descending) // - sort numerically // - ... ? + unsigned int groupIndex(0); for (GroupIDToListType::iterator gIter = groups.begin(); gIter != groups.end(); - ++gIter) + ++groupIndex, ++gIter) { DICOMDatasetList& dsList = gIter->second; + MITK_DEBUG << " --------------------------------------------------------------------------------"; + MITK_DEBUG << " DICOMTagBasedSorter before sorting group : " << groupIndex; + for (DICOMDatasetList::iterator oi = dsList.begin(); + oi != dsList.end(); + ++oi) + { + MITK_DEBUG << " INPUT : " << (*oi)->GetFilenameIfAvailable(); + } + std::sort( dsList.begin(), dsList.end(), ParameterizedDatasetSort( m_SortCriterion ) ); + + MITK_DEBUG << " --------------------------------------------------------------------------------"; + MITK_DEBUG << " DICOMTagBasedSorter after sorting group : " << groupIndex; + for (DICOMDatasetList::iterator oi = dsList.begin(); + oi != dsList.end(); + ++oi) + { + MITK_DEBUG << " OUTPUT : " << (*oi)->GetFilenameIfAvailable(); + } + MITK_DEBUG << " --------------------------------------------------------------------------------"; } } return groups; } mitk::DICOMTagBasedSorter::ParameterizedDatasetSort ::ParameterizedDatasetSort(DICOMSortCriterion::ConstPointer criterion) :m_SortCriterion(criterion) { } bool mitk::DICOMTagBasedSorter::ParameterizedDatasetSort ::operator() (const mitk::DICOMDatasetAccess* left, const mitk::DICOMDatasetAccess* right) { assert(left); assert(right); assert(m_SortCriterion.IsNotNull()); return m_SortCriterion->IsLeftBeforeRight(left, right); } diff --git a/Modules/DICOMReader/mitkDICOMTagBasedSorter.h b/Modules/DICOMReader/mitkDICOMTagBasedSorter.h index 1d348f9fdd..388ff20504 100644 --- a/Modules/DICOMReader/mitkDICOMTagBasedSorter.h +++ b/Modules/DICOMReader/mitkDICOMTagBasedSorter.h @@ -1,73 +1,91 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef mitkDICOMTagBasedSorter_h #define mitkDICOMTagBasedSorter_h #include "mitkDICOMDatasetSorter.h" #include "mitkDICOMSortCriterion.h" namespace mitk { /** \brief sort files based on filename (last resort). */ class DICOMReader_EXPORT DICOMTagBasedSorter : public DICOMDatasetSorter { public: + class TagValueProcessor // TODO use smart pointers here + { + public: + virtual std::string operator()(const std::string&) const = 0; + }; + + class CutDecimalPlaces : public TagValueProcessor + { + public: + CutDecimalPlaces(unsigned int precision); + virtual std::string operator()(const std::string&) const; + private: + unsigned int m_Precision; + }; + mitkClassMacro( DICOMTagBasedSorter, DICOMDatasetSorter ) itkNewMacro( DICOMTagBasedSorter ) - void AddDistinguishingTag( const DICOMTag& ); + void AddDistinguishingTag( const DICOMTag&, TagValueProcessor* tagValueProcessor = NULL ); void SetSortCriterion( DICOMSortCriterion::ConstPointer criterion ); virtual DICOMTagList GetTagsOfInterest(); virtual void Sort(); protected: struct ParameterizedDatasetSort { ParameterizedDatasetSort(DICOMSortCriterion::ConstPointer); bool operator() (const mitk::DICOMDatasetAccess* left, const mitk::DICOMDatasetAccess* right); bool StringCompare(const mitk::DICOMDatasetAccess* left, const mitk::DICOMDatasetAccess* right); bool NumericCompare(const mitk::DICOMDatasetAccess* left, const mitk::DICOMDatasetAccess* right); DICOMSortCriterion::ConstPointer m_SortCriterion; }; DICOMTagBasedSorter(); virtual ~DICOMTagBasedSorter(); DICOMTagBasedSorter(const DICOMTagBasedSorter& other); DICOMTagBasedSorter& operator=(const DICOMTagBasedSorter& other); std::string BuildGroupID( DICOMDatasetAccess* dataset ); typedef std::map GroupIDToListType; GroupIDToListType SplitInputGroups(); GroupIDToListType& SortGroups(GroupIDToListType& groups); DICOMTagList m_DistinguishingTags; + typedef std::map TagValueProcessorMap; + TagValueProcessorMap m_TagValueProcessor; + DICOMSortCriterion::ConstPointer m_SortCriterion; }; } #endif diff --git a/Modules/DICOMReader/mitkEquiDistantBlocksSorter.cpp b/Modules/DICOMReader/mitkEquiDistantBlocksSorter.cpp index d95fd0591b..e9be5c1c85 100644 --- a/Modules/DICOMReader/mitkEquiDistantBlocksSorter.cpp +++ b/Modules/DICOMReader/mitkEquiDistantBlocksSorter.cpp @@ -1,460 +1,390 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkEquiDistantBlocksSorter.h" #include "mitkGantryTiltInformation.h" mitk::EquiDistantBlocksSorter::SliceGroupingAnalysisResult ::SliceGroupingAnalysisResult() :m_GantryTilt(false) { } mitk::DICOMDatasetList mitk::EquiDistantBlocksSorter::SliceGroupingAnalysisResult ::GetBlockFilenames() { return m_GroupedFiles; } mitk::DICOMDatasetList mitk::EquiDistantBlocksSorter::SliceGroupingAnalysisResult ::GetUnsortedFilenames() { return m_UnsortedFiles; } bool mitk::EquiDistantBlocksSorter::SliceGroupingAnalysisResult ::ContainsGantryTilt() { return m_GantryTilt; } void mitk::EquiDistantBlocksSorter::SliceGroupingAnalysisResult ::AddFileToSortedBlock(DICOMDatasetAccess* dataset) { m_GroupedFiles.push_back( dataset ); } void mitk::EquiDistantBlocksSorter::SliceGroupingAnalysisResult ::AddFileToUnsortedBlock(DICOMDatasetAccess* dataset) { m_UnsortedFiles.push_back( dataset ); } void mitk::EquiDistantBlocksSorter::SliceGroupingAnalysisResult ::AddFilesToUnsortedBlock(const DICOMDatasetList& datasets) { m_UnsortedFiles.insert( m_UnsortedFiles.end(), datasets.begin(), datasets.end() ); } void mitk::EquiDistantBlocksSorter::SliceGroupingAnalysisResult ::FlagGantryTilt() { m_GantryTilt = true; } void mitk::EquiDistantBlocksSorter::SliceGroupingAnalysisResult ::UndoPrematureGrouping() { assert( !m_GroupedFiles.empty() ); m_UnsortedFiles.insert( m_UnsortedFiles.begin(), m_GroupedFiles.back() ); m_GroupedFiles.pop_back(); } // ------------------------ end helper class mitk::EquiDistantBlocksSorter ::EquiDistantBlocksSorter() :DICOMDatasetSorter() { } mitk::EquiDistantBlocksSorter ::~EquiDistantBlocksSorter() { } mitk::EquiDistantBlocksSorter ::EquiDistantBlocksSorter(const EquiDistantBlocksSorter& other ) :DICOMDatasetSorter(other) { } mitk::EquiDistantBlocksSorter& mitk::EquiDistantBlocksSorter ::operator=(const EquiDistantBlocksSorter& other) { if (this != &other) { DICOMDatasetSorter::operator=(other); } return *this; } mitk::DICOMTagList mitk::EquiDistantBlocksSorter ::GetTagsOfInterest() { DICOMTagList tags; tags.push_back( DICOMTag(0x0020, 0x0032) ); // ImagePositionPatient tags.push_back( DICOMTag(0x0020, 0x0037) ); // ImageOrientationPatient return tags; } void mitk::EquiDistantBlocksSorter ::Sort() { DICOMDatasetList remainingInput = GetInput(); // copy bool acceptGantryTilt = false; // TODO typedef std::list OutputListType; OutputListType outputs; while (!remainingInput.empty()) // repeat until all files are grouped somehow { SliceGroupingAnalysisResult regularBlock = this->AnalyzeFileForITKImageSeriesReaderSpacingAssumption( remainingInput, acceptGantryTilt ); DICOMDatasetList inBlock = regularBlock.GetBlockFilenames(); DICOMDatasetList laterBlock = regularBlock.GetUnsortedFilenames(); MITK_DEBUG << "Result: sorted 3D group with " << inBlock.size() << " files"; for (DICOMDatasetList::const_iterator diter = inBlock.begin(); diter != inBlock.end(); ++diter) MITK_DEBUG << " IN " << (*diter)->GetFilenameIfAvailable(); for (DICOMDatasetList::const_iterator diter = laterBlock.begin(); diter != laterBlock.end(); ++diter) MITK_DEBUG << " OUT " << (*diter)->GetFilenameIfAvailable(); outputs.push_back( regularBlock.GetBlockFilenames() ); remainingInput = regularBlock.GetUnsortedFilenames(); } unsigned int numberOfOutputs = outputs.size(); this->SetNumberOfOutputs(numberOfOutputs); unsigned int outputIndex(0); for (OutputListType::iterator oIter = outputs.begin(); oIter != outputs.end(); ++outputIndex, ++oIter) { this->SetOutput(outputIndex, *oIter); } } std::string mitk::EquiDistantBlocksSorter ::ConstCharStarToString(const char* s) { return s ? std::string(s) : std::string(); } -mitk::Point3D -mitk::EquiDistantBlocksSorter -::DICOMStringToPoint3D(const std::string& s, bool& successful) -{ - Point3D p; - successful = true; - - std::istringstream originReader(s); - std::string coordinate; - unsigned int dim(0); - while( std::getline( originReader, coordinate, '\\' ) && dim < 3) - { - p[dim++]= atof(coordinate.c_str()); - } - - if (dim && dim != 3) - { - successful = false; - MITK_ERROR << "Reader implementation made wrong assumption on tag (0020,0032). Found " << dim << " instead of 3 values."; - } - else if (dim == 0) - { - successful = false; - p.Fill(0.0); // assume default (0,0,0) - } - - return p; -} - - -void -mitk::EquiDistantBlocksSorter -::DICOMStringToOrientationVectors(const std::string& s, Vector3D& right, Vector3D& up, bool& successful) -{ - successful = true; - - std::istringstream orientationReader(s); - std::string coordinate; - unsigned int dim(0); - while( std::getline( orientationReader, coordinate, '\\' ) && dim < 6 ) - { - if (dim<3) - { - right[dim++] = atof(coordinate.c_str()); - } - else - { - up[dim++ - 3] = atof(coordinate.c_str()); - } - } - - if (dim && dim != 6) - { - successful = false; - MITK_ERROR << "Reader implementation made wrong assumption on tag (0020,0037). Found " << dim << " instead of 6 values."; - } - else if (dim == 0) - { - // fill with defaults - right.Fill(0.0); - right[0] = 1.0; - - up.Fill(0.0); - up[1] = 1.0; - - successful = false; - } -} - - mitk::EquiDistantBlocksSorter::SliceGroupingAnalysisResult mitk::EquiDistantBlocksSorter ::AnalyzeFileForITKImageSeriesReaderSpacingAssumption( const DICOMDatasetList& datasets, bool groupImagesWithGantryTilt) { // result.first = files that fit ITK's assumption // result.second = files that do not fit, should be run through AnalyzeFileForITKImageSeriesReaderSpacingAssumption() again SliceGroupingAnalysisResult result; // we const_cast here, because I could not use a map.at(), which would make the code much more readable const DICOMTag tagImagePositionPatient = DICOMTag(0x0020,0x0032); // Image Position (Patient) const DICOMTag tagImageOrientation = DICOMTag(0x0020, 0x0037); // Image Orientation const DICOMTag tagGantryTilt = DICOMTag(0x0018, 0x1120); // gantry tilt Vector3D fromFirstToSecondOrigin; fromFirstToSecondOrigin.Fill(0.0); bool fromFirstToSecondOriginInitialized(false); Point3D thisOrigin; thisOrigin.Fill(0.0f); Point3D lastOrigin; lastOrigin.Fill(0.0f); Point3D lastDifferentOrigin; lastDifferentOrigin.Fill(0.0f); bool lastOriginInitialized(false); MITK_DEBUG << "--------------------------------------------------------------------------------"; MITK_DEBUG << "Analyzing " << datasets.size() << " files for z-spacing assumption of ITK's ImageSeriesReader (group tilted: " << groupImagesWithGantryTilt << ")"; unsigned int fileIndex(0); for (DICOMDatasetList::const_iterator dsIter = datasets.begin(); dsIter != datasets.end(); ++dsIter, ++fileIndex) { bool fileFitsIntoPattern(false); std::string thisOriginString; // Read tag value into point3D. PLEASE replace this by appropriate GDCM code if you figure out how to do that thisOriginString = (*dsIter)->GetTagValueAsString( tagImagePositionPatient ); if (thisOriginString.empty()) { // don't let such files be in a common group. Everything without position information will be loaded as a single slice: // with standard DICOM files this can happen to: CR, DX, SC MITK_DEBUG << " ==> Sort away " << *dsIter << " for later analysis (no position information)"; // we already have one occupying this position if ( result.GetBlockFilenames().empty() ) // nothing WITH position information yet { // ==> this is a group of its own, stop processing, come back later result.AddFileToSortedBlock( *dsIter ); DICOMDatasetList remainingFiles; remainingFiles.insert( remainingFiles.end(), dsIter+1, datasets.end() ); result.AddFilesToUnsortedBlock( remainingFiles ); fileFitsIntoPattern = false; break; // no files anymore } else { // ==> this does not match, consider later result.AddFileToUnsortedBlock( *dsIter ); // sort away for further analysis fileFitsIntoPattern = false; continue; // next file } } bool ignoredConversionError(-42); // hard to get here, no graceful way to react thisOrigin = DICOMStringToPoint3D( thisOriginString, ignoredConversionError ); MITK_DEBUG << " " << fileIndex << " " << (*dsIter)->GetFilenameIfAvailable() << " at " /* << thisOriginString */ << "(" << thisOrigin[0] << "," << thisOrigin[1] << "," << thisOrigin[2] << ")"; if ( lastOriginInitialized && (thisOrigin == lastOrigin) ) { MITK_DEBUG << " ==> Sort away " << *dsIter << " for separate time step"; // we already have one occupying this position result.AddFileToUnsortedBlock( *dsIter ); // sort away for further analysis fileFitsIntoPattern = false; } else { if (!fromFirstToSecondOriginInitialized && lastOriginInitialized) // calculate vector as soon as possible when we get a new position { fromFirstToSecondOrigin = thisOrigin - lastDifferentOrigin; fromFirstToSecondOriginInitialized = true; // Here we calculate if this slice and the previous one are well aligned, // i.e. we test if the previous origin is on a line through the current // origin, directed into the normal direction of the current slice. // If this is NOT the case, then we have a data set with a TILTED GANTRY geometry, // which cannot be simply loaded into a single mitk::Image at the moment. // For this case, we flag this finding in the result and DicomSeriesReader // can correct for that later. Vector3D right; right.Fill(0.0); Vector3D up; right.Fill(0.0); // might be down as well, but it is just a name at this point std::string orientationValue = (*dsIter)->GetTagValueAsString( tagImageOrientation ); DICOMStringToOrientationVectors( orientationValue, right, up, ignoredConversionError ); GantryTiltInformation tiltInfo( lastDifferentOrigin, thisOrigin, right, up, 1 ); if ( tiltInfo.IsSheared() ) // mitk::eps is too small; 1/1000 of a mm should be enough to detect tilt { /* optimistic approach, accepting gantry tilt: save file for later, check all further files */ // at this point we have TWO slices analyzed! if they are the only two files, we still split, because there is no third to verify our tilting assumption. // later with a third being available, we must check if the initial tilting vector is still valid. if yes, continue. // if NO, we need to split the already sorted part (result.first) and the currently analyzed file (*dsIter) // tell apart gantry tilt from overall skewedness // sort out irregularly sheared slices, that IS NOT tilting if ( groupImagesWithGantryTilt && tiltInfo.IsRegularGantryTilt() ) { bool todoIsDone(false); // TODO add GantryTilt reading // check if this is at least roughly the same angle as recorded in DICOM tags if ( todoIsDone /*&& tagValueMappings[dsIter->c_str()].find(tagGantryTilt) != tagValueMappings[dsIter->c_str()].end() */) { // read value, compare to calculated angle std::string tiltStr = (*dsIter)->GetTagValueAsString( tagGantryTilt ); double angle = atof(tiltStr.c_str()); MITK_DEBUG << "Comparing recorded tilt angle " << angle << " against calculated value " << tiltInfo.GetTiltAngleInDegrees(); // TODO we probably want the signs correct, too (that depends: this is just a rough check, nothing serious) if ( fabs(angle) - tiltInfo.GetTiltAngleInDegrees() > 0.25) { result.AddFileToUnsortedBlock( *dsIter ); // sort away for further analysis fileFitsIntoPattern = false; } else // tilt angle from header is less than 0.25 degrees different from what we calculated, assume this is fine { result.FlagGantryTilt(); result.AddFileToSortedBlock( *dsIter ); // this file is good for current block fileFitsIntoPattern = true; } } else // we cannot check the calculated tilt angle against the one from the dicom header (so we assume we are right) { result.FlagGantryTilt(); result.AddFileToSortedBlock( *dsIter ); // this file is good for current block fileFitsIntoPattern = true; } } else // caller does not want tilt compensation OR shearing is more complicated than tilt { result.AddFileToUnsortedBlock( *dsIter ); // sort away for further analysis fileFitsIntoPattern = false; } } else // not sheared { result.AddFileToSortedBlock( *dsIter ); // this file is good for current block fileFitsIntoPattern = true; } } else if (fromFirstToSecondOriginInitialized) // we already know the offset between slices { Point3D assumedOrigin = lastDifferentOrigin + fromFirstToSecondOrigin; Vector3D originError = assumedOrigin - thisOrigin; double norm = originError.GetNorm(); double toleratedError(0.005); // max. 1/10mm error when measurement crosses 20 slices in z direction if (norm > toleratedError) { MITK_DEBUG << " File does not fit into the inter-slice distance pattern (diff = " << norm << ", allowed " << toleratedError << ")."; MITK_DEBUG << " Expected position (" << assumedOrigin[0] << "," << assumedOrigin[1] << "," << assumedOrigin[2] << "), got position (" << thisOrigin[0] << "," << thisOrigin[1] << "," << thisOrigin[2] << ")"; MITK_DEBUG << " ==> Sort away " << *dsIter << " for later analysis"; // At this point we know we deviated from the expectation of ITK's ImageSeriesReader // We split the input file list at this point, i.e. all files up to this one (excluding it) // are returned as group 1, the remaining files (including the faulty one) are group 2 /* Optimistic approach: check if any of the remaining slices fits in */ result.AddFileToUnsortedBlock( *dsIter ); // sort away for further analysis fileFitsIntoPattern = false; } else { result.AddFileToSortedBlock( *dsIter ); // this file is good for current block fileFitsIntoPattern = true; } } else // this should be the very first slice { result.AddFileToSortedBlock( *dsIter ); // this file is good for current block fileFitsIntoPattern = true; } } // record current origin for reference in later iterations if ( !lastOriginInitialized || ( fileFitsIntoPattern && (thisOrigin != lastOrigin) ) ) { lastDifferentOrigin = thisOrigin; } lastOrigin = thisOrigin; lastOriginInitialized = true; } if ( result.ContainsGantryTilt() ) { // check here how many files were grouped. // IF it was only two files AND we assume tiltedness (e.g. save "distance") // THEN we would want to also split the two previous files (simple) because // we don't have any reason to assume they belong together if ( result.GetBlockFilenames().size() == 2 ) { result.UndoPrematureGrouping(); } } return result; } diff --git a/Modules/DICOMReader/mitkEquiDistantBlocksSorter.h b/Modules/DICOMReader/mitkEquiDistantBlocksSorter.h index 18865065d8..cfdf1685c1 100644 --- a/Modules/DICOMReader/mitkEquiDistantBlocksSorter.h +++ b/Modules/DICOMReader/mitkEquiDistantBlocksSorter.h @@ -1,159 +1,137 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef mitkEquiDistantBlocksSorter_h #define mitkEquiDistantBlocksSorter_h #include "mitkDICOMDatasetSorter.h" #include "mitkDICOMSortCriterion.h" #include "mitkVector.h" #include namespace mitk { /** \brief Split inputs into blocks of equidant slices. This kind of splitting is used as a check before loading a DICOM series with ITK ImageSeriesReader. */ class DICOMReader_EXPORT EquiDistantBlocksSorter : public DICOMDatasetSorter { public: mitkClassMacro( EquiDistantBlocksSorter, DICOMDatasetSorter ) itkNewMacro( EquiDistantBlocksSorter ) virtual DICOMTagList GetTagsOfInterest(); virtual void Sort(); protected: /** \brief Return type of DicomSeriesReader::AnalyzeFileForITKImageSeriesReaderSpacingAssumption. Class contains the grouping result of method DicomSeriesReader::AnalyzeFileForITKImageSeriesReaderSpacingAssumption, which takes as input a number of images, which are all equally oriented and spatially sorted along their normal direction. The result contains of two blocks: a first one is the grouping result, all of those images can be loaded into one image block because they have an equal origin-to-origin distance without any gaps in-between. */ class SliceGroupingAnalysisResult { public: SliceGroupingAnalysisResult(); /** \brief Grouping result, all same origin-to-origin distance w/o gaps. */ DICOMDatasetList GetBlockFilenames(); /** \brief Remaining files, which could not be grouped. */ DICOMDatasetList GetUnsortedFilenames(); /** \brief Wheter or not the grouped result contain a gantry tilt. */ bool ContainsGantryTilt(); /** \brief Meant for internal use by AnalyzeFileForITKImageSeriesReaderSpacingAssumption only. */ void AddFileToSortedBlock(DICOMDatasetAccess* dataset); // TODO Dataset! /** \brief Meant for internal use by AnalyzeFileForITKImageSeriesReaderSpacingAssumption only. */ void AddFileToUnsortedBlock(DICOMDatasetAccess* dataset); void AddFilesToUnsortedBlock(const DICOMDatasetList& datasets); /** \brief Meant for internal use by AnalyzeFileForITKImageSeriesReaderSpacingAssumption only. \todo Could make sense to enhance this with an instance of GantryTiltInformation to store the whole result! */ void FlagGantryTilt(); /** \brief Only meaningful for use by AnalyzeFileForITKImageSeriesReaderSpacingAssumption. */ void UndoPrematureGrouping(); protected: DICOMDatasetList m_GroupedFiles; DICOMDatasetList m_UnsortedFiles; bool m_GantryTilt; }; /** \brief Ensure an equal z-spacing for a group of files. Takes as input a number of images, which are all equally oriented and spatially sorted along their normal direction. Internally used by GetSeries. Returns two lists: the first one contins slices of equal inter-slice spacing. The second list contains remaining files, which need to be run through AnalyzeFileForITKImageSeriesReaderSpacingAssumption again. Relevant code that is matched here is in itkImageSeriesReader.txx (ImageSeriesReader::GenerateOutputInformation(void)), lines 176 to 245 (as of ITK 3.20) */ SliceGroupingAnalysisResult AnalyzeFileForITKImageSeriesReaderSpacingAssumption(const DICOMDatasetList& files, bool groupsOfSimilarImages); /** \brief Safely convert const char* to std::string. */ std::string ConstCharStarToString(const char* s); - /** - \brief Convert DICOM string describing a point to Point3D. - - DICOM tags like ImagePositionPatient contain a position as float numbers separated by backslashes: - \verbatim - 42.7131\13.77\0.7 - \endverbatim - */ - Point3D - DICOMStringToPoint3D(const std::string& s, bool& successful); - - /** - \brief Convert DICOM string describing a point two Vector3D. - - DICOM tags like ImageOrientationPatient contain two vectors as float numbers separated by backslashes: - \verbatim - 42.7131\13.77\0.7\137.76\0.3 - \endverbatim - */ - void - DICOMStringToOrientationVectors(const std::string& s, Vector3D& right, Vector3D& up, bool& successful); - EquiDistantBlocksSorter(); virtual ~EquiDistantBlocksSorter(); EquiDistantBlocksSorter(const EquiDistantBlocksSorter& other); EquiDistantBlocksSorter& operator=(const EquiDistantBlocksSorter& other); }; } #endif diff --git a/Modules/DICOMReader/mitkSortByImagePositionPatient.cpp b/Modules/DICOMReader/mitkSortByImagePositionPatient.cpp index f62d888320..2522da68b1 100644 --- a/Modules/DICOMReader/mitkSortByImagePositionPatient.cpp +++ b/Modules/DICOMReader/mitkSortByImagePositionPatient.cpp @@ -1,195 +1,128 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkSortByImagePositionPatient.h" +#include "mitkDICOMTag.h" mitk::SortByImagePositionPatient ::SortByImagePositionPatient(DICOMSortCriterion::Pointer secondaryCriterion) :DICOMSortCriterion(secondaryCriterion) { } mitk::SortByImagePositionPatient ::~SortByImagePositionPatient() { } mitk::SortByImagePositionPatient ::SortByImagePositionPatient(const SortByImagePositionPatient& other ) :DICOMSortCriterion(other) { } mitk::SortByImagePositionPatient& mitk::SortByImagePositionPatient ::operator=(const SortByImagePositionPatient& other) { if (this != &other) { DICOMSortCriterion::operator=(other); } return *this; } mitk::DICOMTagList mitk::SortByImagePositionPatient ::GetTagsOfInterest() const { DICOMTagList tags; tags.push_back( DICOMTag(0x0020, 0x0032) ); // ImagePositionPatient tags.push_back( DICOMTag(0x0020, 0x0037) ); // ImageOrientationPatient return tags; } -mitk::Point3D -mitk::SortByImagePositionPatient -::DICOMStringToPoint3D(const std::string& s, bool& successful) const -{ - Point3D p; - successful = true; - - std::istringstream originReader(s); - std::string coordinate; - unsigned int dim(0); - while( std::getline( originReader, coordinate, '\\' ) && dim < 3) - { - p[dim++]= atof(coordinate.c_str()); - } - - if (dim && dim != 3) - { - successful = false; - MITK_ERROR << "Reader implementation made wrong assumption on tag (0020,0032). Found " << dim << " instead of 3 values."; - } - else if (dim == 0) - { - successful = false; - p.Fill(0.0); // assume default (0,0,0) - } - - return p; -} - - -void -mitk::SortByImagePositionPatient -::DICOMStringToOrientationVectors(const std::string& s, Vector3D& right, Vector3D& up, bool& successful) const -{ - successful = true; - - std::istringstream orientationReader(s); - std::string coordinate; - unsigned int dim(0); - while( std::getline( orientationReader, coordinate, '\\' ) && dim < 6 ) - { - if (dim<3) - { - right[dim++] = atof(coordinate.c_str()); - } - else - { - up[dim++ - 3] = atof(coordinate.c_str()); - } - } - - if (dim && dim != 6) - { - successful = false; - MITK_ERROR << "Reader implementation made wrong assumption on tag (0020,0037). Found " << dim << " instead of 6 values."; - } - else if (dim == 0) - { - // fill with defaults - right.Fill(0.0); - right[0] = 1.0; - - up.Fill(0.0); - up[1] = 1.0; - - successful = false; - } -} - - bool mitk::SortByImagePositionPatient ::IsLeftBeforeRight(const mitk::DICOMDatasetAccess* left, const mitk::DICOMDatasetAccess* right) const { // sort by distance to world origin, assuming (almost) equal orientation static const DICOMTag tagImagePositionPatient = DICOMTag(0x0020,0x0032); // Image Position (Patient) static const DICOMTag tagImageOrientation = DICOMTag(0x0020, 0x0037); // Image Orientation static Vector3D leftRight; leftRight.Fill(0.0); static Vector3D leftUp; leftUp.Fill(0.0); static bool leftHasOrientation(false); DICOMStringToOrientationVectors( left->GetTagValueAsString( tagImageOrientation ), leftRight, leftUp, leftHasOrientation ); static Vector3D rightRight; rightRight.Fill(0.0); static Vector3D rightUp; rightUp.Fill(0.0); static bool rightHasOrientation(false); DICOMStringToOrientationVectors( right->GetTagValueAsString( tagImageOrientation ), rightRight, rightUp, rightHasOrientation ); static Point3D leftOrigin; leftOrigin.Fill(0.0f); static bool leftHasOrigin(false); leftOrigin = DICOMStringToPoint3D( left->GetTagValueAsString( tagImagePositionPatient ), leftHasOrigin ); static Point3D rightOrigin; rightOrigin.Fill(0.0f); static bool rightHasOrigin(false); rightOrigin = DICOMStringToPoint3D( right->GetTagValueAsString( tagImagePositionPatient ), rightHasOrigin ); // we tolerate very small differences in image orientation, since we got to know about // acquisitions where these values change across a single series (7th decimal digit) // (http://bugs.mitk.org/show_bug.cgi?id=12263) // still, we want to check if our assumption of 'almost equal' orientations is valid for (unsigned int dim = 0; dim < 3; ++dim) { if ( fabs(leftRight[dim] - rightRight[dim]) > 0.0001 || fabs(leftUp[dim] - rightUp[dim]) > 0.0001) { MITK_ERROR << "Dicom images have different orientations."; throw std::logic_error("Dicom images have different orientations. Call GetSeries() first to separate images."); } } static Vector3D normal; normal[0] = leftRight[1] * leftUp[5] - leftRight[2] * leftUp[4]; normal[1] = leftRight[2] * leftUp[3] - leftRight[0] * leftUp[5]; normal[2] = leftRight[0] * leftUp[4] - leftRight[1] * leftUp[3]; static double leftDistance = 0.0; static double rightDistance = 0.0; + leftDistance = 0.0; + rightDistance = 0.0; // this computes the distance from world origin (0,0,0) ALONG THE NORMAL of the image planes for (unsigned int dim = 0; dim < 3; ++dim) { leftDistance += normal[dim] * leftOrigin[dim]; rightDistance += normal[dim] * rightOrigin[dim]; } // if we can sort by just comparing the distance, we do exactly that if ( fabs(leftDistance - rightDistance) >= mitk::eps) { // default: compare position return leftDistance < rightDistance; } else { return this->NextLevelIsLeftBeforeRight(left, right); } } diff --git a/Modules/DICOMReader/mitkSortByImagePositionPatient.h b/Modules/DICOMReader/mitkSortByImagePositionPatient.h index 7f0c887c19..510bb56ea3 100644 --- a/Modules/DICOMReader/mitkSortByImagePositionPatient.h +++ b/Modules/DICOMReader/mitkSortByImagePositionPatient.h @@ -1,53 +1,50 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef mitkSortByImagePositionPatient_h #define mitkSortByImagePositionPatient_h #include "mitkDICOMSortCriterion.h" #include "mitkVector.h" namespace mitk { class DICOMReader_EXPORT SortByImagePositionPatient : public DICOMSortCriterion { public: mitkClassMacro( SortByImagePositionPatient, DICOMSortCriterion ); mitkNewMacro1Param( SortByImagePositionPatient, DICOMSortCriterion::Pointer ); virtual DICOMTagList GetTagsOfInterest() const; virtual bool IsLeftBeforeRight(const mitk::DICOMDatasetAccess* left, const mitk::DICOMDatasetAccess* right) const; protected: SortByImagePositionPatient( DICOMSortCriterion::Pointer secondaryCriterion = NULL ); virtual ~SortByImagePositionPatient(); SortByImagePositionPatient(const SortByImagePositionPatient& other); SortByImagePositionPatient& operator=(const SortByImagePositionPatient& other); - mitk::Point3D DICOMStringToPoint3D(const std::string& s, bool& successful) const; - void DICOMStringToOrientationVectors(const std::string& s, Vector3D& right, Vector3D& up, bool& successful) const; - private: }; } #endif