diff --git a/Modules/Annotation/include/mitkAnnotationFactory.h b/Modules/Annotation/include/mitkAnnotationFactory.h index bee17d448d..389291069e 100644 --- a/Modules/Annotation/include/mitkAnnotationFactory.h +++ b/Modules/Annotation/include/mitkAnnotationFactory.h @@ -1,102 +1,105 @@ /*=================================================================== 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 MITKANNOTATIONFACTORY_H #define MITKANNOTATIONFACTORY_H // mitk #include "MitkAnnotationExports.h" #include "mitkAnnotation.h" #include "mitkCommon.h" #include "mitkDynamicAnnotation.h" #include "mitkLayoutAnnotationRenderer.h" // boost #include // stl #include namespace mitk { /* * Responsibilities * Read json configuration * Create Annotations * Listen to slice change events * Observe (relevant) property modification and deletion * Data container for dynamic annotations */ class MITKANNOTATION_EXPORT AnnotationFactory : public itk::Object { public: mitkClassMacroNoParent(AnnotationFactory); - itkFactorylessNewMacro(AnnotationFactory); + static AnnotationFactory::Pointer GetServiceInstance(); using ActionFunctionMap = std::unordered_map; using EventMap = std::unordered_map; using AnnotationVector = std::vector; void CreateAnnotationsFromJson(const std::string &fileName, ActionFunctionMap actionFunctionMap = ActionFunctionMap(), EventMap eventMap = EventMap()); void SaveConfigurationToJsonFile(const std::string &fileName); void AddSingleAnnotation(DynamicAnnotation annotation); Annotation::Pointer GetAnnotationByName(const std::string &name); void SetProvider(const std::string &providerJsonKey, itk::Object::Pointer provider); void SetProviderForAllPropertyAnnotations(BaseData *provider); bool Update(); + void Reset(); + // for testing std::vector GetManagedAnnotationIds(); unsigned int GetAmountOfPendingAnnotations() const; private: + AnnotationFactory() = default; // private listener functions void ObserveSliceChangedEvents(BaseRenderer *renderer); void OnSliceChanged(Object *caller, const itk::EventObject &event); // private helper functions static std::vector JsonToAnnotationInfoVector(boost::property_tree::ptree const &root); static void SetProviderForAnnotation(itk::Object::Pointer provider, DynamicAnnotation &annotation); LayoutAnnotationRenderer::Alignment GetAlignment(const std::string &alignmentName) const; bool RegisterAnnotation(DynamicAnnotation dynamicAnnotation); // private data members AnnotationVector m_Annotations; AnnotationVector m_WaitingAnnotations; std::unordered_map m_SliceNavigationControllerTags; unsigned m_Slice = 0; unsigned m_TimeStep = 0; const std::unordered_map m_Defaults = { {AnnotationConstants::NAME, "Unnamed Annotation"}, {AnnotationConstants::DEFAULT, "NA"}, {AnnotationConstants::PREFIX, ""}, {AnnotationConstants::SUFFIX, ""}, {AnnotationConstants::TYPE, "text"}, {AnnotationConstants::PROVIDER, ""}, {AnnotationConstants::EVENT, ""}, {AnnotationConstants::ACTION, ""}, {AnnotationConstants::WINDOW, "stdmulti.widget1"}, {AnnotationConstants::LAYOUT, "TopLeft"}, }; }; } // namespace mitk #endif // MITKANNOTATIONFACTORY_H diff --git a/Modules/Annotation/src/mitkAnnotationFactory.cpp b/Modules/Annotation/src/mitkAnnotationFactory.cpp index 0a7de3a311..2ff425017d 100644 --- a/Modules/Annotation/src/mitkAnnotationFactory.cpp +++ b/Modules/Annotation/src/mitkAnnotationFactory.cpp @@ -1,438 +1,452 @@ /*=================================================================== 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 "mitkAnnotationFactory.h" #include "mitkAnnotationUtils.h" #include "mitkColorBarAnnotation.h" #include "mitkDynamicAnnotation.h" #include "mitkLayoutAnnotationRenderer.h" #include "mitkLogoAnnotation.h" #include "mitkScaleLegendAnnotation.h" #include "mitkTemporoSpatialStringProperty.h" #include "mitkTextAnnotation2D.h" #include #include class PropertyDeletedCommand : public itk::Command { public: void Execute(Object *caller, const itk::EventObject &event) override { Execute(const_cast(caller), event); }; void Execute(const Object *caller, const itk::EventObject &event) override { MITK_INFO << "Property " << caller->GetNameOfClass() << " deleted"; }; }; class ItkRetrievalCommand : public itk::Command { mitk::DynamicAnnotation::OnEventAction m_OnEventAction; std::string m_AnnotationId; public: ItkRetrievalCommand(mitk::DynamicAnnotation::OnEventAction dataRetrievalAction, std::string annotationId) : m_OnEventAction(std::move(dataRetrievalAction)), m_AnnotationId(std::move(annotationId)){}; void Execute(Object *caller, const itk::EventObject &event) override { Execute(const_cast(caller), event); }; void Execute(const Object *caller, const itk::EventObject &event) override { auto annotation = mitk::AnnotationUtils::GetAnnotation(m_AnnotationId); if (annotation) { // TODO remove hardcoded 0 slice and time auto updatedText = m_OnEventAction(caller, event, 0, 0); MITK_INFO << "Annotation " << annotation->GetName() << " set to " << updatedText; annotation->SetText(updatedText); } }; }; std::vector mitk::AnnotationFactory::JsonToAnnotationInfoVector( boost::property_tree::ptree const &root) { std::vector elements; auto annotations = root.begin(); if (annotations->first == "annotations") { for (const auto &window : annotations->second) { for (const auto &layout : window.second) { for (const auto &object : layout.second) { DynamicAnnotation annotation; annotation.config[AnnotationConstants::WINDOW] = window.first; annotation.config[AnnotationConstants::LAYOUT] = layout.first; for (const auto &property : object.second) { annotation.config[property.first] = property.second.get_value(); } if (annotation.config[AnnotationConstants::DEFAULT].empty()) { annotation.config[AnnotationConstants::DEFAULT] = annotation.config[AnnotationConstants::NAME]; } elements.push_back(annotation); } } } } return elements; } mitk::LayoutAnnotationRenderer::Alignment mitk::AnnotationFactory::GetAlignment(const std::string &alignmentName) const { static const auto alignmentMap = std::unordered_map{ {"TopLeft", LayoutAnnotationRenderer::TopLeft}, {"Top", LayoutAnnotationRenderer::Top}, {"TopRight", LayoutAnnotationRenderer::TopRight}, {"Left", LayoutAnnotationRenderer::Left}, {"Right", LayoutAnnotationRenderer::Right}, {"BottomLeft", LayoutAnnotationRenderer::BottomLeft}, {"Bottom", LayoutAnnotationRenderer::Bottom}, {"BottomRight", LayoutAnnotationRenderer::BottomRight}}; const auto alignment = alignmentMap.find(alignmentName); const auto &defaultFallback = m_Defaults.at(AnnotationConstants::LAYOUT); return alignment != std::end(alignmentMap) ? alignment->second : alignmentMap.at(defaultFallback); } void mitk::AnnotationFactory::ObserveSliceChangedEvents(BaseRenderer *renderer) { if (!renderer) return; auto name = renderer->GetName(); if (m_SliceNavigationControllerTags.find(name) == std::end(m_SliceNavigationControllerTags)) { auto sliceCommand = itk::MemberCommand::New(); sliceCommand->SetCallbackFunction(this, &AnnotationFactory::OnSliceChanged); auto sliceController = renderer->GetSliceNavigationController(); auto tag = sliceController->AddObserver(SliceNavigationController::GeometrySliceEvent(nullptr, 0), sliceCommand); m_SliceNavigationControllerTags.insert({name, tag}); } } bool mitk::AnnotationFactory::RegisterAnnotation(DynamicAnnotation dynamicAnnotation) { const auto alignment = GetAlignment(dynamicAnnotation.config[AnnotationConstants::LAYOUT]); dynamicAnnotation.ptr->SetName(dynamicAnnotation.config[AnnotationConstants::NAME]); dynamicAnnotation.ptr->SetText(dynamicAnnotation.config[AnnotationConstants::DEFAULT]); MITK_INFO << dynamicAnnotation.config[AnnotationConstants::NAME]; const auto renderer = BaseRenderer::GetByName(dynamicAnnotation.config[AnnotationConstants::WINDOW]); if (renderer) { ObserveSliceChangedEvents(renderer); LayoutAnnotationRenderer::AddAnnotation(dynamicAnnotation.ptr, renderer, alignment); m_Annotations.push_back(dynamicAnnotation); // TODO wait so each annotation gets a unique id see T25927 std::this_thread::sleep_for(std::chrono::seconds(2)); if (dynamicAnnotation.config[AnnotationConstants::PROVIDER] == AnnotationConstants::SLICENAVIGATIONCONTROLLER) { SetProviderForAnnotation(renderer->GetSliceNavigationController(), dynamicAnnotation); } return true; } return false; } void mitk::AnnotationFactory::OnSliceChanged(Object *caller, const itk::EventObject &event) { const auto geometryEvent = dynamic_cast(&event); if (!geometryEvent) return; m_Slice = geometryEvent->GetPos(); const auto sliceNavController = dynamic_cast(caller); if (!sliceNavController) return; const auto renderer = sliceNavController->GetRenderer(); if (!renderer) return; const auto rendererName = renderer->GetName(); for (auto &annotation : m_Annotations) { if (annotation.config[AnnotationConstants::TYPE] == "property" && annotation.config[AnnotationConstants::WINDOW] == rendererName) { annotation.SetText(annotation.providerInfo.dataRetrievalAction( annotation.providerInfo.object, *annotation.providerInfo.dataChangedEvent, m_Slice, m_TimeStep)); } } } +mitk::AnnotationFactory::Pointer mitk::AnnotationFactory::GetServiceInstance() +{ + auto ctx = us::GetModuleContext(); + static auto m_Registration = ctx->RegisterService(new AnnotationFactory()); + return ctx->GetService(ctx->GetServiceReference()); +} + void mitk::AnnotationFactory::CreateAnnotationsFromJson(const std::string &fileName, ActionFunctionMap actionFunctionMap, EventMap eventMap) { boost::property_tree::ptree root; read_json(fileName, root); auto annotations = JsonToAnnotationInfoVector(root); for (auto annotation : annotations) { const auto &type = annotation.config[AnnotationConstants::TYPE]; if (type == "text") { annotation.ptr = mitk::TextAnnotation2D::New(); } else if (type == "property") { annotation.ptr = mitk::TextAnnotation2D::New(); annotation.providerInfo.dataChangedEvent = itk::ModifiedEvent().MakeObject(); const auto &defaultName = annotation.config[AnnotationConstants::DEFAULT]; annotation.providerInfo.dataRetrievalAction = [defaultName](const itk::Object *caller, const itk::EventObject &, unsigned int slice, unsigned int timeStep) { const auto dicomProperty = dynamic_cast(caller); if (dicomProperty) { return dicomProperty->GetValueBySlice(slice); } const auto property = dynamic_cast(caller); if (property) { return property->GetValueAsString(); } return defaultName + " (NA)"; }; } else if (type == "custom") { if (annotation.config[AnnotationConstants::EVENT].empty() || annotation.config[AnnotationConstants::ACTION].empty()) { throw std::logic_error("A custom annotation needs to have an event and an action specified"); } annotation.ptr = TextAnnotation2D::New(); annotation.providerInfo.dataChangedEvent = eventMap[annotation.config[AnnotationConstants::EVENT]]; annotation.providerInfo.dataRetrievalAction = actionFunctionMap[annotation.config[AnnotationConstants::ACTION]]; } else if (type == "colorBar") { annotation.ptr = ColorBarAnnotation::New(); } else if (type == "scaleLegend") { annotation.ptr = ScaleLegendAnnotation::New(); } else if (type == "logo") { auto logoAnnotation = LogoAnnotation::New(); auto path = annotation.config[AnnotationConstants::PATH]; logoAnnotation->SetLogoImagePath(path); logoAnnotation->LoadLogoImageFromPath(); annotation.ptr = logoAnnotation; } else { throw std::logic_error("Unknown or missing annotation type"); } if (!RegisterAnnotation(annotation)) { // Registration of annotation failed probably due to missing base renderer // Store annotation for delayed registration m_WaitingAnnotations.push_back(annotation); } } } void mitk::AnnotationFactory::SetProviderForAnnotation(itk::Object::Pointer provider, DynamicAnnotation &annotation) { if (provider.IsNotNull() && provider != annotation.providerInfo.object) { if (annotation.providerInfo.object) { for (const auto &tag : annotation.providerInfo.tags) { annotation.providerInfo.object->RemoveObserver(tag); } } annotation.providerInfo.object = provider; if (annotation.providerInfo.dataChangedEvent) { annotation.providerInfo.tags.push_back(annotation.providerInfo.object->AddObserver( *annotation.providerInfo.dataChangedEvent, new ItkRetrievalCommand(annotation.providerInfo.dataRetrievalAction, annotation.ptr->GetMicroserviceID()))); + + annotation.SetText(annotation.providerInfo.dataRetrievalAction( + annotation.providerInfo.object, *annotation.providerInfo.dataChangedEvent, 0, 0)); } if (annotation.config[AnnotationConstants::TYPE] == "property") { annotation.providerInfo.tags.push_back( annotation.providerInfo.object->AddObserver(itk::DeleteEvent(), new PropertyDeletedCommand())); } } } void mitk::AnnotationFactory::AddSingleAnnotation(DynamicAnnotation annotation) { if (annotation.ptr) { try { const auto id = annotation.ptr->GetMicroserviceID(); if (AnnotationUtils::GetAnnotation(id)) { // The annotation has already been fully set up so just add it to the managed annotations m_Annotations.push_back(annotation); } else { MITK_ERROR << "An annotation with a valid ID can't be fetched through the microservice registry. Not adding it " "to the managed annotations"; } } catch (const std::logic_error &e) { MITK_ERROR << e.what() << "\nWhen adding single annotations they should already be registered through a layout renderer. " "Trying this for you now."; // The annotation probably hasn't been registered yet so try it if (!RegisterAnnotation(annotation)) { m_WaitingAnnotations.push_back(annotation); } } } } mitk::Annotation::Pointer mitk::AnnotationFactory::GetAnnotationByName(const std::string &name) { for (const auto &annotation : m_Annotations) { if (annotation.ptr->GetName() == name) { return annotation.ptr; } } return nullptr; } void mitk::AnnotationFactory::SetProvider(const std::string &providerJsonKey, itk::Object::Pointer provider) { for (auto &annotation : m_Annotations) { if (providerJsonKey == annotation.config[AnnotationConstants::PROVIDER]) { SetProviderForAnnotation(provider, annotation); } } } void mitk::AnnotationFactory::SetProviderForAllPropertyAnnotations(BaseData *provider) { Update(); // TODO temporary fix for delaying the registration of annotations until the render windows are present for (auto &annotation : m_Annotations) { if (annotation.config[AnnotationConstants::TYPE] != "property") continue; // if provider has property auto propertyName = annotation.config[AnnotationConstants::PROVIDER]; auto property = provider->GetProperty(propertyName.c_str()); if (property) { // set provider (in case the property exists but is empty state this in the annotations text SetProviderForAnnotation(property.GetPointer(), annotation); annotation.ptr->SetColor(0, 200, 0); - annotation.SetText(annotation.providerInfo.dataRetrievalAction( - annotation.providerInfo.object, *annotation.providerInfo.dataChangedEvent, 0, 0)); if (annotation.ptr->GetText().empty()) { annotation.ptr->SetColor(0, 0, 200); annotation.SetText(annotation.config[AnnotationConstants::DEFAULT] + " (empty)"); } } else { annotation.ptr->SetColor(200, 0, 0); annotation.SetText(annotation.config[AnnotationConstants::DEFAULT] + " (NA)"); } } } std::vector mitk::AnnotationFactory::GetManagedAnnotationIds() { auto ids = std::vector{}; for (const auto &annotation : m_Annotations) { ids.push_back(annotation.ptr->GetMicroserviceID()); } return ids; } unsigned mitk::AnnotationFactory::GetAmountOfPendingAnnotations() const { return m_WaitingAnnotations.size(); } void mitk::AnnotationFactory::SaveConfigurationToJsonFile(const std::string &fileName) { using boost::property_tree::ptree; ptree root; const auto createNodes = [&root](DynamicAnnotation &annotation) { const auto &window = annotation.config[AnnotationConstants::WINDOW]; const auto &layout = annotation.config[AnnotationConstants::LAYOUT]; const ptree::path_type path{"annotations:" + window + ":" + layout, ':'}; ptree object; for (const auto &pair : annotation.config) { if (pair.first == AnnotationConstants::WINDOW || pair.first == AnnotationConstants::LAYOUT) continue; object.put(pair.first, pair.second); } ptree layoutNode = root.get_child_optional(path).has_value() ? root.get_child(path) : ptree{}; layoutNode.push_back(std::make_pair("", object)); root.put_child(path, layoutNode); }; std::for_each(std::begin(m_Annotations), std::end(m_Annotations), createNodes); std::for_each(std::begin(m_WaitingAnnotations), std::end(m_WaitingAnnotations), createNodes); ofstream out; out.open(fileName); if (out.is_open()) { boost::property_tree::write_json(out, root); } else { MITK_ERROR << "Error opening file"; } } bool mitk::AnnotationFactory::Update() { const auto firstElementToErase = std::remove_if(m_WaitingAnnotations.begin(), m_WaitingAnnotations.end(), [this](DynamicAnnotation da) { return RegisterAnnotation(da); }); m_WaitingAnnotations.erase(firstElementToErase, m_WaitingAnnotations.end()); return m_WaitingAnnotations.empty(); } + +void mitk::AnnotationFactory::Reset() +{ + m_Annotations.clear(); + m_WaitingAnnotations.clear(); +} diff --git a/Modules/Annotation/test/mitkAnnotationFactoryTest.cpp b/Modules/Annotation/test/mitkAnnotationFactoryTest.cpp index 94f18a411c..f67fe29c1b 100644 --- a/Modules/Annotation/test/mitkAnnotationFactoryTest.cpp +++ b/Modules/Annotation/test/mitkAnnotationFactoryTest.cpp @@ -1,64 +1,135 @@ /*=================================================================== 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 "mitkAnnotationFactory.h" +#include "mitkDynamicAnnotation.h" #include "mitkTestFixture.h" #include "mitkTestingMacros.h" #include "mitkTextAnnotation2D.h" -#include "mitkDynamicAnnotation.h" class mitkAnnotationFactoryTestSuite : public mitk::TestFixture { CPPUNIT_TEST_SUITE(mitkAnnotationFactoryTestSuite); MITK_TEST(ReadJsonConfiguration); + MITK_TEST(SaveJsonConfiguration); MITK_TEST(ReactToPropertyChangedEvent); CPPUNIT_TEST_SUITE_END(); - std::string m_AnnotationTestDataDir = R"(D:\Arbeit\Programming\mitk_m\Plugins\org.mitk.annotations\resources)"; + // TODO get rid of absolute path + std::string m_AnnotationTestDataDir = R"(D:\Arbeit\Programming\mitk_m\Plugins\org.mitk.annotations\resources\)"; std::string m_ValidJsonFile = "annotations.json"; + std::string m_SaveFile = "annotationsSaved.json"; mitk::AnnotationFactory::Pointer m_Factory; public: void setUp() override { - m_Factory = mitk::AnnotationFactory::New(); + m_Factory = mitk::AnnotationFactory::GetServiceInstance(); + CPPUNIT_ASSERT_EQUAL(static_cast(0), m_Factory->GetAmountOfPendingAnnotations()); + CPPUNIT_ASSERT_EQUAL(static_cast(0), m_Factory->GetManagedAnnotationIds().size()); }; - void tearDown() override{}; + void tearDown() override { m_Factory->Reset(); }; void ReadJsonConfiguration() { + // TODO get rid of hardcoded magic number (26 amount of annotations in the json file) m_Factory->CreateAnnotationsFromJson(m_AnnotationTestDataDir + m_ValidJsonFile); const auto amountOfRegisteredAnnotations = m_Factory->GetManagedAnnotationIds().size(); const auto amountOfPendingAnnotations = m_Factory->GetAmountOfPendingAnnotations(); CPPUNIT_ASSERT_EQUAL_MESSAGE("The amount of created annotations seems incorrect", - static_cast(22), + static_cast(26), amountOfRegisteredAnnotations + amountOfPendingAnnotations); }; + void SaveJsonConfiguration() + { + // read the json file and store the amount of annotations contained + m_Factory->CreateAnnotationsFromJson(m_AnnotationTestDataDir + m_ValidJsonFile); + auto amountOfAnnotations = m_Factory->GetManagedAnnotationIds().size() + m_Factory->GetAmountOfPendingAnnotations(); + + // save the actual configuration to json + // (this might not produce the exact same file as optional parameters were filled with their defaults!) + m_Factory->SaveConfigurationToJsonFile(m_AnnotationTestDataDir + m_SaveFile); + + // clear all managed annotations + m_Factory->Reset(); + + // read the file that was just written to disk + m_Factory->CreateAnnotationsFromJson(m_AnnotationTestDataDir + m_SaveFile); + + // check whether the amount of annotations stayed the same + auto amountOfAnnotations2 = + m_Factory->GetManagedAnnotationIds().size() + m_Factory->GetAmountOfPendingAnnotations(); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Saving and reloading the configuration changed the number of total annotations", + amountOfAnnotations, + amountOfAnnotations2); + + // note that this is a very weak check as it doesn't guarantee, that the annotations stayed the same + // especially regarding provider information etc. + // this test should be extended to actually check either the json files directly (dealing with differences in + // optional values) or by comparing two vectors of loaded configurations of these files + } + void ReactToPropertyChangedEvent() { - //auto dynamicAnnotation = mitk::DynamicAnnotation{}; - //dynamicAnnotation.ptr = mitk::TextAnnotation2D::New(); - //m_Factory->AddSingleAnnotation(dynamicAnnotation); + // create annotation wrapper + auto annotation = mitk::DynamicAnnotation{}; + + // create specific annotation + annotation.ptr = mitk::TextAnnotation2D::New(); + + // configure the dynamic annotation + auto customProviderName = "customPropertyProvider"; + annotation.config = {{mitk::AnnotationConstants::PROVIDER, customProviderName}, + {mitk::AnnotationConstants::TYPE, "property"}}; + + // specify the necessary provider information + annotation.providerInfo.dataChangedEvent = itk::ModifiedEvent().MakeObject(); + annotation.providerInfo.dataRetrievalAction = + [](const itk::Object *caller, const itk::EventObject &, unsigned, unsigned) { + auto prop = dynamic_cast(caller); + return prop ? prop->GetValueAsString() : "ERROR"; + }; + + // register annotation with the system + mitk::LayoutAnnotationRenderer::AddAnnotation(annotation.ptr, "stdmulti.widget1"); + + // let the factory take over the management + m_Factory->AddSingleAnnotation(annotation); + + // create the actual data provider + auto prop = mitk::StringProperty::New(); + prop->SetValue("first"); + + // set the provider for the key specified in the annotation's config + // this should retrieve the dynamic data for the first time + m_Factory->SetProvider(customProviderName, prop.GetPointer()); + CPPUNIT_ASSERT_EQUAL(std::string("first"), annotation.ptr->GetText()); + + // change the provider's data + // this should trigger the specified OnEventAction and update the annotation's text accordingly + prop->SetValue("second"); + CPPUNIT_ASSERT_EQUAL(std::string("second"), annotation.ptr->GetText()); }; - void ReactToSliceNumberChangedEvent(); - + void ReactToSliceNumberChangedEvent( + // TODO implement test + ); }; MITK_TEST_SUITE_REGISTRATION(mitkAnnotationFactory) \ No newline at end of file diff --git a/Plugins/org.mitk.annotations/src/internal/mitkAnnotationsActivator.cpp b/Plugins/org.mitk.annotations/src/internal/mitkAnnotationsActivator.cpp index 980fec82ed..38a761af7b 100644 --- a/Plugins/org.mitk.annotations/src/internal/mitkAnnotationsActivator.cpp +++ b/Plugins/org.mitk.annotations/src/internal/mitkAnnotationsActivator.cpp @@ -1,89 +1,115 @@ /*=================================================================== 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 "mitkAnnotationsActivator.h" +#include #include "berryISelectionService.h" #include "berryPlatformUI.h" #include "mitkAnnotationFactory.h" #include "mitkDataNodeSelection.h" +mitk::NodeSelectionListener::NodeSelectionListener(std::function callback) + : m_Callback(std::move(callback)){}; + +void mitk::NodeSelectionListener::SelectionChanged(const berry::IWorkbenchPart::Pointer &part, + const berry::ISelection::ConstPointer &selection) +{ + const auto nodeSelection = dynamic_cast(selection.GetPointer()); + if (nodeSelection) + { + auto nodes = nodeSelection->GetSelectedDataNodes(); + if (!nodes.empty()) + { + m_Callback(nodes.front()); + } + } +}; + +auto mitk::AnnotationsActivator::OnSelectionChangedCallback() +{ + return [this](DataNode::Pointer node) { + if (node.IsNotNull()) + { + const auto baseData = node->GetData(); + this->m_AnnotationFactory->SetProviderForAllPropertyAnnotations(baseData); + } + }; +} + +auto mitk::AnnotationsActivator::SliceNumberAction() const +{ + return [](const itk::Object *caller, const itk::EventObject &event, unsigned int slice, unsigned int timeStep) { + const auto sliceEvent = dynamic_cast(&event); + if (sliceEvent) + { + return std::to_string(sliceEvent->GetPos()); + } + return std::string(); + }; +} + void mitk::AnnotationsActivator::start(ctkPluginContext *context) { - m_AnnotationFactory = AnnotationFactory::New(); - - auto sliceNumberAction = - [](const itk::Object *caller, const itk::EventObject &event, unsigned int slice, unsigned int timeStep) { - auto sliceEvent = dynamic_cast(&event); - if (sliceEvent) - { - return std::to_string(sliceEvent->GetPos()); - } - return std::string(); - }; - - AnnotationFactory::ActionFunctionMap actions{{"SliceNumberAction", sliceNumberAction}}; + m_NodeSelectionListener = std::make_unique(OnSelectionChangedCallback()); + m_AnnotationFactory = AnnotationFactory::GetServiceInstance(); + + AnnotationFactory::ActionFunctionMap actions{{"SliceNumberAction", SliceNumberAction()}}; AnnotationFactory::EventMap events{ {"GeometrySliceEvent", SliceNavigationController::GeometrySliceEvent(nullptr, 0).MakeObject()}}; - // TODO throw comprehensible exceptions on parse error try { m_AnnotationFactory->CreateAnnotationsFromJson( - R"(D:\Arbeit\Programming\mitk_m\Plugins\org.mitk.annotations\resources\annotations.json)", actions, events); - + R"(D:\Arbeit\Programming\mitk_m\Plugins\org.mitk.annotations\resources\annotationsSaved.json)", actions, events); + } + catch (const std::exception &e) + { + MITK_ERROR << "Exception while loading json configuration. Probably no annotations were created"; + MITK_ERROR << e.what(); + } + try + { m_AnnotationFactory->SaveConfigurationToJsonFile( R"(D:\Arbeit\Programming\mitk_m\Plugins\org.mitk.annotations\resources\annotationsSaved.json)"); } - catch (const std::exception& e) + catch (const std::exception &e) { + MITK_ERROR << "Exception while saving json configuration. The save file probably wasn't written or updated"; MITK_ERROR << e.what(); - std::rethrow_exception(std::current_exception()); } + // wait until the selection service is ready m_AnnotationFuture = std::async(std::launch::async, [this]() { std::this_thread::sleep_for(std::chrono::seconds(10)); // m_AnnotationFactory->Update(); leads to crash in vtk (maybe threading issue) - - auto windows = berry::PlatformUI::GetWorkbench()->GetWorkbenchWindows(); - auto activeWindow = berry::PlatformUI::GetWorkbench()->GetActiveWorkbenchWindow(); - auto selectionService = activeWindow->GetSelectionService(); - class NodeSelectionListener : public berry::ISelectionListener - { - public: - NodeSelectionListener(AnnotationFactory *factory) : m_Factory(factory){}; - void SelectionChanged(const berry::IWorkbenchPart::Pointer &part, - const berry::ISelection::ConstPointer &selection) override - { - const auto nodeSelection = dynamic_cast(selection.GetPointer()); - if (nodeSelection) - { - auto nodes = nodeSelection->GetSelectedDataNodes(); - if (!nodes.empty()) - { - m_Factory->SetProviderForAllPropertyAnnotations(nodes.front()->GetData()); - } - } - }; - AnnotationFactory *m_Factory; - }; - selectionService->AddPostSelectionListener(new NodeSelectionListener(m_AnnotationFactory)); - - // RenderingManager::GetInstance()->RequestUpdateAll(); + ObserveSelectionChanges(); }); } void mitk::AnnotationsActivator::stop(ctkPluginContext * /*context*/) {} + +void mitk::AnnotationsActivator::ObserveSelectionChanges() +{ + auto windows = berry::PlatformUI::GetWorkbench()->GetWorkbenchWindows(); + auto activeWindow = berry::PlatformUI::GetWorkbench()->GetActiveWorkbenchWindow(); + auto selectionService = activeWindow->GetSelectionService(); + + if (m_NodeSelectionListener) + { + selectionService->AddPostSelectionListener(m_NodeSelectionListener.get()); + } +} diff --git a/Plugins/org.mitk.annotations/src/internal/mitkAnnotationsActivator.h b/Plugins/org.mitk.annotations/src/internal/mitkAnnotationsActivator.h index 700db8f984..65560e7b30 100644 --- a/Plugins/org.mitk.annotations/src/internal/mitkAnnotationsActivator.h +++ b/Plugins/org.mitk.annotations/src/internal/mitkAnnotationsActivator.h @@ -1,47 +1,68 @@ /*=================================================================== 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 MITKANNOTATIONSACTIVATOR_H_ #define MITKANNOTATIONSACTIVATOR_H_ #include "mitkAnnotationFactory.h" #include #include #include "mitkIRenderWindowPart.h" +#include "berryISelection.h" +#include "berryIWorkbenchPart.h" +#include "berryISelectionListener.h" namespace mitk { + class NodeSelectionListener : public berry::ISelectionListener + { + public: + NodeSelectionListener(std::function callback); + void SelectionChanged(const berry::IWorkbenchPart::Pointer &part, + const berry::ISelection::ConstPointer &selection) override; + private: + std::function m_Callback; + }; + /** * \ingroup org_mitk_annotations_internal * * \brief The plug-in activator for mitk annotations */ class AnnotationsActivator : public QObject, public ctkPluginActivator { Q_OBJECT Q_PLUGIN_METADATA(IID "org.mitk.annotations") Q_INTERFACES(ctkPluginActivator) public: void start(ctkPluginContext *context) override; void stop(ctkPluginContext *context) override; private: + // observers and callbacks + void ObserveSelectionChanges(); + auto OnSelectionChangedCallback(); + + // action functions as defined in the json file + auto SliceNumberAction() const; + AnnotationFactory::Pointer m_AnnotationFactory; std::future m_AnnotationFuture; + std::unique_ptr m_NodeSelectionListener; }; } #endif /* MITKANNOTATIONSACTIVATOR_H_ */