diff --git a/Examples/mitkdump/mitkdump.cpp b/Examples/mitkdump/mitkdump.cpp index 116797a5b3..34a10db93a 100644 --- a/Examples/mitkdump/mitkdump.cpp +++ b/Examples/mitkdump/mitkdump.cpp @@ -1,231 +1,287 @@ /*=================================================================== 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. ===================================================================*/ /** \file mitkdump.cpp \brief Commandline application to see what DICOMFileReaderSelector would produce from a set of files. Usage: \verbatim mitkdump [-v] file1 [... fileN] -v output more details on commandline fileN DICOM file \endverbatim The application will ask a DICOMFileReaderSelector to analyze the files given as file1 .. fileN. Once the reader with the least number of files is selected, this result is printed to commandline. If the "-v" flag is used (as a first parameter), the output will contain details about filenames, which can make the output considerably harder to read. Output is also written to a log file of name "%gt;datetime-stamp%lt;_dir_>directory-name<.mitkdump */ #include "mitkDICOMFileReaderSelector.h" +#include "mitkDICOMReaderConfigurator.h" +#include "mitkDICOMImageFrameInfo.h" using mitk::DICOMTag; std::string buildDateString() { std::time_t rawtime; std::tm* timeinfo; char buffer [80]; std::time(&rawtime); timeinfo = std::localtime(&rawtime); std::strftime(buffer,80,"%Y%m%d-%H%M%S",timeinfo); return std::string(buffer); } void gen_random(char *s, const int len) { static const char alphanum[] = "0123456789" "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; for (int i = 0; i < len; ++i) { s[i] = alphanum[rand() % (sizeof(alphanum) - 1)]; } s[len] = 0; } std::string gen_random(const int len) { if (len > 0 && len < 100) { char retval[100]; gen_random(retval, len); return std::string(retval); } else { return std::string(""); } } std::string removeUnsafeChars(const std::string& str) { std::string retval; for(std::string::const_iterator it = str.begin(); it != str.end(); ++it) { const char& c = *it; if ( (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c == '.') || (c == '-') || (c == '_') ) { retval += c; } } return retval; } std::string extractDirString(const std::string& dirString) { std::string wholeprefix = dirString.substr(0, dirString.find_last_of("/\\")); std::string lastDirectoryPart = wholeprefix.substr(wholeprefix.find_last_of("/\\")+1); std::string cleanLastDirectoryPart = removeUnsafeChars(lastDirectoryPart); if (!cleanLastDirectoryPart.empty()) { return cleanLastDirectoryPart; } else { std::stringstream emptydirname; emptydirname << "noname_" << gen_random(6); return emptydirname.str(); } } int main(int argc, char* argv[]) { bool fileDetails(false); bool loadimage(false); int firstFileIndex = 1; // see if we got the '-v' flag to output file details if (argc > 1 && std::string(argv[firstFileIndex]) == "-v") { fileDetails = true; ++firstFileIndex; } // see if we got the '-l' flag if (argc > 1 && std::string(argv[firstFileIndex]) == "-l") { loadimage = true; ++firstFileIndex; } // analyze files from argv mitk::StringList inputFiles; for (int a = firstFileIndex; a < argc; ++a) { inputFiles.push_back( std::string(argv[a]) ); } if (inputFiles.empty()) { MITK_INFO << "0 input files given, exiting..."; return EXIT_SUCCESS; } mitk::DICOMFileReaderSelector::Pointer configSelector = mitk::DICOMFileReaderSelector::New(); configSelector->LoadBuiltIn3DConfigs(); // a set of compiled in ressources with standard configurations that work well configSelector->SetInputFiles( inputFiles ); mitk::DICOMFileReader::Pointer reader = configSelector->GetFirstReaderWithMinimumNumberOfOutputImages(); if (reader.IsNull()) { MITK_ERROR << "Could not configure any DICOM reader.. Exiting..."; return EXIT_FAILURE; } // output best reader result MITK_INFO << "---- Best reader configuration '" << reader->GetConfigurationLabel() << "' with " << reader->GetNumberOfOutputs() << " outputs"; if (fileDetails) { reader->PrintOutputs(std::cout, fileDetails); } // construct the name of a log file std::string datestring = buildDateString();; std::string dirString = extractDirString(argv[firstFileIndex]); std::string logfilename = datestring + "_dir_" + dirString + ".mitkdump"; MITK_INFO << "Logfile " << logfilename; // write output to file for later analysis std::ofstream fs; fs.open(logfilename.c_str()); fs << "---- " << dirString << ": Best reader configuration '" << reader->GetConfigurationLabel() << "' with " << reader->GetNumberOfOutputs() << " outputs" << std::endl; reader->PrintOutputs( fs, true); // always verbose in log file fs.close(); + // serialize, deserialize, analyze again, verify result! + mitk::DICOMReaderConfigurator::Pointer serializer = mitk::DICOMReaderConfigurator::New(); + std::string readerSerialization = serializer->CreateConfigStringFromReader(reader.GetPointer()); + + bool outputError(false); + for (unsigned int outputIndex = 0; outputIndex < reader->GetNumberOfOutputs(); ++outputIndex) + { + const mitk::DICOMImageBlockDescriptor& outputDescriptor = reader->GetOutput(outputIndex); + + mitk::StringList filenamesOfThisGroup; + const mitk::DICOMImageFrameList& frames = outputDescriptor.GetImageFrameList(); + for (mitk::DICOMImageFrameList::const_iterator fIter = frames.begin(); fIter != frames.end(); ++fIter) + { + filenamesOfThisGroup.push_back( (*fIter)->Filename ); + } + + mitk::DICOMReaderConfigurator::Pointer readerConfigurator = mitk::DICOMReaderConfigurator::New(); + mitk::DICOMFileReader::Pointer dicomReader = readerConfigurator->CreateFromUTF8ConfigString( readerSerialization ); + dicomReader->SetInputFiles( filenamesOfThisGroup ); + dicomReader->AnalyzeInputFiles(); + if (dicomReader->GetNumberOfOutputs() != 1) + { + MITK_ERROR << "****** Re-analyzing files of output group " << outputIndex << " yields " << dicomReader->GetNumberOfOutputs() << " groups"; + outputError = true; + + for (mitk::DICOMImageFrameList::const_iterator fIter = frames.begin(); fIter != frames.end(); ++fIter) + { + MITK_INFO << "filename group " << outputIndex << ": " << (*fIter)->Filename; + } + } + else + { + MITK_INFO << "Re-analyzing files of output group " << outputIndex << " yields " << dicomReader->GetNumberOfOutputs() << " groups"; + } + } + + if (outputError) + { + std::stringstream es; + es << "Original reader configuration: " << std::endl; + reader->PrintConfiguration(es); + es << std::endl; + mitk::DICOMReaderConfigurator::Pointer readerConfigurator = mitk::DICOMReaderConfigurator::New(); + mitk::DICOMFileReader::Pointer dicomReader = readerConfigurator->CreateFromUTF8ConfigString( readerSerialization ); + es << "New reader configuration: " << std::endl; + dicomReader->PrintConfiguration(es); + es << std::endl; + + es << "Original XML: \n" << readerSerialization << std::endl; + std::string newSerialization = serializer->CreateConfigStringFromReader(dicomReader.GetPointer()); + es << "New XML: \n" << newSerialization << std::endl; + MITK_ERROR << es.str(); + } + if (loadimage) { MITK_INFO << "Loading..."; reader->LoadImages(); mitk::Image::Pointer image = reader->GetOutput(0).GetMitkImage(); MITK_INFO << "---- Output image:"; mitk::Geometry3D::Pointer geo3D = image->GetGeometry(); if (geo3D.IsNotNull()) { mitk::SlicedGeometry3D::Pointer sg = dynamic_cast(geo3D.GetPointer()); if (sg.IsNotNull()) { unsigned int nos = sg->GetSlices(); mitk::Geometry2D::Pointer first = sg->GetGeometry2D(0); mitk::Geometry2D::Pointer last = sg->GetGeometry2D(nos-1); mitk::Point3D firstOrigin = first->GetOrigin(); mitk::Point3D lastOrigin = last->GetOrigin(); MITK_INFO << "Geometry says: First slice at " << firstOrigin << ", last slice at " << lastOrigin; mitk::StringLookupTableProperty::Pointer sliceLocations = dynamic_cast( image->GetProperty("dicom.image.0020.1041").GetPointer() ); if (sliceLocations.IsNotNull()) { std::string firstSliceLocation = sliceLocations->GetValue().GetTableValue(0); std::string lastSliceLocation = sliceLocations->GetValue().GetTableValue(nos-1); MITK_INFO << "Image properties says: first slice location at " << firstSliceLocation << ", last slice location at " << lastSliceLocation; } mitk::StringLookupTableProperty::Pointer instanceNumbers = dynamic_cast( image->GetProperty("dicom.image.0020.0013").GetPointer() ); if (instanceNumbers.IsNotNull()) { std::string firstInstanceNumber = instanceNumbers->GetValue().GetTableValue(0); std::string lastInstanceNumber = instanceNumbers->GetValue().GetTableValue(nos-1); MITK_INFO << "Image properties says: first instance number at " << firstInstanceNumber << ", last instance number at " << lastInstanceNumber; } } } MITK_INFO << "---- End of output"; } // if we got so far, everything is fine return EXIT_SUCCESS; } diff --git a/Modules/DICOMReader/Resources/configurations/3D/instancenumber_soft.xml b/Modules/DICOMReader/Resources/configurations/3D/instancenumber_soft.xml new file mode 100644 index 0000000000..fc71950a22 --- /dev/null +++ b/Modules/DICOMReader/Resources/configurations/3D/instancenumber_soft.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + diff --git a/Modules/DICOMReader/files.cmake b/Modules/DICOMReader/files.cmake index e83fc4b691..3e530f565b 100644 --- a/Modules/DICOMReader/files.cmake +++ b/Modules/DICOMReader/files.cmake @@ -1,58 +1,58 @@ set(H_FILES mitkDICOMFileReader.h mitkDICOMGDCMTagScanner.h mitkDICOMImageFrameInfo.h mitkDICOMImageBlockDescriptor.h mitkDICOMGDCMImageFrameInfo.h mitkDICOMITKSeriesGDCMReader.h mitkDICOMDatasetSorter.h mitkDICOMEnums.h mitkDICOMTagBasedSorter.h mitkDICOMSortCriterion.h mitkDICOMSortByTag.h mitkEquiDistantBlocksSorter.h mitkNormalDirectionConsistencySorter.h mitkSortByImagePositionPatient.h mitkClassicDICOMSeriesReader.h mitkThreeDnTDICOMSeriesReader.h mitkDICOMTag.h mitkDICOMTagCache.h mitkDICOMReaderConfigurator.h mitkDICOMFileReaderSelector.h ) set(CPP_FILES mitkDICOMFileReader.cpp mitkDICOMGDCMTagScanner.cpp mitkDICOMImageBlockDescriptor.cpp mitkDICOMITKSeriesGDCMReader.cpp mitkDICOMDatasetSorter.cpp mitkDICOMTagBasedSorter.cpp mitkDICOMGDCMImageFrameInfo.cpp mitkDICOMImageFrameInfo.cpp mitkDICOMSortCriterion.cpp mitkDICOMSortByTag.cpp mitkITKDICOMSeriesReaderHelper.cpp mitkEquiDistantBlocksSorter.cpp mitkNormalDirectionConsistencySorter.cpp mitkSortByImagePositionPatient.cpp mitkGantryTiltInformation.cpp mitkClassicDICOMSeriesReader.cpp mitkThreeDnTDICOMSeriesReader.cpp mitkDICOMTag.cpp mitkDICOMTagCache.cpp mitkDICOMEnums.cpp mitkDICOMReaderConfigurator.cpp mitkDICOMFileReaderSelector.cpp ) set(RESOURCE_FILES configurations/3D/classicreader.xml configurations/3D/imageposition.xml configurations/3D/imageposition_byacquisition.xml configurations/3D/instancenumber.xml + configurations/3D/instancenumber_soft.xml configurations/3D/slicelocation.xml - configurations/3D/imagetime.xml configurations/3DnT/classicreader.xml ) diff --git a/Modules/DICOMReader/mitkDICOMFileReaderSelector.cpp b/Modules/DICOMReader/mitkDICOMFileReaderSelector.cpp index 9ca4dc5391..1548a9f895 100644 --- a/Modules/DICOMReader/mitkDICOMFileReaderSelector.cpp +++ b/Modules/DICOMReader/mitkDICOMFileReaderSelector.cpp @@ -1,259 +1,260 @@ /*=================================================================== 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 "mitkDICOMFileReaderSelector.h" #include "mitkDICOMReaderConfigurator.h" #include "mitkDICOMGDCMTagScanner.h" #include #include #include #include #include mitk::DICOMFileReaderSelector ::DICOMFileReaderSelector() { } mitk::DICOMFileReaderSelector ::~DICOMFileReaderSelector() { } std::list mitk::DICOMFileReaderSelector ::GetAllConfiguredReaders() const { return m_Readers; } void mitk::DICOMFileReaderSelector ::AddConfigsFromResources(const std::string& path) { std::vector configs = us::GetModuleContext()->GetModule()->FindResources(path, "*.xml", false); for (std::vector::iterator iter = configs.begin(); iter != configs.end(); ++iter) { us::ModuleResource& resource = *iter; if (resource.IsValid()) { us::ModuleResourceStream stream(resource); // read all into string s std::string s; stream.seekg(0, std::ios::end); s.reserve(stream.tellg()); stream.seekg(0, std::ios::beg); s.assign((std::istreambuf_iterator(stream)), std::istreambuf_iterator()); this->AddConfig(s); } } } void mitk::DICOMFileReaderSelector ::AddConfigFromResource(us::ModuleResource& resource) { if (resource.IsValid()) { us::ModuleResourceStream stream(resource); // read all into string s std::string s; stream.seekg(0, std::ios::end); s.reserve(stream.tellg()); stream.seekg(0, std::ios::beg); s.assign((std::istreambuf_iterator(stream)), std::istreambuf_iterator()); this->AddConfig(s); } } void mitk::DICOMFileReaderSelector ::AddConfigFromResource(const std::string& resourcename) { us::ModuleResource r = us::GetModuleContext()->GetModule()->GetResource(resourcename); this->AddConfigFromResource(r); } void mitk::DICOMFileReaderSelector ::AddFileReaderCanditate(DICOMFileReader::Pointer reader) { if (reader.IsNotNull()) { m_Readers.push_back( reader ); } } void mitk::DICOMFileReaderSelector ::LoadBuiltIn3DConfigs() { //this->AddConfigsFromResources("configurations/3D"); // in this order of preference... this->AddConfigFromResource("configurations/3D/instancenumber.xml"); + this->AddConfigFromResource("configurations/3D/instancenumber_soft.xml"); this->AddConfigFromResource("configurations/3D/slicelocation.xml"); this->AddConfigFromResource("configurations/3D/imageposition.xml"); this->AddConfigFromResource("configurations/3D/imageposition_byacquisition.xml"); //this->AddConfigFromResource("configurations/3D/classicreader.xml"); // not the best choice in ANY of the images I came across } void mitk::DICOMFileReaderSelector ::LoadBuiltIn3DnTConfigs() { this->AddConfigsFromResources("configurations/3DnT"); } void mitk::DICOMFileReaderSelector ::AddConfig(const std::string& xmlDescription) { DICOMReaderConfigurator::Pointer configurator = DICOMReaderConfigurator::New(); DICOMFileReader::Pointer reader = configurator->CreateFromUTF8ConfigString(xmlDescription); if (reader.IsNotNull()) { m_Readers.push_back( reader ); m_PossibleConfigurations.push_back(xmlDescription); } else { std::stringstream ss; ss << "Could not parse reader configuration. Ignoring it."; throw std::invalid_argument( ss.str() ); } } void mitk::DICOMFileReaderSelector ::AddConfigFile(const std::string& filename) { std::ifstream file(filename.c_str()); std::string s; file.seekg(0, std::ios::end); s.reserve(file.tellg()); file.seekg(0, std::ios::beg); s.assign((std::istreambuf_iterator(file)), std::istreambuf_iterator()); this->AddConfig(s); } void mitk::DICOMFileReaderSelector ::SetInputFiles(StringList filenames) { m_InputFilenames = filenames; } const mitk::StringList& mitk::DICOMFileReaderSelector ::GetInputFiles() const { return m_InputFilenames; } mitk::DICOMFileReader::Pointer mitk::DICOMFileReaderSelector ::GetFirstReaderWithMinimumNumberOfOutputImages() { ReaderList workingCandidates; // do the tag scanning externally and just ONCE DICOMGDCMTagScanner::Pointer gdcmScanner = DICOMGDCMTagScanner::New(); gdcmScanner->SetInputFiles( m_InputFilenames ); // let all readers analyze the file set for (ReaderList::iterator rIter = m_Readers.begin(); rIter != m_Readers.end(); ++rIter) { gdcmScanner->AddTags( (*rIter)->GetTagsOfInterest() ); } gdcmScanner->Scan(); // let all readers analyze the file set unsigned int readerIndex(0); for (ReaderList::iterator rIter = m_Readers.begin(); rIter != m_Readers.end(); ++readerIndex, ++rIter) { (*rIter)->SetInputFiles( m_InputFilenames ); (*rIter)->SetTagCache( gdcmScanner.GetPointer() ); try { (*rIter)->AnalyzeInputFiles(); workingCandidates.push_back( *rIter ); MITK_INFO << "Reader " << readerIndex << " (" << (*rIter)->GetConfigurationLabel() << ") suggests " << (*rIter)->GetNumberOfOutputs() << " 3D blocks"; if ((*rIter)->GetNumberOfOutputs() == 1) { MITK_DEBUG << "Early out with reader #" << readerIndex << " (" << (*rIter)->GetConfigurationLabel() << "), less than 1 block is not possible"; return *rIter; } } catch (std::exception& e) { MITK_ERROR << "Reader " << readerIndex << " (" << (*rIter)->GetConfigurationLabel() << ") threw exception during file analysis, ignoring this reader. Exception: " << e.what(); } catch (...) { MITK_ERROR << "Reader " << readerIndex << " (" << (*rIter)->GetConfigurationLabel() << ") threw unknown exception during file analysis, ignoring this reader."; } } DICOMFileReader::Pointer bestReader; unsigned int minimumNumberOfOutputs = std::numeric_limits::max(); readerIndex = 0; unsigned int bestReaderIndex(0); // select the reader with the minimum number of mitk::Images as output for (ReaderList::iterator rIter = workingCandidates.begin(); rIter != workingCandidates.end(); ++readerIndex, ++rIter) { unsigned int thisReadersNumberOfOutputs = (*rIter)->GetNumberOfOutputs(); if ( thisReadersNumberOfOutputs > 0 // we don't count readers that don't actually produce output && thisReadersNumberOfOutputs < minimumNumberOfOutputs ) { minimumNumberOfOutputs = (*rIter)->GetNumberOfOutputs(); bestReader = *rIter; bestReaderIndex = readerIndex; } } MITK_DEBUG << "Decided for reader #" << bestReaderIndex << " (" << bestReader->GetConfigurationLabel() << ")"; MITK_DEBUG << m_PossibleConfigurations[bestReaderIndex]; return bestReader; } diff --git a/Modules/DICOMReader/mitkDICOMImageBlockDescriptor.cpp b/Modules/DICOMReader/mitkDICOMImageBlockDescriptor.cpp index a312453acd..97f6c9a836 100644 --- a/Modules/DICOMReader/mitkDICOMImageBlockDescriptor.cpp +++ b/Modules/DICOMReader/mitkDICOMImageBlockDescriptor.cpp @@ -1,756 +1,759 @@ /*=================================================================== 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 "mitkLevelWindowProperty.h" #include mitk::DICOMImageBlockDescriptor ::DICOMImageBlockDescriptor() :m_ReaderImplementationLevel(SOPClassUnknown) ,m_PropertyList(PropertyList::New()) ,m_TagCache(NULL) ,m_PropertiesOutOfDate(true) { } 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_ReaderImplementationLevel( other.m_ReaderImplementationLevel ) ,m_TiltInformation( other.m_TiltInformation ) ,m_PropertyList( other.m_PropertyList->Clone() ) ,m_TagCache( other.m_TagCache ) ,m_PropertiesOutOfDate( other.m_PropertiesOutOfDate ) { 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_ReaderImplementationLevel = other.m_ReaderImplementationLevel; 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; m_PropertiesOutOfDate = other.m_PropertiesOutOfDate; } return *this; } mitk::DICOMTagList mitk::DICOMImageBlockDescriptor::GetTagsOfInterest() { DICOMTagList completeList; completeList.push_back( DICOMTag(0x0018,0x1164) ); // pixel spacing completeList.push_back( DICOMTag(0x0028,0x0030) ); // imager pixel spacing completeList.push_back( DICOMTag(0x0008,0x0018) ); // sop instance UID completeList.push_back( DICOMTag(0x0008,0x0016) ); // sop class UID completeList.push_back( DICOMTag(0x0020,0x0011) ); // series number completeList.push_back( DICOMTag(0x0008,0x1030) ); // study description completeList.push_back( DICOMTag(0x0008,0x103e) ); // series description completeList.push_back( DICOMTag(0x0008,0x0060) ); // modality completeList.push_back( DICOMTag(0x0018,0x0024) ); // sequence name completeList.push_back( DICOMTag(0x0020,0x0037) ); // image orientation completeList.push_back( DICOMTag(0x0020,0x1041) ); // slice location completeList.push_back( DICOMTag(0x0020,0x0012) ); // acquisition number completeList.push_back( DICOMTag(0x0020,0x0013) ); // instance number completeList.push_back( DICOMTag(0x0020,0x0032) ); // image position patient completeList.push_back( DICOMTag(0x0028,0x1050) ); // window center completeList.push_back( DICOMTag(0x0028,0x1051) ); // window width completeList.push_back( DICOMTag(0x0008,0x0008) ); // image type completeList.push_back( DICOMTag(0x0028,0x0004) ); // photometric interpretation return completeList; } 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); m_PropertiesOutOfDate = true; } const mitk::DICOMImageFrameList& mitk::DICOMImageBlockDescriptor ::GetImageFrameList() const { return m_ImageFrameList; } void mitk::DICOMImageBlockDescriptor ::SetMitkImage(Image::Pointer image) { if (m_MitkImage != image) { if (m_TagCache.IsNull()) { MITK_ERROR << "Unable to describe MITK image with properties without a tag-cache object!"; m_MitkImage = NULL; return; } if (m_ImageFrameList.empty()) { MITK_ERROR << "Unable to describe MITK image with properties without a frame list!"; m_MitkImage = NULL; return; } // Should verify that the image matches m_ImageFrameList and m_TagCache // however, this is hard to do without re-analyzing all // TODO we should at least make sure that the number of frames is identical (plus rows/columns, orientation) // without gantry tilt correction, we can also check image origin 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; } /* 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 { if ( m_ImageFrameList.empty() || m_TagCache.IsNull() ) { MITK_ERROR << "Invalid call to GetPixelSpacingInterpretation. Need to have initialized tag-cache!"; return SpacingUnknown; } std::string pixelSpacing = this->GetPixelSpacing(); std::string imagerPixelSpacing = this->GetImagerPixelSpacing(); if (pixelSpacing.empty()) { if (imagerPixelSpacing.empty()) { return SpacingUnknown; } else { return SpacingAtDetector; } } else // Pixel Spacing defined { if (imagerPixelSpacing.empty()) { return SpacingInPatient; } else if (pixelSpacing != imagerPixelSpacing) { return SpacingInPatient; } else { return SpacingAtDetector; } } } std::string mitk::DICOMImageBlockDescriptor ::GetPixelSpacing() const { if ( m_ImageFrameList.empty() || m_TagCache.IsNull() ) { MITK_ERROR << "Invalid call to GetPixelSpacing. Need to have initialized tag-cache!"; return std::string(""); } static const DICOMTag tagPixelSpacing(0x0028,0x0030); return m_TagCache->GetTagValue( m_ImageFrameList.front(), tagPixelSpacing ); } std::string mitk::DICOMImageBlockDescriptor ::GetImagerPixelSpacing() const { if ( m_ImageFrameList.empty() || m_TagCache.IsNull() ) { MITK_ERROR << "Invalid call to GetImagerPixelSpacing. Need to have initialized tag-cache!"; return std::string(""); } static const DICOMTag tagImagerPixelSpacing(0x0018,0x1164); return m_TagCache->GetTagValue( m_ImageFrameList.front(), tagImagerPixelSpacing ); } void mitk::DICOMImageBlockDescriptor ::GetDesiredMITKImagePixelSpacing( ScalarType& spacingX, ScalarType& spacingY) const { std::string pixelSpacing = this->GetPixelSpacing(); // preference for "in patient" pixel spacing if ( !DICOMStringToSpacing( pixelSpacing, spacingX, spacingY ) ) { std::string imagerPixelSpacing = this->GetImagerPixelSpacing(); // fallback to "on detector" spacing if ( !DICOMStringToSpacing( 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 { this->UpdateImageDescribingProperties(); return m_PropertyList->GetProperty(key); } std::string mitk::DICOMImageBlockDescriptor ::GetPropertyAsString(const std::string& key) const { this->UpdateImageDescribingProperties(); 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 { this->UpdateImageDescribingProperties(); 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 { this->UpdateImageDescribingProperties(); IntProperty::ConstPointer intProp = dynamic_cast( this->GetProperty(key) ); if (intProp.IsNotNull()) { return intProp->GetValue(); } else { return defaultValue; } } double mitk::DICOMImageBlockDescriptor ::stringtodouble(const std::string& str) const { double d; std::string trimmedstring(str); trimmedstring = trimmedstring.erase(trimmedstring.find_last_not_of(" \n\r\t")+1); - std::istringstream converter(trimmedstring); - if ( !trimmedstring.empty() && (converter >> d) && converter.eof() ) + std::string firstcomponent = trimmedstring.erase(trimmedstring.find_first_of("\\")); + + std::istringstream converter(firstcomponent); + if ( !firstcomponent.empty() && (converter >> d) && converter.eof() ) { return d; } else { throw std::invalid_argument("Argument is not a convertable number"); } } mitk::Image::Pointer mitk::DICOMImageBlockDescriptor ::DescribeImageWithProperties(Image* mitkImage) { // TODO: this is a collection of properties that have been provided by the // legacy DicomSeriesReader. // We should at some point clean up this collection and name them in a more // consistent way! if (!mitkImage) return mitkImage; // first part: add some tags that describe individual slices // these propeties are defined at analysis time (see UpdateImageDescribingProperties()) std::string propertyKeySliceLocation = "dicom.image.0020.1041"; std::string propertyKeyInstanceNumber = "dicom.image.0020.0013"; std::string propertyKeySOPInstanceUID = "dicom.image.0008.0018"; mitkImage->SetProperty( propertyKeySliceLocation.c_str(), this->GetProperty("sliceLocationForSlices") ); mitkImage->SetProperty( propertyKeyInstanceNumber.c_str(), this->GetProperty("instanceNumberForSlices") ); mitkImage->SetProperty( propertyKeySOPInstanceUID.c_str(), this->GetProperty("SOPInstanceUIDForSlices") ); mitkImage->SetProperty( "files", this->GetProperty("filenamesForSlices") ); // second part: add properties that describe the whole image block mitkImage->SetProperty("dicomseriesreader.SOPClassUID", StringProperty::New( this->GetSOPClassUID() ) ); mitkImage->SetProperty("dicomseriesreader.SOPClass", StringProperty::New( this->GetSOPClassUIDAsName() )); mitkImage->SetProperty("dicomseriesreader.PixelSpacingInterpretationString", StringProperty::New( PixelSpacingInterpretationToString( this->GetPixelSpacingInterpretation() )) ); mitkImage->SetProperty("dicomseriesreader.PixelSpacingInterpretation", GenericProperty::New( this->GetPixelSpacingInterpretation() )); mitkImage->SetProperty("dicomseriesreader.ReaderImplementationLevelString", StringProperty::New( ReaderImplementationLevelToString( m_ReaderImplementationLevel ) )); mitkImage->SetProperty("dicomseriesreader.ReaderImplementationLevel", GenericProperty::New( m_ReaderImplementationLevel )); mitkImage->SetProperty("dicomseriesreader.GantyTiltCorrected", BoolProperty::New( this->GetTiltInformation().IsRegularGantryTilt() )); mitkImage->SetProperty("dicomseriesreader.3D+t", BoolProperty::New( this->GetFlag("3D+t",false) )); // level window std::string windowCenter = this->GetPropertyAsString("windowCenter"); std::string windowWidth = this->GetPropertyAsString("windowWidth"); try { + MITK_INFO << "Found happy L/W: " << windowCenter << "/" << windowWidth; double level = stringtodouble( windowCenter ); double window = stringtodouble( windowWidth ); mitkImage->SetProperty("levelwindow", LevelWindowProperty::New(LevelWindow(level,window)) ); } catch (...) { // nothing, no levelwindow to be predicted... } mitkImage->SetProperty("dicom.pixel.PhotometricInterpretation", this->GetProperty("photometricInterpretation") ); mitkImage->SetProperty("dicom.image.imagetype", this->GetProperty("imagetype") ); mitkImage->SetProperty("dicom.study.StudyDescription", this->GetProperty("studyDescription") ); mitkImage->SetProperty("dicom.series.SeriesDescription", this->GetProperty("seriesDescription") ); mitkImage->SetProperty("dicom.pixel.Rows", this->GetProperty("rows") ); mitkImage->SetProperty("dicom.pixel.Columns", this->GetProperty("columns") ); // 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; } mitk::ReaderImplementationLevel mitk::DICOMImageBlockDescriptor ::GetReaderImplementationLevel() const { return m_ReaderImplementationLevel; } std::string mitk::DICOMImageBlockDescriptor ::GetSOPClassUID() const { if ( !m_ImageFrameList.empty() && m_TagCache.IsNotNull() ) { static const DICOMTag tagSOPClassUID(0x0008,0x0016); return m_TagCache->GetTagValue( m_ImageFrameList.front(), tagSOPClassUID ); } else { MITK_ERROR << "Invalid call to DICOMImageBlockDescriptor::GetSOPClassUID(). Need to have initialized tag-cache!"; return std::string(""); } } std::string mitk::DICOMImageBlockDescriptor ::GetSOPClassUIDAsName() const { if ( !m_ImageFrameList.empty() && m_TagCache.IsNotNull() ) { gdcm::UIDs uidKnowledge; uidKnowledge.SetFromUID( this->GetSOPClassUID().c_str() ); const char* name = uidKnowledge.GetName(); if (name) { return std::string(name); } else { return std::string(""); } } else { MITK_ERROR << "Invalid call to DICOMImageBlockDescriptor::GetSOPClassUIDAsName(). Need to have initialized tag-cache!"; return std::string(""); } } void mitk::DICOMImageBlockDescriptor ::SetTagCache(DICOMTagCache* privateCache) { // this must only be used during loading and never afterwards 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.empty() || !last.empty()) \ { \ 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 ); \ if (!first.empty()) \ { \ 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->GetSOPClassUIDAsName() << "'" << std::endl; printProperty("Series Number", seriesNumber); printProperty("Study Description", studyDescription); printProperty("Series Description", seriesDescription); 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->GetTiltInformation().IsRegularGantryTilt()) //printBool("3D+t", this->GetFlag("3D+t",false)) //os << " MITK image loaded: '" << (this->GetMitkImage().IsNotNull() ? "yes" : "no") << "'" << std::endl; if (filenameDetails) { os << " Files in this image block:" << std::endl; for (DICOMImageFrameList::const_iterator frameIter = m_ImageFrameList.begin(); frameIter != m_ImageFrameList.end(); ++frameIter) { os << " " << (*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 ); \ const_cast(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 ); \ const_cast(this)->SetProperty(#tag_name "First", StringProperty::New( tagValueFirst ) ); \ const_cast(this)->SetProperty(#tag_name "Last", StringProperty::New( tagValueLast ) ); \ } void mitk::DICOMImageBlockDescriptor ::UpdateImageDescribingProperties() const { if (!m_PropertiesOutOfDate) return; if (!m_ImageFrameList.empty()) { if (m_TagCache.IsNull()) { MITK_ERROR << "Invalid call to DICOMImageBlockDescriptor::UpdateImageDescribingProperties(). Need to have initialized tag-cache!"; return; } DICOMImageFrameInfo::Pointer firstFrame = m_ImageFrameList.front(); DICOMImageFrameInfo::Pointer lastFrame = m_ImageFrameList.back(); // see macros above storeTagValueToProperty(seriesNumber,0x0020,0x0011) storeTagValueToProperty(studyDescription,0x0008,0x1030) storeTagValueToProperty(seriesDescription,0x0008,0x103e) storeTagValueToProperty(modality,0x0008,0x0060) storeTagValueToProperty(sequenceName,0x0018,0x0024) storeTagValueToProperty(orientation,0x0020,0x0037) storeTagValueToProperty(rows,0x0028,0x0010) storeTagValueToProperty(columns,0x0028,0x0011) storeTagValueRangeToProperty(sliceLocation,0x0020,0x1041) storeTagValueRangeToProperty(acquisitionNumber,0x0020,0x0012) storeTagValueRangeToProperty(instanceNumber,0x0020,0x0013) storeTagValueRangeToProperty(imagePositionPatient,0x0020,0x0032) storeTagValueToProperty(windowCenter,0x0028,0x1050) storeTagValueToProperty(windowWidth,0x0028,0x1051) storeTagValueToProperty(imageType,0x0008,0x0008) storeTagValueToProperty(photometricInterpretation,0x0028,0x0004) // 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; StringLookupTable filenamesForSlices; 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); std::string filename = (*frameIter)->Filename; filenamesForSlices.SetTableValue(slice, filename); MITK_DEBUG << "Tag info for slice " << slice << ": SL '" << sliceLocation << "' IN '" << instanceNumber << "' SOP instance UID '" << sopInstanceUID << "'"; } // add property or properties with proper names const_cast(this)->SetProperty( "sliceLocationForSlices", StringLookupTableProperty::New( sliceLocationForSlices ) ); const_cast(this)->SetProperty( "instanceNumberForSlices", StringLookupTableProperty::New( instanceNumberForSlices ) ); const_cast(this)->SetProperty( "SOPInstanceUIDForSlices", StringLookupTableProperty::New( SOPInstanceUIDForSlices ) ); const_cast(this)->SetProperty( "filenamesForSlices", StringLookupTableProperty::New( filenamesForSlices ) ); m_PropertiesOutOfDate = false; } } diff --git a/Modules/DICOMReader/mitkDICOMSortByTag.cpp b/Modules/DICOMReader/mitkDICOMSortByTag.cpp index 1f56d826d1..4e691f7ddd 100644 --- a/Modules/DICOMReader/mitkDICOMSortByTag.cpp +++ b/Modules/DICOMReader/mitkDICOMSortByTag.cpp @@ -1,175 +1,176 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkDICOMSortByTag.h" mitk::DICOMSortByTag ::DICOMSortByTag(const DICOMTag& tag, DICOMSortCriterion::Pointer secondaryCriterion) :DICOMSortCriterion(secondaryCriterion) ,m_Tag(tag) { } mitk::DICOMSortByTag ::~DICOMSortByTag() { } mitk::DICOMSortByTag ::DICOMSortByTag(const DICOMSortByTag& other ) :DICOMSortCriterion(other) ,m_Tag(other.m_Tag) { } mitk::DICOMSortByTag& mitk::DICOMSortByTag ::operator=(const DICOMSortByTag& other) { if (this != &other) { DICOMSortCriterion::operator=(other); m_Tag = other.m_Tag; } return *this; } bool mitk::DICOMSortByTag ::operator==(const DICOMSortCriterion& other) const { if (const DICOMSortByTag* otherSelf = dynamic_cast(&other)) { if (!(this->m_Tag == otherSelf->m_Tag)) return false; if (this->m_SecondaryCriterion.IsNull() && otherSelf->m_SecondaryCriterion.IsNull()) return true; if (this->m_SecondaryCriterion.IsNull() || otherSelf->m_SecondaryCriterion.IsNull()) return false; return *(this->m_SecondaryCriterion) == *(otherSelf->m_SecondaryCriterion); } else { return false; } } void mitk::DICOMSortByTag ::Print(std::ostream& os) const { m_Tag.Print(os); } mitk::DICOMTagList mitk::DICOMSortByTag ::GetTagsOfInterest() const { DICOMTagList list; list.push_back(m_Tag); return list; } bool mitk::DICOMSortByTag ::IsLeftBeforeRight(const mitk::DICOMDatasetAccess* left, const mitk::DICOMDatasetAccess* right) const { return this->NumericCompare(left, right, m_Tag); } bool mitk::DICOMSortByTag ::StringCompare(const mitk::DICOMDatasetAccess* left, const mitk::DICOMDatasetAccess* right, const DICOMTag& tag) const { assert(left); assert(right); std::string leftString = left->GetTagValueAsString(tag); std::string rightString = right->GetTagValueAsString(tag); if (leftString != rightString) { return leftString.compare(rightString) < 0; } else { return this->NextLevelIsLeftBeforeRight(left, right); } } bool mitk::DICOMSortByTag ::NumericCompare(const mitk::DICOMDatasetAccess* left, const mitk::DICOMDatasetAccess* right, const DICOMTag& tag) const { assert(left); assert(right); std::string leftString = left->GetTagValueAsString(tag); std::string rightString = right->GetTagValueAsString(tag); std::istringstream lefti(leftString); std::istringstream righti(rightString); double leftDouble(0); double rightDouble(0); if ( (lefti >> leftDouble) && (righti >> rightDouble) && lefti.eof() && righti.eof() ) { if (leftDouble != rightDouble) // can we decide? { return leftDouble < rightDouble; } else // ask secondary criterion { return this->NextLevelIsLeftBeforeRight(left, right); } } else // no numerical conversion.. { return this->StringCompare(left,right, tag); // fallback to string compare } } double mitk::DICOMSortByTag ::NumericDistance(const mitk::DICOMDatasetAccess* from, const mitk::DICOMDatasetAccess* to) const { assert(from); assert(to); std::string fromString = from->GetTagValueAsString(m_Tag); std::string toString = to->GetTagValueAsString(m_Tag); std::istringstream fromi(fromString); std::istringstream toi(toString); double fromDouble(0); double toDouble(0); if ( (fromi >> fromDouble) && (toi >> toDouble) && fromi.eof() && toi.eof() ) { return toDouble - fromDouble; } else { + MITK_WARN << "NO NUMERIC DISTANCE between '" << fromString << "' and '" << toString << "'"; return 0; } // TODO second-level compare? } diff --git a/Modules/DICOMReader/mitkDICOMTagBasedSorter.cpp b/Modules/DICOMReader/mitkDICOMTagBasedSorter.cpp index 5475550871..34f4b8fac9 100644 --- a/Modules/DICOMReader/mitkDICOMTagBasedSorter.cpp +++ b/Modules/DICOMReader/mitkDICOMTagBasedSorter.cpp @@ -1,540 +1,559 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkDICOMTagBasedSorter.h" #include #include mitk::DICOMTagBasedSorter::CutDecimalPlaces ::CutDecimalPlaces(unsigned int precision) :m_Precision(precision) { } mitk::DICOMTagBasedSorter::CutDecimalPlaces ::CutDecimalPlaces(const CutDecimalPlaces& other) :m_Precision(other.m_Precision) { } std::string mitk::DICOMTagBasedSorter::CutDecimalPlaces ::operator()(const std::string& input) const { // be a bit tolerant for tags such as image orientation orienatation, let only the first few digits matter (http://bugs.mitk.org/show_bug.cgi?id=12263) // iterate all fields, convert each to a number, cut this number as configured, then return a concatenated string with all cut-off numbers std::ostringstream resultString; resultString.str(std::string()); resultString.clear(); resultString.setf(std::ios::fixed, std::ios::floatfield); resultString.precision(m_Precision); std::stringstream ss(input); ss.str(input); ss.clear(); std::string item; double number(0); std::istringstream converter(item); while (std::getline(ss, item, '\\')) { converter.str(item); converter.clear(); if (converter >> number && converter.eof()) { // converted to double resultString << number; } else { // did not convert to double resultString << item; // just paste the unmodified string } if (!ss.eof()) { resultString << "\\"; } } return resultString.str(); } mitk::DICOMTagBasedSorter::TagValueProcessor* mitk::DICOMTagBasedSorter::CutDecimalPlaces ::Clone() const { return new CutDecimalPlaces(*this); } unsigned int mitk::DICOMTagBasedSorter::CutDecimalPlaces ::GetPrecision() const { return m_Precision; } mitk::DICOMTagBasedSorter ::DICOMTagBasedSorter() :DICOMDatasetSorter() ,m_StrictSorting(true) { } mitk::DICOMTagBasedSorter ::~DICOMTagBasedSorter() { for(TagValueProcessorMap::iterator ti = m_TagValueProcessor.begin(); ti != m_TagValueProcessor.end(); ++ti) { delete ti->second; } } mitk::DICOMTagBasedSorter ::DICOMTagBasedSorter(const DICOMTagBasedSorter& other ) :DICOMDatasetSorter(other) ,m_DistinguishingTags( other.m_DistinguishingTags ) ,m_SortCriterion( other.m_SortCriterion ) ,m_StrictSorting( other.m_StrictSorting ) { for(TagValueProcessorMap::const_iterator ti = other.m_TagValueProcessor.begin(); ti != other.m_TagValueProcessor.end(); ++ti) { m_TagValueProcessor[ti->first] = ti->second->Clone(); } } mitk::DICOMTagBasedSorter& mitk::DICOMTagBasedSorter ::operator=(const DICOMTagBasedSorter& other) { if (this != &other) { DICOMDatasetSorter::operator=(other); m_DistinguishingTags = other.m_DistinguishingTags; m_SortCriterion = other.m_SortCriterion; m_StrictSorting = other.m_StrictSorting; for(TagValueProcessorMap::const_iterator ti = other.m_TagValueProcessor.begin(); ti != other.m_TagValueProcessor.end(); ++ti) { m_TagValueProcessor[ti->first] = ti->second->Clone(); } } return *this; } bool mitk::DICOMTagBasedSorter ::operator==(const DICOMDatasetSorter& other) const { if (const DICOMTagBasedSorter* otherSelf = dynamic_cast(&other)) { if (this->m_StrictSorting != otherSelf->m_StrictSorting) return false; bool allTagsPresentAndEqual(true); if (this->m_DistinguishingTags.size() != otherSelf->m_DistinguishingTags.size()) return false; for (DICOMTagList::const_iterator myTag = this->m_DistinguishingTags.begin(); myTag != this->m_DistinguishingTags.end(); ++myTag) { allTagsPresentAndEqual &= (std::find( otherSelf->m_DistinguishingTags.begin(), otherSelf->m_DistinguishingTags.end(), *myTag ) != otherSelf->m_DistinguishingTags.end()); // other contains this tags // since size is equal, we don't need to check the inverse } if (!allTagsPresentAndEqual) return false; if (this->m_SortCriterion.IsNotNull() && otherSelf->m_SortCriterion.IsNotNull()) { return *(this->m_SortCriterion) == *(otherSelf->m_SortCriterion); } else { return this->m_SortCriterion.IsNull() && otherSelf->m_SortCriterion.IsNull(); } } else { return false; } } void mitk::DICOMTagBasedSorter ::PrintConfiguration(std::ostream& os, const std::string& indent) const { os << indent << "Tag based sorting:" << std::endl; for (DICOMTagList::const_iterator tagIter = m_DistinguishingTags.begin(); tagIter != m_DistinguishingTags.end(); ++tagIter) { os << indent << " Split on "; tagIter->Print(os); os << std::endl; } DICOMSortCriterion::ConstPointer crit = m_SortCriterion.GetPointer(); while (crit.IsNotNull()) { os << indent << " Sort by "; crit->Print(os); os << std::endl; crit = crit->GetSecondaryCriterion(); } } void mitk::DICOMTagBasedSorter ::SetStrictSorting(bool strict) { m_StrictSorting = strict; } bool mitk::DICOMTagBasedSorter ::GetStrictSorting() const { return m_StrictSorting; } mitk::DICOMTagList mitk::DICOMTagBasedSorter ::GetTagsOfInterest() { DICOMTagList allTags = m_DistinguishingTags; DICOMTagList sortingRelevantTags = m_SortCriterion->GetAllTagsOfInterest(); allTags.insert( allTags.end(), sortingRelevantTags.begin(), sortingRelevantTags.end() ); // append return allTags; } mitk::DICOMTagList mitk::DICOMTagBasedSorter ::GetDistinguishingTags() const { return m_DistinguishingTags; } const mitk::DICOMTagBasedSorter::TagValueProcessor* mitk::DICOMTagBasedSorter ::GetTagValueProcessorForDistinguishingTag(const DICOMTag& tag) const { TagValueProcessorMap::const_iterator loc = m_TagValueProcessor.find(tag); if (loc != m_TagValueProcessor.end()) { return loc->second; } else { return NULL; } } void mitk::DICOMTagBasedSorter ::AddDistinguishingTag( const DICOMTag& tag, TagValueProcessor* tagValueProcessor ) { m_DistinguishingTags.push_back(tag); m_TagValueProcessor[tag] = tagValueProcessor; } void mitk::DICOMTagBasedSorter ::SetSortCriterion( DICOMSortCriterion::ConstPointer criterion ) { m_SortCriterion = criterion; } mitk::DICOMSortCriterion::ConstPointer mitk::DICOMTagBasedSorter ::GetSortCriterion() const { return m_SortCriterion; } void mitk::DICOMTagBasedSorter ::Sort() { // 1. split // 2. sort each group GroupIDToListType groups = this->SplitInputGroups(); GroupIDToListType& sortedGroups = this->SortGroups( groups ); // 3. define output this->SetNumberOfOutputs(sortedGroups.size()); unsigned int outputIndex(0); for (GroupIDToListType::iterator groupIter = sortedGroups.begin(); groupIter != sortedGroups.end(); ++outputIndex, ++groupIter) { this->SetOutput(outputIndex, groupIter->second); } } std::string mitk::DICOMTagBasedSorter ::BuildGroupID( DICOMDatasetAccess* dataset ) { // just concatenate all tag values assert(dataset); std::stringstream groupID; groupID << "g"; for (DICOMTagList::iterator tagIter = m_DistinguishingTags.begin(); tagIter != m_DistinguishingTags.end(); ++tagIter) { groupID << tagIter->GetGroup() << tagIter->GetElement(); // make group/element part of the id to cover empty tags std::string rawTagValue = dataset->GetTagValueAsString(*tagIter); std::string processedTagValue; if ( m_TagValueProcessor[*tagIter] != NULL ) { processedTagValue = (*m_TagValueProcessor[*tagIter])(rawTagValue); } else { processedTagValue = rawTagValue; } groupID << processedTagValue; } // shorten ID? return groupID.str(); } mitk::DICOMTagBasedSorter::GroupIDToListType mitk::DICOMTagBasedSorter ::SplitInputGroups() { DICOMDatasetList input = GetInput(); // copy GroupIDToListType listForGroupID; for (DICOMDatasetList::iterator dsIter = input.begin(); dsIter != input.end(); ++dsIter) { DICOMDatasetAccess* dataset = *dsIter; assert(dataset); std::string groupID = this->BuildGroupID( dataset ); MITK_DEBUG << "Group ID for for " << dataset->GetFilenameIfAvailable() << ": " << groupID; listForGroupID[groupID].push_back(dataset); } MITK_DEBUG << "After tag based splitting: " << listForGroupID.size() << " groups"; return listForGroupID; } mitk::DICOMTagBasedSorter::GroupIDToListType& mitk::DICOMTagBasedSorter ::SortGroups(GroupIDToListType& groups) { if (m_SortCriterion.IsNotNull()) { /* Three steps here: 1. sort within each group - this may result in orders such as 1 2 3 4 6 7 8 10 12 13 14 2. create new groups by enforcing consecutive order within each group - resorts above example like 1 2 3 4 ; 6 7 8 ; 10 ; 12 13 14 3. sort all of the groups (not WITHIN each group) by their first frame - if earlier "distinguish" steps created groups like 6 7 8 ; 1 2 3 4 ; 10, then this step would sort them like 1 2 3 4 ; 6 7 8 ; 10 */ // Step 1: sort within the groups // for each output // sort by all configured tags, use secondary tags when equal or empty // make configurable: // - sorting order (ascending, descending) // - sort numerically // - ... ? unsigned int groupIndex(0); for (GroupIDToListType::iterator gIter = groups.begin(); gIter != groups.end(); ++groupIndex, ++gIter) { DICOMDatasetList& dsList = gIter->second; MITK_DEBUG << " --------------------------------------------------------------------------------"; MITK_DEBUG << " DICOMTagBasedSorter before sorting group : " << groupIndex; for (DICOMDatasetList::iterator oi = dsList.begin(); oi != dsList.end(); ++oi) { MITK_DEBUG << " INPUT : " << (*oi)->GetFilenameIfAvailable(); } std::sort( dsList.begin(), dsList.end(), ParameterizedDatasetSort( m_SortCriterion ) ); MITK_DEBUG << " --------------------------------------------------------------------------------"; MITK_DEBUG << " DICOMTagBasedSorter after sorting group : " << groupIndex; for (DICOMDatasetList::iterator oi = dsList.begin(); oi != dsList.end(); ++oi) { MITK_DEBUG << " OUTPUT : " << (*oi)->GetFilenameIfAvailable(); } MITK_DEBUG << " --------------------------------------------------------------------------------"; } GroupIDToListType consecutiveGroups; if (m_StrictSorting) { // Step 2: create new groups by enforcing consecutive order within each group unsigned int groupIndex(0); for (GroupIDToListType::iterator gIter = groups.begin(); gIter != groups.end(); ++gIter) { std::stringstream groupKey; groupKey << std::setfill('0') << std::setw(6) << groupIndex++; DICOMDatasetList& dsList = gIter->second; DICOMDatasetAccess* previousDS(NULL); unsigned int dsIndex(0); double constantDistance(0.0); bool constantDistanceInitialized(false); for (DICOMDatasetList::iterator dataset = dsList.begin(); dataset != dsList.end(); ++dsIndex, ++dataset) { if (dsIndex >0) // ignore the first dataset, we cannot check any distances yet.. { // for the second and every following dataset: // let the sorting criterion calculate a "distance" // if the distance is not 1, split off a new group! double currentDistance = m_SortCriterion->NumericDistance(previousDS, *dataset); if (constantDistanceInitialized) { - if (fabs(currentDistance - constantDistance) < constantDistance * 0.01) // ok, deviation of up to 1% of distance is tolerated + if (fabs(currentDistance - constantDistance) < fabs(constantDistance * 0.01)) // ok, deviation of up to 1% of distance is tolerated { // nothing to do, just ok + MITK_DEBUG << "Checking currentDistance==" << currentDistance << ": small enough"; } - else if (currentDistance < mitk::eps) // close enough to 0 - { - // no numeric comparison possible? - // rare case(?), just accept - } + //else if (currentDistance < mitk::eps) // close enough to 0 else { + MITK_DEBUG << "Split consecutive group at index " << dsIndex << " (current distance " << currentDistance << ", constant distance " << constantDistance << ")"; // split! this is done by simply creating a new group (key) groupKey.str(std::string()); groupKey.clear(); groupKey << std::setfill('0') << std::setw(6) << groupIndex++; } } else { // second slice: learn about the expected distance! // heuristic: if distance is an integer, we check for a special case: // if the distance is integer and not 1/-1, then we assume // a missing slice right after the first slice // ==> split off slices // in all other cases: second dataset at this position, no need to split already, we are still learning about the images if ((currentDistance - (int)currentDistance == 0.0) && fabs(currentDistance) != 1.0) // exact comparison. An integer should not be expressed as 1.000000000000000000000000001! { + MITK_DEBUG << "Split consecutive group at index " << dsIndex << " (strange special case)"; groupKey.str(std::string()); groupKey.clear(); groupKey << std::setfill('0') << std::setw(6) << groupIndex++; } + MITK_DEBUG << "Initialize strict distance to currentDistance=" << currentDistance; + constantDistance = currentDistance; constantDistanceInitialized = true; } } consecutiveGroups[groupKey.str()].push_back(*dataset); previousDS = *dataset; } } } else { consecutiveGroups = groups; } // Step 3: sort all of the groups (not WITHIN each group) by their first frame /* build a list-1 of datasets with the first dataset one of each group sort this list-1 build a new result list-2: - iterate list-1, for each dataset - find the group that contains this dataset - add this group as the next element to list-2 return list-2 as the sorted output */ DICOMDatasetList firstSlices; for (GroupIDToListType::iterator gIter = consecutiveGroups.begin(); gIter != consecutiveGroups.end(); ++gIter) { assert(!gIter->second.empty()); firstSlices.push_back(gIter->second.front()); } std::sort( firstSlices.begin(), firstSlices.end(), ParameterizedDatasetSort( m_SortCriterion ) ); GroupIDToListType sortedResultBlocks; unsigned int groupKeyValue(0); for (DICOMDatasetList::iterator firstSlice = firstSlices.begin(); firstSlice != firstSlices.end(); ++firstSlice) { for (GroupIDToListType::iterator gIter = consecutiveGroups.begin(); gIter != consecutiveGroups.end(); ++groupKeyValue, ++gIter) { if (gIter->second.front() == *firstSlice) { std::stringstream groupKey; groupKey << std::setfill('0') << std::setw(6) << groupKeyValue; // try more than 999,999 groups and you are doomed (your application already is) sortedResultBlocks[groupKey.str()] = gIter->second; } } } groups = sortedResultBlocks; } + unsigned int groupIndex(0); + for (GroupIDToListType::iterator gIter = groups.begin(); + gIter != groups.end(); + ++groupIndex, ++gIter) + { + DICOMDatasetList& dsList = gIter->second; + 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); }