diff --git a/Modules/DICOMReader/files.cmake b/Modules/DICOMReader/files.cmake index 89d9a498e7..b411d136cf 100644 --- a/Modules/DICOMReader/files.cmake +++ b/Modules/DICOMReader/files.cmake @@ -1,38 +1,39 @@ set(H_FILES mitkDICOMFileReader.h mitkDICOMImageFrameInfo.h mitkDICOMImageBlockDescriptor.h mitkDICOMGDCMImageFrameInfo.h mitkDICOMITKSeriesGDCMReader.h mitkDICOMDatasetSorter.h mitkDICOMFilenameSorter.h mitkDICOMEnums.h mitkDICOMTagBasedSorter.h mitkDICOMSortCriterion.h mitkDICOMSortByTag.h mitkEquiDistantBlocksSorter.h mitkSortByImagePositionPatient.h mitkClassicDICOMSeriesReader.h mitkThreeDnTDICOMSeriesReader.h mitkDICOMTag.h ) set(CPP_FILES mitkDICOMFileReader.cpp mitkDICOMImageBlockDescriptor.cpp mitkDICOMITKSeriesGDCMReader.cpp mitkDICOMDatasetSorter.cpp mitkDICOMFilenameSorter.cpp mitkDICOMTagBasedSorter.cpp mitkDICOMGDCMImageFrameInfo.cpp mitkDICOMImageFrameInfo.cpp mitkDICOMSortCriterion.cpp mitkDICOMSortByTag.cpp mitkITKDICOMSeriesReaderHelper.cpp mitkEquiDistantBlocksSorter.cpp mitkSortByImagePositionPatient.cpp mitkGantryTiltInformation.cpp mitkClassicDICOMSeriesReader.cpp mitkThreeDnTDICOMSeriesReader.cpp mitkDICOMTag.cpp + mitkDICOMEnums.cpp ) diff --git a/Modules/DICOMReader/mitkDICOMEnums.cpp b/Modules/DICOMReader/mitkDICOMEnums.cpp new file mode 100644 index 0000000000..766d62b16b --- /dev/null +++ b/Modules/DICOMReader/mitkDICOMEnums.cpp @@ -0,0 +1,43 @@ +/*=================================================================== + +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 "mitkDICOMEnums.h" + +std::string +mitk::PixelSpacingInterpretationToString(const PixelSpacingInterpretation& value) +{ + switch (value) + { + case SpacingInPatient: return "In Patient"; + case SpacingAtDetector: return "At Detector"; + case SpacingUnknown: return "Unknown spacing"; + default: return ""; + }; +} + +std::string +mitk::ReaderImplementationLevelToString( const ReaderImplementationLevel& value ) +{ + switch (value) + { + case SOPClassSupported: return "SOPClassSupported"; + case SOPClassPartlySupported: return "SOPClassPartlySupported"; + case SOPClassImplemented: return "SOPClassImplemented"; + case SOPClassUnsupported: return "SOPClassUnsupported"; + case SOPClassUnknown: return "SOPClassUnknown"; + default: return ""; + }; +} diff --git a/Modules/DICOMReader/mitkDICOMEnums.h b/Modules/DICOMReader/mitkDICOMEnums.h index ee710e64d7..1332db0e6c 100644 --- a/Modules/DICOMReader/mitkDICOMEnums.h +++ b/Modules/DICOMReader/mitkDICOMEnums.h @@ -1,37 +1,64 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef mitkDICOMEnums_h #define mitkDICOMEnums_h #include #include #include namespace mitk { typedef std::vector StringList; typedef std::vector BoolList; - typedef enum + /** + \brief How the mitk::Image spacing should be interpreted. + + Compare DICOM PS 3.3 10.7 (Basic Pixel Spacing Calibration Macro). + */ + typedef enum { SpacingInPatient, /// distances are mm within a patient SpacingAtDetector, /// distances are mm at detector surface SpacingUnknown /// NO spacing information is present, we use (1,1) as default } PixelSpacingInterpretation; + + /** + \brief Describes how well the reader is tested for a certain file type. + + Applications should not rely on the outcome for images which are reported + Implemented or Unsupported. + + Errors to load images which are reported as Supported + are considered bugs. For PartlySupported please check the reader documentation on specifics. + */ + typedef enum + { + SOPClassSupported, /// loader code and tests are established + SOPClassPartlySupported, /// loader code and tests are establised for specific parts of a SOP Class + SOPClassImplemented, /// loader code is implemented but not accompanied by tests + SOPClassUnsupported, /// loader code is not known to work with this SOP Class + SOPClassUnknown, /// loader did not yet inspect any images, unknown fitness + } ReaderImplementationLevel; + + + std::string PixelSpacingInterpretationToString(const PixelSpacingInterpretation& value); + std::string ReaderImplementationLevelToString( const ReaderImplementationLevel& enumValue ); } #endif diff --git a/Modules/DICOMReader/mitkDICOMFileReader.cpp b/Modules/DICOMReader/mitkDICOMFileReader.cpp index 9a4a9bf583..b7ae23911a 100644 --- a/Modules/DICOMReader/mitkDICOMFileReader.cpp +++ b/Modules/DICOMReader/mitkDICOMFileReader.cpp @@ -1,173 +1,155 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkDICOMFileReader.h" #include mitk::DICOMFileReader ::DICOMFileReader() :itk::LightObject() { } mitk::DICOMFileReader ::~DICOMFileReader() { } mitk::DICOMFileReader ::DICOMFileReader(const DICOMFileReader& other ) :itk::LightObject() ,m_Outputs( other.m_Outputs ) { } mitk::DICOMFileReader& mitk::DICOMFileReader ::operator=(const DICOMFileReader& other) { if (this != &other) { m_InputFilenames = other.m_InputFilenames; m_Outputs = other.m_Outputs; } return *this; } void mitk::DICOMFileReader ::SetInputFiles(StringList filenames) { m_InputFilenames = filenames; } const mitk::StringList& mitk::DICOMFileReader ::GetInputFiles() const { return m_InputFilenames; } unsigned int mitk::DICOMFileReader ::GetNumberOfOutputs() const { return m_Outputs.size(); } void mitk::DICOMFileReader ::ClearOutputs() { m_Outputs.clear(); } void mitk::DICOMFileReader ::SetNumberOfOutputs(unsigned int numberOfOutputs) { m_Outputs.resize(numberOfOutputs); } void mitk::DICOMFileReader ::SetOutput(unsigned int index, const mitk::DICOMImageBlockDescriptor& output) { if (index < m_Outputs.size()) { m_Outputs[index] = output; } else { std::stringstream ss; ss << "Index " << index << " out of range (" << m_Outputs.size() << " indices reserved)"; throw std::invalid_argument( ss.str() ); } } void mitk::DICOMFileReader ::PrintOutputs(std::ostream& os, bool filenameDetails) { os << "---- Outputs of DICOMFilereader " << (void*)this << "----"<< std::endl; for (unsigned int o = 0; o < m_Outputs.size(); ++o) { os << "-- Output " << o << std::endl; const DICOMImageBlockDescriptor& block = m_Outputs[o]; - const DICOMImageFrameList& frames = block.GetImageFrameList(); - os << " Number of frames: " << frames.size() << std::endl; - os << " Pixels interpolated: " << (block.GetPixelsInterpolated() ? "true" : "false") << std::endl; - os << " Pixel spacing interpretation: " << (int)block.GetPixelSpacingInterpretation() << std::endl; - os << " MITK image: " << (void*)block.GetMitkImage().GetPointer() << std::endl; - if (filenameDetails) - { - for (DICOMImageFrameList::const_iterator frameIter = frames.begin(); - frameIter != frames.end(); - ++frameIter) - { - os << " " << (*frameIter)->Filename; - if ((*frameIter)->FrameNo > 0) - { - os << ", " << (*frameIter)->FrameNo; - } - os << std::endl; - } - } + block.Print(os, filenameDetails); } os << "---- End of output list ----" << std::endl; } const mitk::DICOMImageBlockDescriptor& mitk::DICOMFileReader ::GetOutput(unsigned int index) const { if (index < m_Outputs.size()) { return m_Outputs[index]; } else { std::stringstream ss; ss << "Index " << index << " out of range (" << m_Outputs.size() << " indices reserved)"; throw std::invalid_argument( ss.str() ); } } mitk::DICOMImageBlockDescriptor& mitk::DICOMFileReader ::InternalGetOutput(unsigned int index) { if (index < m_Outputs.size()) { return m_Outputs[index]; } else { std::stringstream ss; ss << "Index " << index << " out of range (" << m_Outputs.size() << " indices reserved)"; throw std::invalid_argument( ss.str() ); } } bool mitk::DICOMFileReader ::IsDICOM(const std::string& filename) { itk::GDCMImageIO::Pointer io = itk::GDCMImageIO::New(); return io->CanReadFile( filename.c_str() ); } diff --git a/Modules/DICOMReader/mitkDICOMGDCMImageFrameInfo.cpp b/Modules/DICOMReader/mitkDICOMGDCMImageFrameInfo.cpp index 235d7f771a..5325e8f7b0 100644 --- a/Modules/DICOMReader/mitkDICOMGDCMImageFrameInfo.cpp +++ b/Modules/DICOMReader/mitkDICOMGDCMImageFrameInfo.cpp @@ -1,104 +1,111 @@ /*=================================================================== 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; + if (mappedValue->second != NULL) + { + return std::string(mappedValue->second); + } + else + { + return std::string(""); + } } 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 ""; + return std::string(""); } } 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/mitkDICOMITKSeriesGDCMReader.cpp b/Modules/DICOMReader/mitkDICOMITKSeriesGDCMReader.cpp index 7f1845f95b..7e915e22b3 100644 --- a/Modules/DICOMReader/mitkDICOMITKSeriesGDCMReader.cpp +++ b/Modules/DICOMReader/mitkDICOMITKSeriesGDCMReader.cpp @@ -1,463 +1,514 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkDICOMITKSeriesGDCMReader.h" #include "mitkITKDICOMSeriesReaderHelper.h" #include "mitkGantryTiltInformation.h" #include "mitkDICOMTagBasedSorter.h" #include #include +#include mitk::DICOMITKSeriesGDCMReader ::DICOMITKSeriesGDCMReader() :DICOMFileReader() ,m_FixTiltByShearing(true) { } mitk::DICOMITKSeriesGDCMReader ::DICOMITKSeriesGDCMReader(const DICOMITKSeriesGDCMReader& other ) :DICOMFileReader(other) ,m_FixTiltByShearing(false) { } mitk::DICOMITKSeriesGDCMReader ::~DICOMITKSeriesGDCMReader() { } mitk::DICOMITKSeriesGDCMReader& mitk::DICOMITKSeriesGDCMReader ::operator=(const DICOMITKSeriesGDCMReader& other) { if (this != &other) { DICOMFileReader::operator=(other); this->m_FixTiltByShearing = other.m_FixTiltByShearing; this->m_Sorter = other.m_Sorter; // TODO should clone the list items this->m_EquiDistantBlocksSorter = other.m_EquiDistantBlocksSorter->Clone(); } return *this; } void mitk::DICOMITKSeriesGDCMReader ::SetFixTiltByShearing(bool on) { m_FixTiltByShearing = on; } mitk::DICOMGDCMImageFrameList mitk::DICOMITKSeriesGDCMReader ::FromDICOMDatasetList(DICOMDatasetList& input) { DICOMGDCMImageFrameList output; output.reserve(input.size()); for(DICOMDatasetList::iterator inputIter = input.begin(); inputIter != input.end(); ++inputIter) { DICOMGDCMImageFrameInfo* gfi = dynamic_cast(*inputIter); assert(gfi); output.push_back(gfi); } return output; } mitk::DICOMDatasetList mitk::DICOMITKSeriesGDCMReader ::ToDICOMDatasetList(DICOMGDCMImageFrameList& input) { DICOMDatasetList output; output.reserve(input.size()); for(DICOMGDCMImageFrameList::iterator inputIter = input.begin(); inputIter != input.end(); ++inputIter) { DICOMDatasetAccess* da = inputIter->GetPointer(); assert(da); output.push_back(da); } return output; } mitk::DICOMImageFrameList mitk::DICOMITKSeriesGDCMReader ::ToDICOMImageFrameList(DICOMGDCMImageFrameList& input) { DICOMImageFrameList output; output.reserve(input.size()); for(DICOMGDCMImageFrameList::iterator inputIter = input.begin(); inputIter != input.end(); ++inputIter) { DICOMImageFrameInfo::Pointer fi = (*inputIter)->GetFrameInfo(); assert(fi.IsNotNull()); output.push_back(fi); } return output; } std::string mitk::DICOMITKSeriesGDCMReader ::GetActiveLocale() const { return setlocale(LC_NUMERIC, NULL); } void mitk::DICOMITKSeriesGDCMReader ::PushLocale() { std::string currentCLocale = setlocale(LC_NUMERIC, NULL); m_ReplacedCLocales.push( currentCLocale ); setlocale(LC_NUMERIC, "C"); std::locale currentCinLocale( std::cin.getloc() ); m_ReplacedCinLocales.push( currentCinLocale ); std::locale l( "C" ); std::cin.imbue(l); } void mitk::DICOMITKSeriesGDCMReader ::PopLocale() { if (!m_ReplacedCLocales.empty()) { setlocale(LC_NUMERIC, m_ReplacedCLocales.top().c_str()); m_ReplacedCLocales.pop(); } else { MITK_WARN << "Mismatched PopLocale on DICOMITKSeriesGDCMReader."; } if (!m_ReplacedCinLocales.empty()) { std::cin.imbue( m_ReplacedCinLocales.top() ); m_ReplacedCinLocales.pop(); } else { MITK_WARN << "Mismatched PopLocale on DICOMITKSeriesGDCMReader."; } } mitk::DICOMITKSeriesGDCMReader::SortingBlockList mitk::DICOMITKSeriesGDCMReader ::Condense3DBlocks(SortingBlockList& input) { return input; // to be implemented differently by sub-classes } void mitk::DICOMITKSeriesGDCMReader ::AnalyzeInputFiles() { // at this point, make sure we have a sorting element at the end that splits geometrically separate blocks this->EnsureMandatorySortersArePresent(); itk::TimeProbesCollectorBase timer; timer.Start("Reset"); this->ClearOutputs(); m_InputFrameList.clear(); m_GDCMScanner.ClearTags(); timer.Stop("Reset"); // prepare initial sorting (== list of input files) StringList inputFilenames = this->GetInputFiles(); // scan files for sorting-relevant tags timer.Start("Setup scanning"); for(SorterList::iterator sorterIter = m_Sorter.begin(); sorterIter != m_Sorter.end(); ++sorterIter) { assert(sorterIter->IsNotNull()); DICOMTagList tags = (*sorterIter)->GetTagsOfInterest(); for(DICOMTagList::const_iterator tagIter = tags.begin(); tagIter != tags.end(); ++tagIter) { MITK_DEBUG << "Sorting uses tag " << tagIter->GetGroup() << "," << tagIter->GetElement(); m_GDCMScanner.AddTag( gdcm::Tag(tagIter->GetGroup(), tagIter->GetElement()) ); } } // Add some of our own interest + // TODO all tags that are needed in DICOMImageBlockDescriptor should be added by DICOMFileReader (this code location here should query all superclasses for tags) m_GDCMScanner.AddTag( gdcm::Tag(0x0018,0x1164) ); // pixel spacing m_GDCMScanner.AddTag( gdcm::Tag(0x0028,0x0030) ); // imager pixel spacing m_GDCMScanner.AddTag( gdcm::Tag(0x0020,0x1041) ); // slice location m_GDCMScanner.AddTag( gdcm::Tag(0x0020,0x0013) ); // instance number - m_GDCMScanner.AddTag( gdcm::Tag(0x0008,0x0018) ); // sop instance number + m_GDCMScanner.AddTag( gdcm::Tag(0x0008,0x0016) ); // sop class UID + m_GDCMScanner.AddTag( gdcm::Tag(0x0008,0x0018) ); // sop instance UID + + m_GDCMScanner.AddTag( gdcm::Tag(0x0008,0x0060) ); // modality + m_GDCMScanner.AddTag( gdcm::Tag(0x0020,0x0012) ); // acquisition number + m_GDCMScanner.AddTag( gdcm::Tag(0x0018,0x0024) ); // sequence name + m_GDCMScanner.AddTag( gdcm::Tag(0x0020,0x0037) ); // image orientation + m_GDCMScanner.AddTag( gdcm::Tag(0x0020,0x0032) ); // ipp timer.Stop("Setup scanning"); timer.Start("Tag scanning"); PushLocale(); m_GDCMScanner.Scan( inputFilenames ); PopLocale(); timer.Stop("Tag scanning"); timer.Start("Setup sorting"); for (StringList::const_iterator inputIter = inputFilenames.begin(); inputIter != inputFilenames.end(); ++inputIter) { m_InputFrameList.push_back( DICOMGDCMImageFrameInfo::New( DICOMImageFrameInfo::New(*inputIter, 0), m_GDCMScanner.GetMapping(inputIter->c_str()) ) ); } m_SortingResultInProgress.clear(); m_SortingResultInProgress.push_back( m_InputFrameList ); timer.Stop("Setup sorting"); // sort and split blocks as configured timer.Start("Sorting frames"); SortingBlockList nextStepSorting; // we should not modify our input list while processing it unsigned int sorterIndex = 0; for(SorterList::iterator sorterIter = m_Sorter.begin(); sorterIter != m_Sorter.end(); ++sorterIndex, ++sorterIter) { std::stringstream ss; ss << "Sorting step " << sorterIndex; timer.Start( ss.str().c_str() ); nextStepSorting.clear(); DICOMDatasetSorter::Pointer& sorter = *sorterIter; MITK_DEBUG << "================================================================================"; - MITK_DEBUG << "DICOMIKTSeriesGDCMReader: " << ss.str() << ": " << m_SortingResultInProgress.size() << " groups input"; + MITK_DEBUG << "DICOMITKSeriesGDCMReader: " << ss.str() << ": " << m_SortingResultInProgress.size() << " groups input"; unsigned int groupIndex = 0; for(SortingBlockList::iterator blockIter = m_SortingResultInProgress.begin(); blockIter != m_SortingResultInProgress.end(); ++groupIndex, ++blockIter) { DICOMGDCMImageFrameList& gdcmInfoFrameList = *blockIter; DICOMDatasetList datasetList = ToDICOMDatasetList( gdcmInfoFrameList ); MITK_DEBUG << "--------------------------------------------------------------------------------"; - MITK_DEBUG << "DICOMIKTSeriesGDCMReader: " << ss.str() << ", dataset group " << groupIndex << " (" << datasetList.size() << " datasets): "; + MITK_DEBUG << "DICOMITKSeriesGDCMReader: " << ss.str() << ", dataset group " << groupIndex << " (" << datasetList.size() << " datasets): "; for (DICOMDatasetList::iterator oi = datasetList.begin(); oi != datasetList.end(); ++oi) { MITK_DEBUG << " INPUT : " << (*oi)->GetFilenameIfAvailable(); } sorter->SetInput(datasetList); sorter->Sort(); unsigned int numberOfResultingBlocks = sorter->GetNumberOfOutputs(); for (unsigned int b = 0; b < numberOfResultingBlocks; ++b) { DICOMDatasetList blockResult = sorter->GetOutput(b); for (DICOMDatasetList::iterator oi = blockResult.begin(); oi != blockResult.end(); ++oi) { MITK_DEBUG << " OUTPUT(" << b << ") :" << (*oi)->GetFilenameIfAvailable(); } DICOMGDCMImageFrameList sortedGdcmInfoFrameList = FromDICOMDatasetList(blockResult); nextStepSorting.push_back( sortedGdcmInfoFrameList ); } } m_SortingResultInProgress = nextStepSorting; timer.Stop( ss.str().c_str() ); } timer.Stop("Sorting frames"); timer.Start("Condensing 3D blocks (3D+t or vector values)"); m_SortingResultInProgress = this->Condense3DBlocks( m_SortingResultInProgress ); timer.Stop("Condensing 3D blocks (3D+t or vector values)"); // provide final result as output timer.Start("Output"); unsigned int o = this->GetNumberOfOutputs(); this->SetNumberOfOutputs( o + m_SortingResultInProgress.size() ); // Condense3DBlocks may already have added outputs! for (SortingBlockList::iterator blockIter = m_SortingResultInProgress.begin(); blockIter != m_SortingResultInProgress.end(); ++o, ++blockIter) { DICOMGDCMImageFrameList& gdcmFrameInfoList = *blockIter; DICOMImageFrameList frameList = ToDICOMImageFrameList( gdcmFrameInfoList ); assert(!gdcmFrameInfoList.empty()); assert(!frameList.empty()); DICOMImageBlockDescriptor block; + block.SetTagCache( this ); // important: this must be before SetImageFrameList(), because SetImageFrameList will trigger reading of lots of interesting tags! block.SetImageFrameList( frameList ); const GantryTiltInformation& tiltInfo = m_EquiDistantBlocksSorter->GetTiltInformation( (gdcmFrameInfoList.front())->GetFilenameIfAvailable() ); block.SetFlag("gantryTilt", tiltInfo.IsRegularGantryTilt()); block.SetTiltInformation( tiltInfo ); - // assume static const DICOMTag tagPixelSpacing(0x0028,0x0030); static const DICOMTag tagImagerPixelSpacing(0x0018,0x1164); std::string pixelSpacingString = (gdcmFrameInfoList.front())->GetTagValueAsString( tagPixelSpacing ); std::string imagerPixelSpacingString = gdcmFrameInfoList.front()->GetTagValueAsString( tagImagerPixelSpacing ); block.SetPixelSpacingInformation(pixelSpacingString, imagerPixelSpacingString); - block.SetTagCache( this ); + static const DICOMTag tagSOPClassUID(0x0008,0x0016); + std::string sopClassUID = (gdcmFrameInfoList.front())->GetTagValueAsString( tagSOPClassUID ); + block.SetSOPClassUID(sopClassUID); + + block.SetReaderImplementationLevel( this->GetReaderImplementationLevel(sopClassUID) ); this->SetOutput( o, block ); } timer.Stop("Output"); +#ifdef MBILOG_ENABLE_DEBUG std::cout << "---------------------------------------------------------------" << std::endl; timer.Report( std::cout ); std::cout << "---------------------------------------------------------------" << std::endl; +#endif +} + +mitk::ReaderImplementationLevel +mitk::DICOMITKSeriesGDCMReader +::GetReaderImplementationLevel(const std::string uid) const +{ + if (uid.empty()) + { + return SOPClassUnknown; + } + + gdcm::UIDs uidKnowledge; + uidKnowledge.SetFromUID( uid.c_str() ); + + gdcm::UIDs::TSType gdcmType = uidKnowledge; + + switch (gdcmType) + { + case gdcm::UIDs::CTImageStorage: + case gdcm::UIDs::MRImageStorage: + case gdcm::UIDs::PositronEmissionTomographyImageStorage: + case gdcm::UIDs::ComputedRadiographyImageStorage: + case gdcm::UIDs::DigitalXRayImageStorageForPresentation: + case gdcm::UIDs::DigitalXRayImageStorageForProcessing: + return SOPClassSupported; + + case gdcm::UIDs::NuclearMedicineImageStorage: + return SOPClassPartlySupported; + + case gdcm::UIDs::SecondaryCaptureImageStorage: + return SOPClassImplemented; + + default: + return SOPClassUnsupported; + } } // void AllocateOutputImages(); bool mitk::DICOMITKSeriesGDCMReader ::LoadImages() { bool success = true; unsigned int numberOfOutputs = this->GetNumberOfOutputs(); for (unsigned int o = 0; o < numberOfOutputs; ++o) { success &= this->LoadMitkImageForOutput(o); } return success; } bool mitk::DICOMITKSeriesGDCMReader ::LoadMitkImageForOutput(unsigned int o) { PushLocale(); DICOMImageBlockDescriptor& block = this->InternalGetOutput(o); const DICOMImageFrameList& frames = block.GetImageFrameList(); const GantryTiltInformation tiltInfo = block.GetTiltInformation(); bool hasTilt = block.GetFlag("gantryTilt", false); if (hasTilt) { MITK_DEBUG << "When loading image " << o << ": got tilt info:"; - tiltInfo.Print(std::cout); + //tiltInfo.Print(std::cout); } else { MITK_DEBUG << "When loading image " << o << ": has NO info."; } ITKDICOMSeriesReaderHelper::StringContainer filenames; for (DICOMImageFrameList::const_iterator frameIter = frames.begin(); frameIter != frames.end(); ++frameIter) { filenames.push_back( (*frameIter)->Filename ); } mitk::ITKDICOMSeriesReaderHelper helper; mitk::Image::Pointer mitkImage = helper.Load( filenames, m_FixTiltByShearing && hasTilt, tiltInfo ); // TODO preloaded images, caching..? block.SetMitkImage( mitkImage ); PopLocale(); return true; } bool mitk::DICOMITKSeriesGDCMReader ::CanHandleFile(const std::string& itkNotUsed(filename)) { return true; // can handle all // peek into file, check DCM // nice-to-have: check multi-framedness } void mitk::DICOMITKSeriesGDCMReader ::AddSortingElement(DICOMDatasetSorter* sorter, bool atFront) { assert(sorter); if (atFront) { m_Sorter.push_front( sorter ); } else { m_Sorter.push_back( sorter ); } } void mitk::DICOMITKSeriesGDCMReader ::EnsureMandatorySortersArePresent() { // TODO do just once! DICOMTagBasedSorter::Pointer splitter = DICOMTagBasedSorter::New(); splitter->AddDistinguishingTag( DICOMTag(0x0028, 0x0010) ); // Number of Rows splitter->AddDistinguishingTag( DICOMTag(0x0028, 0x0011) ); // Number of Columns splitter->AddDistinguishingTag( DICOMTag(0x0028, 0x0030) ); // Pixel Spacing splitter->AddDistinguishingTag( DICOMTag(0x0018, 0x1164) ); // Imager Pixel Spacing splitter->AddDistinguishingTag( DICOMTag(0x0020, 0x0037), new mitk::DICOMTagBasedSorter::CutDecimalPlaces(5) ); // Image Orientation (Patient) // TODO: configurable! splitter->AddDistinguishingTag( DICOMTag(0x0018, 0x0050) ); // Slice Thickness splitter->AddDistinguishingTag( DICOMTag(0x0028, 0x0008) ); // Number of Frames this->AddSortingElement( splitter, true ); // true = at front if (m_EquiDistantBlocksSorter.IsNull()) { m_EquiDistantBlocksSorter = mitk::EquiDistantBlocksSorter::New(); } m_EquiDistantBlocksSorter->SetAcceptTilt( m_FixTiltByShearing ); this->AddSortingElement( m_EquiDistantBlocksSorter ); } std::string mitk::DICOMITKSeriesGDCMReader ::GetTagValue(DICOMImageFrameInfo* frame, const DICOMTag& tag) const { for(DICOMGDCMImageFrameList::const_iterator frameIter = m_InputFrameList.begin(); frameIter != m_InputFrameList.end(); ++frameIter) { if ( (*frameIter)->GetFrameInfo().IsNotNull() && (*((*frameIter)->GetFrameInfo()) == *frame ) ) { return (*frameIter)->GetTagValueAsString(tag); } } - return "soso"; + + return ""; } diff --git a/Modules/DICOMReader/mitkDICOMITKSeriesGDCMReader.h b/Modules/DICOMReader/mitkDICOMITKSeriesGDCMReader.h index 39baad06e3..bd8f7882b9 100644 --- a/Modules/DICOMReader/mitkDICOMITKSeriesGDCMReader.h +++ b/Modules/DICOMReader/mitkDICOMITKSeriesGDCMReader.h @@ -1,116 +1,116 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef mitkDICOMITKSeriesGDCMReader_h #define mitkDICOMITKSeriesGDCMReader_h #include "mitkDICOMFileReader.h" #include "mitkDICOMDatasetSorter.h" #include "mitkDICOMGDCMImageFrameInfo.h" #include "mitkEquiDistantBlocksSorter.h" #include "DICOMReaderExports.h" #include // TODO tests seem to pass if reader creates NO output at all! -// TODO providing tags as properties! // TODO preloaded volumes?? could be solved in a different way.. namespace mitk { class DICOMReader_EXPORT DICOMITKSeriesGDCMReader : public DICOMFileReader, protected DICOMTagCache { public: mitkClassMacro( DICOMITKSeriesGDCMReader, DICOMFileReader ); mitkCloneMacro( DICOMITKSeriesGDCMReader ); itkNewMacro( DICOMITKSeriesGDCMReader ); virtual void AnalyzeInputFiles(); // void AllocateOutputImages(); virtual bool LoadImages(); virtual bool CanHandleFile(const std::string& filename); virtual void AddSortingElement(DICOMDatasetSorter* sorter, bool atFront = false); void SetFixTiltByShearing(bool on); protected: virtual std::string GetTagValue(DICOMImageFrameInfo* frame, const DICOMTag& tag) const; std::string GetActiveLocale() const; void PushLocale(); void PopLocale(); DICOMITKSeriesGDCMReader(); virtual ~DICOMITKSeriesGDCMReader(); DICOMITKSeriesGDCMReader(const DICOMITKSeriesGDCMReader& other); DICOMITKSeriesGDCMReader& operator=(const DICOMITKSeriesGDCMReader& other); DICOMDatasetList ToDICOMDatasetList(DICOMGDCMImageFrameList& input); DICOMGDCMImageFrameList FromDICOMDatasetList(DICOMDatasetList& input); DICOMImageFrameList ToDICOMImageFrameList(DICOMGDCMImageFrameList& input); typedef std::list SortingBlockList; /// \return REMAINING blocks virtual SortingBlockList Condense3DBlocks(SortingBlockList& resultOf3DGrouping); void EnsureMandatorySortersArePresent(); virtual bool LoadMitkImageForOutput(unsigned int o); Image::Pointer FixupSpacing(Image* mitkImage, const DICOMImageBlockDescriptor& block) const; + ReaderImplementationLevel GetReaderImplementationLevel(const std::string uid) const; private: protected: // NOT nice, made available to ThreeDnTDICOMSeriesReader due to lack of time bool m_FixTiltByShearing; // could be removed by ITKDICOMSeriesReader NOT flagging tilt unless requested to fix it! private: bool m_3DBlocksWereCondensed; SortingBlockList m_SortingResultInProgress; typedef std::list SorterList; SorterList m_Sorter; protected: // NOT nice, made available to ThreeDnTDICOMSeriesReader due to lack of time mitk::EquiDistantBlocksSorter::Pointer m_EquiDistantBlocksSorter; private: std::stack m_ReplacedCLocales; std::stack m_ReplacedCinLocales; DICOMGDCMImageFrameList m_InputFrameList; gdcm::Scanner m_GDCMScanner; }; } #endif diff --git a/Modules/DICOMReader/mitkDICOMImageBlockDescriptor.cpp b/Modules/DICOMReader/mitkDICOMImageBlockDescriptor.cpp index 3c216e79dc..e167bce651 100644 --- a/Modules/DICOMReader/mitkDICOMImageBlockDescriptor.cpp +++ b/Modules/DICOMReader/mitkDICOMImageBlockDescriptor.cpp @@ -1,415 +1,581 @@ /*=================================================================== 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" +#include "mitkStringProperty.h" +#include mitk::DICOMImageBlockDescriptor ::DICOMImageBlockDescriptor() -:m_PixelsInterpolated(false) -,m_PixelSpacingInterpretation() +:m_ReaderImplementationLevel(SOPClassUnknown) ,m_PixelSpacing("") ,m_ImagerPixelSpacing("") +,m_SOPClassUID("") ,m_PropertyList(PropertyList::New()) ,m_TagCache(NULL) { } 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_ReaderImplementationLevel( other.m_ReaderImplementationLevel ) ,m_PixelSpacing( other.m_PixelSpacing ) ,m_ImagerPixelSpacing( other.m_ImagerPixelSpacing ) +,m_SOPClassUID( other.m_SOPClassUID ) ,m_TiltInformation( other.m_TiltInformation ) ,m_PropertyList( other.m_PropertyList->Clone() ) ,m_TagCache( other.m_TagCache ) { 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_ReaderImplementationLevel = other.m_ReaderImplementationLevel; m_PixelSpacing = other.m_PixelSpacing; m_ImagerPixelSpacing = other.m_ImagerPixelSpacing; + m_SOPClassUID = other.m_SOPClassUID; m_TiltInformation = other.m_TiltInformation; if (other.m_PropertyList) { m_PropertyList = other.m_PropertyList->Clone(); } if (other.m_MitkImage) { m_MitkImage = other.m_MitkImage->Clone(); } m_TagCache = other.m_TagCache; } return *this; } void mitk::DICOMImageBlockDescriptor ::SetTiltInformation(const GantryTiltInformation& info) { m_TiltInformation = info; } const mitk::GantryTiltInformation mitk::DICOMImageBlockDescriptor ::GetTiltInformation() const { return m_TiltInformation; } void mitk::DICOMImageBlockDescriptor ::SetImageFrameList(const DICOMImageFrameList& framelist) { m_ImageFrameList = framelist; m_SliceIsLoaded.resize(framelist.size()); m_SliceIsLoaded.assign(framelist.size(), false); + + this->UpdateImageDescribingProperties(); } const mitk::DICOMImageFrameList& mitk::DICOMImageBlockDescriptor ::GetImageFrameList() const { return m_ImageFrameList; } void mitk::DICOMImageBlockDescriptor ::SetMitkImage(Image::Pointer image) { if (m_MitkImage != image) { m_MitkImage = this->DescribeImageWithProperties( this->FixupSpacing(image) ); } } mitk::Image::Pointer mitk::DICOMImageBlockDescriptor ::GetMitkImage() const { return m_MitkImage; } mitk::Image::Pointer mitk::DICOMImageBlockDescriptor ::FixupSpacing(Image* mitkImage) { if (mitkImage) { Vector3D imageSpacing = mitkImage->GetGeometry()->GetSpacing(); ScalarType desiredSpacingX = imageSpacing[0]; ScalarType desiredSpacingY = imageSpacing[1]; this->GetDesiredMITKImagePixelSpacing( desiredSpacingX, desiredSpacingY ); // prefer pixel spacing over imager pixel spacing MITK_DEBUG << "Loaded image with spacing " << imageSpacing[0] << ", " << imageSpacing[1]; MITK_DEBUG << "Found correct spacing info " << desiredSpacingX << ", " << desiredSpacingY; imageSpacing[0] = desiredSpacingX; imageSpacing[1] = desiredSpacingY; mitkImage->GetGeometry()->SetSpacing( imageSpacing ); } return 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 ) +::SetPixelSpacingInformation(const std::string& pixelSpacing, const std::string& imagerPixelSpacing) { - if (m_PixelSpacingInterpretation != interpretation) - { - m_PixelSpacingInterpretation = interpretation; - } + m_PixelSpacing = pixelSpacing; + m_ImagerPixelSpacing = imagerPixelSpacing; } +/* + PS defined IPS defined PS==IPS + 0 0 --> UNKNOWN spacing, loader will invent + 0 1 --> spacing as at detector surface + 1 0 --> spacing as in patient + 1 1 0 --> detector surface spacing CORRECTED for geometrical magnifications: spacing as in patient + 1 1 1 --> detector surface spacing NOT corrected for geometrical magnifications: spacing as at detector +*/ 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; + if (m_PixelSpacing.empty()) + { + if (m_ImagerPixelSpacing.empty()) + { + return SpacingUnknown; + } + else + { + return SpacingAtDetector; + } + } + else // Pixel Spacing defined + { + if (m_ImagerPixelSpacing.empty()) + { + return SpacingInPatient; + } + else if (m_PixelSpacing != m_ImagerPixelSpacing) + { + return SpacingInPatient; + } + else + { + return SpacingAtDetector; + } + } } void mitk::DICOMImageBlockDescriptor ::GetDesiredMITKImagePixelSpacing( ScalarType& spacingX, ScalarType& 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; } } } void mitk::DICOMImageBlockDescriptor ::SetProperty(const std::string& key, BaseProperty* value) { m_PropertyList->SetProperty(key, value); } mitk::BaseProperty* mitk::DICOMImageBlockDescriptor ::GetProperty(const std::string& key) const { return m_PropertyList->GetProperty(key); } +std::string +mitk::DICOMImageBlockDescriptor +::GetPropertyAsString(const std::string& key) const +{ + mitk::BaseProperty::Pointer property = m_PropertyList->GetProperty(key); + if (property.IsNotNull()) + { + return property->GetValueAsString(); + } + else + { + return std::string(""); + } +} + void mitk::DICOMImageBlockDescriptor ::SetFlag(const std::string& key, bool value) { m_PropertyList->ReplaceProperty(key, BoolProperty::New(value)); } bool mitk::DICOMImageBlockDescriptor ::GetFlag(const std::string& key, bool defaultValue) const { BoolProperty::ConstPointer boolProp = dynamic_cast( this->GetProperty(key) ); if (boolProp.IsNotNull()) { return boolProp->GetValue(); } else { return defaultValue; } } void mitk::DICOMImageBlockDescriptor ::SetIntProperty(const std::string& key, int value) { m_PropertyList->ReplaceProperty(key, IntProperty::New(value)); } int mitk::DICOMImageBlockDescriptor ::GetIntProperty(const std::string& key, int defaultValue) const { IntProperty::ConstPointer intProp = dynamic_cast( this->GetProperty(key) ); if (intProp.IsNotNull()) { return intProp->GetValue(); } else { return defaultValue; } } mitk::Image::Pointer mitk::DICOMImageBlockDescriptor ::DescribeImageWithProperties(Image* mitkImage) { - if (mitkImage && m_TagCache) - { - // first part: add some tags that describe individual slices + if (!mitkImage) return mitkImage; + // first part: add some tags that describe individual slices + // these propeties are defined at analysis time (see UpdateImageDescribingProperties()) - // TODO make sure this is available from scanning! - const DICOMTag tagSliceLocation(0x0020,0x1041); - const DICOMTag tagInstanceNumber(0x0020,0x0013); - const DICOMTag tagSOPInstanceNumber(0x0008,0x0018); + std::string propertyKeySliceLocation = "dicom.image.0020.1041"; + std::string propertyKeyInstanceNumber = "dicom.image.0020.0013"; + std::string propertyKeySOPInstanceUID = "dicom.image.0008.0018"; - unsigned int numberOfTimeSteps = mitkImage->GetDimension(3); - MITK_INFO << "Properties for " << numberOfTimeSteps << " timesteps"; - unsigned int numberOfFramesPerTimestep = m_ImageFrameList.size() / numberOfTimeSteps; + mitkImage->SetProperty( propertyKeySliceLocation.c_str(), this->GetProperty("sliceLocationForSlices") ); + mitkImage->SetProperty( propertyKeyInstanceNumber.c_str(), this->GetProperty("instanceNumberForSlices") ); + mitkImage->SetProperty( propertyKeySOPInstanceUID.c_str(), this->GetProperty("SOPInstanceUIDForSlices") ); - DICOMImageFrameList::const_iterator frameIter = m_ImageFrameList.begin(); - for (unsigned int timeStep = 0; - timeStep < numberOfTimeSteps; - ++timeStep) - { - std::string propertyKeySliceLocation = "dicom.image.0020.1041"; - std::string propertyKeyInstanceNumber = "dicom.image.0020.0013"; - std::string propertyKeySOPInstanceNumber = "dicom.image.0008.0018"; - - StringLookupTable filesForSlices; - StringLookupTable sliceLocationForSlices; - StringLookupTable instanceNumberForSlices; - StringLookupTable SOPInstanceNumberForSlices; - - for (unsigned int slice = 0; - slice < numberOfFramesPerTimestep; - ++slice, ++frameIter) - { - std::string sliceLocation = m_TagCache->GetTagValue( *frameIter, tagSliceLocation ); - sliceLocationForSlices.SetTableValue(slice, sliceLocation); - std::string instanceNumber = m_TagCache->GetTagValue( *frameIter, tagInstanceNumber ); - instanceNumberForSlices.SetTableValue(slice, instanceNumber); + // second part: add properties that describe the whole image block + mitkImage->SetProperty("dicomseriesreader.SOPClassUID", + StringProperty::New( m_SOPClassUID ) ); - std::string sopInstanceUID = m_TagCache->GetTagValue( *frameIter, tagSOPInstanceNumber ); - SOPInstanceNumberForSlices.SetTableValue(slice, sopInstanceUID); + mitkImage->SetProperty("dicomseriesreader.SOPClass", + StringProperty::New( this->GetSOPClassUIDAsString() )); - MITK_INFO << "Tag info for slice " << slice - << ": SL '" << sliceLocation - << "' IN '" << instanceNumber - << "' SOP instance UID '" << sopInstanceUID << "'"; - } + mitkImage->SetProperty("dicomseriesreader.PixelSpacingInterpretationString", + StringProperty::New( PixelSpacingInterpretationToString( this->GetPixelSpacingInterpretation() )) ); + mitkImage->SetProperty("dicomseriesreader.PixelSpacingInterpretation", + GenericProperty::New( this->GetPixelSpacingInterpretation() )); - if(timeStep != 0) - { - std::ostringstream postfix; - postfix << ".t" << timeStep; + mitkImage->SetProperty("dicomseriesreader.ReaderImplementationLevelString", + StringProperty::New( ReaderImplementationLevelToString( m_ReaderImplementationLevel ) )); + mitkImage->SetProperty("dicomseriesreader.ReaderImplementationLevel", + GenericProperty::New( m_ReaderImplementationLevel )); - propertyKeySliceLocation.append(postfix.str()); - propertyKeyInstanceNumber.append(postfix.str()); - propertyKeySOPInstanceNumber.append(postfix.str()); - } + mitkImage->SetProperty("dicomseriesreader.GantyTiltCorrected", + BoolProperty::New( this->GetFlag("gantryTilt",false) )); - // add property or properties with proper names - mitkImage->SetProperty( propertyKeySliceLocation.c_str(), StringLookupTableProperty::New( sliceLocationForSlices ) ); - mitkImage->SetProperty( propertyKeyInstanceNumber.c_str(), StringLookupTableProperty::New( instanceNumberForSlices ) ); - mitkImage->SetProperty( propertyKeySOPInstanceNumber.c_str(), StringLookupTableProperty::New( SOPInstanceNumberForSlices ) ); - } + mitkImage->SetProperty("dicomseriesreader.3D+t", + BoolProperty::New( this->GetFlag("3D+t",false) )); + + // third part: get something from ImageIO. BUT this needs to be created elsewhere. or not at all! + + return mitkImage; +} +void +mitk::DICOMImageBlockDescriptor +::SetReaderImplementationLevel(const ReaderImplementationLevel& level) +{ + m_ReaderImplementationLevel = level; +} - // second part: add properties that describe the whole image block +mitk::ReaderImplementationLevel +mitk::DICOMImageBlockDescriptor +::GetReaderImplementationLevel() const +{ + return m_ReaderImplementationLevel; +} - // TODO at this point here, simply provide all properties of DICOMImageBlockDescriptor as image properties - // TODO copy imageblockdescriptor as properties - /* - mitkImage->SetProperty("dicomseriesreader.SOPClass", StringProperty::New(blockInfo.GetSOPClassUIDAsString())); - mitkImage->SetProperty("dicomseriesreader.ReaderImplementationLevelString", StringProperty::New(ReaderImplementationLevelToString( blockInfo.GetReaderImplementationLevel() ))); - mitkImage->SetProperty("dicomseriesreader.ReaderImplementationLevel", GenericProperty::New( blockInfo.GetReaderImplementationLevel() )); - mitkImage->SetProperty("dicomseriesreader.PixelSpacingInterpretationString", StringProperty::New(PixelSpacingInterpretationToString( blockInfo.GetPixelSpacingType() ))); - mitkImage->SetProperty("dicomseriesreader.PixelSpacingInterpretation", GenericProperty::New(blockInfo.GetPixelSpacingType())); - mitkImage->SetProperty("dicomseriesreader.MultiFrameImage", BoolProperty::New(blockInfo.IsMultiFrameImage())); - mitkImage->SetProperty("dicomseriesreader.GantyTiltCorrected", BoolProperty::New(blockInfo.HasGantryTiltCorrected())); - mitkImage->SetProperty("dicomseriesreader.3D+t", BoolProperty::New(blockInfo.HasMultipleTimePoints())); - */ +void +mitk::DICOMImageBlockDescriptor +::SetSOPClassUID(const std::string& uid) +{ + m_SOPClassUID = uid; +} +std::string +mitk::DICOMImageBlockDescriptor +::GetSOPClassUID() const +{ + return m_SOPClassUID; +} - // third part: get something from ImageIO. BUT this needs to be created elsewhere. or not at all! +std::string +mitk::DICOMImageBlockDescriptor +::GetSOPClassUIDAsString() const +{ + gdcm::UIDs uidKnowledge; + uidKnowledge.SetFromUID( m_SOPClassUID.c_str() ); + const char* name = uidKnowledge.GetName(); + if (name) + { + return std::string(name); + } + else + { + return std::string(""); } - return mitkImage; } + void mitk::DICOMImageBlockDescriptor ::SetTagCache(DICOMTagCache* privateCache) { // TODO this must only be used during loading and never afterwards // TODO better: somehow make possible to have smartpointers here m_TagCache = privateCache; } + +#define printPropertyRange(label, property_name) \ +{ \ + std::string first = this->GetPropertyAsString( #property_name "First"); \ + std::string last = this->GetPropertyAsString( #property_name "Last"); \ + if (first == last) \ + { \ + os << " " label ": '" << first << "'" << std::endl; \ + } \ + else \ + { \ + os << " " label ": '" << first << "' - '" << last << "'" << std::endl; \ + } \ +} + +#define printProperty(label, property_name) \ +{ \ + std::string first = this->GetPropertyAsString( #property_name ); \ + os << " " label ": '" << first << "'" << std::endl; \ +} + +#define printBool(label, commands) \ +{ \ + os << " " label ": '" << (commands?"yes":"no") << "'" << std::endl; \ +} + + +void +mitk::DICOMImageBlockDescriptor +::Print(std::ostream& os, bool filenameDetails) const +{ + os << " Number of Frames: '" << m_ImageFrameList.size() << "'" << std::endl; + os << " SOP class: '" << this->GetSOPClassUIDAsString() << "'" << std::endl; + + printProperty("Modality", modality); + printProperty("Sequence Name", sequenceName); + + printPropertyRange("Slice Location", sliceLocation); + printPropertyRange("Acquisition Number", acquisitionNumber); + printPropertyRange("Instance Number", instanceNumber); + printPropertyRange("Image Position", imagePositionPatient); + printProperty("Image Orientation", orientation); + + os << " Pixel spacing interpretation: '" << PixelSpacingInterpretationToString(this->GetPixelSpacingInterpretation()) << "'" << std::endl; + printBool("Gantry Tilt", this->GetFlag("gantryTilt",false)) + printBool("3D+t", this->GetFlag("3D+t",false)) + + os << " MITK image loaded: '" << (this->GetMitkImage().IsNotNull() ? "yes" : "no") << "'" << std::endl; + if (filenameDetails) + { + for (DICOMImageFrameList::const_iterator frameIter = m_ImageFrameList.begin(); + frameIter != m_ImageFrameList.end(); + ++frameIter) + { + os << " File " << (*frameIter)->Filename; + if ((*frameIter)->FrameNo > 0) + { + os << ", " << (*frameIter)->FrameNo; + } + os << std::endl; + } + } +} + +#define storeTagValueToProperty(tag_name, tag_g, tag_e) \ +{ \ + const DICOMTag t(tag_g, tag_e); \ + std::string tagValue = m_TagCache->GetTagValue( firstFrame, t ); \ + this->SetProperty(#tag_name, StringProperty::New( tagValue ) ); \ +} + +#define storeTagValueRangeToProperty(tag_name, tag_g, tag_e) \ +{ \ + const DICOMTag t(tag_g, tag_e); \ + std::string tagValueFirst = m_TagCache->GetTagValue( firstFrame, t ); \ + std::string tagValueLast = m_TagCache->GetTagValue( lastFrame, t ); \ + this->SetProperty(#tag_name "First", StringProperty::New( tagValueFirst ) ); \ + this->SetProperty(#tag_name "Last", StringProperty::New( tagValueLast ) ); \ +} + + +void +mitk::DICOMImageBlockDescriptor +::UpdateImageDescribingProperties() +{ + if (m_TagCache && !m_ImageFrameList.empty()) + { + DICOMImageFrameInfo::Pointer firstFrame = m_ImageFrameList.front();; + DICOMImageFrameInfo::Pointer lastFrame = m_ImageFrameList.back();; + + // see macros above + storeTagValueToProperty(modality,0x0008,0x0060) + storeTagValueToProperty(sequenceName,0x0018,0x0024) + storeTagValueToProperty(orientation,0x0020,0x0037) + + storeTagValueRangeToProperty(sliceLocation,0x0020,0x1041) + storeTagValueRangeToProperty(acquisitionNumber,0x0020,0x0012) + storeTagValueRangeToProperty(instanceNumber,0x0020,0x0013) + storeTagValueRangeToProperty(imagePositionPatient,0x0020,0x0032) + + // some per-image attributes + // frames are just numbered starting from 0. timestep 1 (the second time-step) has frames starting at (number-of-frames-per-timestep) + std::string propertyKeySliceLocation = "dicom.image.0020.1041"; + std::string propertyKeyInstanceNumber = "dicom.image.0020.0013"; + std::string propertyKeySOPInstanceNumber = "dicom.image.0008.0018"; + + StringLookupTable sliceLocationForSlices; + StringLookupTable instanceNumberForSlices; + StringLookupTable SOPInstanceUIDForSlices; + + const DICOMTag tagSliceLocation(0x0020,0x1041); + const DICOMTag tagInstanceNumber(0x0020,0x0013); + const DICOMTag tagSOPInstanceNumber(0x0008,0x0018); + + unsigned int slice(0); + for (DICOMImageFrameList::const_iterator frameIter = m_ImageFrameList.begin(); + frameIter != m_ImageFrameList.end(); + ++slice, ++frameIter) + { + std::string sliceLocation = m_TagCache->GetTagValue( *frameIter, tagSliceLocation ); + sliceLocationForSlices.SetTableValue(slice, sliceLocation); + + std::string instanceNumber = m_TagCache->GetTagValue( *frameIter, tagInstanceNumber ); + instanceNumberForSlices.SetTableValue(slice, instanceNumber); + + std::string sopInstanceUID = m_TagCache->GetTagValue( *frameIter, tagSOPInstanceNumber ); + SOPInstanceUIDForSlices.SetTableValue(slice, sopInstanceUID); + + MITK_DEBUG << "Tag info for slice " << slice + << ": SL '" << sliceLocation + << "' IN '" << instanceNumber + << "' SOP instance UID '" << sopInstanceUID << "'"; + + // add property or properties with proper names + this->SetProperty( "sliceLocationForSlices", StringLookupTableProperty::New( sliceLocationForSlices ) ); + this->SetProperty( "instanceNumberForSlices", StringLookupTableProperty::New( instanceNumberForSlices ) ); + this->SetProperty( "SOPInstanceUIDForSlices", StringLookupTableProperty::New( SOPInstanceUIDForSlices ) ); + } + } +} diff --git a/Modules/DICOMReader/mitkDICOMImageBlockDescriptor.h b/Modules/DICOMReader/mitkDICOMImageBlockDescriptor.h index e2b7b973cc..0698dfd2d1 100644 --- a/Modules/DICOMReader/mitkDICOMImageBlockDescriptor.h +++ b/Modules/DICOMReader/mitkDICOMImageBlockDescriptor.h @@ -1,97 +1,109 @@ /*=================================================================== 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 mitkDICOMImageBlockDescriptor_h #define mitkDICOMImageBlockDescriptor_h #include "mitkDICOMEnums.h" #include "mitkDICOMImageFrameInfo.h" #include "mitkDICOMTag.h" #include "mitkDICOMTagCache.h" #include "mitkImage.h" #include "mitkProperties.h" #include "mitkGantryTiltInformation.h" namespace mitk { +/* + TODO describe attributes common and different for all blocks of a series (perhaps not here) +*/ + class DICOMReader_EXPORT DICOMImageBlockDescriptor { public: DICOMImageBlockDescriptor(); ~DICOMImageBlockDescriptor(); DICOMImageBlockDescriptor(const DICOMImageBlockDescriptor& other); DICOMImageBlockDescriptor& operator=(const DICOMImageBlockDescriptor& other); void SetImageFrameList(const DICOMImageFrameList& framelist); const DICOMImageFrameList& GetImageFrameList() const; void SetProperty(const std::string& key, BaseProperty* value); BaseProperty* GetProperty(const std::string& key) const; + std::string GetPropertyAsString(const std::string&) const; void SetFlag(const std::string& key, bool value); bool GetFlag(const std::string& key, bool defaultValue) const; void SetIntProperty(const std::string& key, int value); int GetIntProperty(const std::string& key, int defaultValue) const; void SetMitkImage(Image::Pointer image); Image::Pointer GetMitkImage() const; void SetSliceIsLoaded(unsigned int index, bool isLoaded); bool IsSliceLoaded(unsigned int index) const; bool AllSlicesAreLoaded() const; - void SetPixelsInterpolated(bool pixelsAreInterpolated); - bool GetPixelsInterpolated() const; - - void SetPixelSpacingInterpretation( PixelSpacingInterpretation interpretation ); - PixelSpacingInterpretation GetPixelSpacingInterpretation() const; - void SetPixelSpacingInformation(const std::string& pixelSpacing, const std::string& imagerPixelSpacing); + PixelSpacingInterpretation GetPixelSpacingInterpretation() const; void GetDesiredMITKImagePixelSpacing( ScalarType& spacingX, ScalarType& spacingY) const; void SetTiltInformation(const GantryTiltInformation& info); const GantryTiltInformation GetTiltInformation() const; + ReaderImplementationLevel GetReaderImplementationLevel() const; + void SetReaderImplementationLevel(const ReaderImplementationLevel& level); + + void SetSOPClassUID(const std::string& uid); + std::string GetSOPClassUID() const; + std::string GetSOPClassUIDAsString() const; + void SetTagCache(DICOMTagCache* privateCache); + void Print(std::ostream& os, bool filenameDetails) const; + private: Image::Pointer FixupSpacing(Image* mitkImage); Image::Pointer DescribeImageWithProperties(Image* mitkImage); + void UpdateImageDescribingProperties(); DICOMImageFrameList m_ImageFrameList; Image::Pointer m_MitkImage; BoolList m_SliceIsLoaded; - bool m_PixelsInterpolated; PixelSpacingInterpretation m_PixelSpacingInterpretation; + ReaderImplementationLevel m_ReaderImplementationLevel; std::string m_PixelSpacing; std::string m_ImagerPixelSpacing; + std::string m_SOPClassUID; + GantryTiltInformation m_TiltInformation; PropertyList::Pointer m_PropertyList; const DICOMTagCache* m_TagCache; }; } #endif diff --git a/Modules/DICOMReader/mitkITKDICOMSeriesReaderHelper.txx b/Modules/DICOMReader/mitkITKDICOMSeriesReaderHelper.txx index 45b2fb48f4..a4b3bb9796 100644 --- a/Modules/DICOMReader/mitkITKDICOMSeriesReaderHelper.txx +++ b/Modules/DICOMReader/mitkITKDICOMSeriesReaderHelper.txx @@ -1,298 +1,297 @@ /*=================================================================== 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 "mitkITKDICOMSeriesReaderHelper.h" #include #include //#include //#include //#include template mitk::Image::Pointer mitk::ITKDICOMSeriesReaderHelper ::LoadDICOMByITK( const StringContainer& filenames, bool correctTilt, const GantryTiltInformation& tiltInfo, itk::GDCMImageIO::Pointer& io, Image::Pointer preLoadedImageBlock ) { /******** Normal Case, 3D (also for GDCM < 2 usable) ***************/ mitk::Image::Pointer image = mitk::Image::New(); typedef itk::Image ImageType; typedef itk::ImageSeriesReader ReaderType; io = itk::GDCMImageIO::New(); typename ReaderType::Pointer reader = ReaderType::New(); reader->SetImageIO(io); reader->ReverseOrderOff(); if (preLoadedImageBlock.IsNull()) { reader->SetFileNames(filenames); reader->Update(); typename ImageType::Pointer readVolume = reader->GetOutput(); // if we detected that the images are from a tilted gantry acquisition, we need to push some pixels into the right position if (correctTilt) { readVolume = FixUpTiltedGeometry( reader->GetOutput(), tiltInfo ); } image->InitializeByItk(readVolume.GetPointer()); image->SetImportVolume(readVolume->GetBufferPointer()); } else { image = preLoadedImageBlock; StringContainer fakeList; fakeList.push_back( filenames.front() ); reader->SetFileNames( fakeList ); // we always need to load at least one file to get the MetaDataDictionary reader->Update(); } MITK_DEBUG << "Volume dimension: [" << image->GetDimension(0) << ", " << image->GetDimension(1) << ", " << image->GetDimension(2) << "]"; MITK_DEBUG << "Volume spacing: [" << image->GetGeometry()->GetSpacing()[0] << ", " << image->GetGeometry()->GetSpacing()[1] << ", " << image->GetGeometry()->GetSpacing()[2] << "]"; return image; } #define MITK_DEBUG_OUTPUT_FILELIST(list)\ MITK_DEBUG << "-------------------------------------------"; \ for (StringContainer::const_iterator _iter = (list).begin(); _iter!=(list).end(); ++_iter) \ { \ MITK_DEBUG <<" file '" << *_iter<< "'"; \ } \ MITK_DEBUG << "-------------------------------------------"; template mitk::Image::Pointer mitk::ITKDICOMSeriesReaderHelper ::LoadDICOMByITK3DnT( const StringContainerList& filenamesForTimeSteps, bool correctTilt, const GantryTiltInformation& tiltInfo, itk::GDCMImageIO::Pointer& io, Image::Pointer preLoadedImageBlock ) { unsigned int numberOfTimeSteps = filenamesForTimeSteps.size(); mitk::Image::Pointer image = mitk::Image::New(); typedef itk::Image ImageType; typedef itk::ImageSeriesReader ReaderType; io = itk::GDCMImageIO::New(); typename ReaderType::Pointer reader = ReaderType::New(); reader->SetImageIO(io); reader->ReverseOrderOff(); if (preLoadedImageBlock.IsNull()) { unsigned int currentTimeStep = 0; MITK_DEBUG << "Start loading timestep " << currentTimeStep; MITK_DEBUG_OUTPUT_FILELIST( filenamesForTimeSteps.front() ) reader->SetFileNames(filenamesForTimeSteps.front()); reader->Update(); typename ImageType::Pointer readVolume = reader->GetOutput(); // if we detected that the images are from a tilted gantry acquisition, we need to push some pixels into the right position if (correctTilt) { readVolume = FixUpTiltedGeometry( reader->GetOutput(), tiltInfo ); } image->InitializeByItk(readVolume.GetPointer(), 1, numberOfTimeSteps); image->SetImportVolume(readVolume->GetBufferPointer(), currentTimeStep++); // timestep 0 // for other time-steps for (StringContainerList::const_iterator timestepsIter = ++(filenamesForTimeSteps.begin()); // start with SECOND entry timestepsIter != filenamesForTimeSteps.end(); ++currentTimeStep, ++timestepsIter) { MITK_DEBUG << "Start loading timestep " << currentTimeStep; MITK_DEBUG_OUTPUT_FILELIST( *timestepsIter ) reader->SetFileNames(*timestepsIter); reader->Update(); readVolume = reader->GetOutput(); if (correctTilt) { readVolume = FixUpTiltedGeometry( reader->GetOutput(), tiltInfo ); } image->SetImportVolume(readVolume->GetBufferPointer(), currentTimeStep); } } else { // TODO check and fix image = preLoadedImageBlock; StringContainer fakeList; fakeList.push_back( filenamesForTimeSteps.front().front() ); reader->SetFileNames( fakeList ); // we always need to load at least one file to get the MetaDataDictionary reader->Update(); } MITK_DEBUG << "Volume dimension: [" << image->GetDimension(0) << ", " << image->GetDimension(1) << ", " << image->GetDimension(2) << "]"; MITK_DEBUG << "Volume spacing: [" << image->GetGeometry()->GetSpacing()[0] << ", " << image->GetGeometry()->GetSpacing()[1] << ", " << image->GetGeometry()->GetSpacing()[2] << "]"; return image; } template typename ImageType::Pointer mitk::ITKDICOMSeriesReaderHelper ::FixUpTiltedGeometry( ImageType* input, const GantryTiltInformation& tiltInfo ) { - tiltInfo.Print(std::cout); typedef itk::ResampleImageFilter ResampleFilterType; typename ResampleFilterType::Pointer resampler = ResampleFilterType::New(); resampler->SetInput( input ); /* Transform for a point is - transform from actual position to index coordinates - apply a shear that undoes the gantry tilt - transform back into world coordinates Anybody who does this in a simpler way: don't forget to write up how and why your solution works */ typedef itk::ScalableAffineTransform< double, ImageType::ImageDimension > TransformType; typename TransformType::Pointer transformShear = TransformType::New(); /** - apply a shear and spacing correction to the image block that corrects the ITK reader's error - ITK ignores the shear and loads slices into an orthogonal volume - ITK calculates the spacing from the origin distance, which is more than the actual spacing with gantry tilt images - to undo the effect - we have calculated some information in tiltInfo: - the shift in Y direction that is added with each additional slice is the most important information - the Y-shift is calculated in mm world coordinates - we apply a shearing transformation to the ITK-read image volume - to do this locally, - we transform the image volume back to origin and "normal" orientation by applying the inverse of its transform (this brings us into the image's "index coordinate" system) - we apply a shear with the Y-shift factor put into a unit transform at row 1, col 2 - we transform the image volume back to its actual position (from index to world coordinates) - we lastly apply modify the image spacing in z direction by replacing this number with the correctly calulcated inter-slice distance */ ScalarType factor = tiltInfo.GetMatrixCoefficientForCorrectionInWorldCoordinates() / input->GetSpacing()[1]; // row 1, column 2 corrects shear in parallel to Y axis, proportional to distance in Z direction transformShear->Shear( 1, 2, factor ); typename TransformType::Pointer imageIndexToWorld = TransformType::New(); imageIndexToWorld->SetOffset( input->GetOrigin().GetVectorFromOrigin() ); typename TransformType::MatrixType indexToWorldMatrix; indexToWorldMatrix = input->GetDirection(); typename ImageType::DirectionType scale; for ( unsigned int i = 0; i < ImageType::ImageDimension; i++ ) { scale[i][i] = input->GetSpacing()[i]; } indexToWorldMatrix *= scale; imageIndexToWorld->SetMatrix( indexToWorldMatrix ); typename TransformType::Pointer imageWorldToIndex = TransformType::New(); imageIndexToWorld->GetInverse( imageWorldToIndex ); typename TransformType::Pointer gantryTiltCorrection = TransformType::New(); gantryTiltCorrection->Compose( imageWorldToIndex ); gantryTiltCorrection->Compose( transformShear ); gantryTiltCorrection->Compose( imageIndexToWorld ); resampler->SetTransform( gantryTiltCorrection ); typedef itk::LinearInterpolateImageFunction< ImageType, double > InterpolatorType; typename InterpolatorType::Pointer interpolator = InterpolatorType::New(); resampler->SetInterpolator( interpolator ); /* This would be the right place to invent a meaningful value for positions outside of the image. For CT, HU -1000 might be meaningful, but a general solution seems not possible. Even for CT, -1000 would only look natural for many not all images. */ // TODO use (0028,0120) Pixel Padding Value if present resampler->SetDefaultPixelValue( itk::NumericTraits< typename ImageType::PixelType >::min() ); // adjust size in Y direction! (maybe just transform the outer last pixel to see how much space we would need resampler->SetOutputParametersFromImage( input ); // we basically need the same image again, just sheared // if tilt positive, then we need additional pixels BELOW origin, otherwise we need pixels behind the end of the block // in any case we need more size to accomodate shifted slices typename ImageType::SizeType largerSize = resampler->GetSize(); // now the resampler already holds the input image's size. double imageSizeZ = largerSize[2]; MITK_DEBUG <<"Calculate lager size = " << largerSize[1] << " + " << tiltInfo.GetTiltCorrectedAdditionalSize(imageSizeZ) << " / " << input->GetSpacing()[1] << "+ 2.0"; largerSize[1] += static_cast(tiltInfo.GetTiltCorrectedAdditionalSize(imageSizeZ) / input->GetSpacing()[1]+ 2.0); resampler->SetSize( largerSize ); MITK_DEBUG << "Fix Y size of image w/ spacing " << input->GetSpacing()[1] << " from " << input->GetLargestPossibleRegion().GetSize()[1] << " to " << largerSize[1]; // in SOME cases this additional size is below/behind origin if ( tiltInfo.GetMatrixCoefficientForCorrectionInWorldCoordinates() > 0.0 ) { typename ImageType::DirectionType imageDirection = input->GetDirection(); Vector3D yDirection; yDirection[0] = imageDirection[0][1]; yDirection[1] = imageDirection[1][1]; yDirection[2] = imageDirection[2][1]; yDirection.Normalize(); typename ImageType::PointType shiftedOrigin; shiftedOrigin = input->GetOrigin(); // add some pixels to make everything fit shiftedOrigin[0] -= yDirection[0] * (tiltInfo.GetTiltCorrectedAdditionalSize(imageSizeZ) + 1.0 * input->GetSpacing()[1]); shiftedOrigin[1] -= yDirection[1] * (tiltInfo.GetTiltCorrectedAdditionalSize(imageSizeZ) + 1.0 * input->GetSpacing()[1]); shiftedOrigin[2] -= yDirection[2] * (tiltInfo.GetTiltCorrectedAdditionalSize(imageSizeZ) + 1.0 * input->GetSpacing()[1]); resampler->SetOutputOrigin( shiftedOrigin ); } resampler->Update(); typename ImageType::Pointer result = resampler->GetOutput(); // ImageSeriesReader calculates z spacing as the distance between the first two origins. // This is not correct in case of gantry tilt, so we set our calculated spacing. typename ImageType::SpacingType correctedSpacing = result->GetSpacing(); correctedSpacing[2] = tiltInfo.GetRealZSpacing(); result->SetSpacing( correctedSpacing ); return result; } diff --git a/Modules/DICOMReader/mitkThreeDnTDICOMSeriesReader.cpp b/Modules/DICOMReader/mitkThreeDnTDICOMSeriesReader.cpp index 7f3f76ac22..e5ee67655a 100644 --- a/Modules/DICOMReader/mitkThreeDnTDICOMSeriesReader.cpp +++ b/Modules/DICOMReader/mitkThreeDnTDICOMSeriesReader.cpp @@ -1,245 +1,246 @@ /*=================================================================== 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() :DICOMITKSeriesGDCMReader() ,m_Group3DandT(true) { } mitk::ThreeDnTDICOMSeriesReader ::ThreeDnTDICOMSeriesReader(const ThreeDnTDICOMSeriesReader& other ) :DICOMITKSeriesGDCMReader(other) ,m_Group3DandT(true) { } mitk::ThreeDnTDICOMSeriesReader ::~ThreeDnTDICOMSeriesReader() { } mitk::ThreeDnTDICOMSeriesReader& mitk::ThreeDnTDICOMSeriesReader ::operator=(const ThreeDnTDICOMSeriesReader& other) { if (this != &other) { DICOMITKSeriesGDCMReader::operator=(other); this->m_Group3DandT = other.m_Group3DandT; } return *this; } void mitk::ThreeDnTDICOMSeriesReader ::SetGroup3DandT(bool on) { m_Group3DandT = on; } 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; // TODO we should provide this tag as needed via a function // (however, we currently know that the superclass will use this tag) const DICOMTag tagImagePositionPatient(0x0020, 0x0032); while (!remainingBlocks.empty()) { // new block to fill up DICOMGDCMImageFrameList& firstBlock = remainingBlocks.front(); DICOMGDCMImageFrameList current3DnTBlock = firstBlock; int current3DnTBlockNumberOfTimeSteps = 1; // get block characteristics of first block unsigned int currentBlockNumberOfSlices = firstBlock.size(); std::string currentBlockFirstOrigin = firstBlock.front()->GetTagValueAsString( tagImagePositionPatient ); std::string currentBlockLastOrigin = firstBlock.back()->GetTagValueAsString( tagImagePositionPatient ); remainingBlocks.pop_front(); // compare all other blocks against the first one for (SortingBlockList::iterator otherBlockIter = remainingBlocks.begin(); otherBlockIter != remainingBlocks.end(); /*++otherBlockIter*/) // <-- inside loop { // get block characteristics from first block DICOMGDCMImageFrameList& otherBlock = *otherBlockIter; unsigned int otherBlockNumberOfSlices = otherBlock.size(); std::string otherBlockFirstOrigin = otherBlock.front()->GetTagValueAsString( tagImagePositionPatient ); std::string otherBlockLastOrigin = otherBlock.back()->GetTagValueAsString( tagImagePositionPatient ); // add matching blocks to current3DnTBlock // keep other blocks for later if ( otherBlockNumberOfSlices == currentBlockNumberOfSlices && otherBlockFirstOrigin == currentBlockFirstOrigin && otherBlockLastOrigin == currentBlockLastOrigin ) { // matching block ++current3DnTBlockNumberOfTimeSteps; current3DnTBlock.insert( current3DnTBlock.end(), otherBlock.begin(), otherBlock.end() ); // append // remove this block from remainingBlocks otherBlockIter = remainingBlocks.erase(otherBlockIter); // make sure iterator otherBlockIter is valid afterwards } else { ++otherBlockIter; } } // in any case, we now now all about the first block of our list ... // ... and we wither call it 3D o 3D+t if (current3DnTBlockNumberOfTimeSteps > 1) { true3DnTBlocks.push_back(current3DnTBlock); true3DnTBlocksTimeStepCount.push_back(current3DnTBlockNumberOfTimeSteps); } else { non3DnTBlocks.push_back(current3DnTBlock); } } // create output for real 3D+t blocks (other outputs will be created by superclass) // set 3D+t flag on output block this->SetNumberOfOutputs( true3DnTBlocks.size() ); unsigned int o = 0; for (SortingBlockList::iterator blockIter = true3DnTBlocks.begin(); blockIter != true3DnTBlocks.end(); ++o, ++blockIter) { DICOMGDCMImageFrameList& gdcmFrameInfoList = *blockIter; DICOMImageFrameList frameList = ToDICOMImageFrameList( gdcmFrameInfoList ); assert(!gdcmFrameInfoList.empty()); assert(!frameList.empty()); DICOMImageBlockDescriptor block; + block.SetTagCache( this ); // important: this must be before SetImageFrameList(), because SetImageFrameList will trigger reading of lots of interesting tags! block.SetImageFrameList( frameList ); // bad copy&paste code, should be handled in a better way const GantryTiltInformation& tiltInfo = m_EquiDistantBlocksSorter->GetTiltInformation( (gdcmFrameInfoList.front())->GetFilenameIfAvailable() ); block.SetTiltInformation( tiltInfo ); // assume static const DICOMTag tagPixelSpacing(0x0028,0x0030); static const DICOMTag tagImagerPixelSpacing(0x0018,0x1164); std::string pixelSpacingString = (gdcmFrameInfoList.front())->GetTagValueAsString( tagPixelSpacing ); std::string imagerPixelSpacingString = gdcmFrameInfoList.front()->GetTagValueAsString( tagImagerPixelSpacing ); block.SetPixelSpacingInformation(pixelSpacingString, imagerPixelSpacingString); block.SetFlag("3D+t", true); block.SetIntProperty("timesteps", true3DnTBlocksTimeStepCount[o]); MITK_DEBUG << "Found " << true3DnTBlocksTimeStepCount[o] << " timesteps"; this->SetOutput( o, block ); } return non3DnTBlocks; } bool mitk::ThreeDnTDICOMSeriesReader ::LoadImages() { bool success = true; unsigned int numberOfOutputs = this->GetNumberOfOutputs(); for (unsigned int o = 0; o < numberOfOutputs; ++o) { DICOMImageBlockDescriptor& block = this->InternalGetOutput(o); if (block.GetFlag("3D+t", false)) { success &= this->LoadMitkImageForOutput(o); } else { success &= DICOMITKSeriesGDCMReader::LoadMitkImageForOutput(o); // let superclass handle non-3D+t } } return success; } // TODO why not handle 3D as a special case of 3D+t?? Late insight?.. .. because of ITK VImageDimension? .. could be a parameter bool mitk::ThreeDnTDICOMSeriesReader ::LoadMitkImageForOutput(unsigned int o) { PushLocale(); DICOMImageBlockDescriptor& block = this->InternalGetOutput(o); const DICOMImageFrameList& frames = block.GetImageFrameList(); const GantryTiltInformation tiltInfo = block.GetTiltInformation(); bool hasTilt = block.GetFlag("gantryTilt", false); if (hasTilt) { MITK_DEBUG << "When loading image " << o << ": got tilt info:"; - tiltInfo.Print(std::cout); + //tiltInfo.Print(std::cout); } else { MITK_DEBUG << "When loading image " << o << ": has NO info."; } int numberOfTimesteps = block.GetIntProperty("timesteps", 1); int numberOfFramesPerTimestep = frames.size() / numberOfTimesteps; assert( int(double((double)frames.size() / (double)numberOfTimesteps )) == numberOfFramesPerTimestep ); // this should hold ITKDICOMSeriesReaderHelper::StringContainerList filenamesPerTimestep; for (int timeStep = 0; timeStepFilename ); } filenamesPerTimestep.push_back( filenamesOfThisTimeStep ); } mitk::ITKDICOMSeriesReaderHelper helper; mitk::Image::Pointer mitkImage = helper.Load3DnT( filenamesPerTimestep, m_FixTiltByShearing && hasTilt, tiltInfo ); // TODO preloaded images, caching..? block.SetMitkImage( mitkImage ); PopLocale(); return true; }