diff --git a/Modules/DICOMReader/include/mitkDICOMFileReaderSelector.h b/Modules/DICOMReader/include/mitkDICOMFileReaderSelector.h index 42ead769e7..bccf689505 100644 --- a/Modules/DICOMReader/include/mitkDICOMFileReaderSelector.h +++ b/Modules/DICOMReader/include/mitkDICOMFileReaderSelector.h @@ -1,102 +1,116 @@ /*============================================================================ 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(). + @remark If you use LoadBuiltIn3DConfigs() and LoadBuiltIn3DnTConfigs() you must + ensure that the MitkDICOMReader module (and therefore its resources) is properly + loaded. If the module is not available these methods will do nothing. + This can especially lead to problem if these methods are used in the scope + of anothers module's activator. */ 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. + /// @remark If you use this function you must ensure that the MitkDICOMReader module(and therefore its resources) + /// is properly loaded. If the module is not available these method will do nothing. + /// This can especially lead to problem if these methods are used in the scope + /// of anothers module's activator. 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. + /// @remark If you use this function you must ensure that the MitkDICOMReader module(and therefore its resources) + /// is properly loaded. If the module is not available these method will do nothing. + /// This can especially lead to problem if these methods are used in the scope + /// of anothers module's activator. 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); private: StringList m_PossibleConfigurations; StringList m_InputFilenames; ReaderList m_Readers; }; } // namespace #endif // mitkDICOMFileReaderSelector_h diff --git a/Modules/DICOMReader/src/mitkDICOMFileReaderSelector.cpp b/Modules/DICOMReader/src/mitkDICOMFileReaderSelector.cpp index bca9f9b9e6..e538eb3f43 100644 --- a/Modules/DICOMReader/src/mitkDICOMFileReaderSelector.cpp +++ b/Modules/DICOMReader/src/mitkDICOMFileReaderSelector.cpp @@ -1,248 +1,252 @@ /*============================================================================ 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 "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) { + if (nullptr == us::GetModuleContext()) return; + const std::vector configs = us::GetModuleContext()->GetModule()->FindResources( path, "*.xml", false ); for ( auto iter = configs.cbegin(); iter != configs.cend(); ++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) { + if (nullptr == us::GetModuleContext()) return; + 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 ( auto rIter = m_Readers.cbegin(); rIter != m_Readers.cend(); ++rIter ) { gdcmScanner->AddTagPaths((*rIter)->GetTagsOfInterest()); } gdcmScanner->Scan(); // let all readers analyze the file set unsigned int readerIndex(0); for ( auto rIter = m_Readers.cbegin(); rIter != m_Readers.cend(); ++readerIndex, ++rIter ) { (*rIter)->SetInputFiles( m_InputFilenames ); (*rIter)->SetTagCache( gdcmScanner->GetScanCache() ); 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 ( const 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 ( auto rIter = workingCandidates.cbegin(); rIter != workingCandidates.cend(); ++readerIndex, ++rIter ) { const 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/DICOMReaderServices/src/mitkDICOMReaderServicesActivator.cpp b/Modules/DICOMReaderServices/src/mitkDICOMReaderServicesActivator.cpp index 61cbded071..97ba946471 100644 --- a/Modules/DICOMReaderServices/src/mitkDICOMReaderServicesActivator.cpp +++ b/Modules/DICOMReaderServices/src/mitkDICOMReaderServicesActivator.cpp @@ -1,94 +1,123 @@ /*============================================================================ 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 "mitkDICOMReaderServicesActivator.h" #include "mitkAutoSelectingDICOMReaderService.h" #include "mitkManualSelectingDICOMReaderService.h" #include "mitkDICOMTagsOfInterestService.h" #include "mitkSimpleVolumeDICOMSeriesReaderService.h" #include "mitkCoreServices.h" #include "mitkPropertyPersistenceInfo.h" #include "mitkDICOMIOMetaInformationPropertyConstants.h" #include "mitkIPropertyPersistence.h" #include "mitkTemporoSpatialStringProperty.h" #include +#include void AddPropertyPersistence(const mitk::PropertyKeyPath& propPath, bool temporoSpatial = false) { mitk::CoreServicePointer persistenceService(mitk::CoreServices::GetPropertyPersistence()); mitk::PropertyPersistenceInfo::Pointer info = mitk::PropertyPersistenceInfo::New(); if (propPath.IsExplicit()) { std::string name = mitk::PropertyKeyPathToPropertyName(propPath); std::string key = name; std::replace(key.begin(), key.end(), '.', '_'); info->SetNameAndKey(name, key); } else { std::string key = mitk::PropertyKeyPathToPersistenceKeyRegEx(propPath); std::string keyTemplate = mitk::PropertyKeyPathToPersistenceKeyTemplate(propPath); std::string propRegEx = mitk::PropertyKeyPathToPropertyRegEx(propPath); std::string propTemplate = mitk::PropertyKeyPathToPersistenceNameTemplate(propPath); info->UseRegEx(propRegEx, propTemplate, key, keyTemplate); } if (temporoSpatial) { info->SetDeserializationFunction(mitk::PropertyPersistenceDeserialization::deserializeJSONToTemporoSpatialStringProperty); info->SetSerializationFunction(mitk::PropertyPersistenceSerialization::serializeTemporoSpatialStringPropertyToJSON); } persistenceService->AddInfo(info); } namespace mitk { void DICOMReaderServicesActivator::Load(us::ModuleContext* context) { + m_Context = context; + m_AutoSelectingDICOMReader = std::make_unique(); m_SimpleVolumeDICOMSeriesReader = std::make_unique(); - m_ManualSelectingDICOMSeriesReader = std::make_unique(); m_DICOMTagsOfInterestService = std::make_unique(); context->RegisterService(m_DICOMTagsOfInterestService.get()); DICOMTagPathMapType tagmap = GetDefaultDICOMTagsOfInterest(); for (auto tag : tagmap) { m_DICOMTagsOfInterestService->AddTagOfInterest(tag.first); } //add properties that should be persistent (if possible/supported by the writer) AddPropertyPersistence(mitk::DICOMIOMetaInformationPropertyConstants::READER_3D_plus_t()); AddPropertyPersistence(mitk::DICOMIOMetaInformationPropertyConstants::READER_CONFIGURATION()); AddPropertyPersistence(mitk::DICOMIOMetaInformationPropertyConstants::READER_DCMTK()); AddPropertyPersistence(mitk::DICOMIOMetaInformationPropertyConstants::READER_FILES(), true); AddPropertyPersistence(mitk::DICOMIOMetaInformationPropertyConstants::READER_GANTRY_TILT_CORRECTED()); AddPropertyPersistence(mitk::DICOMIOMetaInformationPropertyConstants::READER_GDCM()); AddPropertyPersistence(mitk::DICOMIOMetaInformationPropertyConstants::READER_IMPLEMENTATION_LEVEL()); AddPropertyPersistence(mitk::DICOMIOMetaInformationPropertyConstants::READER_IMPLEMENTATION_LEVEL_STRING()); AddPropertyPersistence(mitk::DICOMIOMetaInformationPropertyConstants::READER_PIXEL_SPACING_INTERPRETATION()); AddPropertyPersistence(mitk::DICOMIOMetaInformationPropertyConstants::READER_PIXEL_SPACING_INTERPRETATION_STRING()); + //We have to handle ManualSelectingDICOMSeriesReader different then the other + //readers. Reason: The reader uses in its constructor DICOMFileReaderSelector. + //this class needs to access resources of MitkDICOMReader module, which might + //not be initialized yet (that would lead to a crash, see i.a. T27553). Thus check if the module + //is alreade loaded. If not, register a listener and create the reader as soon + //as the module is available. + auto dicomModule = us::ModuleRegistry::GetModule("MitkDICOMReader"); + if (nullptr == dicomModule) + { + std::lock_guard lock(m_Mutex); + // Listen for events of module life cycle. + m_Context->AddModuleListener(this, &DICOMReaderServicesActivator::OnModuleEvent); + } + else + { + m_ManualSelectingDICOMSeriesReader = std::make_unique(); + } } void DICOMReaderServicesActivator::Unload(us::ModuleContext*) { } + void DICOMReaderServicesActivator::OnModuleEvent(const us::ModuleEvent event) + { + //We have to handle ManualSelectingDICOMSeriesReader different then the other + //readers. For more details the the explinations in the constructor. + std::lock_guard lock(m_Mutex); + if (nullptr == m_ManualSelectingDICOMSeriesReader && event.GetModule()->GetName()=="MitkDICOMReader" && event.GetType() == us::ModuleEvent::LOADED) + { + m_ManualSelectingDICOMSeriesReader = std::make_unique(); + } + } } US_EXPORT_MODULE_ACTIVATOR(mitk::DICOMReaderServicesActivator) diff --git a/Modules/DICOMReaderServices/src/mitkDICOMReaderServicesActivator.h b/Modules/DICOMReaderServices/src/mitkDICOMReaderServicesActivator.h index 4793cd2145..dda0bc4b40 100644 --- a/Modules/DICOMReaderServices/src/mitkDICOMReaderServicesActivator.h +++ b/Modules/DICOMReaderServices/src/mitkDICOMReaderServicesActivator.h @@ -1,48 +1,50 @@ /*============================================================================ 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 MITKDICOMREADERSERVICESACTIVATOR_H #define MITKDICOMREADERSERVICESACTIVATOR_H #include -#include +#include #include +#include namespace mitk { struct IFileReader; class IDICOMTagsOfInterest; class DICOMReaderServicesActivator : public us::ModuleActivator { public: void Load(us::ModuleContext* context) override; void Unload(us::ModuleContext* context) override; - void AliasServiceChanged(const us::ServiceEvent event); - private: + void OnModuleEvent(const us::ModuleEvent event); std::unique_ptr m_AutoSelectingDICOMReader; std::unique_ptr m_ManualSelectingDICOMSeriesReader; std::unique_ptr m_SimpleVolumeDICOMSeriesReader; std::unique_ptr m_DICOMTagsOfInterestService; - us::ModuleContext* mitkContext; + us::ModuleContext* m_Context; + /**mutex to guard the module listening */ + std::mutex m_Mutex; }; } #endif // MITKDICOMREADERSERVICESACTIVATOR_H