diff --git a/Modules/CEST/autoload/IO/files.cmake b/Modules/CEST/autoload/IO/files.cmake index 68729cebf4..8864bec575 100644 --- a/Modules/CEST/autoload/IO/files.cmake +++ b/Modules/CEST/autoload/IO/files.cmake @@ -1,6 +1,9 @@ set(CPP_FILES mitkCESTDICOMReaderService.cpp mitkCESTIOMimeTypes.cpp mitkCESTIOActivator.cpp ) +set(RESOURCE_FILES + cest_DKFZ.xml +) diff --git a/Modules/CEST/autoload/IO/mitkCESTDICOMReaderService.cpp b/Modules/CEST/autoload/IO/mitkCESTDICOMReaderService.cpp index 08dbbda1d5..7b4315660e 100644 --- a/Modules/CEST/autoload/IO/mitkCESTDICOMReaderService.cpp +++ b/Modules/CEST/autoload/IO/mitkCESTDICOMReaderService.cpp @@ -1,142 +1,147 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkCESTDICOMReaderService.h" #include "mitkCESTIOMimeTypes.h" #include #include #include #include "mitkCESTImageNormalizationFilter.h" #include +#include +#include +#include + namespace mitk { CESTDICOMReaderService::CESTDICOMReaderService() : BaseDICOMReaderService(CustomMimeType(MitkCESTIOMimeTypes::CEST_DICOM_MIMETYPE_NAME()), "MITK CEST DICOM Reader") { Options defaultOptions; std::vector parseStrategy; parseStrategy.push_back("Automatic"); parseStrategy.push_back("CEST/WASABI"); parseStrategy.push_back("T1"); defaultOptions["Force type"] = parseStrategy; std::vector mappingStrategy; mappingStrategy.push_back("Strict"); mappingStrategy.push_back("Fuzzy"); defaultOptions["Revision mapping"] = mappingStrategy; std::vector normalizationStrategy; normalizationStrategy.push_back("Automatic"); normalizationStrategy.push_back("No"); defaultOptions["Normalize data"] = normalizationStrategy; this->SetDefaultOptions(defaultOptions); + this->SetOnlyRegardOwnSeries(false); this->RegisterService(); } DICOMFileReader::Pointer CESTDICOMReaderService::GetReader(const mitk::StringList &relevantFiles) const { mitk::DICOMFileReaderSelector::Pointer selector = mitk::DICOMFileReaderSelector::New(); - selector->LoadBuiltIn3DConfigs(); - selector->LoadBuiltIn3DnTConfigs(); + auto r = ::us::GetModuleContext()->GetModule()->GetResource("cest_DKFZ.xml"); + selector->AddConfigFromResource(r); selector->SetInputFiles(relevantFiles); mitk::DICOMFileReader::Pointer reader = selector->GetFirstReaderWithMinimumNumberOfOutputImages(); if (reader.IsNotNull()) { // reset tag cache to ensure that additional tags of interest // will be regarded by the reader if set later on. reader->SetTagCache(nullptr); } return reader; } std::vector> CESTDICOMReaderService::Read() { std::vector result; std::vector dicomResult = BaseDICOMReaderService::Read(); const Options options = this->GetOptions(); const std::string parseStrategy = options.find("Force type")->second.ToString(); const std::string mappingStrategy = options.find("Revision mapping")->second.ToString(); const std::string normalizationStrategy = options.find("Normalize data")->second.ToString(); for (auto &item : dicomResult) { auto prop = item->GetProperty("files"); auto fileProp = dynamic_cast(prop.GetPointer()); if (!fileProp) { mitkThrow() << "Cannot load CEST file. Property \"files\" is missing after BaseDICOMReaderService::Read()."; } mitk::StringList relevantFiles = { fileProp->GetValue().GetTableValue(0) }; mitk::DICOMDCMTKTagScanner::Pointer scanner = mitk::DICOMDCMTKTagScanner::New(); DICOMTag siemensCESTprivateTag(0x0029, 0x1020); scanner->AddTag(siemensCESTprivateTag); scanner->SetInputFiles(relevantFiles); scanner->Scan(); mitk::DICOMTagCache::Pointer tagCache = scanner->GetScanCache(); DICOMImageFrameList imageFrameList = mitk::ConvertToDICOMImageFrameList(tagCache->GetFrameInfoList()); DICOMImageFrameInfo *firstFrame = imageFrameList.begin()->GetPointer(); std::string byteString = tagCache->GetTagValue(firstFrame, siemensCESTprivateTag).value; mitk::CustomTagParser tagParser(relevantFiles[0]); tagParser.SetParseStrategy(parseStrategy); tagParser.SetRevisionMappingStrategy(mappingStrategy); auto parsedPropertyList = tagParser.ParseDicomPropertyString(byteString); item->GetPropertyList()->ConcatenatePropertyList(parsedPropertyList); auto image = dynamic_cast(item.GetPointer()); if (normalizationStrategy == "Automatic" && mitk::IsNotNormalizedCESTImage(image)) { MITK_INFO << "Unnormalized CEST image was loaded and will be normalized automatically."; auto normalizationFilter = mitk::CESTImageNormalizationFilter::New(); normalizationFilter->SetInput(image); normalizationFilter->Update(); auto normalizedImage = normalizationFilter->GetOutput(); auto nameProp = item->GetProperty("name"); if (!nameProp) { mitkThrow() << "Cannot load CEST file. Property \"name\" is missing after BaseDICOMReaderService::Read()."; } normalizedImage->SetProperty("name", mitk::StringProperty::New(nameProp->GetValueAsString() + "_normalized")); result.push_back(normalizedImage); } else { result.push_back(item); } } return result; } CESTDICOMReaderService *CESTDICOMReaderService::Clone() const { return new CESTDICOMReaderService(*this); } } diff --git a/Modules/CEST/autoload/IO/resource/cest_DKFZ.xml b/Modules/CEST/autoload/IO/resource/cest_DKFZ.xml new file mode 100644 index 0000000000..b87f554740 --- /dev/null +++ b/Modules/CEST/autoload/IO/resource/cest_DKFZ.xml @@ -0,0 +1,11 @@ + + + \ No newline at end of file diff --git a/Modules/CEST/test/mitkCESTDICOMReaderServiceTest.cpp b/Modules/CEST/test/mitkCESTDICOMReaderServiceTest.cpp index 9f342f147f..9e3e9d1bde 100644 --- a/Modules/CEST/test/mitkCESTDICOMReaderServiceTest.cpp +++ b/Modules/CEST/test/mitkCESTDICOMReaderServiceTest.cpp @@ -1,91 +1,94 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ // Testing #include "mitkTestFixture.h" #include "mitkTestingMacros.h" // std includes #include // MITK includes #include #include #include #include "mitkCESTImageDetectionHelper.h" #include "mitkCustomTagParser.h" class mitkCESTDICOMReaderServiceTestSuite : public mitk::TestFixture { CPPUNIT_TEST_SUITE(mitkCESTDICOMReaderServiceTestSuite); // Test the dicom property parsing MITK_TEST(LoadCESTDICOMData_Success); MITK_TEST(LoadNormalizedCESTDICOMData_Success); MITK_TEST(LoadT1DICOMData_Success); CPPUNIT_TEST_SUITE_END(); private: public: void setUp() override { } void tearDown() override { } void LoadCESTDICOMData_Success() { mitk::IFileReader::Options options; options["Force type"] = std::string( "Automatic" ); options["Revision mapping"] = std::string( "Strict" ); options["Normalize data"] = std::string("No"); mitk::Image::Pointer cestImage = mitk::IOUtil::Load(GetTestDataFilePath("CEST/B1=0.6MUT/MEI_NER_PHANTOM.MR.E0202_MEISSNER.0587.0001.2017.10.25.22.11.10.373351.41828677.IMA"), options); CPPUNIT_ASSERT_MESSAGE("Make certain offsets have been correctly loaded for CEST image." ,cestImage->GetProperty(mitk::CEST_PROPERTY_NAME_OFFSETS().c_str())->GetValueAsString() == "-300 2 -2 1.92982 -1.92982 1.85965 -1.85965 1.78947 -1.78947 1.7193 -1.7193 1.64912 -1.64912 1.57895 -1.57895 1.50877 -1.50877 1.4386 -1.4386 1.36842 -1.36842 1.29825 -1.29825 1.22807 -1.22807 1.15789 -1.15789 1.08772 -1.08772 1.01754 -1.01754 0.947368 -0.947368 0.877193 -0.877193 0.807018 -0.807018 0.736842 -0.736842 0.666667 -0.666667 0.596491 -0.596491 0.526316 -0.526316 0.45614 -0.45614 0.385965 -0.385965 0.315789 -0.315789 0.245614 -0.245614 0.175439 -0.175439 0.105263 -0.105263 0.0350877 -0.0350877"); + CPPUNIT_ASSERT_MESSAGE("Wrong number of frames are reconstructed.", cestImage->GetTimeSteps() == 59); std::string temp; CPPUNIT_ASSERT_MESSAGE("Make certain image is not loaded as T1.", !mitk::IsCESTT1Image(cestImage)); } void LoadNormalizedCESTDICOMData_Success() { mitk::IFileReader::Options options; options["Force type"] = std::string("Automatic"); options["Revision mapping"] = std::string("Strict"); options["Normalize data"] = std::string("Automatic"); mitk::Image::Pointer cestImage = mitk::IOUtil::Load(GetTestDataFilePath("CEST/B1=0.6MUT/MEI_NER_PHANTOM.MR.E0202_MEISSNER.0587.0001.2017.10.25.22.11.10.373351.41828677.IMA"), options); auto tempREsult = cestImage->GetProperty(mitk::CEST_PROPERTY_NAME_OFFSETS().c_str())->GetValueAsString(); CPPUNIT_ASSERT_MESSAGE("Make certain offsets have been correctly loaded for CEST image.", cestImage->GetProperty(mitk::CEST_PROPERTY_NAME_OFFSETS().c_str())->GetValueAsString() == "2 -2 1.92982 -1.92982 1.85965 -1.85965 1.78947 -1.78947 1.7193 -1.7193 1.64912 -1.64912 1.57895 -1.57895 1.50877 -1.50877 1.4386 -1.4386 1.36842 -1.36842 1.29825 -1.29825 1.22807 -1.22807 1.15789 -1.15789 1.08772 -1.08772 1.01754 -1.01754 0.947368 -0.947368 0.877193 -0.877193 0.807018 -0.807018 0.736842 -0.736842 0.666667 -0.666667 0.596491 -0.596491 0.526316 -0.526316 0.45614 -0.45614 0.385965 -0.385965 0.315789 -0.315789 0.245614 -0.245614 0.175439 -0.175439 0.105263 -0.105263 0.0350877 -0.0350877 "); + CPPUNIT_ASSERT_MESSAGE("Wrong number of frames are reconstructed.", cestImage->GetTimeSteps() == 58); std::string temp; CPPUNIT_ASSERT_MESSAGE("Make certain image is not loaded as T1.", !mitk::IsCESTT1Image(cestImage)); } void LoadT1DICOMData_Success() { mitk::IFileReader::Options options; options["Force type"] = std::string("Automatic"); options["Revision mapping"] = std::string("Strict"); mitk::Image::Pointer cestImage = mitk::IOUtil::Load(GetTestDataFilePath("CEST/T1MAP/MEI_NER_PHANTOM.MR.E0202_MEISSNER.0279.0001.2017.10.25.20.21.27.996834.41803047.IMA"), options); std::string temp; CPPUNIT_ASSERT_MESSAGE("Make certain image is loaded as T1.", mitk::IsCESTT1Image(cestImage)); + CPPUNIT_ASSERT_MESSAGE("Wrong number of frames are reconstructed.", cestImage->GetTimeSteps() == 17); } }; MITK_TEST_SUITE_REGISTRATION(mitkCESTDICOMReaderService) diff --git a/Modules/DICOMReader/include/mitkBaseDICOMReaderService.h b/Modules/DICOMReader/include/mitkBaseDICOMReaderService.h index ed9be04529..1acff14ce9 100644 --- a/Modules/DICOMReader/include/mitkBaseDICOMReaderService.h +++ b/Modules/DICOMReader/include/mitkBaseDICOMReaderService.h @@ -1,60 +1,70 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef MITKBASEDICOMREADERSERVICE_H #define MITKBASEDICOMREADERSERVICE_H #include #include #include "MitkDICOMReaderExports.h" namespace mitk { /** Base class for service wrappers that make DICOMFileReader from the DICOMReader module usable. */ class MITKDICOMREADER_EXPORT BaseDICOMReaderService : public AbstractFileReader { public: BaseDICOMReaderService(const std::string& description); BaseDICOMReaderService(const mitk::CustomMimeType& customType, const std::string& description); using AbstractFileReader::Read; /** Uses this->GetRelevantFile() and this->GetReader to load the image. * data and puts it into base data instances-*/ std::vector > Read() override; IFileReader::ConfidenceLevel GetConfidenceLevel() const override; protected: /** Returns the list of all DCM files that are in the same directory * like this->GetLocalFileName().*/ - mitk::StringList GetRelevantFiles() const; + mitk::StringList GetDICOMFilesInSameDirectory() const; /** Returns the reader instance that should be used. The descission may be based * one the passed relevant file list.*/ virtual mitk::DICOMFileReader::Pointer GetReader(const mitk::StringList& relevantFiles) const = 0; + + void SetOnlyRegardOwnSeries(bool); + bool GetOnlyRegardOwnSeries() const; + +private: + /** Flags that constrols if the read() operation should only regard DICOM files of the same series + if the specified GetLocalFileName() is a file. If it is a director, this flag has no impact (it is + assumed false then). + */ + bool m_OnlyRegardOwnSeries = true; }; class IPropertyProvider; /** Helper function that generates a name string (e.g. for DataNode names) from the DICOM properties of the passed provider instance. If the instance is nullptr, or has no dicom properties DataNode::NO_NAME_VALUE() will be returned.*/ std::string MITKDICOMREADER_EXPORT GenerateNameFromDICOMProperties(const mitk::IPropertyProvider* provider); } #endif // MITKBASEDICOMREADERSERVICE_H diff --git a/Modules/DICOMReader/include/mitkDICOMFileReaderSelector.h b/Modules/DICOMReader/include/mitkDICOMFileReaderSelector.h index ff12cd7730..42ead769e7 100644 --- a/Modules/DICOMReader/include/mitkDICOMFileReaderSelector.h +++ b/Modules/DICOMReader/include/mitkDICOMFileReaderSelector.h @@ -1,100 +1,102 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkDICOMFileReaderSelector_h #define mitkDICOMFileReaderSelector_h #include "mitkDICOMFileReader.h" #include namespace mitk { /** \ingroup DICOMReaderModule \brief Simple best-reader selection. This class implements a process of comparing different DICOMFileReader%s and selecting the reader with the minimal number of mitk::Image%s in its output. The code found in this class can - just be used to select a reader using this simple strategy - be taken as an example of how to use DICOMFileReader%s To create a selection of potential readers, the class makes use of mitk::DICOMReaderConfigurator, i.e. DICOMFileReaderSelector also expects the configuration files/strings to be in the format expected by mitk::DICOMReaderConfigurator. Two convenience methods load "default" configurations from compiled-in resources: LoadBuiltIn3DConfigs() and LoadBuiltIn3DnTConfigs(). */ class MITKDICOMREADER_EXPORT DICOMFileReaderSelector : public itk::LightObject { public: typedef std::list ReaderList; mitkClassMacroItkParent( DICOMFileReaderSelector, itk::LightObject ); itkNewMacro( DICOMFileReaderSelector ); /// \brief Add a configuration as expected by DICOMReaderConfigurator. /// Configs can only be reset by instantiating a new DICOMFileReaderSelector. void AddConfig(const std::string& xmlDescription); /// \brief Add a configuration as expected by DICOMReaderConfigurator. /// Configs can only be reset by instantiating a new DICOMFileReaderSelector. void AddConfigFile(const std::string& filename); + /// \brief Add a configuration that is stored in the passed us::ModuleResourse. + /// Configs can only be reset by instantiating a new DICOMFileReaderSelector. + void AddConfigFromResource(us::ModuleResource& resource); /// \brief Add a whole pre-configured reader to the selection process. void AddFileReaderCanditate(DICOMFileReader::Pointer reader); /// \brief Load 3D image creating configurations from the MITK module system (see us::Module::FindResources()). /// For a default set of configurations, look into the directory Resources of the DICOMReader module. void LoadBuiltIn3DConfigs(); /// \brief Load 3D+t image creating configurations from the MITK module system (see us::Module::FindResources()). /// For a default set of configurations, look into the directory Resources of the DICOMReader module. void LoadBuiltIn3DnTConfigs(); /// \brief Return all the DICOMFileReader%s that are currently used for selection by this class. /// The readers returned by this method depend on what config files have been added earlier /// (or which of the built-in readers have been loaded) ReaderList GetAllConfiguredReaders() const; /// Input files void SetInputFiles(StringList filenames); /// Input files const StringList& GetInputFiles() const; /// Execute the analysis and selection process. The first reader with a minimal number of outputs will be returned. DICOMFileReader::Pointer GetFirstReaderWithMinimumNumberOfOutputImages(); protected: DICOMFileReaderSelector(); ~DICOMFileReaderSelector() override; void AddConfigsFromResources(const std::string& path); void AddConfigFromResource(const std::string& resourcename); - void AddConfigFromResource(us::ModuleResource& resource); private: StringList m_PossibleConfigurations; StringList m_InputFilenames; ReaderList m_Readers; }; } // namespace #endif // mitkDICOMFileReaderSelector_h diff --git a/Modules/DICOMReader/include/mitkDICOMFilesHelper.h b/Modules/DICOMReader/include/mitkDICOMFilesHelper.h index 5a80333bd4..104b7f2511 100644 --- a/Modules/DICOMReader/include/mitkDICOMFilesHelper.h +++ b/Modules/DICOMReader/include/mitkDICOMFilesHelper.h @@ -1,37 +1,41 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef MITKDICOMFILESHELPER_H #define MITKDICOMFILESHELPER_H #include #include #include namespace mitk { typedef std::vector DICOMFilePathList; /** Helper functions. Searches for all files in the directory of the passed file path. All files will be checked if they are DICOM files. All DICOM files will be added to the result and returned. @remark The helper does no sorting of any kind.*/ DICOMFilePathList GetDICOMFilesInSameDirectory(const std::string& filePath); /** All passed files will be checked if they are DICOM files. All DICOM files will be added to the result and returned. @remark The helper does no sorting of any kind.*/ DICOMFilePathList FilterForDICOMFiles(const DICOMFilePathList& fileList); + +/** Returns all DICOM files passed with fileList that have the same series instance UID then the passed refFilePath. +@pre refFilePath must point to a valid DICOM file.*/ +DICOMFilePathList FilterDICOMFilesForSameSeries(const std::string& refFilePath, const DICOMFilePathList& fileList); } #endif // MITKDICOMFILESHELPER_H diff --git a/Modules/DICOMReader/include/mitkThreeDnTDICOMSeriesReader.h b/Modules/DICOMReader/include/mitkThreeDnTDICOMSeriesReader.h index d6da52f1ee..6bbb263c93 100644 --- a/Modules/DICOMReader/include/mitkThreeDnTDICOMSeriesReader.h +++ b/Modules/DICOMReader/include/mitkThreeDnTDICOMSeriesReader.h @@ -1,89 +1,99 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkThreeDnTDICOMSeriesReader_h #define mitkThreeDnTDICOMSeriesReader_h #include "mitkDICOMITKSeriesGDCMReader.h" #include "MitkDICOMReaderExports.h" namespace mitk { /** \ingroup DICOMReader \brief Extends DICOMITKSeriesGDCMReader by sorting/grouping into 3D+t image blocks. This class reuses the DICOMITKSeriesGDCMReader class and adds the option of grouping 3D blocks at the same spatial position into a single block, which is loaded as a 3D+t mitk::Image (compare to \ref DICOMITKSeriesGDCMReader_Condensing). To group two output blocks into a single 3D+t block, this class tests a number of requirements that the two blocks must fulfill: - the origin of the first slice must be identical - the origin of the last slice must be identical - the number of slices must be identical The output blocks described by DICOMImageBlockDescriptor will contains the following properties: - \b "3D+t": true if the image is 3D+t - \b "timesteps": number of timesteps of an image (only defined if "3D+t" is true) */ class MITKDICOMREADER_EXPORT ThreeDnTDICOMSeriesReader : public DICOMITKSeriesGDCMReader { public: mitkClassMacro( ThreeDnTDICOMSeriesReader, DICOMITKSeriesGDCMReader ); mitkCloneMacro( ThreeDnTDICOMSeriesReader ); itkNewMacro( ThreeDnTDICOMSeriesReader ); mitkNewMacro1Param( ThreeDnTDICOMSeriesReader, unsigned int ); /// \brief Control whether 3D+t grouping shall actually be attempted. void SetGroup3DandT(bool on); bool GetGroup3DandT() const; + itkBooleanMacro(OnlyCondenseSameSeries); + itkSetMacro(OnlyCondenseSameSeries, bool); + itkGetConstMacro(OnlyCondenseSameSeries, bool); + // void AllocateOutputImages(); /// \brief Load via multiple calls to itk::ImageSeriesReader. bool LoadImages() override; bool operator==(const DICOMFileReader& other) const override; static bool GetDefaultGroup3DandT() { return m_DefaultGroup3DandT; } + static bool GetDefaultOnlyCondenseSameSeries() + { + return m_DefaultOnlyCondenseSameSeries; + } protected: ThreeDnTDICOMSeriesReader(unsigned int decimalPlacesForOrientation = Superclass::m_DefaultDecimalPlacesForOrientation); ~ThreeDnTDICOMSeriesReader() override; ThreeDnTDICOMSeriesReader(const ThreeDnTDICOMSeriesReader& other); ThreeDnTDICOMSeriesReader& operator=(const ThreeDnTDICOMSeriesReader& other); /** \brief Analyze the groups produced by DICOMITKSeriesGDCMReader for 3D+t properties. This method tests whether some blocks are at the same spatial position and groups them into single blocks. */ SortingBlockList Condense3DBlocks(SortingBlockList&) override; bool LoadMitkImageForImageBlockDescriptor(DICOMImageBlockDescriptor& block) const override; bool m_Group3DandT; + bool m_OnlyCondenseSameSeries; const static bool m_DefaultGroup3DandT = true; + const static bool m_DefaultOnlyCondenseSameSeries = true; }; } #endif diff --git a/Modules/DICOMReader/resource/configurations/3DnT/classicreader.xml b/Modules/DICOMReader/resource/configurations/3DnT/classicreader.xml index dfc9c4bbb4..1a0ca7f09a 100644 --- a/Modules/DICOMReader/resource/configurations/3DnT/classicreader.xml +++ b/Modules/DICOMReader/resource/configurations/3DnT/classicreader.xml @@ -1,10 +1,11 @@ \ No newline at end of file diff --git a/Modules/DICOMReader/src/mitkBaseDICOMReaderService.cpp b/Modules/DICOMReader/src/mitkBaseDICOMReaderService.cpp index 7133c0a14f..aa9d0f3e4b 100644 --- a/Modules/DICOMReader/src/mitkBaseDICOMReaderService.cpp +++ b/Modules/DICOMReader/src/mitkBaseDICOMReaderService.cpp @@ -1,209 +1,221 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkBaseDICOMReaderService.h" #include #include #include #include #include #include #include #include "legacy/mitkDicomSeriesReader.h" #include #include #include "mitkIPropertyProvider.h" #include "mitkPropertyNameHelper.h" #include #include #include -namespace mitk { +namespace mitk +{ BaseDICOMReaderService::BaseDICOMReaderService(const std::string& description) : AbstractFileReader(CustomMimeType(IOMimeTypes::DICOM_MIMETYPE()), description) { } - BaseDICOMReaderService::BaseDICOMReaderService(const mitk::CustomMimeType& customType, const std::string& description) - : AbstractFileReader(customType, description) - { - } +BaseDICOMReaderService::BaseDICOMReaderService(const mitk::CustomMimeType& customType, const std::string& description) + : AbstractFileReader(customType, description) +{ +} + +void BaseDICOMReaderService::SetOnlyRegardOwnSeries(bool regard) +{ + m_OnlyRegardOwnSeries = regard; +} + +bool BaseDICOMReaderService::GetOnlyRegardOwnSeries() const +{ + return m_OnlyRegardOwnSeries; +} + std::vector > BaseDICOMReaderService::Read() { std::vector result; - std::string fileName = this->GetLocalFileName(); + const std::string fileName = this->GetLocalFileName(); //special handling of Philips 3D US DICOM. //Copied from DICOMSeriesReaderService if (DicomSeriesReader::IsPhilips3DDicom(fileName)) { MITK_INFO << "it is a Philips3D US Dicom file" << std::endl; mitk::LocaleSwitch localeSwitch("C"); std::locale previousCppLocale(std::cin.getloc()); std::locale l("C"); std::cin.imbue(l); DataNode::Pointer node = DataNode::New(); mitk::DicomSeriesReader::StringContainer stringvec; stringvec.push_back(fileName); if (DicomSeriesReader::LoadDicomSeries(stringvec, *node)) { BaseData::Pointer data = node->GetData(); StringProperty::Pointer nameProp = StringProperty::New(itksys::SystemTools::GetFilenameName(fileName)); data->GetPropertyList()->SetProperty("name", nameProp); result.push_back(data); } std::cin.imbue(previousCppLocale); return result; } //Normal DICOM handling (It wasn't a Philips 3D US) - mitk::StringList relevantFiles = this->GetRelevantFiles(); - - // check whether directory or file - // if directory try to find first file within it instead - // We only support this for a single directory at once - if (relevantFiles.empty()) - { - bool pathIsDirectory = itksys::SystemTools::FileIsDirectory(this->GetLocalFileName()); - if (pathIsDirectory) - { - itksys::Directory input; - input.Load(this->GetLocalFileName().c_str()); - - std::vector files; - for (unsigned long idx = 0; idxGetLocalFileName() + "/" + std::string(input.GetFile(idx)); - files.push_back(fullpath.c_str()); - } - } - relevantFiles = files; - } - } + mitk::StringList relevantFiles = this->GetDICOMFilesInSameDirectory(); if (relevantFiles.empty()) { MITK_INFO << "DICOMReader service found no relevant files in specified location. No data is loaded. Location: "<GetReader(relevantFiles); + bool pathIsDirectory = itksys::SystemTools::FileIsDirectory(fileName); + + if (!pathIsDirectory && m_OnlyRegardOwnSeries) + { + relevantFiles = mitk::FilterDICOMFilesForSameSeries(fileName, relevantFiles); + } + + mitk::DICOMFileReader::Pointer reader = this->GetReader(relevantFiles); if(reader.IsNull()) { MITK_INFO << "DICOMReader service found no suitable reader configuration for relevant files."; } else { + if (!pathIsDirectory) + { //we ensure that we only load the relevant image block files + const auto nrOfOutputs = reader->GetNumberOfOutputs(); + for (unsigned int outputIndex = 0; outputIndex < nrOfOutputs; ++outputIndex) + { + const auto frameList = reader->GetOutput(outputIndex).GetImageFrameList(); + + auto finding = std::find_if(frameList.begin(), frameList.end(), [&](const DICOMImageFrameInfo::Pointer& frame) { return frame->Filename == fileName; }); + + if (finding != frameList.end()) + { //we have the block containing the fileName -> these are the realy relevant files. + relevantFiles.resize(frameList.size()); + std::transform(frameList.begin(), frameList.end(), relevantFiles.begin(), [](const DICOMImageFrameInfo::Pointer& frame) { return frame->Filename; }); + break; + } + } + } const unsigned int ntotalfiles = relevantFiles.size(); for( unsigned int i=0; i< ntotalfiles; i++) { m_ReadFiles.push_back( relevantFiles.at(i) ); } reader->SetAdditionalTagsOfInterest(mitk::GetCurrentDICOMTagsOfInterest()); reader->SetTagLookupTableToPropertyFunctor(mitk::GetDICOMPropertyForDICOMValuesFunctor); reader->SetInputFiles(relevantFiles); mitk::DICOMDCMTKTagScanner::Pointer scanner = mitk::DICOMDCMTKTagScanner::New(); scanner->AddTagPaths(reader->GetTagsOfInterest()); scanner->SetInputFiles(relevantFiles); scanner->Scan(); reader->SetTagCache(scanner->GetScanCache()); reader->AnalyzeInputFiles(); reader->LoadImages(); for (unsigned int i = 0; i < reader->GetNumberOfOutputs(); ++i) { const mitk::DICOMImageBlockDescriptor& desc = reader->GetOutput(i); mitk::BaseData::Pointer data = desc.GetMitkImage().GetPointer(); std::string nodeName = GenerateNameFromDICOMProperties(&desc); StringProperty::Pointer nameProp = StringProperty::New(nodeName); data->SetProperty("name", nameProp); result.push_back(data); } } } return result; } -StringList BaseDICOMReaderService::GetRelevantFiles() const +StringList BaseDICOMReaderService::GetDICOMFilesInSameDirectory() const { std::string fileName = this->GetLocalFileName(); mitk::StringList relevantFiles = mitk::GetDICOMFilesInSameDirectory(fileName); return relevantFiles; } IFileReader::ConfidenceLevel BaseDICOMReaderService::GetConfidenceLevel() const { IFileReader::ConfidenceLevel abstractConfidence = AbstractFileReader::GetConfidenceLevel(); if (Unsupported == abstractConfidence) { if (itksys::SystemTools::FileIsDirectory(this->GetInputLocation().c_str())) { // In principle we support dicom directories return Supported; } } return abstractConfidence; } std::string GenerateNameFromDICOMProperties(const mitk::IPropertyProvider* provider) { std::string nodeName = mitk::DataNode::NO_NAME_VALUE(); auto studyProp = provider->GetConstProperty(mitk::GeneratePropertyNameForDICOMTag(0x0008, 0x1030).c_str()); if (studyProp.IsNotNull()) { nodeName = studyProp->GetValueAsString(); } auto seriesProp = provider->GetConstProperty(mitk::GeneratePropertyNameForDICOMTag(0x0008, 0x103E).c_str()); if (seriesProp.IsNotNull()) { if (studyProp.IsNotNull()) { nodeName += " / "; } else { nodeName = ""; } nodeName += seriesProp->GetValueAsString(); } return nodeName; }; } diff --git a/Modules/DICOMReader/src/mitkClassicDICOMSeriesReader.cpp b/Modules/DICOMReader/src/mitkClassicDICOMSeriesReader.cpp index 5a2fc65985..46636f17ef 100644 --- a/Modules/DICOMReader/src/mitkClassicDICOMSeriesReader.cpp +++ b/Modules/DICOMReader/src/mitkClassicDICOMSeriesReader.cpp @@ -1,94 +1,95 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkClassicDICOMSeriesReader.h" #include "mitkDICOMTagBasedSorter.h" #include "mitkDICOMSortByTag.h" #include "mitkSortByImagePositionPatient.h" mitk::ClassicDICOMSeriesReader ::ClassicDICOMSeriesReader() :ThreeDnTDICOMSeriesReader() { mitk::DICOMTagBasedSorter::Pointer tagSorter = mitk::DICOMTagBasedSorter::New(); // all the things that split by tag in mitk::DicomSeriesReader tagSorter->AddDistinguishingTag( DICOMTag(0x0020, 0x000e) ); // Series Instance UID //tagSorter->AddDistinguishingTag( DICOMTag(0x0020, 0x0052) ); // Frame of Reference UID // a sorter... mitk::DICOMSortCriterion::ConstPointer sorting = mitk::SortByImagePositionPatient::New( // image position patient and image orientation mitk::DICOMSortByTag::New( DICOMTag(0x0020, 0x0012), // aqcuisition number mitk::DICOMSortByTag::New( DICOMTag(0x0008, 0x0032), // aqcuisition time mitk::DICOMSortByTag::New( DICOMTag(0x0018, 0x1060), // trigger time mitk::DICOMSortByTag::New( DICOMTag(0x0008, 0x0018) // SOP instance UID (last resort, not really meaningful but decides clearly) ).GetPointer() ).GetPointer() ).GetPointer() ).GetPointer() ).GetPointer(); tagSorter->SetSortCriterion( sorting ); tagSorter->SetStrictSorting(false); // nothing did enforce constant distances before, there was just the EquiDistantBlocksSorter logic // define above sorting for this class this->AddSortingElement( tagSorter ); this->SetFixTiltByShearing(true); // that was configurable, default was true this->SetToleratedOriginOffset(0.005); // was hard-coded this->SetGroup3DandT(true); // that was configurable, default was true + this->OnlyCondenseSameSeriesOff(); m_EquiDistantBlocksSorter->SetAcceptTwoSlicesGroups(false); // old reader did not accept that this->SetConfigurationLabel("2013 sorting logic"); - this->SetConfigurationDescription("Sort by Image Position, then Acquisition Number, Time, Trigger time, group by 3D+t, group tilted images"); + this->SetConfigurationDescription("Sort by Image Position, then Acquisition Number, Time, Trigger time, group by 3D+t, group tilted images, condense blocks even if series does not match"); } mitk::ClassicDICOMSeriesReader ::ClassicDICOMSeriesReader(const ClassicDICOMSeriesReader& other ) :ThreeDnTDICOMSeriesReader(other) { } mitk::ClassicDICOMSeriesReader ::~ClassicDICOMSeriesReader() { } mitk::ClassicDICOMSeriesReader& mitk::ClassicDICOMSeriesReader ::operator=(const ClassicDICOMSeriesReader& other) { if (this != &other) { ThreeDnTDICOMSeriesReader::operator=(other); } return *this; } bool mitk::ClassicDICOMSeriesReader ::operator==(const DICOMFileReader& other) const { if (dynamic_cast(&other)) { return true; } else { return false; } } diff --git a/Modules/DICOMReader/src/mitkDICOMFilesHelper.cpp b/Modules/DICOMReader/src/mitkDICOMFilesHelper.cpp index c0faed897c..5cd3ed1ba2 100644 --- a/Modules/DICOMReader/src/mitkDICOMFilesHelper.cpp +++ b/Modules/DICOMReader/src/mitkDICOMFilesHelper.cpp @@ -1,49 +1,80 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkDICOMFilesHelper.h" #include #include #include +#include mitk::DICOMFilePathList mitk::GetDICOMFilesInSameDirectory(const std::string& filePath) { DICOMFilePathList result; if (!filePath.empty()) { - std::string dir = itksys::SystemTools::GetFilenamePath(filePath); + + std::string dir = filePath; + if (!itksys::SystemTools::FileIsDirectory(filePath)) + { + dir = itksys::SystemTools::GetFilenamePath(filePath); + } gdcm::Directory directoryLister; directoryLister.Load(dir.c_str(), false); // non-recursive result = FilterForDICOMFiles(directoryLister.GetFilenames()); } return result; }; mitk::DICOMFilePathList mitk::FilterForDICOMFiles(const DICOMFilePathList& fileList) { mitk::DICOMFilePathList result; itk::GDCMImageIO::Pointer io = itk::GDCMImageIO::New(); for (auto aFile : fileList) { if (io->CanReadFile(aFile.c_str())) { result.push_back(aFile); } } return result; }; + +mitk::DICOMFilePathList mitk::FilterDICOMFilesForSameSeries(const std::string& refFilePath, const DICOMFilePathList& fileList) +{ + auto dicomFiles = FilterForDICOMFiles(fileList); + + //use the gdcm scanner directly instead of our wrapping classes, as it is a very simple task + //and I want to spare the indirection/overhead. + gdcm::Scanner scanner; + + const gdcm::Tag seriesInstanceUIDTag(0x0020, 0x000e); + + scanner.AddTag(seriesInstanceUIDTag); //Series Instance UID; + + scanner.Scan({ refFilePath }); + auto uid = scanner.GetValue(refFilePath.c_str(), seriesInstanceUIDTag); + + if (uid != nullptr) + { + const std::string refUID = uid; + scanner.Scan(dicomFiles); + return scanner.GetAllFilenamesFromTagToValue(seriesInstanceUIDTag, refUID.c_str()); + } + + return mitk::DICOMFilePathList(); +} diff --git a/Modules/DICOMReader/src/mitkDICOMReaderConfigurator.cpp b/Modules/DICOMReader/src/mitkDICOMReaderConfigurator.cpp index 90db76fd72..87e64e6c1c 100644 --- a/Modules/DICOMReader/src/mitkDICOMReaderConfigurator.cpp +++ b/Modules/DICOMReader/src/mitkDICOMReaderConfigurator.cpp @@ -1,673 +1,678 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkDICOMReaderConfigurator.h" #include "mitkDICOMSortByTag.h" #include "mitkSortByImagePositionPatient.h" mitk::DICOMReaderConfigurator ::DICOMReaderConfigurator() { } mitk::DICOMReaderConfigurator ::~DICOMReaderConfigurator() { } mitk::DICOMFileReader::Pointer mitk::DICOMReaderConfigurator ::CreateFromConfigFile(const std::string& filename) const { TiXmlDocument doc (filename); if (doc.LoadFile()) { return this->CreateFromTiXmlDocument( doc ); } else { MITK_ERROR << "Unable to load file at '" << filename <<"'"; return DICOMFileReader::Pointer(); } } mitk::DICOMFileReader::Pointer mitk::DICOMReaderConfigurator ::CreateFromUTF8ConfigString(const std::string& xmlContents) const { TiXmlDocument doc; doc.Parse(xmlContents.c_str(), nullptr, TIXML_ENCODING_UTF8); return this->CreateFromTiXmlDocument( doc ); } mitk::DICOMFileReader::Pointer mitk::DICOMReaderConfigurator ::CreateFromTiXmlDocument(TiXmlDocument& doc) const { TiXmlHandle root(doc.RootElement()); if (TiXmlElement* rootElement = root.ToElement()) { if (strcmp(rootElement->Value(), "DICOMFileReader")) // :-( no std::string methods { MITK_ERROR << "File should contain a tag at top-level! Found '" << (rootElement->Value() ? std::string(rootElement->Value()) : std::string("!nothing!")) << "' instead"; return nullptr; } const char* classnameC = rootElement->Attribute("class"); if (!classnameC) { MITK_ERROR << "File should name a reader class in the class attribute: . Found nothing instead"; return nullptr; } int version(1); if ( rootElement->QueryIntAttribute("version", &version) == TIXML_SUCCESS) { if (version == 1) { MITK_WARN << "Warning the given configuration is for DICOMFileReaders of version 1. " << "This old version may be interpreted differently. Reason: " << "The default values for the following xml settings have been changed: " << "FixTiltByShearing (false -> true); StrictSorting (true -> false); ExpectDistanceOne (true -> false)."; } else if (version >2) { MITK_WARN << "This reader is only capable of creating DICOMFileReaders of version 1 and 2. " << "Will not continue, because given configuration is meant for version " << version << "."; return nullptr; } } else { MITK_ERROR << "File should name the version of the reader class in the version attribute: ." << " Found nothing instead, assuming version 1!"; version = 1; } std::string classname(classnameC); double decimalPlacesForOrientation(mitk::DICOMITKSeriesGDCMReader::GetDefaultDecimalPlacesForOrientation()); bool useDecimalPlacesForOrientation(false); useDecimalPlacesForOrientation = rootElement->QueryDoubleAttribute("decimalPlacesForOrientation", &decimalPlacesForOrientation) == TIXML_SUCCESS; // attribute present and a double value if (classname == "ClassicDICOMSeriesReader") { mitk::ClassicDICOMSeriesReader::Pointer reader = mitk::ClassicDICOMSeriesReader::New(); this->ConfigureCommonPropertiesOfDICOMITKSeriesGDCMReader(reader.GetPointer(), rootElement); this->ConfigureCommonPropertiesOfThreeDnTDICOMSeriesReader(reader.GetPointer(), rootElement); return reader.GetPointer(); } if (classname == "ThreeDnTDICOMSeriesReader") { mitk::ThreeDnTDICOMSeriesReader::Pointer reader; if (useDecimalPlacesForOrientation) reader = mitk::ThreeDnTDICOMSeriesReader::New(decimalPlacesForOrientation); else reader = mitk::ThreeDnTDICOMSeriesReader::New(); return ConfigureThreeDnTDICOMSeriesReader(reader, rootElement).GetPointer(); } else if (classname == "DICOMITKSeriesGDCMReader") { bool simpleVolumeImport = QueryBooleanAttribute(rootElement, "simpleVolumeImport", mitk::DICOMITKSeriesGDCMReader::GetDefaultSimpleVolumeImport()); mitk::DICOMITKSeriesGDCMReader::Pointer reader; if (useDecimalPlacesForOrientation) reader = mitk::DICOMITKSeriesGDCMReader::New( decimalPlacesForOrientation, simpleVolumeImport ); else reader = mitk::DICOMITKSeriesGDCMReader::New( mitk::DICOMITKSeriesGDCMReader::GetDefaultDecimalPlacesForOrientation(), simpleVolumeImport ); // simple volume import that ignores number of frames and inter slice distance return ConfigureDICOMITKSeriesGDCMReader(reader, rootElement).GetPointer(); } else { MITK_ERROR << "DICOMFileReader tag names unknown class '" << classname << "'"; return nullptr; } } else { MITK_ERROR << "Great confusion: no root element in XML document. Expecting a DICOMFileReader tag at top-level."; return nullptr; } } #define boolStringTrue(s) \ ( s == "true" || s == "on" || s == "1" \ || s == "TRUE" || s == "ON") bool mitk::DICOMReaderConfigurator ::QueryBooleanAttribute(const TiXmlElement* element, const char* attributeName, bool defaultValue) const { bool value(defaultValue); const char* valueC = element->Attribute(attributeName); if (valueC) { std::string valueS(valueC); value = boolStringTrue(valueS); } return value; } void mitk::DICOMReaderConfigurator ::ConfigureCommonPropertiesOfThreeDnTDICOMSeriesReader(ThreeDnTDICOMSeriesReader::Pointer reader, TiXmlElement* element) const { // add the "group3DnT" flag bool group3DnT = QueryBooleanAttribute(element, "group3DnT", ThreeDnTDICOMSeriesReader::GetDefaultGroup3DandT()); reader->SetGroup3DandT( group3DnT ); + + // add the "onlyCondenseSameSeries" flag + bool onlyCondenseSameSeries = QueryBooleanAttribute(element, "onlyCondenseSameSeries", ThreeDnTDICOMSeriesReader::GetDefaultOnlyCondenseSameSeries()); + + reader->SetOnlyCondenseSameSeries(onlyCondenseSameSeries); } mitk::ThreeDnTDICOMSeriesReader::Pointer mitk::DICOMReaderConfigurator ::ConfigureThreeDnTDICOMSeriesReader(ThreeDnTDICOMSeriesReader::Pointer reader, TiXmlElement* element) const { assert(element); // use all the base class configuration if (this->ConfigureDICOMITKSeriesGDCMReader( reader.GetPointer(), element ).IsNull()) { return nullptr; } this->ConfigureCommonPropertiesOfThreeDnTDICOMSeriesReader(reader,element); return reader; } void mitk::DICOMReaderConfigurator ::ConfigureCommonPropertiesOfDICOMITKSeriesGDCMReader(DICOMITKSeriesGDCMReader::Pointer reader, TiXmlElement* element) const { assert(element); const char* configLabelC = element->Attribute("label"); if (configLabelC) { std::string configLabel(configLabelC); reader->SetConfigurationLabel(configLabel); } const char* configDescriptionC = element->Attribute("description"); if (configDescriptionC) { reader->SetConfigurationDescription(configDescriptionC); } // "fixTiltByShearing" flag bool fixTiltByShearing = QueryBooleanAttribute(element, "fixTiltByShearing", DICOMITKSeriesGDCMReader::GetDefaultFixTiltByShearing()); reader->SetFixTiltByShearing( fixTiltByShearing ); } mitk::DICOMITKSeriesGDCMReader::Pointer mitk::DICOMReaderConfigurator ::ConfigureDICOMITKSeriesGDCMReader(DICOMITKSeriesGDCMReader::Pointer reader, TiXmlElement* element) const { assert(element); this->ConfigureCommonPropertiesOfDICOMITKSeriesGDCMReader(reader, element); // "acceptTwoSlicesGroups" flag bool acceptTwoSlicesGroups = QueryBooleanAttribute(element, "acceptTwoSlicesGroups", true); reader->SetAcceptTwoSlicesGroups( acceptTwoSlicesGroups ); // "toleratedOriginError" attribute (double) bool toleratedOriginErrorIsAbsolute = QueryBooleanAttribute(element, "toleratedOriginErrorIsAbsolute", false); double toleratedOriginError(0.3); if (element->QueryDoubleAttribute("toleratedOriginError", &toleratedOriginError) == TIXML_SUCCESS) // attribute present and a double value { if (toleratedOriginErrorIsAbsolute) { reader->SetToleratedOriginOffset( toleratedOriginError ); } else { reader->SetToleratedOriginOffsetToAdaptive( toleratedOriginError ); } } // DICOMTagBasedSorters are the only thing we create at this point // TODO for-loop over all child elements of type DICOMTagBasedSorter, BUT actually a single sorter of this type is enough. TiXmlElement* dElement = element->FirstChildElement("DICOMDatasetSorter"); if (dElement) { const char* classnameC = dElement->Attribute("class"); if (!classnameC) { MITK_ERROR << "File should name a DICOMDatasetSorter class in the class attribute of . Found nothing instead"; return nullptr; } std::string classname(classnameC); if (classname == "DICOMTagBasedSorter") { DICOMTagBasedSorter::Pointer tagSorter = CreateDICOMTagBasedSorter(dElement); if (tagSorter.IsNotNull()) { reader->AddSortingElement( tagSorter ); } } else { MITK_ERROR << "DICOMDatasetSorter tag names unknown class '" << classname << "'"; return nullptr; } } return reader; } mitk::DICOMTagBasedSorter::Pointer mitk::DICOMReaderConfigurator ::CreateDICOMTagBasedSorter(TiXmlElement* element) const { mitk::DICOMTagBasedSorter::Pointer tagSorter = mitk::DICOMTagBasedSorter::New(); // "strictSorting" parameter! bool strictSorting = QueryBooleanAttribute(element, "strictSorting", mitk::DICOMTagBasedSorter::GetDefaultStrictSorting()); tagSorter->SetStrictSorting(strictSorting); // "strictSorting" parameter! bool expectDistanceOne = QueryBooleanAttribute(element, "expectDistanceOne", mitk::DICOMTagBasedSorter::GetDefaultExpectDistanceOne()); tagSorter->SetExpectDistanceOne(expectDistanceOne); TiXmlElement* dElement = element->FirstChildElement("Distinguishing"); if (dElement) { for ( TiXmlElement* tChild = dElement->FirstChildElement(); tChild != nullptr; tChild = tChild->NextSiblingElement() ) { try { mitk::DICOMTag tag = tagFromXMLElement(tChild); int i(5); if (tChild->QueryIntAttribute("cutDecimalPlaces", &i) == TIXML_SUCCESS) { tagSorter->AddDistinguishingTag( tag, new mitk::DICOMTagBasedSorter::CutDecimalPlaces(i) ); } else { tagSorter->AddDistinguishingTag( tag ); } } catch(...) { return nullptr; } } } // "sorting tags" TiXmlElement* sElement = element->FirstChildElement("Sorting"); if (sElement) { DICOMSortCriterion::Pointer previousCriterion; DICOMSortCriterion::Pointer currentCriterion; for ( TiXmlNode* tChildNode = sElement->LastChild(); tChildNode != nullptr; tChildNode = tChildNode->PreviousSibling() ) { TiXmlElement* tChild = tChildNode->ToElement(); if (!tChild) continue; if (!strcmp(tChild->Value(), "Tag")) { try { currentCriterion = this->CreateDICOMSortByTag(tChild, previousCriterion); } catch(...) { std::stringstream ss; ss << "Could not parse element at (input line " << tChild->Row() << ", col. " << tChild->Column() << ")!"; MITK_ERROR << ss.str(); return nullptr; } } else if (!strcmp(tChild->Value(), "ImagePositionPatient")) { try { currentCriterion = this->CreateSortByImagePositionPatient(tChild, previousCriterion); } catch(...) { std::stringstream ss; ss << "Could not parse element at (input line " << tChild->Row() << ", col. " << tChild->Column() << ")!"; MITK_ERROR << ss.str(); return nullptr; } } else { MITK_ERROR << "File contain unknown tag <" << tChild->Value() << "> tag as child to ! Cannot interpret..."; } previousCriterion = currentCriterion; } tagSorter->SetSortCriterion( currentCriterion.GetPointer() ); } return tagSorter; } std::string mitk::DICOMReaderConfigurator ::requiredStringAttribute(TiXmlElement* xmlElement, const std::string& key) const { assert(xmlElement); const char* gC = xmlElement->Attribute(key.c_str()); if (gC) { std::string gS(gC); return gS; } else { std::stringstream ss; ss << "Expected an attribute '" << key << "' at this position " "(input line " << xmlElement->Row() << ", col. " << xmlElement->Column() << ")!"; MITK_ERROR << ss.str(); throw std::invalid_argument( ss.str() ); } } unsigned int mitk::DICOMReaderConfigurator ::hexStringToUInt(const std::string& s) const { std::stringstream converter(s); unsigned int ui; converter >> std::hex >> ui; MITK_DEBUG << "Converted string '" << s << "' to unsigned int " << ui; return ui; } mitk::DICOMTag mitk::DICOMReaderConfigurator ::tagFromXMLElement(TiXmlElement* xmlElement) const { assert(xmlElement); if (strcmp(xmlElement->Value(), "Tag")) // :-( no std::string methods { std::stringstream ss; ss << "Expected a tag at this position " "(input line " << xmlElement->Row() << ", col. " << xmlElement->Column() << ")!"; MITK_ERROR << ss.str(); throw std::invalid_argument( ss.str() ); } std::string groupS = requiredStringAttribute(xmlElement, "group"); std::string elementS = requiredStringAttribute(xmlElement, "element"); try { // convert string to int (assuming string is in hex format with leading "0x" like "0x0020") unsigned int group = hexStringToUInt(groupS); unsigned int element = hexStringToUInt(elementS); return DICOMTag(group, element); } catch(...) { std::stringstream ss; ss << "Expected group and element values in to be hexadecimal with leading 0x, e.g. '0x0020'" "(input line " << xmlElement->Row() << ", col. " << xmlElement->Column() << ")!"; MITK_ERROR << ss.str(); throw std::invalid_argument( ss.str() ); } } mitk::DICOMSortCriterion::Pointer mitk::DICOMReaderConfigurator ::CreateDICOMSortByTag(TiXmlElement* xmlElement, DICOMSortCriterion::Pointer secondaryCriterion) const { mitk::DICOMTag tag = tagFromXMLElement(xmlElement); return DICOMSortByTag::New(tag, secondaryCriterion).GetPointer(); } mitk::DICOMSortCriterion::Pointer mitk::DICOMReaderConfigurator ::CreateSortByImagePositionPatient(TiXmlElement*, DICOMSortCriterion::Pointer secondaryCriterion) const { return SortByImagePositionPatient::New(secondaryCriterion).GetPointer(); } std::string mitk::DICOMReaderConfigurator ::CreateConfigStringFromReader(DICOMFileReader::ConstPointer reader) const { // check possible sub-classes from the most-specific one up to the most generic one const DICOMFileReader* cPointer = reader; TiXmlElement* root; if (const auto* specificReader = dynamic_cast(cPointer)) { root = this->CreateConfigStringFromReader(specificReader); } else if (const auto* specificReader = dynamic_cast(cPointer)) { root = this->CreateConfigStringFromReader(specificReader); } else if (const auto* specificReader = dynamic_cast(cPointer)) { root = this->CreateConfigStringFromReader(specificReader); } else { MITK_WARN << "Unknown reader class passed to DICOMReaderConfigurator::CreateConfigStringFromReader(). Cannot serialize."; return ""; // no serialization, what a pity } if (root) { TiXmlDocument document; document.LinkEndChild( root ); TiXmlPrinter printer; printer.SetIndent( " " ); document.Accept( &printer ); std::string xmltext = printer.CStr(); return xmltext; } else { MITK_WARN << "DICOMReaderConfigurator::CreateConfigStringFromReader() created empty serialization. Problem?"; return ""; } } TiXmlElement* mitk::DICOMReaderConfigurator ::CreateConfigStringFromReader(const DICOMITKSeriesGDCMReader* reader) const { TiXmlElement* root = this->CreateDICOMFileReaderTag(reader); assert(root); root->SetAttribute("fixTiltByShearing", toString(reader->GetFixTiltByShearing())); root->SetAttribute("acceptTwoSlicesGroups", toString(reader->GetAcceptTwoSlicesGroups())); root->SetDoubleAttribute("toleratedOriginError", reader->GetToleratedOriginError()); root->SetAttribute("toleratedOriginErrorIsAbsolute", toString(reader->IsToleratedOriginOffsetAbsolute())); root->SetDoubleAttribute("decimalPlacesForOrientation", reader->GetDecimalPlacesForOrientation()); // iterate DICOMDatasetSorter objects DICOMITKSeriesGDCMReader::ConstSorterList sorterList = reader->GetFreelyConfiguredSortingElements(); for(auto sorterIter = sorterList.begin(); sorterIter != sorterList.end(); ++sorterIter) { const DICOMDatasetSorter* sorter = *sorterIter; if (const auto* specificSorter = dynamic_cast(sorter)) { TiXmlElement* sorterTag = this->CreateConfigStringFromDICOMDatasetSorter(specificSorter); root->LinkEndChild(sorterTag); } else { MITK_WARN << "Unknown DICOMDatasetSorter class passed to DICOMReaderConfigurator::CreateConfigStringFromReader(). Cannot serialize."; return nullptr; } } return root; } TiXmlElement* mitk::DICOMReaderConfigurator ::CreateConfigStringFromDICOMDatasetSorter(const DICOMTagBasedSorter* sorter) const { assert(sorter); auto sorterTag = new TiXmlElement("DICOMDatasetSorter"); sorterTag->SetAttribute("class", sorter->GetNameOfClass()); sorterTag->SetAttribute("strictSorting", toString(sorter->GetStrictSorting())); sorterTag->SetAttribute("expectDistanceOne", toString(sorter->GetExpectDistanceOne())); auto distinguishingTagsElement = new TiXmlElement("Distinguishing"); sorterTag->LinkEndChild(distinguishingTagsElement); mitk::DICOMTagList distinguishingTags = sorter->GetDistinguishingTags(); for (auto tagIter = distinguishingTags.begin(); tagIter != distinguishingTags.end(); ++tagIter) { TiXmlElement* tag = this->CreateConfigStringFromDICOMTag(*tagIter); distinguishingTagsElement->LinkEndChild(tag); const DICOMTagBasedSorter::TagValueProcessor* processor = sorter->GetTagValueProcessorForDistinguishingTag(*tagIter); if (const auto* specificProcessor = dynamic_cast(processor)) { tag->SetDoubleAttribute("cutDecimalPlaces", specificProcessor->GetPrecision()); } } auto sortingElement = new TiXmlElement("Sorting"); sorterTag->LinkEndChild(sortingElement); mitk::DICOMSortCriterion::ConstPointer sortCriterion = sorter->GetSortCriterion(); while (sortCriterion.IsNotNull()) { std::string classname = sortCriterion->GetNameOfClass(); if (classname == "SortByImagePositionPatient") { sortingElement->LinkEndChild( new TiXmlElement("ImagePositionPatient") ); // no parameters } else if (classname == "DICOMSortByTag") { DICOMTagList pseudoTagList = sortCriterion->GetTagsOfInterest(); if (pseudoTagList.size() == 1) { DICOMTag firstTag = pseudoTagList.front(); TiXmlElement* tagElement = this->CreateConfigStringFromDICOMTag(firstTag); sortingElement->LinkEndChild( tagElement ); } else { MITK_ERROR << "Encountered SortByTag class with MULTIPLE tag in CreateConfigStringFromDICOMDatasetSorter. Cannot serialize."; return nullptr; } } else { MITK_ERROR << "Encountered unknown class '" << classname << "' in CreateConfigStringFromDICOMDatasetSorter. Cannot serialize."; return nullptr; } sortCriterion = sortCriterion->GetSecondaryCriterion(); } return sorterTag; } TiXmlElement* mitk::DICOMReaderConfigurator ::CreateConfigStringFromDICOMTag(const DICOMTag& tag) const { auto tagElement = new TiXmlElement("Tag"); // name group element tagElement->SetAttribute("name", tag.GetName().c_str()); tagElement->SetAttribute("group", toHexString(tag.GetGroup())); tagElement->SetAttribute("element", toHexString(tag.GetElement())); return tagElement; } std::string mitk::DICOMReaderConfigurator ::toHexString(unsigned int i) const { std::stringstream ss; ss << "0x" << std::setfill ('0') << std::setw(4) << std::hex << i; return ss.str(); } TiXmlElement* mitk::DICOMReaderConfigurator ::CreateConfigStringFromReader(const ThreeDnTDICOMSeriesReader* reader) const { TiXmlElement* root = this->CreateConfigStringFromReader(static_cast(reader)); assert(root); root->SetAttribute("group3DnT", toString(reader->GetGroup3DandT())); return root; } const char* mitk::DICOMReaderConfigurator ::toString(bool b) const { return b ? "true" : "false"; } TiXmlElement* mitk::DICOMReaderConfigurator ::CreateConfigStringFromReader(const ClassicDICOMSeriesReader* reader) const { return this->CreateDICOMFileReaderTag(reader); } TiXmlElement* mitk::DICOMReaderConfigurator ::CreateDICOMFileReaderTag(const DICOMFileReader* reader) const { auto readerTag = new TiXmlElement("DICOMFileReader"); readerTag->SetAttribute("class", reader->GetNameOfClass()); readerTag->SetAttribute("label", reader->GetConfigurationLabel().c_str()); readerTag->SetAttribute("description", reader->GetConfigurationDescription().c_str()); readerTag->SetAttribute("version", "1"); return readerTag; } diff --git a/Modules/DICOMReader/src/mitkThreeDnTDICOMSeriesReader.cpp b/Modules/DICOMReader/src/mitkThreeDnTDICOMSeriesReader.cpp index ef35f1aa25..ed8a80528e 100644 --- a/Modules/DICOMReader/src/mitkThreeDnTDICOMSeriesReader.cpp +++ b/Modules/DICOMReader/src/mitkThreeDnTDICOMSeriesReader.cpp @@ -1,260 +1,264 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkThreeDnTDICOMSeriesReader.h" #include "mitkITKDICOMSeriesReaderHelper.h" mitk::ThreeDnTDICOMSeriesReader ::ThreeDnTDICOMSeriesReader(unsigned int decimalPlacesForOrientation) :DICOMITKSeriesGDCMReader(decimalPlacesForOrientation) -,m_Group3DandT(m_DefaultGroup3DandT) +,m_Group3DandT(m_DefaultGroup3DandT), m_OnlyCondenseSameSeries(m_DefaultOnlyCondenseSameSeries) { } mitk::ThreeDnTDICOMSeriesReader ::ThreeDnTDICOMSeriesReader(const ThreeDnTDICOMSeriesReader& other ) :DICOMITKSeriesGDCMReader(other) -,m_Group3DandT(m_DefaultGroup3DandT) +,m_Group3DandT(m_DefaultGroup3DandT), m_OnlyCondenseSameSeries(m_DefaultOnlyCondenseSameSeries) { } mitk::ThreeDnTDICOMSeriesReader ::~ThreeDnTDICOMSeriesReader() { } mitk::ThreeDnTDICOMSeriesReader& mitk::ThreeDnTDICOMSeriesReader ::operator=(const ThreeDnTDICOMSeriesReader& other) { if (this != &other) { DICOMITKSeriesGDCMReader::operator=(other); this->m_Group3DandT = other.m_Group3DandT; } return *this; } bool mitk::ThreeDnTDICOMSeriesReader ::operator==(const DICOMFileReader& other) const { if (const auto* otherSelf = dynamic_cast(&other)) { return DICOMITKSeriesGDCMReader::operator==(other) && this->m_Group3DandT == otherSelf->m_Group3DandT; } else { return false; } } void mitk::ThreeDnTDICOMSeriesReader ::SetGroup3DandT(bool on) { m_Group3DandT = on; } bool mitk::ThreeDnTDICOMSeriesReader ::GetGroup3DandT() const { return m_Group3DandT; } mitk::DICOMITKSeriesGDCMReader::SortingBlockList mitk::ThreeDnTDICOMSeriesReader ::Condense3DBlocks(SortingBlockList& resultOf3DGrouping) { if (!m_Group3DandT) { return resultOf3DGrouping; // don't work if nobody asks us to } SortingBlockList remainingBlocks = resultOf3DGrouping; SortingBlockList non3DnTBlocks; SortingBlockList true3DnTBlocks; std::vector true3DnTBlocksTimeStepCount; // we should describe our need for this tag as needed via a function // (however, we currently know that the superclass will always need this tag) const DICOMTag tagImagePositionPatient(0x0020, 0x0032); + const DICOMTag tagSeriesInstaceUID(0x0020, 0x000e); while (!remainingBlocks.empty()) { // new block to fill up const DICOMDatasetAccessingImageFrameList& firstBlock = remainingBlocks.front(); DICOMDatasetAccessingImageFrameList current3DnTBlock = firstBlock; int current3DnTBlockNumberOfTimeSteps = 1; // get block characteristics of first block const unsigned int currentBlockNumberOfSlices = firstBlock.size(); const std::string currentBlockFirstOrigin = firstBlock.front()->GetTagValueAsString( tagImagePositionPatient ).value; const std::string currentBlockLastOrigin = firstBlock.back()->GetTagValueAsString( tagImagePositionPatient ).value; + const auto currentBlockSeriesInstanceUID = firstBlock.back()->GetTagValueAsString(tagSeriesInstaceUID).value; remainingBlocks.erase( remainingBlocks.begin() ); // compare all other blocks against the first one for (auto otherBlockIter = remainingBlocks.begin(); otherBlockIter != remainingBlocks.cend(); /*++otherBlockIter*/) // <-- inside loop { // get block characteristics from first block const DICOMDatasetAccessingImageFrameList otherBlock = *otherBlockIter; const unsigned int otherBlockNumberOfSlices = otherBlock.size(); const std::string otherBlockFirstOrigin = otherBlock.front()->GetTagValueAsString( tagImagePositionPatient ).value; const std::string otherBlockLastOrigin = otherBlock.back()->GetTagValueAsString( tagImagePositionPatient ).value; + const auto otherBlockSeriesInstanceUID = otherBlock.back()->GetTagValueAsString(tagSeriesInstaceUID).value; // add matching blocks to current3DnTBlock // keep other blocks for later if ( otherBlockNumberOfSlices == currentBlockNumberOfSlices + && (!m_OnlyCondenseSameSeries || otherBlockSeriesInstanceUID == currentBlockSeriesInstanceUID) && otherBlockFirstOrigin == currentBlockFirstOrigin && otherBlockLastOrigin == currentBlockLastOrigin ) { // matching block ++current3DnTBlockNumberOfTimeSteps; current3DnTBlock.insert( current3DnTBlock.end(), otherBlock.begin(), otherBlock.end() ); // append // remove this block from remainingBlocks otherBlockIter = remainingBlocks.erase(otherBlockIter); // make sure iterator otherBlockIter is valid afterwards } else { ++otherBlockIter; } } // in any case, we now now all about the first block of our list ... // ... and we wither call it 3D o 3D+t if (current3DnTBlockNumberOfTimeSteps > 1) { true3DnTBlocks.push_back(current3DnTBlock); true3DnTBlocksTimeStepCount.push_back(current3DnTBlockNumberOfTimeSteps); } else { non3DnTBlocks.push_back(current3DnTBlock); } } // create output for real 3D+t blocks (other outputs will be created by superclass) // set 3D+t flag on output block this->SetNumberOfOutputs( true3DnTBlocks.size() ); unsigned int o = 0; for (auto blockIter = true3DnTBlocks.cbegin(); blockIter != true3DnTBlocks.cend(); ++o, ++blockIter) { // bad copy&paste code from DICOMITKSeriesGDCMReader, should be handled in a better way DICOMDatasetAccessingImageFrameList gdcmFrameInfoList = *blockIter; assert(!gdcmFrameInfoList.empty()); // reverse frames if necessary // update tilt information from absolute last sorting const DICOMDatasetList datasetList = ConvertToDICOMDatasetList( gdcmFrameInfoList ); m_NormalDirectionConsistencySorter->SetInput( datasetList ); m_NormalDirectionConsistencySorter->Sort(); const DICOMDatasetAccessingImageFrameList sortedGdcmInfoFrameList = ConvertToDICOMDatasetAccessingImageFrameList( m_NormalDirectionConsistencySorter->GetOutput(0) ); const GantryTiltInformation& tiltInfo = m_NormalDirectionConsistencySorter->GetTiltInformation(); // set frame list for current block const DICOMImageFrameList frameList = ConvertToDICOMImageFrameList( sortedGdcmInfoFrameList ); assert(!frameList.empty()); DICOMImageBlockDescriptor block; block.SetTagCache( this->GetTagCache() ); // important: this must be before SetImageFrameList(), because SetImageFrameList will trigger reading of lots of interesting tags! block.SetAdditionalTagsOfInterest(GetAdditionalTagsOfInterest()); block.SetTagLookupTableToPropertyFunctor(GetTagLookupTableToPropertyFunctor()); block.SetImageFrameList( frameList ); block.SetTiltInformation( tiltInfo ); block.SetFlag("3D+t", true); block.SetIntProperty("timesteps", true3DnTBlocksTimeStepCount[o]); MITK_DEBUG << "Found " << true3DnTBlocksTimeStepCount[o] << " timesteps"; this->SetOutput( o, block ); } return non3DnTBlocks; } bool mitk::ThreeDnTDICOMSeriesReader ::LoadImages() { bool success = true; unsigned int numberOfOutputs = this->GetNumberOfOutputs(); for (unsigned int o = 0; o < numberOfOutputs; ++o) { const DICOMImageBlockDescriptor& block = this->InternalGetOutput(o); if (block.GetFlag("3D+t", false)) { success &= this->LoadMitkImageForOutput(o); } else { success &= DICOMITKSeriesGDCMReader::LoadMitkImageForOutput(o); // let superclass handle non-3D+t } } return success; } bool mitk::ThreeDnTDICOMSeriesReader ::LoadMitkImageForImageBlockDescriptor(DICOMImageBlockDescriptor& block) const { PushLocale(); const DICOMImageFrameList& frames = block.GetImageFrameList(); const GantryTiltInformation tiltInfo = block.GetTiltInformation(); const bool hasTilt = tiltInfo.IsRegularGantryTilt(); const int numberOfTimesteps = block.GetNumberOfTimeSteps(); if (numberOfTimesteps == 1) { return DICOMITKSeriesGDCMReader::LoadMitkImageForImageBlockDescriptor(block); } const int numberOfFramesPerTimestep = block.GetNumberOfFramesPerTimeStep(); ITKDICOMSeriesReaderHelper::StringContainerList filenamesPerTimestep; for (int timeStep = 0; timeStepFilename ); } filenamesPerTimestep.push_back( filenamesOfThisTimeStep ); } mitk::ITKDICOMSeriesReaderHelper helper; mitk::Image::Pointer mitkImage = helper.Load3DnT( filenamesPerTimestep, m_FixTiltByShearing && hasTilt, tiltInfo ); block.SetMitkImage( mitkImage ); PopLocale(); return true; } diff --git a/Modules/DicomUI/include/QmitkDicomExternalDataWidget.h b/Modules/DicomUI/include/QmitkDicomExternalDataWidget.h index b4f9b30781..4c689b3b5b 100644 --- a/Modules/DicomUI/include/QmitkDicomExternalDataWidget.h +++ b/Modules/DicomUI/include/QmitkDicomExternalDataWidget.h @@ -1,114 +1,120 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef QmitkDicomExternalDataWidget_h #define QmitkDicomExternalDataWidget_h #include "ui_QmitkDicomExternalDataWidgetControls.h" #include // include ctk #include #include // include QT #include #include #include #include #include #include #include class ctkFileDialog; /** * \brief QmitkDicomExternalDataWidget is a QWidget providing functionality for dicom import. * * \sa QmitkFunctionality * \ingroup Functionalities */ class MITKDICOMUI_EXPORT QmitkDicomExternalDataWidget : public QWidget { // this is needed for all Qt objects that should have a Qt meta-object // (everything that derives from QObject and wants to have signal/slots) Q_OBJECT public: static const std::string Widget_ID; /** * \brief QmitkDicomExternalDataWidget(QWidget *parent) constructor. * * \param parent is a pointer to the parent widget */ QmitkDicomExternalDataWidget(QWidget *parent); /** * \brief QmitkDicomExternalDataWidget destructor. */ ~QmitkDicomExternalDataWidget() override; /** * \brief CreateQtPartControl(QWidget *parent) sets the view objects from ui_QmitkDicomExternalDataWidgetControls.h. * * \param parent is a pointer to the parent widget */ virtual void CreateQtPartControl(QWidget *parent); /** * \brief Initializes the widget. This method has to be called before widget can start. */ void Initialize(); signals: /// @brief emitted when import into database is finished. void SignalStartDicomImport(const QStringList &); /// @brief emitted when view button is clicked. void SignalDicomToDataManager(QHash); public slots: /// @brief Called when download button was clicked. void OnDownloadButtonClicked(); /// @brief Called when view button was clicked. void OnViewButtonClicked(); /// @brief Called when adding a dicom directory. Starts a thread adding the directory. void OnStartDicomImport(const QString &); void OnSeriesSelectionChanged(const QStringList &s); +protected slots: + void OnProgressStep(const QString&); + + void OnProgressDetail(const QString&); + protected: /// \brief Get the list of filepath from current selected index in TreeView. All file paths referring to the index /// will be returned. QStringList GetFileNamesFromIndex(); /// \brief SetupImportDialog Sets up import dialog. void SetupImportDialog(); void SetupProgressDialog(); ctkDICOMDatabase *m_ExternalDatabase; ctkDICOMIndexer *m_ExternalIndexer; ctkFileDialog *m_ImportDialog; QProgressDialog *m_ProgressDialog; QString m_LastImportDirectory; + QString m_ProgressStep; Ui::QmitkDicomExternalDataWidgetControls *m_Controls; }; #endif // _QmitkDicomExternalDataWidget_H_INCLUDED diff --git a/Modules/DicomUI/src/QmitkDicomExternalDataWidget.cpp b/Modules/DicomUI/src/QmitkDicomExternalDataWidget.cpp index 91b5354c00..783ecb4f4e 100644 --- a/Modules/DicomUI/src/QmitkDicomExternalDataWidget.cpp +++ b/Modules/DicomUI/src/QmitkDicomExternalDataWidget.cpp @@ -1,214 +1,229 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ // Qmitk #include "QmitkDicomExternalDataWidget.h" #include // CTK #include // Qt #include #include #include const std::string QmitkDicomExternalDataWidget::Widget_ID = "org.mitk.Widgets.QmitkDicomExternalDataWidget"; QmitkDicomExternalDataWidget::QmitkDicomExternalDataWidget(QWidget *parent) : QWidget(parent), m_ProgressDialog(nullptr), m_Controls(nullptr) { Initialize(); CreateQtPartControl(this); } QmitkDicomExternalDataWidget::~QmitkDicomExternalDataWidget() { } void QmitkDicomExternalDataWidget::CreateQtPartControl(QWidget *parent) { // build up qt Widget, unless already done if (!m_Controls) { // create GUI widgets from the Qt Designer's .ui file m_Controls = new Ui::QmitkDicomExternalDataWidgetControls; m_Controls->setupUi(parent); m_Controls->viewExternalDataButton->setVisible(true); m_Controls->ctkDICOMBrowser->setTableOrientation(Qt::Vertical); m_Controls->ctkDICOMBrowser->setDICOMDatabase(m_ExternalDatabase); SetupImportDialog(); SetupProgressDialog(); // connect buttons connect(m_Controls->downloadButton, SIGNAL(clicked()), this, SLOT(OnDownloadButtonClicked())); connect(m_Controls->viewExternalDataButton, SIGNAL(clicked()), this, SLOT(OnViewButtonClicked())); connect(m_Controls->directoryButton, SIGNAL(clicked()), m_ImportDialog, SLOT(show())); connect(m_Controls->ctkDICOMBrowser, SIGNAL(seriesSelectionChanged(const QStringList &)), this, SLOT(OnSeriesSelectionChanged(const QStringList &))); connect( m_Controls->ctkDICOMBrowser, SIGNAL(seriesDoubleClicked(const QModelIndex &)), this, SLOT(OnViewButtonClicked())); connect(m_ImportDialog, SIGNAL(fileSelected(QString)), this, SLOT(OnStartDicomImport(QString))); connect(m_ExternalIndexer, - SIGNAL(indexingFilePath(const QString &)), - m_ProgressDialog, - SLOT(setLabelText(const QString &))); + SIGNAL(progressStep(QString)), + this, + SLOT(OnProgressStep(const QString&))); + connect(m_ExternalIndexer, + SIGNAL(progressDetail(QString)), + this, + SLOT(OnProgressDetail(const QString &))); connect(m_ExternalIndexer, SIGNAL(progress(int)), m_ProgressDialog, SLOT(setValue(int))); // actually the progress dialog closes if the maximum value is reached, BUT // the following line is needed since the external indexer wont reach maximum value (100 % progress) - connect(m_ExternalIndexer, SIGNAL(indexingComplete()), m_ProgressDialog, SLOT(close())); + connect(m_ExternalIndexer, SIGNAL(indexingComplete(int, int, int, int)), m_ProgressDialog, SLOT(close())); connect(m_ProgressDialog, SIGNAL(canceled()), m_ExternalIndexer, SLOT(cancel())); } } void QmitkDicomExternalDataWidget::Initialize() { m_ExternalDatabase = new ctkDICOMDatabase(this); try { // this used to be an in-memory database, but latest CTK enhancements made it difficult // to maintain this mechanism QTemporaryFile tmpDatabaseFile; tmpDatabaseFile.open(); tmpDatabaseFile.setAutoRemove(false); m_ExternalDatabase->openDatabase(tmpDatabaseFile.fileName(), QString("EXTERNAL-DB")); } catch (const std::exception&) { MITK_ERROR << "Database error: " << m_ExternalDatabase->lastError().toStdString(); m_ExternalDatabase->closeDatabase(); return; } m_ExternalIndexer = new ctkDICOMIndexer(this); } void QmitkDicomExternalDataWidget::OnDownloadButtonClicked() { QStringList filesToDownload = GetFileNamesFromIndex(); if (filesToDownload.size() == 0) { QMessageBox info; info.setText("You have to select an entry in the DICOM browser for import."); info.exec(); return; } emit SignalStartDicomImport(GetFileNamesFromIndex()); } void QmitkDicomExternalDataWidget::OnViewButtonClicked() { QStringList uids = m_Controls->ctkDICOMBrowser->currentSeriesSelection(); QString uid; foreach (uid, uids) { QStringList filesForSeries = m_ExternalDatabase->filesForSeries(uid); QHash eventProperty; eventProperty.insert("FilesForSeries", filesForSeries); if (!filesForSeries.isEmpty()) { QString modality = m_ExternalDatabase->fileValue(filesForSeries.at(0), "0008,0060"); eventProperty.insert("Modality", modality); } emit SignalDicomToDataManager(eventProperty); } } +void QmitkDicomExternalDataWidget::OnProgressStep(const QString& step) +{ + m_ProgressStep = step; + m_ProgressDialog->setLabelText(step); +} + +void QmitkDicomExternalDataWidget::OnProgressDetail(const QString& detail) +{ + m_ProgressDialog->setLabelText(m_ProgressStep+"\n"+detail); +} + QStringList QmitkDicomExternalDataWidget::GetFileNamesFromIndex() { QStringList filePaths; QString uid; QStringList seriesUIDs = m_Controls->ctkDICOMBrowser->currentSeriesSelection(); foreach (uid, seriesUIDs) { filePaths.append(m_ExternalDatabase->filesForSeries(uid)); } if (!filePaths.empty()) return filePaths; QStringList studyUIDs = m_Controls->ctkDICOMBrowser->currentStudiesSelection(); foreach (uid, studyUIDs) { seriesUIDs = m_ExternalDatabase->seriesForStudy(uid); foreach (uid, seriesUIDs) { filePaths.append(m_ExternalDatabase->filesForSeries(uid)); } } if (!filePaths.empty()) return filePaths; QStringList patientsUIDs = m_Controls->ctkDICOMBrowser->currentPatientsSelection(); foreach (uid, patientsUIDs) { studyUIDs = m_ExternalDatabase->studiesForPatient(uid); foreach (uid, studyUIDs) { seriesUIDs = m_ExternalDatabase->seriesForStudy(uid); foreach (uid, seriesUIDs) { filePaths.append(m_ExternalDatabase->filesForSeries(uid)); } } } return filePaths; } void QmitkDicomExternalDataWidget::OnStartDicomImport(const QString &directory) { m_ImportDialog->close(); // no need to show / start the progress dialog, as the dialog // appears by receiving the progress signal from the external indexer m_LastImportDirectory = directory; m_ExternalIndexer->addDirectory(m_ExternalDatabase, m_LastImportDirectory); } void QmitkDicomExternalDataWidget::OnSeriesSelectionChanged(const QStringList &s) { m_Controls->viewExternalDataButton->setEnabled((s.size() != 0)); } void QmitkDicomExternalDataWidget::SetupImportDialog() { // Initialize import widget m_ImportDialog = new ctkFileDialog(this); // Since copy on import is not working at the moment // this feature is disabled // QCheckBox* importCheckbox = new QCheckBox("Copy on import", m_ImportDialog); // m_ImportDialog->setBottomWidget(importCheckbox); m_ImportDialog->setFileMode(QFileDialog::Directory); m_ImportDialog->setLabelText(QFileDialog::Accept, "Import"); m_ImportDialog->setWindowTitle("Import DICOM files from directory"); m_ImportDialog->setWindowModality(Qt::ApplicationModal); } void QmitkDicomExternalDataWidget::SetupProgressDialog() { m_ProgressDialog = new QProgressDialog("Initialization ...", "Cancel", 0, 100, this); m_ProgressDialog->setWindowTitle("DICOM Import"); m_ProgressDialog->setWindowModality(Qt::ApplicationModal); m_ProgressDialog->setMinimumDuration(0); // FIX T20008: immediately set the progress dialog value to maximum --> will close the dialog m_ProgressDialog->setValue(100); } diff --git a/Modules/DicomUI/src/QmitkDicomLocalStorageWidget.cpp b/Modules/DicomUI/src/QmitkDicomLocalStorageWidget.cpp index b3bce27c1a..ba2679faa5 100644 --- a/Modules/DicomUI/src/QmitkDicomLocalStorageWidget.cpp +++ b/Modules/DicomUI/src/QmitkDicomLocalStorageWidget.cpp @@ -1,228 +1,228 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ // Qmitk #include "QmitkDicomLocalStorageWidget.h" //#include // Qt #include #include #include #include const std::string QmitkDicomLocalStorageWidget::Widget_ID = "org.mitk.Widgets.QmitkDicomLocalStorageWidget"; QmitkDicomLocalStorageWidget::QmitkDicomLocalStorageWidget(QWidget *parent) : QWidget(parent), m_LocalIndexer(new ctkDICOMIndexer(parent)), m_Controls(nullptr) { CreateQtPartControl(this); } QmitkDicomLocalStorageWidget::~QmitkDicomLocalStorageWidget() { m_LocalDatabase->closeDatabase(); } void QmitkDicomLocalStorageWidget::CreateQtPartControl(QWidget *parent) { if (!m_Controls) { m_Controls = new Ui::QmitkDicomLocalStorageWidgetControls; m_Controls->setupUi(parent); connect(m_Controls->deleteButton, SIGNAL(clicked()), this, SLOT(OnDeleteButtonClicked())); connect(m_Controls->viewInternalDataButton, SIGNAL(clicked()), this, SLOT(OnViewButtonClicked())); connect(m_Controls->ctkDicomBrowser, SIGNAL(seriesSelectionChanged(const QStringList &)), this, SLOT(OnSeriesSelectionChanged(const QStringList &))); connect(m_Controls->ctkDicomBrowser, SIGNAL(seriesSelectionChanged(const QStringList &)), this, SLOT(OnSeriesSelectionChanged(const QStringList &))); connect( m_Controls->ctkDicomBrowser, SIGNAL(seriesDoubleClicked(const QModelIndex &)), this, SLOT(OnViewButtonClicked())); - connect(m_LocalIndexer, SIGNAL(indexingComplete()), this, SIGNAL(SignalFinishedImport())); + connect(m_LocalIndexer, SIGNAL(indexingComplete(int, int, int, int)), this, SIGNAL(SignalFinishedImport())); m_Controls->ctkDicomBrowser->setTableOrientation(Qt::Vertical); } } void QmitkDicomLocalStorageWidget::OnStartDicomImport(const QString &dicomData) { if (m_LocalDatabase->isOpen()) { m_LocalIndexer->addDirectory(dicomData); } } void QmitkDicomLocalStorageWidget::OnStartDicomImport(const QStringList &dicomData) { if (m_LocalDatabase->isOpen()) { m_LocalIndexer->addListOfFiles( dicomData); } } void QmitkDicomLocalStorageWidget::OnDeleteButtonClicked() { if (!this->DeletePatients()) { if (!this->DeleteStudies()) { this->DeleteSeries(); } } m_Controls->ctkDicomBrowser->updateTableViews(); } bool QmitkDicomLocalStorageWidget::DeletePatients() { auto selectedPatientUIDs = m_Controls->ctkDicomBrowser->currentPatientsSelection(); if (!selectedPatientUIDs.empty()) { QStringList studyUIDs; for (const auto &patientUID : selectedPatientUIDs) studyUIDs.append(m_LocalDatabase->studiesForPatient(patientUID)); QStringList seriesUIDs; for (const auto &studyUID : studyUIDs) seriesUIDs.append(m_LocalDatabase->seriesForStudy(studyUID)); auto answer = QMessageBox::question(nullptr, "Delete Patients", QString("Do you really want to delete %1 %2, containing %3 series in %4 %5?") .arg(selectedPatientUIDs.count()) .arg(selectedPatientUIDs.count() != 1 ? "patients" : "patient") .arg(seriesUIDs.count()) .arg(studyUIDs.count()) .arg(studyUIDs.count() != 1 ? "studies" : "study"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No); if (answer == QMessageBox::Yes) { for (const auto &patientUID : selectedPatientUIDs) m_LocalDatabase->removePatient(patientUID); } return true; } return false; } bool QmitkDicomLocalStorageWidget::DeleteStudies() { auto selectedStudyUIDs = m_Controls->ctkDicomBrowser->currentStudiesSelection(); if (!selectedStudyUIDs.empty()) { QStringList seriesUIDs; for (const auto &studyUID : selectedStudyUIDs) seriesUIDs.append(m_LocalDatabase->seriesForStudy(studyUID)); auto answer = QMessageBox::question(nullptr, "Delete Studies", QString("Do you really want to delete %1 %2, containing %3 series?") .arg(selectedStudyUIDs.count()) .arg(selectedStudyUIDs.count() != 1 ? "studies" : "study") .arg(seriesUIDs.count()), QMessageBox::Yes | QMessageBox::No, QMessageBox::No); if (answer == QMessageBox::Yes) { for (const auto &studyUID : selectedStudyUIDs) m_LocalDatabase->removeStudy(studyUID); } return true; } return false; } bool QmitkDicomLocalStorageWidget::DeleteSeries() { auto selectedSeriesUIDs = m_Controls->ctkDicomBrowser->currentSeriesSelection(); if (!selectedSeriesUIDs.empty()) { auto answer = QMessageBox::question(nullptr, "Delete Series", QString("Do you really want to delete %1 series?").arg(selectedSeriesUIDs.count()), QMessageBox::Yes | QMessageBox::No, QMessageBox::No); if (answer == QMessageBox::Yes) { for (const auto &seriesUID : selectedSeriesUIDs) m_LocalDatabase->removeSeries(seriesUID); } return true; } return false; } void QmitkDicomLocalStorageWidget::OnViewButtonClicked() { QStringList uids = m_Controls->ctkDicomBrowser->currentSeriesSelection(); QString uid; foreach (uid, uids) { QStringList filesForSeries = m_LocalDatabase->filesForSeries(uid); QHash eventProperty; eventProperty.insert("FilesForSeries", filesForSeries); if (!filesForSeries.isEmpty()) { QString modality = m_LocalDatabase->fileValue(filesForSeries.at(0), "0008,0060"); eventProperty.insert("Modality", modality); } emit SignalDicomToDataManager(eventProperty); } } void QmitkDicomLocalStorageWidget::SetDatabaseDirectory(QString newDatatbaseDirectory) { QDir databaseDirecory = QDir(newDatatbaseDirectory); if (!databaseDirecory.exists()) { databaseDirecory.mkpath(databaseDirecory.absolutePath()); } QString newDatatbaseFile = databaseDirecory.absolutePath() + QString("/ctkDICOM.sql"); this->SetDatabase(newDatatbaseFile); } void QmitkDicomLocalStorageWidget::SetDatabase(QString databaseFile) { m_LocalDatabase = new ctkDICOMDatabase(databaseFile); m_LocalDatabase->setParent(this); m_Controls->ctkDicomBrowser->setDICOMDatabase(m_LocalDatabase); m_LocalIndexer->setDatabase(m_LocalDatabase); } void QmitkDicomLocalStorageWidget::OnSeriesSelectionChanged(const QStringList &s) { m_Controls->viewInternalDataButton->setEnabled((s.size() != 0)); }