diff --git a/Modules/DynamicMappers/CMakeLists.txt b/Modules/DynamicMappers/CMakeLists.txt new file mode 100644 index 0000000000..dfef8bac38 --- /dev/null +++ b/Modules/DynamicMappers/CMakeLists.txt @@ -0,0 +1,7 @@ +MITK_CREATE_MODULE( +# INCLUDE_DIRS . + DEPENDS MitkCore + PACKAGE_DEPENDS +) + +add_subdirectory(Testing) \ No newline at end of file diff --git a/Modules/DynamicMappers/Testing/CMakeLists.txt b/Modules/DynamicMappers/Testing/CMakeLists.txt new file mode 100644 index 0000000000..b2da3440f3 --- /dev/null +++ b/Modules/DynamicMappers/Testing/CMakeLists.txt @@ -0,0 +1,3 @@ +MITK_CREATE_MODULE_TESTS() + +#mitkAddCustomModuleTest(mitkDynamicMappersTest mitkDynamicMappersTest) \ No newline at end of file diff --git a/Modules/DynamicMappers/Testing/files.cmake b/Modules/DynamicMappers/Testing/files.cmake new file mode 100644 index 0000000000..3bb8625ea0 --- /dev/null +++ b/Modules/DynamicMappers/Testing/files.cmake @@ -0,0 +1,3 @@ +set(MODULE_TESTS + mitkDynamicMappersTest.cpp +) \ No newline at end of file diff --git a/Modules/DynamicMappers/Testing/mitkDynamicMappersTest.cpp b/Modules/DynamicMappers/Testing/mitkDynamicMappersTest.cpp new file mode 100644 index 0000000000..3c8d8be68c --- /dev/null +++ b/Modules/DynamicMappers/Testing/mitkDynamicMappersTest.cpp @@ -0,0 +1,129 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#include "mitkDynamicMappers.h" +#include "mitkIOUtil.h" +#include "usGetModuleContext.h" +#include +#include + +/** + * \brief Test class for mitkDynamicMappersModule + * + * This test covers: + * - Registering new mapper services + * - Retrieving info about all registered services + * - Changing a data node's mapper service + */ +class mitkDynamicMappersTestSuite : public mitk::TestFixture +{ + CPPUNIT_TEST_SUITE(mitkDynamicMappersTestSuite); + + MITK_TEST(RegisterMapperService_SUCCESS); + MITK_TEST(RegisterMapperServiceTwice_FAILURE); + MITK_TEST(RegisterMapperServiceInvalidDll_FAILURE); + MITK_TEST(FetchMapperServiceInfo_SUCCESS); + MITK_TEST(ChangeMapperService_SUCCESS); + MITK_TEST(ChangeMapperServiceInvalidId_FAILURE); + MITK_TEST(ChangeMapperServiceInvalidDataNode_FAILURE); + CPPUNIT_TEST_SUITE_END(); + +public: + void setUp() override; + void tearDown() override; + + void RegisterMapperService_SUCCESS(); + void RegisterMapperServiceTwice_FAILURE(); + void RegisterMapperServiceInvalidDll_FAILURE(); + void FetchMapperServiceInfo_SUCCESS(); + void ChangeMapperService_SUCCESS(); + void ChangeMapperServiceInvalidId_FAILURE(); + void ChangeMapperServiceInvalidDataNode_FAILURE(); + +private: + std::shared_ptr instance; + // TODO remove hard coded string + std::string dllPath = "D:\\Arbeit\\Programming\\mitk_bin\\MITK-build\\bin\\Debug\\MitkAwesomeMapperService.dll"; + std::string invalidDllPath = ".\\invalid.dll"; + std::set infoKeys; +}; + +void mitkDynamicMappersTestSuite::setUp() +{ + instance = mitk::DynamicMappers::GetInstance(); +} + +void mitkDynamicMappersTestSuite::tearDown() +{ + instance->UnloadAllSharedLibraries(); + infoKeys.clear(); +} + +void mitkDynamicMappersTestSuite::RegisterMapperService_SUCCESS() +{ + CPPUNIT_ASSERT_NO_THROW(instance->RegisterMapper(dllPath)); +} + +void mitkDynamicMappersTestSuite::RegisterMapperServiceTwice_FAILURE() +{ + CPPUNIT_ASSERT_NO_THROW(instance->RegisterMapper(dllPath)); + CPPUNIT_ASSERT_THROW(instance->RegisterMapper(dllPath), mitk::Exception); +} + +void mitkDynamicMappersTestSuite::RegisterMapperServiceInvalidDll_FAILURE() +{ + CPPUNIT_ASSERT_THROW(instance->RegisterMapper(invalidDllPath), mitk::Exception); +} + +void mitkDynamicMappersTestSuite::FetchMapperServiceInfo_SUCCESS() +{ + auto infoCollection = instance->GetRegisteredMapperInfo(infoKeys); + auto refs = us::GetModuleContext()->GetServiceReferences(); + CPPUNIT_ASSERT(infoCollection.size() == refs.size()); +} + +void mitkDynamicMappersTestSuite::ChangeMapperService_SUCCESS() +{ + // TODO create test + auto infoCollection = instance->GetRegisteredMapperInfo(infoKeys); + if (!infoCollection.empty()) + { + auto info = infoCollection[0]; + } +} + +void mitkDynamicMappersTestSuite::ChangeMapperServiceInvalidId_FAILURE() +{ + auto node = mitk::DataNode::New(); + auto dataCollection = mitk::IOUtil::Load(GetTestDataFilePath("brain.nrrd")); + if (dataCollection.empty()) + { + CPPUNIT_FAIL("Couldn't create a valid data node for testing"); + } + node->SetData(dataCollection[0]); + CPPUNIT_ASSERT_THROW(instance->ChangeMapperService(node, -1), mitk::Exception); + CPPUNIT_ASSERT_THROW(instance->ChangeMapperService(node, -2), mitk::Exception); +} + +void mitkDynamicMappersTestSuite::ChangeMapperServiceInvalidDataNode_FAILURE() +{ + auto infoCollection = instance->GetRegisteredMapperInfo(infoKeys); + long serviceId = + infoCollection.empty() ? 0 : std::stol(infoCollection[0]["id"]); + CPPUNIT_ASSERT_THROW(instance->ChangeMapperService(nullptr, serviceId), mitk::Exception); + CPPUNIT_ASSERT_THROW(instance->ChangeMapperService(mitk::DataNode::New(), serviceId), mitk::Exception); +} +MITK_TEST_SUITE_REGISTRATION(mitkDynamicMappers) \ No newline at end of file diff --git a/Modules/DynamicMappers/files.cmake b/Modules/DynamicMappers/files.cmake new file mode 100644 index 0000000000..1a331927e3 --- /dev/null +++ b/Modules/DynamicMappers/files.cmake @@ -0,0 +1,4 @@ +set(CPP_FILES + mitkDynamicMappers.cpp +) + diff --git a/Modules/DynamicMappers/mitkDynamicMappers.cpp b/Modules/DynamicMappers/mitkDynamicMappers.cpp new file mode 100644 index 0000000000..489aa534f7 --- /dev/null +++ b/Modules/DynamicMappers/mitkDynamicMappers.cpp @@ -0,0 +1,187 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#include "mitkDynamicMappers.h" +#include "mitkMapper.h" +#include "usLDAPProp.h" +#include "usModuleContext.h" +#include +#include +#include +#include +#include + +mitk::DynamicMappers::~DynamicMappers() +{ + UnloadAllSharedLibraries(); +} + +std::shared_ptr mitk::DynamicMappers::GetInstance() +{ + if (nullptr == m_Self) + { + m_Self = std::shared_ptr(new DynamicMappers()); + } + return m_Self; +} + +void mitk::DynamicMappers::RegisterMapper(const std::string &pathToDll) +{ + auto separator = pathToDll.find_last_of('/'); + if (std::string::npos == separator) + { + separator = pathToDll.find_last_of('\\'); + } + if (std::string::npos == separator) + { + mitkThrow() << "Relative or invalid path given"; + } + auto suffixPos = pathToDll.find_last_of('.'); + + auto dllBasePath = pathToDll.substr(0, separator); + auto dllName = + pathToDll.substr(separator + 1, std::string::npos == suffixPos ? suffixPos : suffixPos - (separator + 1)); + + RegisterMapper(dllBasePath, dllName); +} + +void mitk::DynamicMappers::RegisterMapper(const std::string &dllBasePath, const std::string &dllName) +{ + if (nullptr == m_LibraryHandles[dllName]) + { + m_LibraryHandles[dllName] = std::make_shared(); + m_LibraryHandles[dllName]->SetLibraryPath(dllBasePath); + m_LibraryHandles[dllName]->SetName(dllName); + } + if (m_LibraryHandles[dllName]->IsLoaded()) + { + mitkThrow() << "Tried loading an already loaded mapper service"; + } + else + { + try + { + m_LibraryHandles[dllName]->Load(); + } + catch (const std::runtime_error &e) + { + // TODO catch or propagate? + mitkThrow() << e.what(); + } + } +} + +void mitk::DynamicMappers::UnloadAllSharedLibraries() +{ + for (const auto &slp : m_LibraryHandles) + { + slp.second->Unload(); + } +} + +mitk::DynamicMappers::MapperInfoCollection mitk::DynamicMappers::GetRegisteredMapperInfo( + std::set &infoKeys) +{ + MapperInfoCollection infoCollections; + auto context = us::GetModuleContext(); + auto refs = context->GetServiceReferences(); + + for (const auto &ref : refs) + { + MapperInfo info = CollectServiceInfo(ref); + infoCollections.push_back(info); + for (auto iter = info.begin(); iter != info.end(); ++iter) + { + infoKeys.insert(iter->first); + } + } + return infoCollections; +} + +mitk::DynamicMappers::MapperInfo mitk::DynamicMappers::CollectServiceInfo(const us::ServiceReference &ref) +{ + if (!ref) + { + mitkThrow() << "The service reference was invalid"; + } + + MapperInfo info; + info["id"] = ReadPropertyWithFallback(ref, "service.id", "NA"); + info["scope"] = ReadPropertyWithFallback(ref, "service.scope", "NA"); + info["name"] = ReadPropertyWithFallback(ref, "PROP_CLASS_NAME", "No name"); + info["dataType"] = ReadPropertyWithFallback(ref, "PROP_SUPPORTED_DATA_TYPE_NAME", "No data type specified"); + info["slot"] = ReadPropertyWithFallback(ref, "PROP_SUPPORTED_SLOT_ID", "No slot id"); + info["priority"] = ReadPropertyWithFallback(ref, "PROP_PRIORITY", "No priority"); + info["module"] = ref.GetModule()->GetName(); + + return info; +} + +void mitk::DynamicMappers::ChangeMapperService(DataNode::Pointer node, long mapperServiceId, bool forceChange) +{ + auto context = us::GetModuleContext(); + auto refs = context->GetServiceReferences(us::LDAPProp("service.id") == mapperServiceId); + + if (nullptr == node) + { + mitkThrow() << "The data node was nullptr"; + } + if (refs.empty()) + { + mitkThrow() << "No mapper with specified service id found"; + } + + const auto ref = refs[0]; + + const int slotId = ReadServiceProperty(ref, "PROP_SUPPORTED_SLOT_ID"); + const std::string futureMapperClass = ReadServiceProperty(ref, "PROP_CLASS_NAME"); + + mitk::Mapper *currentMapper = node->GetMapper(slotId); + if (nullptr == currentMapper) + { + mitkThrow() << "Mapper returned from node->GetMapper(" << slotId << ") was nullptr"; + } + + if (futureMapperClass != currentMapper->GetNameOfClass() || forceChange) + { + const auto service = context->GetServiceObjects(ref).GetService(); + node->SetMapper(slotId, service); + } +} +MITKDYNAMICMAPPERS_DEPRECATED std::string mitk::DynamicMappers::ReadPropertyWithFallback( + const us::ServiceReference &ref, const std::string &propName, const std::string &fallbackString) const +{ + if (!ref) + { + return fallbackString; + } + us::Any prop = ref.GetProperty(propName); + if (typeid(std::string) == prop.Type()) + { + return us::any_cast(prop); + } + if (typeid(int) == prop.Type()) + { + return std::to_string(us::any_cast(prop)); + } + if (typeid(long) == prop.Type()) + { + return std::to_string(us::any_cast(prop)); + } + return fallbackString; +} + +std::shared_ptr mitk::DynamicMappers::m_Self = nullptr; \ No newline at end of file diff --git a/Modules/DynamicMappers/mitkDynamicMappers.h b/Modules/DynamicMappers/mitkDynamicMappers.h new file mode 100644 index 0000000000..493bd32871 --- /dev/null +++ b/Modules/DynamicMappers/mitkDynamicMappers.h @@ -0,0 +1,96 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#ifndef MITKDYNAMICMAPPERS_H +#define MITKDYNAMICMAPPERS_H +#include "MitkDynamicMappersExports.h" +#include "mitkMapper.h" +#include "usServiceReference.h" +#include "usSharedLibrary.h" +#include +#include + +namespace mitk +{ + class MITKDYNAMICMAPPERS_EXPORT DynamicMappers + { + public: + //TODO discuss with Stefan + /*struct MapperInfo + { + enum class Keys + { + ID, + SCOPE, + NAME, + DATA_TYPE, + SLOT, + PRIORITY, + MODULE + }; + std::unordered_map data; + };*/ + typedef std::map MapperInfo; + typedef std::vector MapperInfoCollection; + + DynamicMappers(const DynamicMappers &) = delete; + DynamicMappers(const DynamicMappers &&) = delete; + DynamicMappers &operator=(const DynamicMappers &) = delete; + DynamicMappers &operator=(DynamicMappers &&) = delete; + ~DynamicMappers(); + static std::shared_ptr GetInstance(); + + void RegisterMapper(const std::string &pathToDll); + void RegisterMapper(const std::string &dllBasePath, const std::string &dllName); + void UnloadAllSharedLibraries(); + + MapperInfoCollection GetRegisteredMapperInfo(std::set& infoKeys); + + void ChangeMapperService(DataNode::Pointer node, long mapperServiceId, bool forceChange = false); + + private: + DynamicMappers() = default; + + MapperInfo CollectServiceInfo(const us::ServiceReference &ref); + + std::string ReadPropertyWithFallback(const us::ServiceReference &ref, + const std::string &propName, + const std::string &fallbackString) const; + template + T ReadServiceProperty(const us::ServiceReference &ref, const std::string &propName) const; + + static std::shared_ptr m_Self; + std::map> m_LibraryHandles; + }; + + template + T DynamicMappers::ReadServiceProperty(const us::ServiceReference &ref, const std::string &propName) const + { + if (!ref) + mitkThrow() << "The service reference was invalid"; + + auto any = ref.GetProperty(propName); + if (typeid(T) == any.Type()) + { + T value = us::any_cast(any); + return value; + } + mitkThrow() << "Bad cast: The property" << propName << " of type" << any.Type().name() << "can't be cast to" + << typeid(T).name(); + } +} + +#endif diff --git a/Modules/ModuleList.cmake b/Modules/ModuleList.cmake index 00e894d947..93f36bf535 100644 --- a/Modules/ModuleList.cmake +++ b/Modules/ModuleList.cmake @@ -1,82 +1,83 @@ # The entries in the mitk_modules list must be # ordered according to their dependencies. set(MITK_MODULES Core CommandLine AppUtil RDF LegacyIO DataTypesExt Annotation LegacyGL AlgorithmsExt MapperExt DICOMReader DICOMReaderServices DICOMTesting SceneSerializationBase PlanarFigure ImageDenoising ImageExtraction SceneSerialization Gizmo GraphAlgorithms Multilabel ImageStatistics ContourModel SurfaceInterpolation Segmentation PlanarFigureSegmentation QtWidgets QtWidgetsExt Chart SegmentationUI MatchPointRegistration MatchPointRegistrationUI Classification GPGPU OpenIGTLink IGTBase IGT CameraCalibration OpenCL OpenCVVideoSupport QtOverlays ToFHardware ToFProcessing ToFUI PhotoacousticsHardware PhotoacousticsAlgorithms PhotoacousticsLib US USUI DicomUI Remeshing Python QtPython Persistence OpenIGTLinkUI IGTUI DicomRT RTUI IOExt XNAT TubeGraph BiophotonicsHardware DiffusionImaging TumorInvasionAnalysis BoundingShape RenderWindowManager RenderWindowManagerUI CEST BasicImageProcessing ModelFit ModelFitUI Pharmacokinetics PharmacokineticsUI + DynamicMappers ) if(MITK_ENABLE_PIC_READER) list(APPEND MITK_MODULES IpPicSupportIO) endif()