diff --git a/Modules/Segmentation/Controllers/mitkToolManager.cpp b/Modules/Segmentation/Controllers/mitkToolManager.cpp index 6e2fbd852c..4a71cac9a9 100644 --- a/Modules/Segmentation/Controllers/mitkToolManager.cpp +++ b/Modules/Segmentation/Controllers/mitkToolManager.cpp @@ -1,589 +1,603 @@ /*============================================================================ 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 "mitkToolManager.h" #include "mitkToolManagerProvider.h" #include "mitkCoreObjectFactory.h" #include #include #include #include "mitkInteractionEventObserver.h" #include "mitkSegTool2D.h" #include "mitkRenderingManager.h" #include "mitkSliceNavigationController.h" #include "usGetModuleContext.h" #include "usModuleContext.h" mitk::ToolManager::ToolManager(DataStorage *storage) : m_ActiveTool(nullptr), m_ActiveToolID(-1), m_RegisteredClients(0), m_DataStorage(storage) { CoreObjectFactory::GetInstance(); // to make sure a CoreObjectFactory was instantiated (and in turn, possible tools // are registered) - bug 1029 this->InitializeTools(); } void mitk::ToolManager::EnsureTimeObservation() { if (nullptr != mitk::RenderingManager::GetInstance() && nullptr != mitk::RenderingManager::GetInstance()->GetTimeNavigationController()) { auto timeController = mitk::RenderingManager::GetInstance()->GetTimeNavigationController(); m_LastTimePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); auto currentTimeController = m_CurrentTimeNavigationController.Lock(); if (timeController != currentTimeController) { if (currentTimeController.IsNotNull()) { currentTimeController->RemoveObserver(m_TimePointObserverTag); } itk::MemberCommand::Pointer command = itk::MemberCommand::New(); command->SetCallbackFunction(this, &ToolManager::OnTimeChanged); command->SetCallbackFunction(this, &ToolManager::OnTimeChangedConst); m_CurrentTimeNavigationController = timeController; m_TimePointObserverTag = timeController->AddObserver(SliceNavigationController::GeometryTimeEvent(nullptr,0), command); } } } void mitk::ToolManager::StopTimeObservation() { auto currentTimeController = m_CurrentTimeNavigationController.Lock(); if (currentTimeController.IsNotNull()) { currentTimeController->RemoveObserver(m_TimePointObserverTag); m_CurrentTimeNavigationController = nullptr; m_TimePointObserverTag = 0; } } mitk::ToolManager::~ToolManager() { for (auto dataIter = m_WorkingData.begin(); dataIter != m_WorkingData.end(); ++dataIter) (*dataIter)->RemoveObserver(m_WorkingDataObserverTags[(*dataIter)]); if (this->GetDataStorage() != nullptr) this->GetDataStorage()->RemoveNodeEvent.RemoveListener( mitk::MessageDelegate1(this, &ToolManager::OnNodeRemoved)); if (m_ActiveTool) { m_ActiveTool->Deactivated(); m_ActiveToolRegistration.Unregister(); m_ActiveTool = nullptr; m_ActiveToolID = -1; // no tool active ActiveToolChanged.Send(); } for (auto observerTagMapIter = m_ReferenceDataObserverTags.begin(); observerTagMapIter != m_ReferenceDataObserverTags.end(); ++observerTagMapIter) { observerTagMapIter->first->RemoveObserver(observerTagMapIter->second); } this->StopTimeObservation(); } void mitk::ToolManager::InitializeTools() { // clear all previous tool pointers (tools may be still activated from another recently used plugin) if (m_ActiveTool) { m_ActiveTool->Deactivated(); m_ActiveToolRegistration.Unregister(); m_ActiveTool = nullptr; m_ActiveToolID = -1; // no tool active ActiveToolChanged.Send(); } m_Tools.clear(); // get a list of all known mitk::Tools std::list thingsThatClaimToBeATool = itk::ObjectFactoryBase::CreateAllInstance("mitkTool"); // remember these tools for (auto iter = thingsThatClaimToBeATool.begin(); iter != thingsThatClaimToBeATool.end(); ++iter) { if (auto *tool = dynamic_cast(iter->GetPointer())) { tool->InitializeStateMachine(); tool->SetToolManager(this); // important to call right after instantiation tool->ErrorMessage += MessageDelegate1(this, &ToolManager::OnToolErrorMessage); tool->GeneralMessage += MessageDelegate1(this, &ToolManager::OnGeneralToolMessage); m_Tools.push_back(tool); } } } void mitk::ToolManager::OnToolErrorMessage(std::string s) { this->ToolErrorMessage(s); } void mitk::ToolManager::OnGeneralToolMessage(std::string s) { this->GeneralToolMessage(s); } const mitk::ToolManager::ToolVectorTypeConst mitk::ToolManager::GetTools() { ToolVectorTypeConst resultList; for (auto iter = m_Tools.begin(); iter != m_Tools.end(); ++iter) { resultList.push_back(iter->GetPointer()); } return resultList; } mitk::Tool *mitk::ToolManager::GetToolById(int id) { try { return m_Tools.at(id); } catch (const std::exception &) { return nullptr; } } bool mitk::ToolManager::ActivateTool(int id) { - if (id != -1 && !this->GetToolById(id)->CanHandle(this->GetReferenceData(0)->GetData())) + const auto workingDataNode = this->GetWorkingData(0); + const mitk::BaseData* workingData = nullptr; + if (nullptr != workingDataNode) + { + workingData = workingDataNode->GetData(); + } + + const auto referenceDataNode = this->GetReferenceData(0); + const mitk::BaseData* referenceData = nullptr; + if (nullptr != referenceDataNode) + { + referenceData = referenceDataNode->GetData(); + } + + if (id != -1 && !this->GetToolById(id)->CanHandle(referenceData, workingData)) return false; if (this->GetDataStorage()) { this->GetDataStorage()->RemoveNodeEvent.AddListener( mitk::MessageDelegate1(this, &ToolManager::OnNodeRemoved)); } if (GetToolById(id) == m_ActiveTool) return true; // no change needed static int nextTool = -1; nextTool = id; static bool inActivateTool = false; if (inActivateTool) { return true; } inActivateTool = true; while (nextTool != m_ActiveToolID) { // Deactivate all other active tools to ensure a globally single active tool for (const auto& toolManager : ToolManagerProvider::GetInstance()->GetToolManagers()) { if (nullptr != toolManager.second->m_ActiveTool) { toolManager.second->m_ActiveTool->Deactivated(); toolManager.second->m_ActiveToolRegistration.Unregister(); // The active tool of *this* ToolManager is handled below this loop if (this != toolManager.second) { toolManager.second->m_ActiveTool = nullptr; toolManager.second->m_ActiveToolID = -1; toolManager.second->ActiveToolChanged.Send(); } } } m_ActiveTool = GetToolById(nextTool); m_ActiveToolID = m_ActiveTool ? nextTool : -1; // current ID if tool is valid, otherwise -1 ActiveToolChanged.Send(); if (m_ActiveTool) { this->EnsureTimeObservation(); if (m_RegisteredClients > 0) { m_ActiveTool->Activated(); m_ActiveToolRegistration = us::GetModuleContext()->RegisterService(m_ActiveTool, us::ServiceProperties()); } } } inActivateTool = false; return (m_ActiveTool != nullptr); } void mitk::ToolManager::SetReferenceData(DataVectorType data) { if (data != m_ReferenceData) { // remove observers from old nodes for (auto dataIter = m_ReferenceData.begin(); dataIter != m_ReferenceData.end(); ++dataIter) { auto searchIter = m_ReferenceDataObserverTags.find(*dataIter); if (searchIter != m_ReferenceDataObserverTags.end()) { (*dataIter)->RemoveObserver(searchIter->second); } } m_ReferenceData = data; // TODO tell active tool? // attach new observers m_ReferenceDataObserverTags.clear(); for (auto dataIter = m_ReferenceData.begin(); dataIter != m_ReferenceData.end(); ++dataIter) { itk::MemberCommand::Pointer command = itk::MemberCommand::New(); command->SetCallbackFunction(this, &ToolManager::OnOneOfTheReferenceDataDeleted); command->SetCallbackFunction(this, &ToolManager::OnOneOfTheReferenceDataDeletedConst); m_ReferenceDataObserverTags.insert( std::pair((*dataIter), (*dataIter)->AddObserver(itk::DeleteEvent(), command))); } ReferenceDataChanged.Send(); } } void mitk::ToolManager::OnOneOfTheReferenceDataDeletedConst(const itk::Object *caller, const itk::EventObject &e) { OnOneOfTheReferenceDataDeleted(const_cast(caller), e); } void mitk::ToolManager::OnOneOfTheReferenceDataDeleted(itk::Object *caller, const itk::EventObject &itkNotUsed(e)) { DataVectorType v; for (auto dataIter = m_ReferenceData.begin(); dataIter != m_ReferenceData.end(); ++dataIter) { if ((void *)(*dataIter) != (void *)caller) { v.push_back(*dataIter); } else { m_ReferenceDataObserverTags.erase(*dataIter); // no tag to remove anymore } } this->SetReferenceData(v); } void mitk::ToolManager::SetReferenceData(DataNode *data) { DataVectorType v; if (data) { v.push_back(data); } SetReferenceData(v); } void mitk::ToolManager::SetWorkingData(DataVectorType data) { if (data != m_WorkingData) { // remove observers from old nodes for (auto dataIter = m_WorkingData.begin(); dataIter != m_WorkingData.end(); ++dataIter) { auto searchIter = m_WorkingDataObserverTags.find(*dataIter); if (searchIter != m_WorkingDataObserverTags.end()) { (*dataIter)->RemoveObserver(searchIter->second); } } m_WorkingData = data; // TODO tell active tool? // Quick workaround for bug #16598 if (m_WorkingData.empty()) this->ActivateTool(-1); // workaround end // attach new observers m_WorkingDataObserverTags.clear(); for (auto dataIter = m_WorkingData.begin(); dataIter != m_WorkingData.end(); ++dataIter) { itk::MemberCommand::Pointer command = itk::MemberCommand::New(); command->SetCallbackFunction(this, &ToolManager::OnOneOfTheWorkingDataDeleted); command->SetCallbackFunction(this, &ToolManager::OnOneOfTheWorkingDataDeletedConst); m_WorkingDataObserverTags.insert( std::pair((*dataIter), (*dataIter)->AddObserver(itk::DeleteEvent(), command))); } WorkingDataChanged.Send(); } } void mitk::ToolManager::OnOneOfTheWorkingDataDeletedConst(const itk::Object *caller, const itk::EventObject &e) { OnOneOfTheWorkingDataDeleted(const_cast(caller), e); } void mitk::ToolManager::OnOneOfTheWorkingDataDeleted(itk::Object *caller, const itk::EventObject &itkNotUsed(e)) { DataVectorType v; for (auto dataIter = m_WorkingData.begin(); dataIter != m_WorkingData.end(); ++dataIter) { if ((void *)(*dataIter) != (void *)caller) { v.push_back(*dataIter); } else { m_WorkingDataObserverTags.erase(*dataIter); // no tag to remove anymore } } this->SetWorkingData(v); } void mitk::ToolManager::SetWorkingData(DataNode *data) { DataVectorType v; if (data) // don't allow for nullptr nodes { v.push_back(data); } SetWorkingData(v); } void mitk::ToolManager::SetRoiData(DataVectorType data) { if (data != m_RoiData) { // remove observers from old nodes for (auto dataIter = m_RoiData.begin(); dataIter != m_RoiData.end(); ++dataIter) { auto searchIter = m_RoiDataObserverTags.find(*dataIter); if (searchIter != m_RoiDataObserverTags.end()) { (*dataIter)->RemoveObserver(searchIter->second); } } m_RoiData = data; // TODO tell active tool? // attach new observers m_RoiDataObserverTags.clear(); for (auto dataIter = m_RoiData.begin(); dataIter != m_RoiData.end(); ++dataIter) { itk::MemberCommand::Pointer command = itk::MemberCommand::New(); command->SetCallbackFunction(this, &ToolManager::OnOneOfTheRoiDataDeleted); command->SetCallbackFunction(this, &ToolManager::OnOneOfTheRoiDataDeletedConst); m_RoiDataObserverTags.insert( std::pair((*dataIter), (*dataIter)->AddObserver(itk::DeleteEvent(), command))); } RoiDataChanged.Send(); } } void mitk::ToolManager::SetRoiData(DataNode *data) { DataVectorType v; if (data) { v.push_back(data); } this->SetRoiData(v); } void mitk::ToolManager::OnOneOfTheRoiDataDeletedConst(const itk::Object *caller, const itk::EventObject &e) { OnOneOfTheRoiDataDeleted(const_cast(caller), e); } void mitk::ToolManager::OnOneOfTheRoiDataDeleted(itk::Object *caller, const itk::EventObject &itkNotUsed(e)) { DataVectorType v; for (auto dataIter = m_RoiData.begin(); dataIter != m_RoiData.end(); ++dataIter) { if ((void *)(*dataIter) != (void *)caller) { v.push_back(*dataIter); } else { m_RoiDataObserverTags.erase(*dataIter); // no tag to remove anymore } } this->SetRoiData(v); } mitk::ToolManager::DataVectorType mitk::ToolManager::GetReferenceData() { return m_ReferenceData; } mitk::DataNode *mitk::ToolManager::GetReferenceData(int idx) { try { return m_ReferenceData.at(idx); } catch (const std::exception &) { return nullptr; } } mitk::ToolManager::DataVectorType mitk::ToolManager::GetWorkingData() { return m_WorkingData; } mitk::ToolManager::DataVectorType mitk::ToolManager::GetRoiData() { return m_RoiData; } mitk::DataNode *mitk::ToolManager::GetRoiData(int idx) { try { return m_RoiData.at(idx); } catch (const std::exception &) { return nullptr; } } mitk::DataStorage *mitk::ToolManager::GetDataStorage() { if (!m_DataStorage.IsExpired()) { return m_DataStorage.Lock(); } else { return nullptr; } } void mitk::ToolManager::SetDataStorage(DataStorage &storage) { m_DataStorage = &storage; } mitk::DataNode *mitk::ToolManager::GetWorkingData(unsigned int idx) { if (m_WorkingData.empty()) return nullptr; if (m_WorkingData.size() > idx) return m_WorkingData[idx]; return nullptr; } int mitk::ToolManager::GetActiveToolID() { return m_ActiveToolID; } mitk::Tool *mitk::ToolManager::GetActiveTool() { return m_ActiveTool; } void mitk::ToolManager::RegisterClient() { if (m_RegisteredClients < 1) { if (m_ActiveTool) { m_ActiveTool->Activated(); m_ActiveToolRegistration = us::GetModuleContext()->RegisterService(m_ActiveTool, us::ServiceProperties()); } } ++m_RegisteredClients; } void mitk::ToolManager::UnregisterClient() { if (m_RegisteredClients < 1) return; --m_RegisteredClients; if (m_RegisteredClients < 1) { if (m_ActiveTool) { m_ActiveTool->Deactivated(); m_ActiveToolRegistration.Unregister(); } } } int mitk::ToolManager::GetToolID(const Tool *tool) { int id(0); for (auto iter = m_Tools.begin(); iter != m_Tools.end(); ++iter, ++id) { if (tool == iter->GetPointer()) { return id; } } return -1; } void mitk::ToolManager::OnNodeRemoved(const mitk::DataNode *node) { // check all storage vectors OnOneOfTheReferenceDataDeleted(const_cast(node), itk::DeleteEvent()); OnOneOfTheRoiDataDeleted(const_cast(node), itk::DeleteEvent()); OnOneOfTheWorkingDataDeleted(const_cast(node), itk::DeleteEvent()); } void mitk::ToolManager::OnTimeChanged(itk::Object* caller, const itk::EventObject& e) { this->OnTimeChangedConst(caller, e); } void mitk::ToolManager::OnTimeChangedConst(const itk::Object* caller, const itk::EventObject& /*e*/) { auto currentController = m_CurrentTimeNavigationController.Lock(); if (caller == currentController) { const auto currentTimePoint = currentController->GetSelectedTimePoint(); if (currentTimePoint != m_LastTimePoint) { m_LastTimePoint = currentTimePoint; SelectedTimePointChanged.Send(); } } } mitk::TimePointType mitk::ToolManager::GetCurrentTimePoint() const { return m_LastTimePoint; } diff --git a/Modules/Segmentation/Interactions/mitkAdaptiveRegionGrowingTool.cpp b/Modules/Segmentation/Interactions/mitkAdaptiveRegionGrowingTool.cpp index 306d0fcac8..64de93da55 100644 --- a/Modules/Segmentation/Interactions/mitkAdaptiveRegionGrowingTool.cpp +++ b/Modules/Segmentation/Interactions/mitkAdaptiveRegionGrowingTool.cpp @@ -1,117 +1,117 @@ /*============================================================================ 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 "mitkAdaptiveRegionGrowingTool.h" #include "mitkImage.h" #include "mitkProperties.h" #include "mitkToolManager.h" // us #include #include #include #include namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, AdaptiveRegionGrowingTool, "AdaptiveRegionGrowingTool"); } mitk::AdaptiveRegionGrowingTool::AdaptiveRegionGrowingTool() { m_PointSetNode = mitk::DataNode::New(); m_PointSetNode->GetPropertyList()->SetProperty("name", mitk::StringProperty::New("3D_Regiongrowing_Seedpoint")); m_PointSetNode->GetPropertyList()->SetProperty("helper object", mitk::BoolProperty::New(true)); m_PointSet = mitk::PointSet::New(); m_PointSetNode->SetData(m_PointSet); } mitk::AdaptiveRegionGrowingTool::~AdaptiveRegionGrowingTool() { } -bool mitk::AdaptiveRegionGrowingTool::CanHandle(BaseData *referenceData) const +bool mitk::AdaptiveRegionGrowingTool::CanHandle(const BaseData* referenceData, const BaseData* /*workingData*/) const { if (referenceData == nullptr) return false; - auto *image = dynamic_cast(referenceData); + auto *image = dynamic_cast(referenceData); if (image == nullptr) return false; if (image->GetDimension() < 3) return false; return true; } const char **mitk::AdaptiveRegionGrowingTool::GetXPM() const { return nullptr; } const char *mitk::AdaptiveRegionGrowingTool::GetName() const { return "Region Growing 3D"; } us::ModuleResource mitk::AdaptiveRegionGrowingTool::GetIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); us::ModuleResource resource = module->GetResource("RegionGrowing_48x48.png"); return resource; } void mitk::AdaptiveRegionGrowingTool::Activated() { Superclass::Activated(); if (!GetDataStorage()->Exists(m_PointSetNode)) GetDataStorage()->Add(m_PointSetNode, GetWorkingData()); m_SeedPointInteractor = mitk::SinglePointDataInteractor::New(); m_SeedPointInteractor->LoadStateMachine("PointSet.xml"); m_SeedPointInteractor->SetEventConfig("PointSetConfig.xml"); m_SeedPointInteractor->SetDataNode(m_PointSetNode); } void mitk::AdaptiveRegionGrowingTool::Deactivated() { m_PointSet->Clear(); GetDataStorage()->Remove(m_PointSetNode); Superclass::Deactivated(); } void mitk::AdaptiveRegionGrowingTool::ConfirmSegmentation() { m_ToolManager->ActivateTool(-1); } mitk::DataNode *mitk::AdaptiveRegionGrowingTool::GetReferenceData() { return this->m_ToolManager->GetReferenceData(0); } mitk::DataStorage *mitk::AdaptiveRegionGrowingTool::GetDataStorage() { return this->m_ToolManager->GetDataStorage(); } mitk::DataNode *mitk::AdaptiveRegionGrowingTool::GetWorkingData() { return this->m_ToolManager->GetWorkingData(0); } mitk::DataNode::Pointer mitk::AdaptiveRegionGrowingTool::GetPointSetNode() { return m_PointSetNode; } diff --git a/Modules/Segmentation/Interactions/mitkAdaptiveRegionGrowingTool.h b/Modules/Segmentation/Interactions/mitkAdaptiveRegionGrowingTool.h index 31797bffbc..7924978442 100644 --- a/Modules/Segmentation/Interactions/mitkAdaptiveRegionGrowingTool.h +++ b/Modules/Segmentation/Interactions/mitkAdaptiveRegionGrowingTool.h @@ -1,128 +1,128 @@ /*============================================================================ 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 mitkAdaptiveRegionGrowingTool_h_Included #define mitkAdaptiveRegionGrowingTool_h_Included #include "mitkAutoSegmentationTool.h" #include "mitkCommon.h" #include "mitkDataStorage.h" #include "mitkPointSet.h" #include "mitkSinglePointDataInteractor.h" #include namespace us { class ModuleResource; } namespace mitk { /** \brief Dummy Tool for AdaptiveRegionGrowingToolGUI to get Tool functionality for AdaptiveRegionGrowing. The actual logic is implemented in QmitkAdaptiveRegionGrowingToolGUI. \ingroup ToolManagerEtAl \sa mitk::Tool \sa QmitkInteractiveSegmentation */ class MITKSEGMENTATION_EXPORT AdaptiveRegionGrowingTool : public AutoSegmentationTool { public: /** * @brief mitkClassMacro */ mitkClassMacro(AdaptiveRegionGrowingTool, AutoSegmentationTool); itkFactorylessNewMacro(Self); itkCloneMacro(Self); - bool CanHandle(BaseData *referenceData) const override; + bool CanHandle(const BaseData* referenceData, const BaseData* workingData) const override; /** * @brief Get XPM * @return nullptr */ const char **GetXPM() const override; /** * @brief Get name * @return name of the Tool */ const char *GetName() const override; /** * @brief Get icon resource * @return the resource Object of the Icon */ us::ModuleResource GetIconResource() const override; /** * @brief Adds interactor for the seedpoint and creates a seedpoint if neccessary. * * */ void Activated() override; /** * @brief Removes all set points and interactors. * * */ void Deactivated() override; /** * @brief get pointset node * @return the point set node */ virtual DataNode::Pointer GetPointSetNode(); /** * @brief get reference data * @return the current reference data. */ mitk::DataNode *GetReferenceData(); /** * @brief Get working data * @return a list of all working data. */ mitk::DataNode *GetWorkingData(); /** * @brief Get datastorage * @return the current data storage. */ mitk::DataStorage *GetDataStorage(); void ConfirmSegmentation(); protected: /** * @brief constructor */ AdaptiveRegionGrowingTool(); // purposely hidden /** * @brief destructor */ ~AdaptiveRegionGrowingTool() override; private: PointSet::Pointer m_PointSet; SinglePointDataInteractor::Pointer m_SeedPointInteractor; DataNode::Pointer m_PointSetNode; }; } // namespace #endif diff --git a/Modules/Segmentation/Interactions/mitkAutoSegmentationTool.cpp b/Modules/Segmentation/Interactions/mitkAutoSegmentationTool.cpp index 0539cb7df1..96ba360d0f 100644 --- a/Modules/Segmentation/Interactions/mitkAutoSegmentationTool.cpp +++ b/Modules/Segmentation/Interactions/mitkAutoSegmentationTool.cpp @@ -1,97 +1,97 @@ /*============================================================================ 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 "mitkAutoSegmentationTool.h" #include "mitkImage.h" #include "mitkToolManager.h" #include mitk::AutoSegmentationTool::AutoSegmentationTool() : Tool("dummy"), m_OverwriteExistingSegmentation(false) { } mitk::AutoSegmentationTool::~AutoSegmentationTool() { } const char *mitk::AutoSegmentationTool::GetGroup() const { return "autoSegmentation"; } -mitk::Image::ConstPointer mitk::AutoSegmentationTool::Get3DImage(const mitk::Image* image, unsigned int timestep) const +mitk::Image::ConstPointer mitk::AutoSegmentationTool::GetImageByTimeStep(const mitk::Image* image, unsigned int timestep) { if (nullptr == image) return image; if (image->GetDimension() != 4) return image; mitk::ImageTimeSelector::Pointer imageTimeSelector = mitk::ImageTimeSelector::New(); imageTimeSelector->SetInput(image); imageTimeSelector->SetTimeNr(static_cast(timestep)); imageTimeSelector->UpdateLargestPossibleRegion(); return imageTimeSelector->GetOutput(); } -mitk::Image::ConstPointer mitk::AutoSegmentationTool::Get3DImageByTimePoint(const mitk::Image* image, TimePointType timePoint) const +mitk::Image::ConstPointer mitk::AutoSegmentationTool::GetImageByTimePoint(const mitk::Image* image, TimePointType timePoint) { if (nullptr == image) return image; if (!image->GetTimeGeometry()->IsValidTimePoint(timePoint)) return nullptr; - return this->Get3DImage(image, image->GetTimeGeometry()->TimePointToTimeStep(timePoint)); + return AutoSegmentationTool::GetImageByTimeStep(image, image->GetTimeGeometry()->TimePointToTimeStep(timePoint)); } void mitk::AutoSegmentationTool::SetOverwriteExistingSegmentation(bool overwrite) { m_OverwriteExistingSegmentation = overwrite; } std::string mitk::AutoSegmentationTool::GetCurrentSegmentationName() { if (m_ToolManager->GetWorkingData(0)) return m_ToolManager->GetWorkingData(0)->GetName(); else return ""; } mitk::DataNode *mitk::AutoSegmentationTool::GetTargetSegmentationNode() { mitk::DataNode::Pointer segmentationNode = m_ToolManager->GetWorkingData(0); if (!m_OverwriteExistingSegmentation) { mitk::DataNode::Pointer refNode = m_ToolManager->GetReferenceData(0); if (refNode.IsNull()) { // TODO create and use segmentation exceptions instead!! MITK_ERROR << "No valid reference data!"; return nullptr; } std::string nodename = refNode->GetName() + "_" + this->GetName(); mitk::Color color; color.SetRed(1); color.SetBlue(0); color.SetGreen(0); //create a new segmentation node based on the current segmentation as template segmentationNode = CreateEmptySegmentationNode(dynamic_cast(segmentationNode->GetData()), nodename, color); m_ToolManager->GetDataStorage()->Add(segmentationNode, refNode); } return segmentationNode; } diff --git a/Modules/Segmentation/Interactions/mitkAutoSegmentationTool.h b/Modules/Segmentation/Interactions/mitkAutoSegmentationTool.h index 1a2d458950..dfb36b908c 100644 --- a/Modules/Segmentation/Interactions/mitkAutoSegmentationTool.h +++ b/Modules/Segmentation/Interactions/mitkAutoSegmentationTool.h @@ -1,65 +1,71 @@ /*============================================================================ 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 mitkAutoSegmentationTool_h_Included #define mitkAutoSegmentationTool_h_Included #include "mitkCommon.h" #include "mitkTool.h" #include namespace mitk { class Image; /** \brief Superclass for tool that create a new segmentation without user interaction in render windows This class is undocumented. Ask the creator ($Author$) to supply useful comments. */ class MITKSEGMENTATION_EXPORT AutoSegmentationTool : public Tool { public: mitkClassMacro(AutoSegmentationTool, Tool); + /** This function controls wether a confirmed segmentation should replace the old + * segmentation/working node (true) or if it should be stored as new and additional + * node (false). + */ void SetOverwriteExistingSegmentation(bool overwrite); /** * @brief Gets the name of the currently selected segmentation node * @return the name of the segmentation node or an empty string if * none is selected */ std::string GetCurrentSegmentationName(); /** * @brief Depending on the selected mode either returns the currently selected segmentation * or creates a new one from the selected reference data and adds the new segmentation * to the datastorage * @return a mitk::DataNode which contains a segmentation image */ virtual mitk::DataNode *GetTargetSegmentationNode(); protected: AutoSegmentationTool(); // purposely hidden ~AutoSegmentationTool() override; const char *GetGroup() const override; - virtual Image::ConstPointer Get3DImage(const Image* image, unsigned int timestep) const; - virtual Image::ConstPointer Get3DImageByTimePoint(const Image* image, TimePointType timePoint) const; + /** Helper that extracts the image for the passed timestep, if the image has multiple time steps.*/ + static Image::ConstPointer GetImageByTimeStep(const Image* image, unsigned int timestep); + /** Helper that extracts the image for the passed time point, if the image has multiple time steps.*/ + static Image::ConstPointer GetImageByTimePoint(const Image* image, TimePointType timePoint); bool m_OverwriteExistingSegmentation; }; } // namespace #endif diff --git a/Modules/Segmentation/Interactions/mitkAutoSegmentationWithPreviewTool.cpp b/Modules/Segmentation/Interactions/mitkAutoSegmentationWithPreviewTool.cpp new file mode 100644 index 0000000000..8e9b4291f7 --- /dev/null +++ b/Modules/Segmentation/Interactions/mitkAutoSegmentationWithPreviewTool.cpp @@ -0,0 +1,474 @@ +/*============================================================================ + +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 "mitkAutoSegmentationWithPreviewTool.h" + +#include "mitkToolManager.h" + +#include "mitkColorProperty.h" +#include "mitkLevelWindowProperty.h" +#include "mitkProperties.h" + +#include "mitkDataStorage.h" +#include "mitkRenderingManager.h" +#include + +#include "mitkImageAccessByItk.h" +#include "mitkImageCast.h" +#include "mitkImageStatisticsHolder.h" +#include "mitkImageTimeSelector.h" +#include "mitkLabelSetImage.h" +#include "mitkMaskAndCutRoiImageFilter.h" +#include "mitkPadImageFilter.h" +#include "mitkNodePredicateGeometry.h" + +mitk::AutoSegmentationWithPreviewTool::AutoSegmentationWithPreviewTool(bool lazyDynamicPreviews): m_LazyDynamicPreviews(lazyDynamicPreviews) +{ + m_ProgressCommand = mitk::ToolCommand::New(); +} + +mitk::AutoSegmentationWithPreviewTool::~AutoSegmentationWithPreviewTool() +{ +} + +bool mitk::AutoSegmentationWithPreviewTool::CanHandle(const BaseData* referenceData, const BaseData* workingData) const +{ + if (!Superclass::CanHandle(referenceData, workingData)) + return false; + + if (workingData == nullptr) + return true; + + auto* labelSet = dynamic_cast(workingData); + + if (labelSet != nullptr) + return true; + + auto* image = dynamic_cast(workingData); + + if (image == nullptr) + return false; + + //if it is a normal image and not a label set image is used as working data + //it must have the same pixel type as a label set. + return MakeScalarPixelType< DefaultSegmentationDataType >() == image->GetPixelType(); +} + +void mitk::AutoSegmentationWithPreviewTool::Activated() +{ + Superclass::Activated(); + + m_ToolManager->RoiDataChanged += + mitk::MessageDelegate(this, &mitk::AutoSegmentationWithPreviewTool::OnRoiDataChanged); + + m_ToolManager->SelectedTimePointChanged += + mitk::MessageDelegate(this, &mitk::AutoSegmentationWithPreviewTool::OnTimePointChanged); + + m_ReferenceDataNode = m_ToolManager->GetReferenceData(0); + m_SegmentationInputNode = m_ReferenceDataNode; + + m_LastTimePointOfUpdate = 0; + + if (m_PreviewSegmentationNode.IsNull()) + { + m_PreviewSegmentationNode = DataNode::New(); + m_PreviewSegmentationNode->SetProperty("color", ColorProperty::New(0.0, 1.0, 0.0)); + m_PreviewSegmentationNode->SetProperty("name", StringProperty::New(std::string(this->GetName())+" preview")); + m_PreviewSegmentationNode->SetProperty("opacity", FloatProperty::New(0.3)); + m_PreviewSegmentationNode->SetProperty("binary", BoolProperty::New(true)); + m_PreviewSegmentationNode->SetProperty("helper object", BoolProperty::New(true)); + } + + if (m_SegmentationInputNode.IsNotNull()) + { + this->ResetPreviewNode(); + this->InitiateToolByInput(); + } + else + { + m_ToolManager->ActivateTool(-1); + } +} + +void mitk::AutoSegmentationWithPreviewTool::Deactivated() +{ + m_ToolManager->RoiDataChanged -= + mitk::MessageDelegate(this, &mitk::AutoSegmentationWithPreviewTool::OnRoiDataChanged); + + m_ToolManager->SelectedTimePointChanged -= + mitk::MessageDelegate(this, &mitk::AutoSegmentationWithPreviewTool::OnTimePointChanged); + + m_SegmentationInputNode = nullptr; + m_ReferenceDataNode = nullptr; + + try + { + if (DataStorage *storage = m_ToolManager->GetDataStorage()) + { + storage->Remove(m_PreviewSegmentationNode); + RenderingManager::GetInstance()->RequestUpdateAll(); + } + } + catch (...) + { + // don't care + } + + m_PreviewSegmentationNode->SetData(nullptr); + + Superclass::Deactivated(); +} + +void mitk::AutoSegmentationWithPreviewTool::ConfirmSegmentation() +{ + if (m_LazyDynamicPreviews && m_CreateAllTimeSteps) + { // The tool should create all time steps but is currently in lazy mode, + // thus ensure that a preview for all time steps is available. + this->UpdatePreview(true); + } + + CreateResultSegmentationFromPreview(); + + RenderingManager::GetInstance()->RequestUpdateAll(); + + if (!m_KeepActiveAfterAccept) + { + m_ToolManager->ActivateTool(-1); + } +} + +void mitk::AutoSegmentationWithPreviewTool::InitiateToolByInput() +{ + //default implementation does nothing. + //implement in derived classes to change behavior +} + +mitk::Image* mitk::AutoSegmentationWithPreviewTool::GetPreviewSegmentation() +{ + if (m_PreviewSegmentationNode.IsNull()) + { + return nullptr; + } + + return dynamic_cast(m_PreviewSegmentationNode->GetData()); +} + +mitk::DataNode* mitk::AutoSegmentationWithPreviewTool::GetPreviewSegmentationNode() +{ + return m_PreviewSegmentationNode; +} + +const mitk::Image* mitk::AutoSegmentationWithPreviewTool::GetSegmentationInput() const +{ + if (m_SegmentationInputNode.IsNull()) + { + return nullptr; + } + + return dynamic_cast(m_SegmentationInputNode->GetData()); +} + +const mitk::Image* mitk::AutoSegmentationWithPreviewTool::GetReferenceData() const +{ + if (m_ReferenceDataNode.IsNull()) + { + return nullptr; + } + + return dynamic_cast(m_ReferenceDataNode->GetData()); +} + +void mitk::AutoSegmentationWithPreviewTool::ResetPreviewNode() +{ + itk::RGBPixel previewColor; + previewColor[0] = 0.0f; + previewColor[1] = 1.0f; + previewColor[2] = 0.0f; + + const auto image = this->GetSegmentationInput(); + if (nullptr != image) + { + mitk::LabelSetImage::ConstPointer workingImage = + dynamic_cast(m_ToolManager->GetWorkingData(0)->GetData()); + + if (workingImage.IsNotNull()) + { + auto newPreviewImage = workingImage->Clone(); + if (newPreviewImage.IsNull()) + { + MITK_ERROR << "Cannot create preview helper objects. Unable to clone working image"; + return; + } + + m_PreviewSegmentationNode->SetData(newPreviewImage); + + // Let's paint the feedback node green... + newPreviewImage->GetActiveLabel()->SetColor(previewColor); + newPreviewImage->GetActiveLabelSet()->UpdateLookupTable(newPreviewImage->GetActiveLabel()->GetValue()); + } + else + { + mitk::Image::ConstPointer workingImageBin = dynamic_cast(m_ToolManager->GetWorkingData(0)->GetData()); + if (workingImageBin.IsNotNull()) + { + auto newPreviewImage = workingImageBin->Clone(); + if (newPreviewImage.IsNull()) + { + MITK_ERROR << "Cannot create preview helper objects. Unable to clone working image"; + return; + } + + m_PreviewSegmentationNode->SetData(newPreviewImage->Clone()); + } + else + { + mitkThrow() << "Tool is an invalid state. Cannot setup preview node. Working data is an unsupported class and should have not been accepted by CanHandle()."; + } + } + + m_PreviewSegmentationNode->SetColor(previewColor); + m_PreviewSegmentationNode->SetOpacity(0.5); + + int layer(50); + m_ReferenceDataNode->GetIntProperty("layer", layer); + m_PreviewSegmentationNode->SetIntProperty("layer", layer + 1); + + if (DataStorage *ds = m_ToolManager->GetDataStorage()) + { + if (!ds->Exists(m_PreviewSegmentationNode)) + ds->Add(m_PreviewSegmentationNode, m_ReferenceDataNode); + } + } +} + +template +static void ITKSetVolume(const itk::Image *originalImage, + mitk::Image *segmentation, + unsigned int timeStep) +{ + auto constPixelContainer = originalImage->GetPixelContainer(); + //have to make a const cast because itk::PixelContainer does not provide a const correct access :( + auto pixelContainer = const_cast::PixelContainer*>(constPixelContainer); + + segmentation->SetVolume((void *)pixelContainer->GetBufferPointer(), timeStep); +} + +void mitk::AutoSegmentationWithPreviewTool::TransferImageAtTimeStep(const Image* sourceImage, Image* destinationImage, const TimeStepType timeStep) +{ + try + { + Image::ConstPointer image3D = this->GetImageByTimeStep(sourceImage, timeStep); + + if (image3D->GetPixelType() != destinationImage->GetPixelType()) + { + mitkThrow() << "Cannot transfer images. Tool is in an invalid state, source image and destination image do not have the same pixel type. " + << "Source pixel type: " << sourceImage->GetPixelType().GetTypeAsString() + << "; destination pixel type: " << destinationImage->GetPixelType().GetTypeAsString(); + } + + if (!Equal(*(sourceImage->GetGeometry(timeStep)), *(destinationImage->GetGeometry(timeStep)), NODE_PREDICATE_GEOMETRY_DEFAULT_CHECK_PRECISION, false)) + { + mitkThrow() << "Cannot transfer images. Tool is in an invalid state, source image and destination image do not have the same geometry."; + } + + if (image3D->GetDimension() == 2) + { + AccessFixedDimensionByItk_2( + image3D, ITKSetVolume, 2, destinationImage, timeStep); + } + else + { + AccessFixedDimensionByItk_2( + image3D, ITKSetVolume, 3, destinationImage, timeStep); + } + } + catch (...) + { + Tool::ErrorMessage("Error accessing single time steps of the original image. Cannot create segmentation."); + throw; + } +} + +void mitk::AutoSegmentationWithPreviewTool::CreateResultSegmentationFromPreview() +{ + const auto segInput = this->GetSegmentationInput(); + auto previewImage = this->GetPreviewSegmentation(); + if (nullptr != segInput && nullptr != previewImage) + { + DataNode::Pointer resultSegmentationNode = GetTargetSegmentationNode(); + + if (resultSegmentationNode.IsNotNull()) + { + const auto timePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); + auto resultSegmentation = dynamic_cast(resultSegmentationNode->GetData()); + + // REMARK: the following code in this scope assumes that previewImage and resultSegmentation + // are clones of the working image (segmentation provided to the tool). Therefore they have + // the same time geometry. + if (previewImage->GetTimeSteps() != resultSegmentation->GetTimeSteps()) + { + mitkThrow() << "Cannot perform threshold. Internal tool state is invalid." + << " Preview segmentation and segmentation result image have different time geometries."; + } + + if (m_CreateAllTimeSteps) + { + for (unsigned int timeStep = 0; timeStep < previewImage->GetTimeSteps(); ++timeStep) + { + TransferImageAtTimeStep(previewImage, resultSegmentation, timeStep); + } + } + else + { + const auto timeStep = resultSegmentation->GetTimeGeometry()->TimePointToTimeStep(timePoint); + TransferImageAtTimeStep(previewImage, resultSegmentation, timeStep); + } + + // since we are maybe working on a smaller image, pad it to the size of the original image + if (m_ReferenceDataNode.GetPointer() != m_SegmentationInputNode.GetPointer()) + { + mitk::PadImageFilter::Pointer padFilter = mitk::PadImageFilter::New(); + + padFilter->SetInput(0, resultSegmentation); + padFilter->SetInput(1, dynamic_cast(m_ReferenceDataNode->GetData())); + padFilter->SetBinaryFilter(true); + padFilter->SetUpperThreshold(1); + padFilter->SetLowerThreshold(1); + padFilter->Update(); + + resultSegmentationNode->SetData(padFilter->GetOutput()); + } + + m_ToolManager->SetWorkingData(resultSegmentationNode); + m_ToolManager->GetWorkingData(0)->Modified(); + } + } +} + +void mitk::AutoSegmentationWithPreviewTool::OnRoiDataChanged() +{ + mitk::DataNode::ConstPointer node = m_ToolManager->GetRoiData(0); + + if (node.IsNotNull()) + { + mitk::MaskAndCutRoiImageFilter::Pointer roiFilter = mitk::MaskAndCutRoiImageFilter::New(); + mitk::Image::Pointer image = dynamic_cast(m_SegmentationInputNode->GetData()); + + if (image.IsNull()) + return; + + roiFilter->SetInput(image); + roiFilter->SetRegionOfInterest(node->GetData()); + roiFilter->Update(); + + mitk::DataNode::Pointer tmpNode = mitk::DataNode::New(); + tmpNode->SetData(roiFilter->GetOutput()); + + m_SegmentationInputNode = tmpNode; + } + else + m_SegmentationInputNode = m_ReferenceDataNode; + + this->ResetPreviewNode(); + this->InitiateToolByInput(); + this->UpdatePreview(); +} + +void mitk::AutoSegmentationWithPreviewTool::OnTimePointChanged() +{ + if (m_IsTimePointChangeAware && m_PreviewSegmentationNode.IsNotNull() && m_SegmentationInputNode.IsNotNull()) + { + const auto timePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); + + const bool isStaticSegOnDynamicImage = m_PreviewSegmentationNode->GetData()->GetTimeSteps() == 1 && m_SegmentationInputNode->GetData()->GetTimeSteps() > 1; + if (timePoint!=m_LastTimePointOfUpdate && (isStaticSegOnDynamicImage || m_LazyDynamicPreviews)) + { //we only need to update either because we are lazzy + //or because we have a static segmentation with a dynamic image + this->UpdatePreview(); + } + } +} + +void mitk::AutoSegmentationWithPreviewTool::UpdatePreview(bool ignoreLazyPreviewSetting) +{ + const auto inputImage = this->GetSegmentationInput(); + auto previewImage = this->GetPreviewSegmentation(); + int progress_steps = 200; + + this->UpdatePrepare(); + + const auto timePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); + + try + { + this->CurrentlyBusy.Send(true); + if (nullptr != inputImage && nullptr != previewImage) + { + m_ProgressCommand->AddStepsToDo(progress_steps); + + if (previewImage->GetTimeSteps() > 1 && (ignoreLazyPreviewSetting || !m_LazyDynamicPreviews)) + { + for (unsigned int timeStep = 0; timeStep < inputImage->GetTimeSteps(); ++timeStep) + { + auto feedBackImage3D = this->GetImageByTimeStep(inputImage, timeStep); + + this->DoUpdatePreview(feedBackImage3D, previewImage, timeStep); + } + } + else + { + auto feedBackImage3D = this->GetImageByTimePoint(inputImage, timePoint); + auto timeStep = previewImage->GetTimeGeometry()->TimePointToTimeStep(timePoint); + + this->DoUpdatePreview(feedBackImage3D, previewImage, timeStep); + } + RenderingManager::GetInstance()->RequestUpdateAll(); + } + } + catch (itk::ExceptionObject & excep) + { + MITK_ERROR << "Exception caught: " << excep.GetDescription(); + + m_ProgressCommand->SetProgress(progress_steps); + + std::string msg = excep.GetDescription(); + ErrorMessage.Send(msg); + } + catch (...) + { + m_ProgressCommand->SetProgress(progress_steps); + CurrentlyBusy.Send(false); + throw; + } + + this->UpdateCleanUp(); + m_LastTimePointOfUpdate = timePoint; + m_ProgressCommand->SetProgress(progress_steps); + CurrentlyBusy.Send(false); +} + +void mitk::AutoSegmentationWithPreviewTool::UpdatePrepare() +{ + // default implementation does nothing + //reimplement in derived classes for special behavior +} + +void mitk::AutoSegmentationWithPreviewTool::UpdateCleanUp() +{ + // default implementation does nothing + //reimplement in derived classes for special behavior +} + +mitk::TimePointType mitk::AutoSegmentationWithPreviewTool::GetLastTimePointOfUpdate() const +{ + return m_LastTimePointOfUpdate; +} diff --git a/Modules/Segmentation/Interactions/mitkAutoSegmentationWithPreviewTool.h b/Modules/Segmentation/Interactions/mitkAutoSegmentationWithPreviewTool.h new file mode 100644 index 0000000000..848ff0c005 --- /dev/null +++ b/Modules/Segmentation/Interactions/mitkAutoSegmentationWithPreviewTool.h @@ -0,0 +1,152 @@ +/*============================================================================ + +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 mitkAutoSegmentationWithPreviewTool_h_Included +#define mitkAutoSegmentationWithPreviewTool_h_Included + +#include "mitkAutoSegmentationTool.h" +#include "mitkCommon.h" +#include "mitkDataNode.h" +#include "mitkToolCommand.h" +#include + +namespace mitk +{ + /** + \brief Base class for any auto segmentation tool that provides a preview of the new segmentation. + + This tool class implements a lot basic logic to handle auto segmentation tools with preview, + Time point and ROI support. Derived classes will ask to update the segmentation preview if needed + (e.g. because the ROI or the current time point has changed) or because derived tools + indicated the need to update themselves. + This class also takes care to properly transfer a confirmed preview into the segementation + result. + + \ingroup ToolManagerEtAl + \sa mitk::Tool + \sa QmitkInteractiveSegmentation + */ + class MITKSEGMENTATION_EXPORT AutoSegmentationWithPreviewTool : public AutoSegmentationTool + { + public: + + mitkClassMacro(AutoSegmentationWithPreviewTool, AutoSegmentationTool); + + void Activated() override; + void Deactivated() override; + + void ConfirmSegmentation(); + + itkSetMacro(CreateAllTimeSteps, bool); + itkGetMacro(CreateAllTimeSteps, bool); + itkBooleanMacro(CreateAllTimeSteps); + + itkSetMacro(KeepActiveAfterAccept, bool); + itkGetMacro(KeepActiveAfterAccept, bool); + itkBooleanMacro(KeepActiveAfterAccept); + + itkSetMacro(IsTimePointChangeAware, bool); + itkGetMacro(IsTimePointChangeAware, bool); + itkBooleanMacro(IsTimePointChangeAware); + + bool CanHandle(const BaseData* referenceData, const BaseData* workingData) const override; + + /** Triggers the actualization of the preview + * @param ignoreLazyPreviewSetting If set true UpdatePreview will always + * generate the preview for all time steps. If set to false, UpdatePreview + * will regard the setting specified by the constructor. + * To define the update generation for time steps implement DoUpdatePreview. + * To alter what should be done directly before or after the update of the preview, + * reimplement UpdatePrepare() or UpdateCleanUp().*/ + void UpdatePreview(bool ignoreLazyPreviewSetting = false); + + protected: + mitk::ToolCommand::Pointer m_ProgressCommand; + + /** Member is always called if GetSegmentationInput() has changed + * (e.g. because a new ROI was defined, or on activation) to give derived + * classes the posibility to initiate their state accordingly. + * Reimplement this function to implement special behavior. + */ + virtual void InitiateToolByInput(); + + virtual void UpdatePrepare(); + virtual void UpdateCleanUp(); + + /** This function does the real work. Here the preview for a given + * input image should be computed and stored in the also passed + * preview image at the passed time step. + */ + virtual void DoUpdatePreview(const Image* inputAtTimeStep, Image* previewImage, TimeStepType timeStep) = 0; + + AutoSegmentationWithPreviewTool(bool lazyDynamicPreviews = false); + ~AutoSegmentationWithPreviewTool() override; + + /** Returns the image that contains the preview of the current segmentation. + * Returns null if the node is not set or does not contain an image.*/ + Image* GetPreviewSegmentation(); + DataNode* GetPreviewSegmentationNode(); + + /** Returns the input that should be used for any segmentation/preview or tool update. + * It is either the data of ReferenceDataNode itself or a part of it defined by a ROI mask + * provided by the tool manager. Derived classes should regard this as the relevant + * input data for any processing. + * Returns null if the node is not set or does not contain an image.*/ + const Image* GetSegmentationInput() const; + + /** Returns the image that is provided by the ReferenceDataNode. + * Returns null if the node is not set or does not contain an image.*/ + const Image* GetReferenceData() const; + + /** Resets the preview node so it is empty and ready to be filled by the tool*/ + void ResetPreviewNode(); + + TimePointType GetLastTimePointOfUpdate() const; + + private: + void TransferImageAtTimeStep(const Image* sourceImage, Image* destinationImage, const TimeStepType timeStep); + + void CreateResultSegmentationFromPreview(); + + void OnRoiDataChanged(); + void OnTimePointChanged(); + + /** Node that containes the preview data generated and managed by this class or derived ones.*/ + DataNode::Pointer m_PreviewSegmentationNode; + /** The reference data recieved from ToolManager::GetReferenceData when tool was activated.*/ + DataNode::Pointer m_ReferenceDataNode; + /** Node that containes the data that should be used as input for any auto segmentation. It might + * be the same like m_ReferenceDataNode (if no ROI is set) or a sub region (if ROI is set).*/ + DataNode::Pointer m_SegmentationInputNode; + + /** Indicates if Accepting the threshold should transfer/create the segmentations + of all time steps (true) or only of the currently selected timepoint (false).*/ + bool m_CreateAllTimeSteps = false; + + /** Indicates if the tool should kept active after accepting the segmentation or not.*/ + bool m_KeepActiveAfterAccept = false; + + /** Relevant if the working data / preview image has multiple time steps (dynamic segmentations). + * This flag has to be set by derived classes accordingly to there way to generate dynamic previews. + * If LazyDynamicPreview is true, the tool generates only the preview for the current time step. + * Therefore it always has to update the preview if current time point has changed and it has to (re)compute + * all timeframes if ConfirmSegmentation() is called.*/ + bool m_LazyDynamicPreviews = false; + + bool m_IsTimePointChangeAware = true; + + TimePointType m_LastTimePointOfUpdate = 0.; + }; + +} // namespace + +#endif diff --git a/Modules/Segmentation/Interactions/mitkBinaryThresholdBaseTool.cpp b/Modules/Segmentation/Interactions/mitkBinaryThresholdBaseTool.cpp index f48232e055..a8ee655db0 100644 --- a/Modules/Segmentation/Interactions/mitkBinaryThresholdBaseTool.cpp +++ b/Modules/Segmentation/Interactions/mitkBinaryThresholdBaseTool.cpp @@ -1,466 +1,126 @@ /*============================================================================ 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 "mitkBinaryThresholdBaseTool.h" -#include "mitkToolManager.h" - -#include "mitkColorProperty.h" -#include "mitkLevelWindowProperty.h" -#include "mitkProperties.h" - -#include "mitkDataStorage.h" -#include "mitkRenderingManager.h" -#include - #include "mitkImageAccessByItk.h" #include "mitkImageCast.h" #include "mitkImageStatisticsHolder.h" -#include "mitkImageTimeSelector.h" #include "mitkLabelSetImage.h" -#include "mitkMaskAndCutRoiImageFilter.h" -#include "mitkPadImageFilter.h" #include #include mitk::BinaryThresholdBaseTool::BinaryThresholdBaseTool() : m_SensibleMinimumThresholdValue(-100), m_SensibleMaximumThresholdValue(+100), m_CurrentLowerThresholdValue(1), m_CurrentUpperThresholdValue(1) { - m_ThresholdFeedbackNode = DataNode::New(); - m_ThresholdFeedbackNode->SetProperty("color", ColorProperty::New(0.0, 1.0, 0.0)); - m_ThresholdFeedbackNode->SetProperty("name", StringProperty::New("Thresholding feedback")); - m_ThresholdFeedbackNode->SetProperty("opacity", FloatProperty::New(0.3)); - m_ThresholdFeedbackNode->SetProperty("binary", BoolProperty::New(true)); - m_ThresholdFeedbackNode->SetProperty("helper object", BoolProperty::New(true)); } mitk::BinaryThresholdBaseTool::~BinaryThresholdBaseTool() { } -void mitk::BinaryThresholdBaseTool::Activated() -{ - Superclass::Activated(); - - m_ToolManager->RoiDataChanged += - mitk::MessageDelegate(this, &mitk::BinaryThresholdBaseTool::OnRoiDataChanged); - - m_ToolManager->SelectedTimePointChanged += - mitk::MessageDelegate(this, &mitk::BinaryThresholdBaseTool::OnTimePointChanged); - - m_OriginalImageNode = m_ToolManager->GetReferenceData(0); - m_NodeForThresholding = m_OriginalImageNode; - - if (m_NodeForThresholding.IsNotNull()) - { - SetupPreviewNode(); - } - else - { - m_ToolManager->ActivateTool(-1); - } -} - -void mitk::BinaryThresholdBaseTool::Deactivated() -{ - m_ToolManager->RoiDataChanged -= - mitk::MessageDelegate(this, &mitk::BinaryThresholdBaseTool::OnRoiDataChanged); - - m_ToolManager->SelectedTimePointChanged -= - mitk::MessageDelegate(this, &mitk::BinaryThresholdBaseTool::OnTimePointChanged); - - m_NodeForThresholding = nullptr; - m_OriginalImageNode = nullptr; - try - { - if (DataStorage *storage = m_ToolManager->GetDataStorage()) - { - storage->Remove(m_ThresholdFeedbackNode); - RenderingManager::GetInstance()->RequestUpdateAll(); - } - } - catch (...) - { - // don't care - } - m_ThresholdFeedbackNode->SetData(nullptr); - - Superclass::Deactivated(); -} - void mitk::BinaryThresholdBaseTool::SetThresholdValues(double lower, double upper) { /* If value is not in the min/max range, do nothing. In that case, this method will be called again with a proper value right after. The only known case where this happens is with an [0.0, 1.0[ image, where value could be an epsilon greater than the max. */ if (lower < m_SensibleMinimumThresholdValue || lower > m_SensibleMaximumThresholdValue || upper < m_SensibleMinimumThresholdValue || upper > m_SensibleMaximumThresholdValue) { return; } m_CurrentLowerThresholdValue = lower; m_CurrentUpperThresholdValue = upper; - if (m_ThresholdFeedbackNode.IsNotNull()) + if (nullptr != this->GetPreviewSegmentation()) { - UpdatePreview(); } } -void mitk::BinaryThresholdBaseTool::AcceptCurrentThresholdValue() -{ - CreateNewSegmentationFromThreshold(); - - RenderingManager::GetInstance()->RequestUpdateAll(); - m_ToolManager->ActivateTool(-1); -} - -void mitk::BinaryThresholdBaseTool::CancelThresholding() +void mitk::BinaryThresholdBaseTool::InitiateToolByInput() { - m_ToolManager->ActivateTool(-1); -} - -void mitk::BinaryThresholdBaseTool::SetupPreviewNode() -{ - itk::RGBPixel pixel; - pixel[0] = 0.0f; - pixel[1] = 1.0f; - pixel[2] = 0.0f; - - if (m_NodeForThresholding.IsNotNull()) + const auto referenceImage = this->GetReferenceData(); + if (nullptr != referenceImage) { - Image::Pointer image = dynamic_cast(m_NodeForThresholding->GetData()); - Image::Pointer originalImage = dynamic_cast(m_OriginalImageNode->GetData()); - - if (image.IsNotNull()) + m_SensibleMinimumThresholdValue = std::numeric_limits::max(); + m_SensibleMaximumThresholdValue = std::numeric_limits::lowest(); + Image::StatisticsHolderPointer statistics = referenceImage->GetStatistics(); + for (unsigned int ts = 0; ts < referenceImage->GetTimeSteps(); ++ts) { - mitk::LabelSetImage::Pointer workingImage = - dynamic_cast(m_ToolManager->GetWorkingData(0)->GetData()); - - if (workingImage.IsNotNull()) - { - m_ThresholdFeedbackNode->SetData(workingImage->Clone()); - m_IsOldBinary = false; - - // Let's paint the feedback node green... - mitk::LabelSetImage::Pointer previewImage = - dynamic_cast(m_ThresholdFeedbackNode->GetData()); - - if (previewImage.IsNull()) - { - MITK_ERROR << "Cannot create helper objects."; - return; - } - - previewImage->GetActiveLabel()->SetColor(pixel); - previewImage->GetActiveLabelSet()->UpdateLookupTable(previewImage->GetActiveLabel()->GetValue()); - } - else - { - mitk::Image::Pointer workingImageBin = dynamic_cast(m_ToolManager->GetWorkingData(0)->GetData()); - if (workingImageBin) - { - m_ThresholdFeedbackNode->SetData(workingImageBin->Clone()); - m_IsOldBinary = true; - } - else - m_ThresholdFeedbackNode->SetData(mitk::Image::New()); - } - - m_ThresholdFeedbackNode->SetColor(pixel); - m_ThresholdFeedbackNode->SetOpacity(0.5); - - int layer(50); - m_NodeForThresholding->GetIntProperty("layer", layer); - m_ThresholdFeedbackNode->SetIntProperty("layer", layer + 1); - - if (DataStorage *ds = m_ToolManager->GetDataStorage()) - { - if (!ds->Exists(m_ThresholdFeedbackNode)) - ds->Add(m_ThresholdFeedbackNode, m_OriginalImageNode); - } - - if (image.GetPointer() == originalImage.GetPointer()) - { - m_SensibleMinimumThresholdValue = std::numeric_limits::max(); - m_SensibleMaximumThresholdValue = std::numeric_limits::lowest(); - Image::StatisticsHolderPointer statistics = originalImage->GetStatistics(); - for (unsigned int ts = 0; ts < originalImage->GetTimeSteps(); ++ts) - { - m_SensibleMinimumThresholdValue = std::min(m_SensibleMinimumThresholdValue, static_cast(statistics->GetScalarValueMin())); - m_SensibleMaximumThresholdValue = std::max(m_SensibleMaximumThresholdValue, static_cast(statistics->GetScalarValueMax())); - } - } - - if (m_LockedUpperThreshold) - { - m_CurrentLowerThresholdValue = (m_SensibleMaximumThresholdValue + m_SensibleMinimumThresholdValue) / 2.0; - m_CurrentUpperThresholdValue = m_SensibleMaximumThresholdValue; - } - else - { - double range = m_SensibleMaximumThresholdValue - m_SensibleMinimumThresholdValue; - m_CurrentLowerThresholdValue = m_SensibleMinimumThresholdValue + range / 3.0; - m_CurrentUpperThresholdValue = m_SensibleMinimumThresholdValue + 2 * range / 3.0; - } - - bool isFloatImage = false; - if ((originalImage->GetPixelType().GetPixelType() == itk::ImageIOBase::SCALAR) && - (originalImage->GetPixelType().GetComponentType() == itk::ImageIOBase::FLOAT || - originalImage->GetPixelType().GetComponentType() == itk::ImageIOBase::DOUBLE)) - { - isFloatImage = true; - } - - IntervalBordersChanged.Send(m_SensibleMinimumThresholdValue, m_SensibleMaximumThresholdValue, isFloatImage); - ThresholdingValuesChanged.Send(m_CurrentLowerThresholdValue, m_CurrentUpperThresholdValue); + m_SensibleMinimumThresholdValue = std::min(m_SensibleMinimumThresholdValue, static_cast(statistics->GetScalarValueMin())); + m_SensibleMaximumThresholdValue = std::max(m_SensibleMaximumThresholdValue, static_cast(statistics->GetScalarValueMax())); } - } -} -template -static void ITKSetVolume(const itk::Image *originalImage, - mitk::Image *segmentation, - unsigned int timeStep) -{ - auto constPixelContainer = originalImage->GetPixelContainer(); - //have to make a const cast because itk::PixelContainer does not provide a const correct access :( - auto pixelContainer = const_cast::PixelContainer*>(constPixelContainer); - - segmentation->SetVolume((void *)pixelContainer->GetBufferPointer(), timeStep); -} - -void mitk::BinaryThresholdBaseTool::TransferImageAtTimeStep(const Image* sourceImage, Image* destinationImage, const TimeStepType timeStep) -{ - try - { - Image::ConstPointer image3D = this->Get3DImage(sourceImage, timeStep); - - if (image3D->GetDimension() == 2) + if (m_LockedUpperThreshold) { - AccessFixedDimensionByItk_2( - image3D, ITKSetVolume, 2, destinationImage, timeStep); + m_CurrentLowerThresholdValue = (m_SensibleMaximumThresholdValue + m_SensibleMinimumThresholdValue) / 2.0; + m_CurrentUpperThresholdValue = m_SensibleMaximumThresholdValue; } else { - AccessFixedDimensionByItk_2( - image3D, ITKSetVolume, 3, destinationImage, timeStep); + double range = m_SensibleMaximumThresholdValue - m_SensibleMinimumThresholdValue; + m_CurrentLowerThresholdValue = m_SensibleMinimumThresholdValue + range / 3.0; + m_CurrentUpperThresholdValue = m_SensibleMinimumThresholdValue + 2 * range / 3.0; } - } - catch (...) - { - Tool::ErrorMessage("Error accessing single time steps of the original image. Cannot create segmentation."); - } -} -void mitk::BinaryThresholdBaseTool::CreateNewSegmentationFromThreshold() -{ - if (m_NodeForThresholding.IsNotNull() && m_ThresholdFeedbackNode.IsNotNull()) - { - Image::Pointer feedBackImage = dynamic_cast(m_ThresholdFeedbackNode->GetData()); - if (feedBackImage.IsNotNull()) + bool isFloatImage = false; + if ((referenceImage->GetPixelType().GetPixelType() == itk::ImageIOBase::SCALAR) && + (referenceImage->GetPixelType().GetComponentType() == itk::ImageIOBase::FLOAT || + referenceImage->GetPixelType().GetComponentType() == itk::ImageIOBase::DOUBLE)) { - // create a new image of the same dimensions and smallest possible pixel type - DataNode::Pointer emptySegmentationNode = GetTargetSegmentationNode(); - - if (emptySegmentationNode) - { - const auto timePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); - auto emptySegmentation = dynamic_cast(emptySegmentationNode->GetData()); - - // actually perform a thresholding - // REMARK: the following code in this scope assumes that feedBackImage and emptySegmentationImage - // are clones of the working image (segmentation provided to the tool). Therefor the have the same - // time geometry. - if (feedBackImage->GetTimeSteps() != emptySegmentation->GetTimeSteps()) - { - mitkThrow() << "Cannot performe threshold. Internal tool state is invalid." - << " Preview segmentation and segmentation result image have different time geometries."; - } - - if (m_CreateAllTimeSteps) - { - for (unsigned int timeStep = 0; timeStep < feedBackImage->GetTimeSteps(); ++timeStep) - { - TransferImageAtTimeStep(feedBackImage, emptySegmentation, timeStep); - } - } - else - { - const auto timeStep = emptySegmentation->GetTimeGeometry()->TimePointToTimeStep(timePoint); - TransferImageAtTimeStep(feedBackImage, emptySegmentation, timeStep); - } - - // since we are maybe working on a smaller image, pad it to the size of the original image - if (m_OriginalImageNode.GetPointer() != m_NodeForThresholding.GetPointer()) - { - mitk::PadImageFilter::Pointer padFilter = mitk::PadImageFilter::New(); - - padFilter->SetInput(0, emptySegmentation); - padFilter->SetInput(1, dynamic_cast(m_OriginalImageNode->GetData())); - padFilter->SetBinaryFilter(true); - padFilter->SetUpperThreshold(1); - padFilter->SetLowerThreshold(1); - padFilter->Update(); - - emptySegmentationNode->SetData(padFilter->GetOutput()); - } - - m_ToolManager->SetWorkingData(emptySegmentationNode); - m_ToolManager->GetWorkingData(0)->Modified(); - } + isFloatImage = true; } - } -} - -void mitk::BinaryThresholdBaseTool::OnRoiDataChanged() -{ - mitk::DataNode::Pointer node = m_ToolManager->GetRoiData(0); - - if (node.IsNotNull()) - { - mitk::MaskAndCutRoiImageFilter::Pointer roiFilter = mitk::MaskAndCutRoiImageFilter::New(); - mitk::Image::Pointer image = dynamic_cast(m_NodeForThresholding->GetData()); - - if (image.IsNull()) - return; - roiFilter->SetInput(image); - roiFilter->SetRegionOfInterest(node->GetData()); - roiFilter->Update(); - - mitk::DataNode::Pointer tmpNode = mitk::DataNode::New(); - tmpNode->SetData(roiFilter->GetOutput()); - - m_SensibleMinimumThresholdValue = static_cast(roiFilter->GetMinValue()); - m_SensibleMaximumThresholdValue = static_cast(roiFilter->GetMaxValue()); - - m_NodeForThresholding = tmpNode; - } - else - m_NodeForThresholding = m_OriginalImageNode; - - this->SetupPreviewNode(); - this->UpdatePreview(); -} - -void mitk::BinaryThresholdBaseTool::OnTimePointChanged() -{ - if (m_ThresholdFeedbackNode.IsNotNull() && m_NodeForThresholding.IsNotNull()) - { - if (m_ThresholdFeedbackNode->GetData()->GetTimeSteps() == 1) - { - this->UpdatePreview(); - } + IntervalBordersChanged.Send(m_SensibleMinimumThresholdValue, m_SensibleMaximumThresholdValue, isFloatImage); + ThresholdingValuesChanged.Send(m_CurrentLowerThresholdValue, m_CurrentUpperThresholdValue); } } template static void ITKThresholding(const itk::Image *originalImage, mitk::Image *segmentation, double lower, double upper, unsigned int timeStep) { typedef itk::Image ImageType; typedef itk::Image SegmentationType; typedef itk::BinaryThresholdImageFilter ThresholdFilterType; typename ThresholdFilterType::Pointer filter = ThresholdFilterType::New(); filter->SetInput(originalImage); filter->SetLowerThreshold(lower); filter->SetUpperThreshold(upper); filter->SetInsideValue(1); filter->SetOutsideValue(0); filter->Update(); segmentation->SetVolume((void *)(filter->GetOutput()->GetPixelContainer()->GetBufferPointer()), timeStep); } -template -static void ITKThresholdingOldBinary(const itk::Image *originalImage, - mitk::Image *segmentation, - double lower, - double upper, - unsigned int timeStep) +void mitk::BinaryThresholdBaseTool::DoUpdatePreview(const Image* inputAtTimeStep, Image* previewImage, TimeStepType timeStep) { - typedef itk::Image ImageType; - typedef itk::Image SegmentationType; - typedef itk::BinaryThresholdImageFilter ThresholdFilterType; - - typename ThresholdFilterType::Pointer filter = ThresholdFilterType::New(); - filter->SetInput(originalImage); - filter->SetLowerThreshold(lower); - filter->SetUpperThreshold(upper); - filter->SetInsideValue(1); - filter->SetOutsideValue(0); - filter->Update(); - - segmentation->SetVolume((void *)(filter->GetOutput()->GetPixelContainer()->GetBufferPointer()), timeStep); -} - -void mitk::BinaryThresholdBaseTool::UpdatePreview() -{ - mitk::Image::Pointer thresholdImage = dynamic_cast(m_NodeForThresholding->GetData()); - mitk::Image::Pointer previewImage = dynamic_cast(m_ThresholdFeedbackNode->GetData()); - if (thresholdImage && previewImage) + if (nullptr != inputAtTimeStep && nullptr != previewImage) { - if (previewImage->GetTimeSteps() > 1) - { - for (unsigned int timeStep = 0; timeStep < thresholdImage->GetTimeSteps(); ++timeStep) - { - auto feedBackImage3D = this->Get3DImage(thresholdImage, timeStep); - - if (m_IsOldBinary) - { - AccessByItk_n(feedBackImage3D, - ITKThresholdingOldBinary, - (previewImage, m_CurrentLowerThresholdValue, m_CurrentUpperThresholdValue, timeStep)); - } - else - { - AccessByItk_n(feedBackImage3D, - ITKThresholding, - (previewImage, m_CurrentLowerThresholdValue, m_CurrentUpperThresholdValue, timeStep)); - } - } - } - else - { - const auto timePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); - auto feedBackImage3D = this->Get3DImageByTimePoint(thresholdImage, timePoint); - auto timeStep = previewImage->GetTimeGeometry()->TimePointToTimeStep(timePoint); - - if (m_IsOldBinary) - { - AccessByItk_n(feedBackImage3D, - ITKThresholdingOldBinary, - (previewImage, m_CurrentLowerThresholdValue, m_CurrentUpperThresholdValue, timeStep)); - } - else - { - AccessByItk_n(feedBackImage3D, - ITKThresholding, - (previewImage, m_CurrentLowerThresholdValue, m_CurrentUpperThresholdValue, timeStep)); - } - } - RenderingManager::GetInstance()->RequestUpdateAll(); + AccessByItk_n(inputAtTimeStep, + ITKThresholding, + (previewImage, m_CurrentLowerThresholdValue, m_CurrentUpperThresholdValue, timeStep)); } } diff --git a/Modules/Segmentation/Interactions/mitkBinaryThresholdBaseTool.h b/Modules/Segmentation/Interactions/mitkBinaryThresholdBaseTool.h index dece9ae498..cdc15f18be 100644 --- a/Modules/Segmentation/Interactions/mitkBinaryThresholdBaseTool.h +++ b/Modules/Segmentation/Interactions/mitkBinaryThresholdBaseTool.h @@ -1,98 +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 mitkBinaryThresholdBaseTool_h_Included #define mitkBinaryThresholdBaseTool_h_Included -#include "mitkAutoSegmentationTool.h" +#include "mitkAutoSegmentationWithPreviewTool.h" #include "mitkCommon.h" #include "mitkDataNode.h" #include #include #include namespace mitk { /** \brief Base class for binary threshold tools. \ingroup ToolManagerEtAl \sa mitk::Tool \sa QmitkInteractiveSegmentation */ - class MITKSEGMENTATION_EXPORT BinaryThresholdBaseTool : public AutoSegmentationTool + class MITKSEGMENTATION_EXPORT BinaryThresholdBaseTool : public AutoSegmentationWithPreviewTool { public: Message3 IntervalBordersChanged; Message2 ThresholdingValuesChanged; - mitkClassMacro(BinaryThresholdBaseTool, AutoSegmentationTool); - - void Activated() override; - void Deactivated() override; + mitkClassMacro(BinaryThresholdBaseTool, AutoSegmentationWithPreviewTool); virtual void SetThresholdValues(double lower, double upper); - virtual void AcceptCurrentThresholdValue(); - virtual void CancelThresholding(); - - itkSetMacro(CreateAllTimeSteps, bool); - itkGetMacro(CreateAllTimeSteps, bool); - itkBooleanMacro(CreateAllTimeSteps); - protected: BinaryThresholdBaseTool(); // purposely hidden ~BinaryThresholdBaseTool() override; itkSetMacro(LockedUpperThreshold, bool); itkGetMacro(LockedUpperThreshold, bool); itkBooleanMacro(LockedUpperThreshold); itkGetMacro(SensibleMinimumThresholdValue, ScalarType); itkGetMacro(SensibleMaximumThresholdValue, ScalarType); - private: - void SetupPreviewNode(); - - void TransferImageAtTimeStep(const Image* sourceImage, Image* destinationImage, const TimeStepType timeStep); - - void CreateNewSegmentationFromThreshold(); - - void OnRoiDataChanged(); - void OnTimePointChanged(); - - void UpdatePreview(); - - DataNode::Pointer m_ThresholdFeedbackNode; - DataNode::Pointer m_OriginalImageNode; - DataNode::Pointer m_NodeForThresholding; + void InitiateToolByInput() override; + void DoUpdatePreview(const Image* inputAtTimeStep, Image* previewImage, TimeStepType timeStep) override; + private: ScalarType m_SensibleMinimumThresholdValue; ScalarType m_SensibleMaximumThresholdValue; ScalarType m_CurrentLowerThresholdValue; ScalarType m_CurrentUpperThresholdValue; - bool m_IsOldBinary = false; - - /** Indicates if Accepting the threshold should transfer/create the segmentations - of all time steps (true) or only of the currently selected timepoint (false).*/ - bool m_CreateAllTimeSteps = false; - /** Indicates if the tool should behave like a single threshold tool (true) or like a upper/lower threshold tool (false)*/ bool m_LockedUpperThreshold = false; }; } // namespace #endif diff --git a/Modules/Segmentation/Interactions/mitkFastMarchingTool3D.cpp b/Modules/Segmentation/Interactions/mitkFastMarchingTool3D.cpp index 4e5191b32f..48a1301bf6 100644 --- a/Modules/Segmentation/Interactions/mitkFastMarchingTool3D.cpp +++ b/Modules/Segmentation/Interactions/mitkFastMarchingTool3D.cpp @@ -1,460 +1,334 @@ /*============================================================================ 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 "mitkFastMarchingTool3D.h" #include "mitkToolManager.h" #include "mitkBaseRenderer.h" #include "mitkInteractionConst.h" #include "mitkRenderingManager.h" -#include "itkOrImageFilter.h" -#include "mitkImageCast.h" -#include "mitkImageTimeSelector.h" +#include "mitkImageAccessByItk.h" + +// itk filter +#include "itkBinaryThresholdImageFilter.h" +#include "itkCurvatureAnisotropicDiffusionImageFilter.h" +#include "itkGradientMagnitudeRecursiveGaussianImageFilter.h" +#include "itkSigmoidImageFilter.h" // us #include #include #include #include namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, FastMarchingTool3D, "FastMarching3D tool"); } mitk::FastMarchingTool3D::FastMarchingTool3D() - : /*FeedbackContourTool*/ AutoSegmentationTool(), - m_NeedUpdate(true), - m_CurrentTimeStep(0), + : AutoSegmentationWithPreviewTool(), m_LowerThreshold(0), m_UpperThreshold(200), m_StoppingValue(100), m_Sigma(1.0), m_Alpha(-0.5), m_Beta(3.0), m_PointSetAddObserverTag(0), m_PointSetRemoveObserverTag(0) { } mitk::FastMarchingTool3D::~FastMarchingTool3D() { } -bool mitk::FastMarchingTool3D::CanHandle(BaseData *referenceData) const +bool mitk::FastMarchingTool3D::CanHandle(const BaseData* referenceData, const BaseData* workingData) const { + if(!Superclass::CanHandle(referenceData, workingData)) + return false; + if (referenceData == nullptr) return false; - auto *image = dynamic_cast(referenceData); + auto *image = dynamic_cast(referenceData); if (image == nullptr) return false; if (image->GetDimension() < 3) return false; return true; } const char **mitk::FastMarchingTool3D::GetXPM() const { return nullptr; // mitkFastMarchingTool3D_xpm; } us::ModuleResource mitk::FastMarchingTool3D::GetIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); us::ModuleResource resource = module->GetResource("FastMarching_48x48.png"); return resource; } const char *mitk::FastMarchingTool3D::GetName() const { return "Fast Marching 3D"; } void mitk::FastMarchingTool3D::SetUpperThreshold(double value) { m_UpperThreshold = value / 10.0; - m_ThresholdFilter->SetUpperThreshold(m_UpperThreshold); - m_NeedUpdate = true; } void mitk::FastMarchingTool3D::SetLowerThreshold(double value) { m_LowerThreshold = value / 10.0; - m_ThresholdFilter->SetLowerThreshold(m_LowerThreshold); - m_NeedUpdate = true; } void mitk::FastMarchingTool3D::SetBeta(double value) { if (m_Beta != value) { m_Beta = value; - m_SigmoidFilter->SetBeta(m_Beta); - m_NeedUpdate = true; } } void mitk::FastMarchingTool3D::SetSigma(double value) { if (m_Sigma != value) { if (value > 0.0) { m_Sigma = value; - m_GradientMagnitudeFilter->SetSigma(m_Sigma); - m_NeedUpdate = true; } } } void mitk::FastMarchingTool3D::SetAlpha(double value) { if (m_Alpha != value) { m_Alpha = value; - m_SigmoidFilter->SetAlpha(m_Alpha); - m_NeedUpdate = true; } } void mitk::FastMarchingTool3D::SetStoppingValue(double value) { if (m_StoppingValue != value) { m_StoppingValue = value; - m_FastMarchingFilter->SetStoppingValue(m_StoppingValue); - m_NeedUpdate = true; } } void mitk::FastMarchingTool3D::Activated() { Superclass::Activated(); - m_ResultImageNode = mitk::DataNode::New(); - m_ResultImageNode->SetName("FastMarching_Preview"); - m_ResultImageNode->SetBoolProperty("helper object", true); - m_ResultImageNode->SetColor(0.0, 1.0, 0.0); - m_ResultImageNode->SetVisibility(true); - m_ToolManager->GetDataStorage()->Add(this->m_ResultImageNode, m_ToolManager->GetReferenceData(0)); - m_SeedsAsPointSet = mitk::PointSet::New(); m_SeedsAsPointSetNode = mitk::DataNode::New(); m_SeedsAsPointSetNode->SetData(m_SeedsAsPointSet); m_SeedsAsPointSetNode->SetName("3D_FastMarching_PointSet"); m_SeedsAsPointSetNode->SetBoolProperty("helper object", true); m_SeedsAsPointSetNode->SetColor(0.0, 1.0, 0.0); m_SeedsAsPointSetNode->SetVisibility(true); // Create PointSetData Interactor m_SeedPointInteractor = mitk::PointSetDataInteractor::New(); // Load the according state machine for regular point set interaction m_SeedPointInteractor->LoadStateMachine("PointSet.xml"); // Set the configuration file that defines the triggers for the transitions m_SeedPointInteractor->SetEventConfig("PointSetConfig.xml"); // set the DataNode (which already is added to the DataStorage m_SeedPointInteractor->SetDataNode(m_SeedsAsPointSetNode); - m_ReferenceImageAsITK = InternalImageType::New(); - - m_ProgressCommand = mitk::ToolCommand::New(); - - m_ThresholdFilter = ThresholdingFilterType::New(); - m_ThresholdFilter->SetLowerThreshold(m_LowerThreshold); - m_ThresholdFilter->SetUpperThreshold(m_UpperThreshold); - m_ThresholdFilter->SetOutsideValue(0); - m_ThresholdFilter->SetInsideValue(1.0); - - m_SmoothFilter = SmoothingFilterType::New(); - m_SmoothFilter->AddObserver(itk::ProgressEvent(), m_ProgressCommand); - m_SmoothFilter->SetTimeStep(0.05); - m_SmoothFilter->SetNumberOfIterations(2); - m_SmoothFilter->SetConductanceParameter(9.0); - - m_GradientMagnitudeFilter = GradientFilterType::New(); - m_GradientMagnitudeFilter->AddObserver(itk::ProgressEvent(), m_ProgressCommand); - m_GradientMagnitudeFilter->SetSigma(m_Sigma); - - m_SigmoidFilter = SigmoidFilterType::New(); - m_SigmoidFilter->AddObserver(itk::ProgressEvent(), m_ProgressCommand); - m_SigmoidFilter->SetAlpha(m_Alpha); - m_SigmoidFilter->SetBeta(m_Beta); - m_SigmoidFilter->SetOutputMinimum(0.0); - m_SigmoidFilter->SetOutputMaximum(1.0); - - m_FastMarchingFilter = FastMarchingFilterType::New(); - m_FastMarchingFilter->AddObserver(itk::ProgressEvent(), m_ProgressCommand); - m_FastMarchingFilter->SetStoppingValue(m_StoppingValue); + m_ToolManager->GetDataStorage()->Add(m_SeedsAsPointSetNode, m_ToolManager->GetWorkingData(0)); m_SeedContainer = NodeContainer::New(); m_SeedContainer->Initialize(); - m_FastMarchingFilter->SetTrialPoints(m_SeedContainer); - - // set up pipeline - m_SmoothFilter->SetInput(m_ReferenceImageAsITK); - m_GradientMagnitudeFilter->SetInput(m_SmoothFilter->GetOutput()); - m_SigmoidFilter->SetInput(m_GradientMagnitudeFilter->GetOutput()); - m_FastMarchingFilter->SetInput(m_SigmoidFilter->GetOutput()); - m_ThresholdFilter->SetInput(m_FastMarchingFilter->GetOutput()); - - m_ToolManager->GetDataStorage()->Add(m_SeedsAsPointSetNode, m_ToolManager->GetWorkingData(0)); itk::SimpleMemberCommand::Pointer pointAddedCommand = itk::SimpleMemberCommand::New(); pointAddedCommand->SetCallbackFunction(this, &mitk::FastMarchingTool3D::OnAddPoint); m_PointSetAddObserverTag = m_SeedsAsPointSet->AddObserver(mitk::PointSetAddEvent(), pointAddedCommand); itk::SimpleMemberCommand::Pointer pointRemovedCommand = itk::SimpleMemberCommand::New(); pointRemovedCommand->SetCallbackFunction(this, &mitk::FastMarchingTool3D::OnDelete); m_PointSetRemoveObserverTag = m_SeedsAsPointSet->AddObserver(mitk::PointSetRemoveEvent(), pointRemovedCommand); - - this->Initialize(); } void mitk::FastMarchingTool3D::Deactivated() { - m_ToolManager->GetDataStorage()->Remove(this->m_ResultImageNode); - m_ToolManager->GetDataStorage()->Remove(this->m_SeedsAsPointSetNode); this->ClearSeeds(); - this->m_SmoothFilter->RemoveAllObservers(); - this->m_SigmoidFilter->RemoveAllObservers(); - this->m_GradientMagnitudeFilter->RemoveAllObservers(); - this->m_FastMarchingFilter->RemoveAllObservers(); - m_ResultImageNode = nullptr; - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); - unsigned int numberOfPoints = m_SeedsAsPointSet->GetSize(); - for (unsigned int i = 0; i < numberOfPoints; ++i) - { - mitk::Point3D point = m_SeedsAsPointSet->GetPoint(i); - auto *doOp = new mitk::PointOperation(mitk::OpREMOVE, point, 0); - m_SeedsAsPointSet->ExecuteOperation(doOp); - } // Deactivate Interaction m_SeedPointInteractor->SetDataNode(nullptr); m_ToolManager->GetDataStorage()->Remove(m_SeedsAsPointSetNode); m_SeedsAsPointSetNode = nullptr; m_SeedsAsPointSet->RemoveObserver(m_PointSetAddObserverTag); m_SeedsAsPointSet->RemoveObserver(m_PointSetRemoveObserverTag); + m_SeedsAsPointSet = nullptr; Superclass::Deactivated(); } -void mitk::FastMarchingTool3D::Initialize() -{ - m_ReferenceImage = dynamic_cast(m_ToolManager->GetReferenceData(0)->GetData()); - if (m_ReferenceImage->GetTimeGeometry()->CountTimeSteps() > 1) - { - mitk::ImageTimeSelector::Pointer timeSelector = ImageTimeSelector::New(); - timeSelector->SetInput(m_ReferenceImage); - timeSelector->SetTimeNr(m_CurrentTimeStep); - timeSelector->UpdateLargestPossibleRegion(); - m_ReferenceImage = timeSelector->GetOutput(); - } - CastToItkImage(m_ReferenceImage, m_ReferenceImageAsITK); - m_SmoothFilter->SetInput(m_ReferenceImageAsITK); - m_NeedUpdate = true; -} - -void mitk::FastMarchingTool3D::ConfirmSegmentation() -{ - // combine preview image with current working segmentation - if (dynamic_cast(m_ResultImageNode->GetData())) - { - // logical or combination of preview and segmentation slice - OutputImageType::Pointer segmentationImageInITK = OutputImageType::New(); - - mitk::Image::Pointer workingImage = dynamic_cast(GetTargetSegmentationNode()->GetData()); - if (workingImage->GetTimeGeometry()->CountTimeSteps() > 1) - { - mitk::ImageTimeSelector::Pointer timeSelector = mitk::ImageTimeSelector::New(); - timeSelector->SetInput(workingImage); - timeSelector->SetTimeNr(m_CurrentTimeStep); - timeSelector->UpdateLargestPossibleRegion(); - CastToItkImage(timeSelector->GetOutput(), segmentationImageInITK); - } - else - { - CastToItkImage(workingImage, segmentationImageInITK); - } - - typedef itk::OrImageFilter OrImageFilterType; - OrImageFilterType::Pointer orFilter = OrImageFilterType::New(); - - orFilter->SetInput(0, m_ThresholdFilter->GetOutput()); - orFilter->SetInput(1, segmentationImageInITK); - orFilter->Update(); - - // set image volume in current time step from itk image - workingImage->SetVolume((void *)(m_ThresholdFilter->GetOutput()->GetPixelContainer()->GetBufferPointer()), - m_CurrentTimeStep); - this->m_ResultImageNode->SetVisibility(false); - this->ClearSeeds(); - workingImage->Modified(); - } - - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); - m_ToolManager->ActivateTool(-1); -} - void mitk::FastMarchingTool3D::OnAddPoint() { // Add a new seed point for FastMarching algorithm mitk::Point3D clickInIndex; - m_ReferenceImage->GetGeometry()->WorldToIndex(m_SeedsAsPointSet->GetPoint(m_SeedsAsPointSet->GetSize() - 1), + this->GetReferenceData()->GetGeometry()->WorldToIndex(m_SeedsAsPointSet->GetPoint(m_SeedsAsPointSet->GetSize() - 1), clickInIndex); itk::Index<3> seedPosition; seedPosition[0] = clickInIndex[0]; seedPosition[1] = clickInIndex[1]; seedPosition[2] = clickInIndex[2]; NodeType node; const double seedValue = 0.0; node.SetValue(seedValue); node.SetIndex(seedPosition); this->m_SeedContainer->InsertElement(this->m_SeedContainer->Size(), node); - m_FastMarchingFilter->Modified(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); - m_NeedUpdate = true; - - this->Update(); - - m_ReadyMessage.Send(); + this->UpdatePreview(); } void mitk::FastMarchingTool3D::OnDelete() { // delete last seed point if (!(this->m_SeedContainer->empty())) { // delete last element of seeds container this->m_SeedContainer->pop_back(); - m_FastMarchingFilter->Modified(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); - m_NeedUpdate = true; - - this->Update(); - } -} - -void mitk::FastMarchingTool3D::Update() -{ - const unsigned int progress_steps = 200; - - if (m_NeedUpdate) - { - m_ProgressCommand->AddStepsToDo(progress_steps); - - // remove interaction with poinset while updating - m_SeedPointInteractor->SetDataNode(nullptr); - CurrentlyBusy.Send(true); - try - { - m_ThresholdFilter->Update(); - } - catch (itk::ExceptionObject &excep) - { - MITK_ERROR << "Exception caught: " << excep.GetDescription(); - - m_ProgressCommand->SetProgress(progress_steps); - CurrentlyBusy.Send(false); - - std::string msg = excep.GetDescription(); - ErrorMessage.Send(msg); - - return; - } - m_ProgressCommand->SetProgress(progress_steps); - CurrentlyBusy.Send(false); - - // make output visible - mitk::Image::Pointer result = mitk::Image::New(); - CastToMitkImage(m_ThresholdFilter->GetOutput(), result); - result->GetGeometry()->SetOrigin(m_ReferenceImage->GetGeometry()->GetOrigin()); - result->GetGeometry()->SetIndexToWorldTransform(m_ReferenceImage->GetGeometry()->GetIndexToWorldTransform()); - m_ResultImageNode->SetData(result); - m_ResultImageNode->SetVisibility(true); - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); - - // add interaction with poinset again - m_SeedPointInteractor->SetDataNode(m_SeedsAsPointSetNode); + this->UpdatePreview(); } } void mitk::FastMarchingTool3D::ClearSeeds() { // clear seeds for FastMarching as well as the PointSet for visualization if (this->m_SeedContainer.IsNotNull()) this->m_SeedContainer->Initialize(); if (this->m_SeedsAsPointSet.IsNotNull()) { // remove observers from current pointset m_SeedsAsPointSet->RemoveObserver(m_PointSetAddObserverTag); m_SeedsAsPointSet->RemoveObserver(m_PointSetRemoveObserverTag); // renew pointset this->m_SeedsAsPointSet = mitk::PointSet::New(); this->m_SeedsAsPointSetNode->SetData(this->m_SeedsAsPointSet); m_SeedsAsPointSetNode->SetName("Seeds_Preview"); m_SeedsAsPointSetNode->SetBoolProperty("helper object", true); m_SeedsAsPointSetNode->SetColor(0.0, 1.0, 0.0); m_SeedsAsPointSetNode->SetVisibility(true); // add callback function for adding and removing points itk::SimpleMemberCommand::Pointer pointAddedCommand = itk::SimpleMemberCommand::New(); pointAddedCommand->SetCallbackFunction(this, &mitk::FastMarchingTool3D::OnAddPoint); m_PointSetAddObserverTag = m_SeedsAsPointSet->AddObserver(mitk::PointSetAddEvent(), pointAddedCommand); itk::SimpleMemberCommand::Pointer pointRemovedCommand = itk::SimpleMemberCommand::New(); pointRemovedCommand->SetCallbackFunction(this, &mitk::FastMarchingTool3D::OnDelete); m_PointSetRemoveObserverTag = m_SeedsAsPointSet->AddObserver(mitk::PointSetRemoveEvent(), pointRemovedCommand); } +} - if (this->m_FastMarchingFilter.IsNotNull()) - m_FastMarchingFilter->Modified(); +template +void mitk::FastMarchingTool3D::DoITKFastMarching(const itk::Image* inputImage, + mitk::Image* segmentation, unsigned int timeStep) +{ + typedef itk::Image InputImageType; + + /* typedefs for itk pipeline */ + + typedef mitk::Tool::DefaultSegmentationDataType OutputPixelType; + typedef itk::Image OutputImageType; + + typedef itk::CurvatureAnisotropicDiffusionImageFilter SmoothingFilterType; + typedef itk::GradientMagnitudeRecursiveGaussianImageFilter GradientFilterType; + typedef itk::SigmoidImageFilter SigmoidFilterType; + typedef itk::BinaryThresholdImageFilter ThresholdingFilterType; + + auto smoothFilter = SmoothingFilterType::New(); + smoothFilter->AddObserver(itk::ProgressEvent(), m_ProgressCommand); + smoothFilter->SetTimeStep(0.05); + smoothFilter->SetNumberOfIterations(2); + smoothFilter->SetConductanceParameter(9.0); + + auto gradientMagnitudeFilter = GradientFilterType::New(); + gradientMagnitudeFilter->AddObserver(itk::ProgressEvent(), m_ProgressCommand); + gradientMagnitudeFilter->SetSigma(m_Sigma); + + auto sigmoidFilter = SigmoidFilterType::New(); + sigmoidFilter->AddObserver(itk::ProgressEvent(), m_ProgressCommand); + sigmoidFilter->SetAlpha(m_Alpha); + sigmoidFilter->SetBeta(m_Beta); + sigmoidFilter->SetOutputMinimum(0.0); + sigmoidFilter->SetOutputMaximum(1.0); + + auto fastMarchingFilter = FastMarchingFilterType::New(); + fastMarchingFilter->AddObserver(itk::ProgressEvent(), m_ProgressCommand); + fastMarchingFilter->SetStoppingValue(m_StoppingValue); + fastMarchingFilter->SetTrialPoints(m_SeedContainer); + + auto thresholdFilter = ThresholdingFilterType::New(); + thresholdFilter->SetLowerThreshold(m_LowerThreshold); + thresholdFilter->SetUpperThreshold(m_UpperThreshold); + thresholdFilter->SetOutsideValue(0); + thresholdFilter->SetInsideValue(1.0); - this->m_NeedUpdate = true; + // set up pipeline + smoothFilter->SetInput(inputImage); + gradientMagnitudeFilter->SetInput(smoothFilter->GetOutput()); + sigmoidFilter->SetInput(gradientMagnitudeFilter->GetOutput()); + fastMarchingFilter->SetInput(sigmoidFilter->GetOutput()); + thresholdFilter->SetInput(fastMarchingFilter->GetOutput()); + thresholdFilter->Update(); + + segmentation->SetVolume((void*)(thresholdFilter->GetOutput()->GetPixelContainer()->GetBufferPointer()), timeStep); } -void mitk::FastMarchingTool3D::Reset() +void mitk::FastMarchingTool3D::UpdatePrepare() { - // clear all seeds and preview empty result - this->ClearSeeds(); - - m_ResultImageNode->SetVisibility(false); + // remove interaction with poinset while updating + if (m_SeedPointInteractor.IsNotNull()) + m_SeedPointInteractor->SetDataNode(nullptr); +} - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); +void mitk::FastMarchingTool3D::UpdateCleanUp() +{ + // add interaction with poinset again + if (m_SeedPointInteractor.IsNotNull()) + m_SeedPointInteractor->SetDataNode(m_SeedsAsPointSetNode); } -void mitk::FastMarchingTool3D::SetCurrentTimeStep(int t) +void mitk::FastMarchingTool3D::DoUpdatePreview(const Image* inputAtTimeStep, Image* previewImage, TimeStepType timeStep) { - if (m_CurrentTimeStep != t) + if (nullptr != inputAtTimeStep && nullptr != previewImage && m_SeedContainer.IsNotNull() && !m_SeedContainer->empty()) { - m_CurrentTimeStep = t; - - this->Initialize(); + AccessFixedDimensionByItk_n(inputAtTimeStep, DoITKFastMarching, 3, (previewImage, timeStep)); } } diff --git a/Modules/Segmentation/Interactions/mitkFastMarchingTool3D.h b/Modules/Segmentation/Interactions/mitkFastMarchingTool3D.h index eeefc20b4f..652e0d550b 100644 --- a/Modules/Segmentation/Interactions/mitkFastMarchingTool3D.h +++ b/Modules/Segmentation/Interactions/mitkFastMarchingTool3D.h @@ -1,164 +1,124 @@ /*============================================================================ 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 mitkFastMarchingTool3D_h_Included #define mitkFastMarchingTool3D_h_Included -#include "mitkAutoSegmentationTool.h" +#include "mitkAutoSegmentationWithPreviewTool.h" #include "mitkDataNode.h" #include "mitkPointSet.h" #include "mitkPointSetDataInteractor.h" #include "mitkToolCommand.h" -#include - -#include "mitkMessage.h" #include "itkImage.h" - -// itk filter -#include "itkBinaryThresholdImageFilter.h" -#include "itkCurvatureAnisotropicDiffusionImageFilter.h" #include "itkFastMarchingImageFilter.h" -#include "itkGradientMagnitudeRecursiveGaussianImageFilter.h" -#include "itkSigmoidImageFilter.h" + +#include namespace us { class ModuleResource; } namespace mitk { /** \brief FastMarching semgentation tool. The segmentation is done by setting one or more seed points on the image and adapting the time range and threshold. The pipeline is: Smoothing->GradientMagnitude->SigmoidFunction->FastMarching->Threshold The resulting binary image is seen as a segmentation of an object. For detailed documentation see ITK Software Guide section 9.3.1 Fast Marching Segmentation. */ - class MITKSEGMENTATION_EXPORT FastMarchingTool3D : public AutoSegmentationTool + class MITKSEGMENTATION_EXPORT FastMarchingTool3D : public AutoSegmentationWithPreviewTool { - mitkNewMessageMacro(Ready); - public: - mitkClassMacro(FastMarchingTool3D, AutoSegmentationTool); + mitkClassMacro(FastMarchingTool3D, AutoSegmentationWithPreviewTool); itkFactorylessNewMacro(Self); itkCloneMacro(Self); - /* typedefs for itk pipeline */ - typedef float InternalPixelType; - typedef itk::Image InternalImageType; - typedef mitk::Tool::DefaultSegmentationDataType OutputPixelType; - typedef itk::Image OutputImageType; - - typedef itk::BinaryThresholdImageFilter ThresholdingFilterType; - typedef itk::CurvatureAnisotropicDiffusionImageFilter SmoothingFilterType; - typedef itk::GradientMagnitudeRecursiveGaussianImageFilter GradientFilterType; - typedef itk::SigmoidImageFilter SigmoidFilterType; - typedef itk::FastMarchingImageFilter FastMarchingFilterType; - typedef FastMarchingFilterType::NodeContainer NodeContainer; - typedef FastMarchingFilterType::NodeType NodeType; - - bool CanHandle(BaseData *referenceData) const override; + bool CanHandle(const BaseData* referenceData, const BaseData* workingData) const override; /* icon stuff */ const char **GetXPM() const override; const char *GetName() const override; us::ModuleResource GetIconResource() const override; + void Activated() override; + void Deactivated() override; + /// \brief Set parameter used in Threshold filter. void SetUpperThreshold(double); /// \brief Set parameter used in Threshold filter. void SetLowerThreshold(double); /// \brief Set parameter used in Fast Marching filter. void SetStoppingValue(double); /// \brief Set parameter used in Gradient Magnitude filter. void SetSigma(double); /// \brief Set parameter used in Fast Marching filter. void SetAlpha(double); /// \brief Set parameter used in Fast Marching filter. void SetBeta(double); - /// \brief Adds the feedback image to the current working image. - virtual void ConfirmSegmentation(); - - /// \brief Set the working time step. - virtual void SetCurrentTimeStep(int t); - /// \brief Clear all seed points. void ClearSeeds(); - /// \brief Updates the itk pipeline and shows the result of FastMarching. - void Update(); - protected: FastMarchingTool3D(); ~FastMarchingTool3D() override; - void Activated() override; - void Deactivated() override; - virtual void Initialize(); - /// \brief Add point action of StateMachine pattern virtual void OnAddPoint(); /// \brief Delete action of StateMachine pattern virtual void OnDelete(); - /// \brief Reset all relevant inputs of the itk pipeline. - void Reset(); - - mitk::ToolCommand::Pointer m_ProgressCommand; - - Image::Pointer m_ReferenceImage; - - bool m_NeedUpdate; + void UpdatePrepare() override; + void UpdateCleanUp() override; + void DoUpdatePreview(const Image* inputAtTimeStep, Image* previewImage, TimeStepType timeStep) override; - int m_CurrentTimeStep; + template + void DoITKFastMarching(const itk::Image* inputImage, + mitk::Image* segmentation, unsigned int timeStep); float m_LowerThreshold; // used in Threshold filter float m_UpperThreshold; // used in Threshold filter float m_StoppingValue; // used in Fast Marching filter float m_Sigma; // used in GradientMagnitude filter float m_Alpha; // used in Sigmoid filter float m_Beta; // used in Sigmoid filter - NodeContainer::Pointer m_SeedContainer; // seed points for FastMarching - - InternalImageType::Pointer m_ReferenceImageAsITK; // the reference image as itk::Image + typedef float InternalPixelType; + typedef itk::Image InternalImageType; + typedef itk::FastMarchingImageFilter FastMarchingFilterType; + typedef FastMarchingFilterType::NodeContainer NodeContainer; + typedef FastMarchingFilterType::NodeType NodeType; - mitk::DataNode::Pointer m_ResultImageNode; // holds the result as a preview image + NodeContainer::Pointer m_SeedContainer; // seed points for FastMarching mitk::DataNode::Pointer m_SeedsAsPointSetNode; // used to visualize the seed points mitk::PointSet::Pointer m_SeedsAsPointSet; mitk::PointSetDataInteractor::Pointer m_SeedPointInteractor; unsigned int m_PointSetAddObserverTag; unsigned int m_PointSetRemoveObserverTag; - - ThresholdingFilterType::Pointer m_ThresholdFilter; - SmoothingFilterType::Pointer m_SmoothFilter; - GradientFilterType::Pointer m_GradientMagnitudeFilter; - SigmoidFilterType::Pointer m_SigmoidFilter; - FastMarchingFilterType::Pointer m_FastMarchingFilter; }; } // namespace #endif diff --git a/Modules/Segmentation/Interactions/mitkOtsuTool3D.cpp b/Modules/Segmentation/Interactions/mitkOtsuTool3D.cpp index 917451a48c..9a5f4cc388 100644 --- a/Modules/Segmentation/Interactions/mitkOtsuTool3D.cpp +++ b/Modules/Segmentation/Interactions/mitkOtsuTool3D.cpp @@ -1,277 +1,253 @@ /*============================================================================ 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. ============================================================================*/ // MITK #include "mitkOtsuTool3D.h" #include "mitkImageAccessByItk.h" #include "mitkLabelSetImage.h" #include "mitkOtsuSegmentationFilter.h" #include "mitkRenderingManager.h" #include "mitkToolManager.h" #include #include #include #include #include #include // ITK #include #include #include // us #include #include #include #include #include namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, OtsuTool3D, "Otsu Segmentation"); } -mitk::OtsuTool3D::OtsuTool3D() +mitk::OtsuTool3D::OtsuTool3D() : AutoSegmentationWithPreviewTool(true) { } mitk::OtsuTool3D::~OtsuTool3D() { } +void mitk::OtsuTool3D::SetSelectedRegions(const SelectedRegionVectorType& regions) +{ + if (m_SelectedRegions != regions) + { + m_SelectedRegions = regions; + //Note: we do not call this->Modified() on puprose. Reason: changing the + //selected regions should not force to run otsu filter in DoUpdatePreview due to changed MTime. + } +} + +mitk::OtsuTool3D::SelectedRegionVectorType mitk::OtsuTool3D::GetSelectedRegions() const +{ + return this->m_SelectedRegions; +} + void mitk::OtsuTool3D::Activated() { Superclass::Activated(); - if (m_ToolManager) - { - m_OriginalImage = dynamic_cast(m_ToolManager->GetReferenceData(0)->GetData()); - - m_BinaryPreviewNode = mitk::DataNode::New(); - m_BinaryPreviewNode->SetName("Binary_Preview"); - m_BinaryPreviewNode->SetProperty("color", ColorProperty::New(0.0, 1.0, 0.0)); - m_BinaryPreviewNode->SetProperty("opacity", FloatProperty::New(0.3)); - // m_BinaryPreviewNode->SetBoolProperty("helper object", true); - // m_BinaryPreviewNode->SetProperty("binary", mitk::BoolProperty::New(true)); - m_ToolManager->GetDataStorage()->Add(this->m_BinaryPreviewNode); - - m_MultiLabelResultNode = mitk::DataNode::New(); - m_MultiLabelResultNode->SetName("Otsu_Preview"); - // m_MultiLabelResultNode->SetBoolProperty("helper object", true); - m_MultiLabelResultNode->SetVisibility(true); - - m_MaskedImagePreviewNode = mitk::DataNode::New(); - m_MaskedImagePreviewNode->SetName("Volume_Preview"); - // m_MultiLabelResultNode->SetBoolProperty("helper object", true); - m_MaskedImagePreviewNode->SetVisibility(false); - - m_ToolManager->GetDataStorage()->Add(this->m_MultiLabelResultNode); - } + m_SelectedRegions = {}; + m_NumberOfBins = 128; + m_NumberOfRegions = 2; + m_UseValley = false; + + m_OtsuResultNode = mitk::DataNode::New(); + m_OtsuResultNode->SetName("Otsu_Preview"); + // m_MultiLabelResultNode->SetBoolProperty("helper object", true); + m_OtsuResultNode->SetVisibility(true); + m_OtsuResultNode->SetOpacity(1.0); + + m_ToolManager->GetDataStorage()->Add(m_OtsuResultNode); } void mitk::OtsuTool3D::Deactivated() { - m_ToolManager->GetDataStorage()->Remove(this->m_MultiLabelResultNode); - m_MultiLabelResultNode = nullptr; - m_ToolManager->GetDataStorage()->Remove(this->m_BinaryPreviewNode); - m_BinaryPreviewNode = nullptr; - m_ToolManager->GetDataStorage()->Remove(this->m_MaskedImagePreviewNode); - m_MaskedImagePreviewNode = nullptr; + m_ToolManager->GetDataStorage()->Remove(m_OtsuResultNode); + m_OtsuResultNode = nullptr; Superclass::Deactivated(); } const char **mitk::OtsuTool3D::GetXPM() const { return nullptr; } us::ModuleResource mitk::OtsuTool3D::GetIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); us::ModuleResource resource = module->GetResource("Otsu_48x48.png"); return resource; } -void mitk::OtsuTool3D::RunSegmentation(int regions, bool useValley, int numberOfBins) +const char* mitk::OtsuTool3D::GetName() const { - int numberOfThresholds = regions - 1; + return "Otsu"; +} +void mitk::OtsuTool3D::UpdateCleanUp() +{ + if (m_OtsuResultNode.IsNotNull()) + m_OtsuResultNode->SetVisibility(m_SelectedRegions.empty()); - const auto timePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); - auto image3D = Get3DImageByTimePoint(m_OriginalImage, timePoint); + if (nullptr != this->GetPreviewSegmentationNode()) + this->GetPreviewSegmentationNode()->SetVisibility(!m_SelectedRegions.empty()); - if (nullptr == image3D) + if (m_SelectedRegions.empty()) { - MITK_WARN << "Cannot run segementation. Currently selected timepoint is not in the time bounds of the selected reference image. Time point: " << timePoint; - return; + this->ResetPreviewNode(); } +} - mitk::OtsuSegmentationFilter::Pointer otsuFilter = mitk::OtsuSegmentationFilter::New(); - otsuFilter->SetNumberOfThresholds(numberOfThresholds); - otsuFilter->SetValleyEmphasis(useValley); - otsuFilter->SetNumberOfBins(numberOfBins); - otsuFilter->SetInput(image3D); +void mitk::OtsuTool3D::DoUpdatePreview(const Image* inputAtTimeStep, Image* previewImage, TimeStepType timeStep) +{ + int numberOfThresholds = m_NumberOfRegions - 1; - try + const auto timePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); + mitk::LabelSetImage::Pointer otsuResultImage = dynamic_cast(this->m_OtsuResultNode->GetData()); + + if (nullptr == m_OtsuResultNode->GetData() + || this->GetMTime() > m_OtsuResultNode->GetData()->GetMTime() + || this->m_LastOtsuTimeStep != timeStep //this covers the case where dynamic + //segmentations have to compute a preview + //for all time steps on confirmation + || this->GetLastTimePointOfUpdate() != timePoint //this ensures that static seg + //previews work with dynamic images + //with avoiding unnecessary other otsu computations + ) { - otsuFilter->Update(); + if (nullptr == inputAtTimeStep) + { + MITK_WARN << "Cannot run segementation. Currently selected input image is not set."; + return; + } + + this->m_LastOtsuTimeStep = timeStep; + + mitk::OtsuSegmentationFilter::Pointer otsuFilter = mitk::OtsuSegmentationFilter::New(); + otsuFilter->SetNumberOfThresholds(numberOfThresholds); + otsuFilter->SetValleyEmphasis(m_UseValley); + otsuFilter->SetNumberOfBins(m_NumberOfBins); + otsuFilter->SetInput(inputAtTimeStep); + otsuFilter->AddObserver(itk::ProgressEvent(), m_ProgressCommand); + + try + { + otsuFilter->Update(); + } + catch (...) + { + mitkThrow() << "itkOtsuFilter error (image dimension must be in {2, 3} and image must not be RGB)"; + } + + otsuResultImage = mitk::LabelSetImage::New(); + otsuResultImage->InitializeByLabeledImage(otsuFilter->GetOutput()); + this->m_OtsuResultNode->SetData(otsuResultImage); + this->m_OtsuResultNode->SetProperty("binary", mitk::BoolProperty::New(false)); + mitk::RenderingModeProperty::Pointer renderingMode = mitk::RenderingModeProperty::New(); + renderingMode->SetValue(mitk::RenderingModeProperty::LOOKUPTABLE_LEVELWINDOW_COLOR); + this->m_OtsuResultNode->SetProperty("Image Rendering.Mode", renderingMode); + mitk::LookupTable::Pointer lut = mitk::LookupTable::New(); + mitk::LookupTableProperty::Pointer prop = mitk::LookupTableProperty::New(lut); + vtkSmartPointer lookupTable = vtkSmartPointer::New(); + lookupTable->SetHueRange(1.0, 0.0); + lookupTable->SetSaturationRange(1.0, 1.0); + lookupTable->SetValueRange(1.0, 1.0); + lookupTable->SetTableRange(-1.0, 1.0); + lookupTable->Build(); + lut->SetVtkLookupTable(lookupTable); + prop->SetLookupTable(lut); + this->m_OtsuResultNode->SetProperty("LookupTable", prop); + mitk::LevelWindowProperty::Pointer levWinProp = mitk::LevelWindowProperty::New(); + mitk::LevelWindow levelwindow; + levelwindow.SetRangeMinMax(0, numberOfThresholds + 1); + levWinProp->SetLevelWindow(levelwindow); + this->m_OtsuResultNode->SetProperty("levelwindow", levWinProp); } - catch (...) + + if (!m_SelectedRegions.empty()) { - mitkThrow() << "itkOtsuFilter error (image dimension must be in {2, 3} and image must not be RGB)"; + AccessByItk_n(otsuResultImage, CalculatePreview, (previewImage, timeStep)); } - - m_ToolManager->GetDataStorage()->Remove(this->m_MultiLabelResultNode); - m_MultiLabelResultNode = nullptr; - m_MultiLabelResultNode = mitk::DataNode::New(); - m_MultiLabelResultNode->SetName("Otsu_Preview"); - m_MultiLabelResultNode->SetVisibility(true); - m_ToolManager->GetDataStorage()->Add(this->m_MultiLabelResultNode); - m_MultiLabelResultNode->SetOpacity(1.0); - - mitk::LabelSetImage::Pointer resultImage = mitk::LabelSetImage::New(); - resultImage->InitializeByLabeledImage(otsuFilter->GetOutput()); - this->m_MultiLabelResultNode->SetData(resultImage); - m_MultiLabelResultNode->SetProperty("binary", mitk::BoolProperty::New(false)); - mitk::RenderingModeProperty::Pointer renderingMode = mitk::RenderingModeProperty::New(); - renderingMode->SetValue(mitk::RenderingModeProperty::LOOKUPTABLE_LEVELWINDOW_COLOR); - m_MultiLabelResultNode->SetProperty("Image Rendering.Mode", renderingMode); - mitk::LookupTable::Pointer lut = mitk::LookupTable::New(); - mitk::LookupTableProperty::Pointer prop = mitk::LookupTableProperty::New(lut); - vtkSmartPointer lookupTable = vtkSmartPointer::New(); - lookupTable->SetHueRange(1.0, 0.0); - lookupTable->SetSaturationRange(1.0, 1.0); - lookupTable->SetValueRange(1.0, 1.0); - lookupTable->SetTableRange(-1.0, 1.0); - lookupTable->Build(); - lut->SetVtkLookupTable(lookupTable); - prop->SetLookupTable(lut); - m_MultiLabelResultNode->SetProperty("LookupTable", prop); - mitk::LevelWindowProperty::Pointer levWinProp = mitk::LevelWindowProperty::New(); - mitk::LevelWindow levelwindow; - levelwindow.SetRangeMinMax(0, numberOfThresholds + 1); - levWinProp->SetLevelWindow(levelwindow); - m_MultiLabelResultNode->SetProperty("levelwindow", levWinProp); - - // m_BinaryPreviewNode->SetVisibility(false); - // m_MultiLabelResultNode->SetVisibility(true); - // this->m_OtsuSegmentationDialog->setCursor(Qt::ArrowCursor); - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); -} - -void mitk::OtsuTool3D::ConfirmSegmentation() -{ - mitk::LabelSetImage::Pointer resultImage = mitk::LabelSetImage::New(); - resultImage->InitializeByLabeledImage(dynamic_cast(m_BinaryPreviewNode->GetData())); - GetTargetSegmentationNode()->SetData(resultImage); - - m_ToolManager->ActivateTool(-1); -} - -void mitk::OtsuTool3D::UpdateBinaryPreview(std::vector regionIDs) -{ - m_MultiLabelResultNode->SetVisibility(false); - mitk::Image::Pointer multiLabelSegmentation = dynamic_cast(m_MultiLabelResultNode->GetData()); - AccessByItk_1(multiLabelSegmentation, CalculatePreview, regionIDs); } template -void mitk::OtsuTool3D::CalculatePreview(itk::Image *itkImage, std::vector regionIDs) +void mitk::OtsuTool3D::CalculatePreview(itk::Image *itkImage, mitk::Image* segmentation, unsigned int timeStep) { typedef itk::Image InputImageType; typedef itk::Image OutputImageType; typedef itk::BinaryThresholdImageFilter FilterType; typename FilterType::Pointer filter = FilterType::New(); // InputImageType::Pointer itkImage; - typename OutputImageType::Pointer itkBinaryTempImage1; - typename OutputImageType::Pointer itkBinaryTempImage2; typename OutputImageType::Pointer itkBinaryResultImage; - // mitk::Image::Pointer multiLabelSegmentation = dynamic_cast(m_MultiLabelResultNode->GetData()); - // mitk::CastToItkImage(multiLabelSegmentation, itkImage); - filter->SetInput(itkImage); - filter->SetLowerThreshold(regionIDs[0]); - filter->SetUpperThreshold(regionIDs[0]); + filter->SetLowerThreshold(m_SelectedRegions[0]); + filter->SetUpperThreshold(m_SelectedRegions[0]); filter->SetInsideValue(1); filter->SetOutsideValue(0); + filter->AddObserver(itk::ProgressEvent(), m_ProgressCommand); filter->Update(); - itkBinaryTempImage2 = filter->GetOutput(); - - typename itk::OrImageFilter::Pointer orFilter = - itk::OrImageFilter::New(); + itkBinaryResultImage = filter->GetOutput(); + itkBinaryResultImage->DisconnectPipeline(); // if more than one region id is used compute the union of all given binary regions - for (auto it = regionIDs.begin(); it != regionIDs.end(); ++it) + for (const auto regionID : m_SelectedRegions) { - filter->SetLowerThreshold(*it); - filter->SetUpperThreshold(*it); - filter->SetInsideValue(1); - filter->SetOutsideValue(0); - filter->Update(); - itkBinaryTempImage1 = filter->GetOutput(); - - orFilter->SetInput1(itkBinaryTempImage1); - orFilter->SetInput2(itkBinaryTempImage2); - - orFilter->UpdateLargestPossibleRegion(); - itkBinaryResultImage = orFilter->GetOutput(); - itkBinaryTempImage2 = itkBinaryResultImage; + if (regionID != m_SelectedRegions[0]) + { + filter->SetLowerThreshold(regionID); + filter->SetUpperThreshold(regionID); + filter->SetInsideValue(1); + filter->SetOutsideValue(0); + filter->Update(); + + typename OutputImageType::Pointer tempImage = filter->GetOutput(); + + typename itk::OrImageFilter::Pointer orFilter = + itk::OrImageFilter::New(); + orFilter->SetInput1(tempImage); + orFilter->SetInput2(itkBinaryResultImage); + orFilter->AddObserver(itk::ProgressEvent(), m_ProgressCommand); + + orFilter->UpdateLargestPossibleRegion(); + itkBinaryResultImage = orFilter->GetOutput(); + } } //---------------------------------------------------------------------------------------------------- - mitk::Image::Pointer binarySegmentation; - mitk::CastToMitkImage(itkBinaryResultImage, binarySegmentation); - m_BinaryPreviewNode->SetData(binarySegmentation); - m_BinaryPreviewNode->SetVisibility(true); - m_BinaryPreviewNode->SetProperty("outline binary", mitk::BoolProperty::New(false)); - - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); -} - -const char *mitk::OtsuTool3D::GetName() const -{ - return "Otsu"; -} -void mitk::OtsuTool3D::UpdateVolumePreview(bool volumeRendering) -{ - if (volumeRendering) - { - m_MaskedImagePreviewNode->SetBoolProperty("volumerendering", true); - m_MaskedImagePreviewNode->SetBoolProperty("volumerendering.uselod", true); - } - else - { - m_MaskedImagePreviewNode->SetBoolProperty("volumerendering", false); - } - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); -} - -void mitk::OtsuTool3D::ShowMultiLabelResultNode(bool show) -{ - m_MultiLabelResultNode->SetVisibility(show); - m_BinaryPreviewNode->SetVisibility(!show); - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); + segmentation->SetVolume((void*)(itkBinaryResultImage->GetPixelContainer()->GetBufferPointer()), timeStep); } -int mitk::OtsuTool3D::GetNumberOfBins() +unsigned int mitk::OtsuTool3D::GetMaxNumberOfBins() const { - ScalarType min = m_OriginalImage.GetPointer()->GetStatistics()->GetScalarValueMin(); - ScalarType max = m_OriginalImage.GetPointer()->GetStatistics()->GetScalarValueMaxNoRecompute(); - return static_cast(max - min) + 1; + const auto min = this->GetReferenceData()->GetStatistics()->GetScalarValueMin(); + const auto max = this->GetReferenceData()->GetStatistics()->GetScalarValueMaxNoRecompute(); + return static_cast(max - min) + 1; } diff --git a/Modules/Segmentation/Interactions/mitkOtsuTool3D.h b/Modules/Segmentation/Interactions/mitkOtsuTool3D.h index f4e22cab62..7761ec60dd 100644 --- a/Modules/Segmentation/Interactions/mitkOtsuTool3D.h +++ b/Modules/Segmentation/Interactions/mitkOtsuTool3D.h @@ -1,68 +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. ============================================================================*/ #ifndef MITKOTSUTOOL3D_H #define MITKOTSUTOOL3D_H #include "itkImage.h" -#include "mitkAutoSegmentationTool.h" +#include "mitkAutoSegmentationWithPreviewTool.h" +#include "mitkDataNode.h" #include namespace us { class ModuleResource; } namespace mitk { class Image; - class MITKSEGMENTATION_EXPORT OtsuTool3D : public AutoSegmentationTool + class MITKSEGMENTATION_EXPORT OtsuTool3D : public AutoSegmentationWithPreviewTool { public: - mitkClassMacro(OtsuTool3D, AutoSegmentationTool); + mitkClassMacro(OtsuTool3D, AutoSegmentationWithPreviewTool); itkFactorylessNewMacro(Self); itkCloneMacro(Self); - const char *GetName() const override; + const char *GetName() const override; const char **GetXPM() const override; us::ModuleResource GetIconResource() const override; void Activated() override; void Deactivated() override; - void RunSegmentation(int regions, bool useValley, int numberOfBins); - void ConfirmSegmentation(); - // void UpdateBinaryPreview(int regionID); - void UpdateBinaryPreview(std::vector regionIDs); - void UpdateVolumePreview(bool volumeRendering); - void ShowMultiLabelResultNode(bool); + itkSetMacro(NumberOfBins, unsigned int); + itkGetConstMacro(NumberOfBins, unsigned int); - int GetNumberOfBins(); + itkSetMacro(NumberOfRegions, unsigned int); + itkGetConstMacro(NumberOfRegions, unsigned int); + + itkSetMacro(UseValley, bool); + itkGetConstMacro(UseValley, bool); + itkBooleanMacro(UseValley); + + using SelectedRegionVectorType = std::vector; + void SetSelectedRegions(const SelectedRegionVectorType& regions); + SelectedRegionVectorType GetSelectedRegions() const; + + /**Returns the number of max bins based on the current input image.*/ + unsigned int GetMaxNumberOfBins() const; protected: OtsuTool3D(); ~OtsuTool3D() override; + void UpdateCleanUp() override; + void DoUpdatePreview(const Image* inputAtTimeStep, Image* previewImage, TimeStepType timeStep) override; + template - void CalculatePreview(itk::Image *itkImage, std::vector regionIDs); + void CalculatePreview(itk::Image *itkImage, mitk::Image* segmentation, unsigned int timeStep); - itk::SmartPointer m_OriginalImage; - // holds the user selected binary segmentation - mitk::DataNode::Pointer m_BinaryPreviewNode; - // holds the multilabel result as a preview image - mitk::DataNode::Pointer m_MultiLabelResultNode; - // holds the user selected binary segmentation masked original image - mitk::DataNode::Pointer m_MaskedImagePreviewNode; + unsigned int m_NumberOfBins = 128; + unsigned int m_NumberOfRegions = 2; + bool m_UseValley = false; + SelectedRegionVectorType m_SelectedRegions = {}; + // holds the multilabel result as a preview image + mitk::DataNode::Pointer m_OtsuResultNode; + TimeStepType m_LastOtsuTimeStep = 0; }; // class } // namespace #endif diff --git a/Modules/Segmentation/Interactions/mitkTool.cpp b/Modules/Segmentation/Interactions/mitkTool.cpp index 6b17128971..2c2f32e1e5 100644 --- a/Modules/Segmentation/Interactions/mitkTool.cpp +++ b/Modules/Segmentation/Interactions/mitkTool.cpp @@ -1,330 +1,333 @@ /*============================================================================ 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 "mitkTool.h" #include #include "mitkDisplayInteractor.h" #include "mitkDisplayActionEventBroadcast.h" #include "mitkImageReadAccessor.h" #include "mitkImageWriteAccessor.h" #include "mitkLevelWindowProperty.h" #include "mitkLookupTableProperty.h" #include "mitkProperties.h" #include "mitkVtkResliceInterpolationProperty.h" #include // us #include #include // itk #include mitk::Tool::Tool(const char *type, const us::Module *interactorModule) : m_EventConfig("DisplayConfigMITK.xml"), m_ToolManager(nullptr), m_PredicateImages(NodePredicateDataType::New("Image")), // for reference images m_PredicateDim3(NodePredicateDimension::New(3, 1)), m_PredicateDim4(NodePredicateDimension::New(4, 1)), m_PredicateDimension(mitk::NodePredicateOr::New(m_PredicateDim3, m_PredicateDim4)), m_PredicateImage3D(NodePredicateAnd::New(m_PredicateImages, m_PredicateDimension)), m_PredicateBinary(NodePredicateProperty::New("binary", BoolProperty::New(true))), m_PredicateNotBinary(NodePredicateNot::New(m_PredicateBinary)), m_PredicateSegmentation(NodePredicateProperty::New("segmentation", BoolProperty::New(true))), m_PredicateNotSegmentation(NodePredicateNot::New(m_PredicateSegmentation)), m_PredicateHelper(NodePredicateProperty::New("helper object", BoolProperty::New(true))), m_PredicateNotHelper(NodePredicateNot::New(m_PredicateHelper)), m_PredicateImageColorful(NodePredicateAnd::New(m_PredicateNotBinary, m_PredicateNotSegmentation)), m_PredicateImageColorfulNotHelper(NodePredicateAnd::New(m_PredicateImageColorful, m_PredicateNotHelper)), m_PredicateReference(NodePredicateAnd::New(m_PredicateImage3D, m_PredicateImageColorfulNotHelper)), m_IsSegmentationPredicate( NodePredicateAnd::New(NodePredicateOr::New(m_PredicateBinary, m_PredicateSegmentation), m_PredicateNotHelper)), m_InteractorType(type), m_DisplayInteractorConfigs(), m_InteractorModule(interactorModule) { } mitk::Tool::~Tool() { } -bool mitk::Tool::CanHandle(BaseData *) const +bool mitk::Tool::CanHandle(const BaseData* referenceData, const BaseData* /*workingData*/) const { + if (referenceData == nullptr) + return false; + return true; } void mitk::Tool::InitializeStateMachine() { if (m_InteractorType.empty()) return; try { auto isThisModule = nullptr == m_InteractorModule; auto module = isThisModule ? us::GetModuleContext()->GetModule() : m_InteractorModule; LoadStateMachine(m_InteractorType + ".xml", module); SetEventConfig(isThisModule ? "SegmentationToolsConfig.xml" : m_InteractorType + "Config.xml", module); } catch (const std::exception &e) { MITK_ERROR << "Could not load statemachine pattern " << m_InteractorType << ".xml with exception: " << e.what(); } } void mitk::Tool::Notify(InteractionEvent *interactionEvent, bool isHandled) { // to use the state machine pattern, // the event is passed to the state machine interface to be handled if (!isHandled) { this->HandleEvent(interactionEvent, nullptr); } } void mitk::Tool::ConnectActionsAndFunctions() { } bool mitk::Tool::FilterEvents(InteractionEvent *, DataNode *) { return true; } const char *mitk::Tool::GetGroup() const { return "default"; } void mitk::Tool::SetToolManager(ToolManager *manager) { m_ToolManager = manager; } void mitk::Tool::Activated() { // As a legacy solution the display interaction of the new interaction framework is disabled here to avoid conflicts // with tools // Note: this only affects InteractionEventObservers (formerly known as Listeners) all DataNode specific interaction // will still be enabled m_DisplayInteractorConfigs.clear(); std::vector> listEventObserver = us::GetModuleContext()->GetServiceReferences(); for (auto it = listEventObserver.begin(); it != listEventObserver.end(); ++it) { auto displayInteractor = dynamic_cast(us::GetModuleContext()->GetService(*it)); if (displayInteractor != nullptr) { // remember the original configuration m_DisplayInteractorConfigs.insert(std::make_pair(*it, displayInteractor->GetEventConfig())); // here the alternative configuration is loaded displayInteractor->SetEventConfig(m_EventConfig.c_str()); } auto displayActionEventBroadcast = dynamic_cast(us::GetModuleContext()->GetService(*it)); if (displayActionEventBroadcast != nullptr) { // remember the original configuration m_DisplayInteractorConfigs.insert(std::make_pair(*it, displayActionEventBroadcast->GetEventConfig())); // here the alternative configuration is loaded displayActionEventBroadcast->SetEventConfig(m_EventConfig.c_str()); } } } void mitk::Tool::Deactivated() { // Re-enabling InteractionEventObservers that have been previously disabled for legacy handling of Tools // in new interaction framework for (auto it = m_DisplayInteractorConfigs.begin(); it != m_DisplayInteractorConfigs.end(); ++it) { if (it->first) { auto displayInteractor = static_cast(us::GetModuleContext()->GetService(it->first)); if (displayInteractor != nullptr) { // here the regular configuration is loaded again displayInteractor->SetEventConfig(it->second); } auto displayActionEventBroadcast = dynamic_cast(us::GetModuleContext()->GetService(it->first)); if (displayActionEventBroadcast != nullptr) { // here the regular configuration is loaded again displayActionEventBroadcast->SetEventConfig(it->second); } } } m_DisplayInteractorConfigs.clear(); } itk::Object::Pointer mitk::Tool::GetGUI(const std::string &toolkitPrefix, const std::string &toolkitPostfix) { itk::Object::Pointer object; std::string classname = this->GetNameOfClass(); std::string guiClassname = toolkitPrefix + classname + toolkitPostfix; std::list allGUIs = itk::ObjectFactoryBase::CreateAllInstance(guiClassname.c_str()); for (auto iter = allGUIs.begin(); iter != allGUIs.end(); ++iter) { if (object.IsNull()) { object = dynamic_cast(iter->GetPointer()); } else { MITK_ERROR << "There is more than one GUI for " << classname << " (several factories claim ability to produce a " << guiClassname << " ) " << std::endl; return nullptr; // people should see and fix this error } } return object; } mitk::NodePredicateBase::ConstPointer mitk::Tool::GetReferenceDataPreference() const { return m_PredicateReference.GetPointer(); } mitk::NodePredicateBase::ConstPointer mitk::Tool::GetWorkingDataPreference() const { return m_IsSegmentationPredicate.GetPointer(); } mitk::DataNode::Pointer mitk::Tool::CreateEmptySegmentationNode(const Image *original, const std::string &organName, const mitk::Color &color) { // we NEED a reference image for size etc. if (!original) return nullptr; // actually create a new empty segmentation PixelType pixelType(mitk::MakeScalarPixelType()); LabelSetImage::Pointer segmentation = LabelSetImage::New(); if (original->GetDimension() == 2) { const unsigned int dimensions[] = {original->GetDimension(0), original->GetDimension(1), 1}; segmentation->Initialize(pixelType, 3, dimensions); segmentation->AddLayer(); } else { segmentation->Initialize(original); } mitk::Label::Pointer label = mitk::Label::New(); label->SetName(organName); label->SetColor(color); label->SetValue(1); segmentation->GetActiveLabelSet()->AddLabel(label); segmentation->GetActiveLabelSet()->SetActiveLabel(1); unsigned int byteSize = sizeof(mitk::Label::PixelType); if (segmentation->GetDimension() < 4) { for (unsigned int dim = 0; dim < segmentation->GetDimension(); ++dim) { byteSize *= segmentation->GetDimension(dim); } mitk::ImageWriteAccessor writeAccess(segmentation.GetPointer(), segmentation->GetVolumeData(0)); memset(writeAccess.GetData(), 0, byteSize); } else { // if we have a time-resolved image we need to set memory to 0 for each time step for (unsigned int dim = 0; dim < 3; ++dim) { byteSize *= segmentation->GetDimension(dim); } for (unsigned int volumeNumber = 0; volumeNumber < segmentation->GetDimension(3); volumeNumber++) { mitk::ImageWriteAccessor writeAccess(segmentation.GetPointer(), segmentation->GetVolumeData(volumeNumber)); memset(writeAccess.GetData(), 0, byteSize); } } if (original->GetTimeGeometry()) { TimeGeometry::Pointer originalGeometry = original->GetTimeGeometry()->Clone(); segmentation->SetTimeGeometry(originalGeometry); } else { Tool::ErrorMessage("Original image does not have a 'Time sliced geometry'! Cannot create a segmentation."); return nullptr; } return CreateSegmentationNode(segmentation, organName, color); } mitk::DataNode::Pointer mitk::Tool::CreateSegmentationNode(Image *image, const std::string &organName, const mitk::Color &color) { if (!image) return nullptr; // decorate the datatreenode with some properties DataNode::Pointer segmentationNode = DataNode::New(); segmentationNode->SetData(image); // name segmentationNode->SetProperty("name", StringProperty::New(organName)); // visualization properties segmentationNode->SetProperty("binary", BoolProperty::New(true)); segmentationNode->SetProperty("color", ColorProperty::New(color)); mitk::LookupTable::Pointer lut = mitk::LookupTable::New(); lut->SetType(mitk::LookupTable::MULTILABEL); mitk::LookupTableProperty::Pointer lutProp = mitk::LookupTableProperty::New(); lutProp->SetLookupTable(lut); segmentationNode->SetProperty("LookupTable", lutProp); segmentationNode->SetProperty("texture interpolation", BoolProperty::New(false)); segmentationNode->SetProperty("layer", IntProperty::New(10)); segmentationNode->SetProperty("levelwindow", LevelWindowProperty::New(LevelWindow(0.5, 1))); segmentationNode->SetProperty("opacity", FloatProperty::New(0.3)); segmentationNode->SetProperty("segmentation", BoolProperty::New(true)); segmentationNode->SetProperty("reslice interpolation", VtkResliceInterpolationProperty::New()); // otherwise -> segmentation appears in 2 // slices sometimes (only visual effect, not // different data) // For MITK-3M3 release, the volume of all segmentations should be shown segmentationNode->SetProperty("showVolume", BoolProperty::New(true)); return segmentationNode; } us::ModuleResource mitk::Tool::GetIconResource() const { // Each specific tool should load its own resource. This one will be invalid return us::ModuleResource(); } us::ModuleResource mitk::Tool::GetCursorIconResource() const { // Each specific tool should load its own resource. This one will be invalid return us::ModuleResource(); } diff --git a/Modules/Segmentation/Interactions/mitkTool.h b/Modules/Segmentation/Interactions/mitkTool.h index e9110e2a7a..16b2cf15a9 100644 --- a/Modules/Segmentation/Interactions/mitkTool.h +++ b/Modules/Segmentation/Interactions/mitkTool.h @@ -1,265 +1,270 @@ /*============================================================================ 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 mitkTool_h_Included #define mitkTool_h_Included #include "itkObjectFactoryBase.h" #include "itkVersion.h" #include "mitkCommon.h" #include "mitkDataNode.h" #include "mitkEventStateMachine.h" #include "mitkInteractionEventObserver.h" #include "mitkLabelSetImage.h" #include "mitkMessage.h" #include "mitkNodePredicateAnd.h" #include "mitkNodePredicateDataType.h" #include "mitkNodePredicateDimension.h" #include "mitkNodePredicateNot.h" #include "mitkNodePredicateOr.h" #include "mitkNodePredicateProperty.h" #include "mitkToolEvents.h" #include "mitkToolFactoryMacro.h" #include #include #include #include #include #include #include "usServiceRegistration.h" namespace us { class ModuleResource; } namespace mitk { class ToolManager; /** \brief Base class of all tools used by mitk::ToolManager. \sa ToolManager \sa SegTool2D \ingroup Interaction \ingroup ToolManagerEtAl Every tool is a mitk::StateMachine, which can follow any transition pattern that it likes. One important thing to know is, that every derived tool should always call SuperClass::Deactivated() at the end of its own implementation of Deactivated, because mitk::Tool resets the StateMachine in this method. Only if you are very sure that you covered all possible things that might happen to your own tool, you should consider not to reset the StateMachine from time to time. To learn about the MITK implementation of state machines in general, have a look at \ref InteractionPage. To derive a non-abstract tool, you inherit from mitk::Tool (or some other base class further down the inheritance tree), and in your own parameterless constructor (that is called from the itkFactorylessNewMacro that you use) you pass a StateMachine pattern name to the superclass. Names for valid patterns can be found in StateMachine.xml (which might be enhanced by you). You have to implement at least GetXPM() and GetName() to provide some identification. Each Tool knows its ToolManager, which can provide the data that the tool should work on. \warning Only to be instantiated by mitk::ToolManager (because SetToolManager has to be called). All other uses are unsupported. $Author$ */ class MITKSEGMENTATION_EXPORT Tool : public EventStateMachine, public InteractionEventObserver { public: typedef mitk::Label::PixelType DefaultSegmentationDataType; /** * \brief To let GUI process new events (e.g. qApp->processEvents() ) */ Message<> GUIProcessEventsMessage; /** * \brief To send error messages (to be shown by some GUI) */ Message1 ErrorMessage; /** * \brief To send whether the tool is busy (to be shown by some GUI) */ Message1 CurrentlyBusy; /** * \brief To send general messages (to be shown by some GUI) */ Message1 GeneralMessage; mitkClassMacro(Tool, EventStateMachine); // no New(), there should only be subclasses /** \brief Returns an icon in the XPM format. This icon has to fit into some kind of button in most applications, so make it smaller than 25x25 pixels. XPM is e.g. supported by The Gimp. But if you open any XPM file in your text editor, you will see that you could also "draw" it with an editor. */ virtual const char **GetXPM() const = 0; /** * \brief Returns the path of an icon. * * This icon is preferred to the XPM icon. */ virtual std::string GetIconPath() const { return ""; } /** * \brief Returns the path of a cursor icon. * */ virtual us::ModuleResource GetCursorIconResource() const; /** * @brief Returns the tool button icon of the tool wrapped by a usModuleResource * @return a valid ModuleResource or an invalid if this function * is not reimplemented */ virtual us::ModuleResource GetIconResource() const; /** \brief Returns the name of this tool. Make it short! This name has to fit into some kind of button in most applications, so take some time to think of a good name! */ virtual const char *GetName() const = 0; /** \brief Name of a group. You can group several tools by assigning a group name. Graphical tool selectors might use this information to group tools. (What other reason could there be?) */ virtual const char *GetGroup() const; virtual void InitializeStateMachine(); /** * \brief Interface for GUI creation. * * This is the basic interface for creation of a GUI object belonging to one tool. * * Tools that support a GUI (e.g. for display/editing of parameters) should follow some rules: * * - A Tool and its GUI are two separate classes * - There may be several instances of a GUI at the same time. * - mitk::Tool is toolkit (Qt, wxWidgets, etc.) independent, the GUI part is of course dependent * - The GUI part inherits both from itk::Object and some GUI toolkit class * - The GUI class name HAS to be constructed like "toolkitPrefix" tool->GetClassName() + "toolkitPostfix", e.g. * MyTool -> wxMyToolGUI * - For each supported toolkit there is a base class for tool GUIs, which contains some convenience methods * - Tools notify the GUI about changes using ITK events. The GUI must observe interesting events. * - The GUI base class may convert all ITK events to the GUI toolkit's favoured messaging system (Qt -> signals) * - Calling methods of a tool by its GUI is done directly. * In some cases GUIs don't want to be notified by the tool when they cause a change in a tool. * There is a macro CALL_WITHOUT_NOTICE(method()), which will temporarily disable all notifications during a * method call. */ virtual itk::Object::Pointer GetGUI(const std::string &toolkitPrefix, const std::string &toolkitPostfix); virtual NodePredicateBase::ConstPointer GetReferenceDataPreference() const; virtual NodePredicateBase::ConstPointer GetWorkingDataPreference() const; DataNode::Pointer CreateEmptySegmentationNode(const Image *original, const std::string &organName, const mitk::Color &color); DataNode::Pointer CreateSegmentationNode(Image *image, const std::string &organName, const mitk::Color &color); - virtual bool CanHandle(BaseData *referenceData) const; + /** Function used to check if a tool can handle the referenceData and (if specified) the working data. + @pre referenceData must be a valid pointer + @param referenceData Pointer to the data that should be checked as valid reference for the tool. + @param workingData Pointer to the data that should be checked as valid working data for this tool. + This parameter can be null if no working data is specified so far.*/ + virtual bool CanHandle(const BaseData *referenceData, const BaseData *workingData) const; protected: friend class ToolManager; virtual void SetToolManager(ToolManager *); void ConnectActionsAndFunctions() override; /** \brief Called when the tool gets activated. Derived tools should call their parents implementation at the beginning of the overriding function. */ virtual void Activated(); /** \brief Called when the tool gets deactivated. Derived tools should call their parents implementation at the end of the overriding function. */ virtual void Deactivated(); /** \brief Let subclasses change their event configuration. */ std::string m_EventConfig; Tool(); // purposely hidden Tool(const char *, const us::Module *interactorModule = nullptr); // purposely hidden ~Tool() override; void Notify(InteractionEvent *interactionEvent, bool isHandled) override; bool FilterEvents(InteractionEvent *, DataNode *) override; ToolManager *m_ToolManager; private: // for reference data NodePredicateDataType::Pointer m_PredicateImages; NodePredicateDimension::Pointer m_PredicateDim3; NodePredicateDimension::Pointer m_PredicateDim4; NodePredicateOr::Pointer m_PredicateDimension; NodePredicateAnd::Pointer m_PredicateImage3D; NodePredicateProperty::Pointer m_PredicateBinary; NodePredicateNot::Pointer m_PredicateNotBinary; NodePredicateProperty::Pointer m_PredicateSegmentation; NodePredicateNot::Pointer m_PredicateNotSegmentation; NodePredicateProperty::Pointer m_PredicateHelper; NodePredicateNot::Pointer m_PredicateNotHelper; NodePredicateAnd::Pointer m_PredicateImageColorful; NodePredicateAnd::Pointer m_PredicateImageColorfulNotHelper; NodePredicateAnd::Pointer m_PredicateReference; // for working data NodePredicateAnd::Pointer m_IsSegmentationPredicate; std::string m_InteractorType; std::map m_DisplayInteractorConfigs; const us::Module *m_InteractorModule; }; } // namespace #endif diff --git a/Modules/Segmentation/Interactions/mitkWatershedTool.cpp b/Modules/Segmentation/Interactions/mitkWatershedTool.cpp index 15fd3ff488..47e29e4da4 100644 --- a/Modules/Segmentation/Interactions/mitkWatershedTool.cpp +++ b/Modules/Segmentation/Interactions/mitkWatershedTool.cpp @@ -1,188 +1,188 @@ /*============================================================================ 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 "mitkWatershedTool.h" #include "mitkIOUtil.h" #include "mitkITKImageImport.h" #include "mitkImage.h" #include "mitkLabelSetImage.h" #include "mitkImageAccessByItk.h" #include "mitkImageCast.h" #include "mitkImageStatisticsHolder.h" #include "mitkLevelWindowManager.h" #include "mitkLookupTable.h" #include "mitkLookupTableProperty.h" #include "mitkProgressBar.h" #include "mitkRenderingManager.h" #include "mitkRenderingModeProperty.h" #include "mitkToolCommand.h" #include "mitkToolManager.h" #include #include #include #include #include #include #include #include #include namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, WatershedTool, "Watershed tool"); } mitk::WatershedTool::WatershedTool() : m_Threshold(0.0), m_Level(0.0) { } mitk::WatershedTool::~WatershedTool() { } void mitk::WatershedTool::Activated() { Superclass::Activated(); } void mitk::WatershedTool::Deactivated() { Superclass::Deactivated(); } us::ModuleResource mitk::WatershedTool::GetIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); us::ModuleResource resource = module->GetResource("Watershed_48x48.png"); return resource; } const char **mitk::WatershedTool::GetXPM() const { return nullptr; } const char *mitk::WatershedTool::GetName() const { return "Watershed"; } void mitk::WatershedTool::DoIt() { // get image from tool manager mitk::DataNode::Pointer referenceData = m_ToolManager->GetReferenceData(0); mitk::Image::ConstPointer input = dynamic_cast(referenceData->GetData()); if (input.IsNull()) return; const auto timePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); - input = Get3DImageByTimePoint(input, timePoint); + input = GetImageByTimePoint(input, timePoint); if (nullptr == input) { MITK_WARN << "Cannot run segementation. Currently selected timepoint is not in the time bounds of the selected reference image. Time point: " << timePoint; return; } mitk::Image::Pointer output; try { // create and run itk filter pipeline AccessByItk_1(input.GetPointer(), ITKWatershed, output); mitk::LabelSetImage::Pointer labelSetOutput = mitk::LabelSetImage::New(); labelSetOutput->InitializeByLabeledImage(output); // create a new datanode for output mitk::DataNode::Pointer dataNode = mitk::DataNode::New(); dataNode->SetData(labelSetOutput); // set name of data node std::string name = referenceData->GetName() + "_Watershed"; dataNode->SetName(name); // look, if there is already a node with this name mitk::DataStorage::SetOfObjects::ConstPointer children = m_ToolManager->GetDataStorage()->GetDerivations(referenceData); mitk::DataStorage::SetOfObjects::ConstIterator currentNode = children->Begin(); mitk::DataNode::Pointer removeNode; while (currentNode != children->End()) { if (dataNode->GetName().compare(currentNode->Value()->GetName()) == 0) { removeNode = currentNode->Value(); } currentNode++; } // remove node with same name if (removeNode.IsNotNull()) m_ToolManager->GetDataStorage()->Remove(removeNode); // add output to the data storage m_ToolManager->GetDataStorage()->Add(dataNode, referenceData); } catch (itk::ExceptionObject &e) { MITK_ERROR << "Watershed Filter Error: " << e.GetDescription(); } RenderingManager::GetInstance()->RequestUpdateAll(); } template void mitk::WatershedTool::ITKWatershed(const itk::Image *originalImage, mitk::Image::Pointer &segmentation) { typedef itk::WatershedImageFilter> WatershedFilter; typedef itk::GradientMagnitudeRecursiveGaussianImageFilter, itk::Image> MagnitudeFilter; // at first add a gradient magnitude filter typename MagnitudeFilter::Pointer magnitude = MagnitudeFilter::New(); magnitude->SetInput(originalImage); magnitude->SetSigma(1.0); // use the progress bar mitk::ToolCommand::Pointer command = mitk::ToolCommand::New(); command->AddStepsToDo(60); // then add the watershed filter to the pipeline typename WatershedFilter::Pointer watershed = WatershedFilter::New(); watershed->SetInput(magnitude->GetOutput()); watershed->SetThreshold(m_Threshold); watershed->SetLevel(m_Level); watershed->AddObserver(itk::ProgressEvent(), command); watershed->Update(); // then make sure, that the output has the desired pixel type typedef itk::CastImageFilter> CastFilter; typename CastFilter::Pointer cast = CastFilter::New(); cast->SetInput(watershed->GetOutput()); // start the whole pipeline cast->Update(); // reset the progress bar by setting progress command->SetProgress(10); // since we obtain a new image from our pipeline, we have to make sure, that our mitk::Image::Pointer // is responsible for the memory management of the output image segmentation = mitk::GrabItkImageMemory(cast->GetOutput()); } diff --git a/Modules/Segmentation/files.cmake b/Modules/Segmentation/files.cmake index 676362200a..1deb993fd5 100644 --- a/Modules/Segmentation/files.cmake +++ b/Modules/Segmentation/files.cmake @@ -1,116 +1,117 @@ set(CPP_FILES Algorithms/mitkCalculateSegmentationVolume.cpp Algorithms/mitkContourModelSetToImageFilter.cpp Algorithms/mitkContourSetToPointSetFilter.cpp Algorithms/mitkContourUtils.cpp Algorithms/mitkCorrectorAlgorithm.cpp Algorithms/mitkDiffImageApplier.cpp Algorithms/mitkDiffSliceOperation.cpp Algorithms/mitkDiffSliceOperationApplier.cpp Algorithms/mitkFeatureBasedEdgeDetectionFilter.cpp Algorithms/mitkImageLiveWireContourModelFilter.cpp Algorithms/mitkImageToContourFilter.cpp #Algorithms/mitkImageToContourModelFilter.cpp Algorithms/mitkImageToLiveWireContourFilter.cpp Algorithms/mitkManualSegmentationToSurfaceFilter.cpp Algorithms/mitkOtsuSegmentationFilter.cpp Algorithms/mitkOverwriteDirectedPlaneImageFilter.cpp Algorithms/mitkOverwriteSliceImageFilter.cpp Algorithms/mitkSegmentationObjectFactory.cpp Algorithms/mitkShapeBasedInterpolationAlgorithm.cpp Algorithms/mitkShowSegmentationAsSmoothedSurface.cpp Algorithms/mitkShowSegmentationAsSurface.cpp Algorithms/mitkVtkImageOverwrite.cpp Controllers/mitkSegmentationInterpolationController.cpp Controllers/mitkToolManager.cpp Controllers/mitkSegmentationModuleActivator.cpp Controllers/mitkToolManagerProvider.cpp DataManagement/mitkContour.cpp DataManagement/mitkContourSet.cpp DataManagement/mitkExtrudedContour.cpp Interactions/mitkAdaptiveRegionGrowingTool.cpp Interactions/mitkAddContourTool.cpp Interactions/mitkAutoCropTool.cpp Interactions/mitkAutoSegmentationTool.cpp + Interactions/mitkAutoSegmentationWithPreviewTool.cpp Interactions/mitkBinaryThresholdBaseTool.cpp Interactions/mitkBinaryThresholdTool.cpp Interactions/mitkBinaryThresholdULTool.cpp Interactions/mitkCalculateGrayValueStatisticsTool.cpp Interactions/mitkCalculateVolumetryTool.cpp Interactions/mitkContourModelInteractor.cpp Interactions/mitkContourModelLiveWireInteractor.cpp Interactions/mitkLiveWireTool2D.cpp Interactions/mitkContourTool.cpp Interactions/mitkCorrectorTool2D.cpp Interactions/mitkCreateSurfaceTool.cpp Interactions/mitkDrawPaintbrushTool.cpp Interactions/mitkErasePaintbrushTool.cpp Interactions/mitkEraseRegionTool.cpp Interactions/mitkFastMarchingTool.cpp Interactions/mitkFastMarchingTool3D.cpp Interactions/mitkFeedbackContourTool.cpp Interactions/mitkFillRegionTool.cpp Interactions/mitkOtsuTool3D.cpp Interactions/mitkPaintbrushTool.cpp Interactions/mitkPixelManipulationTool.cpp Interactions/mitkRegionGrowingTool.cpp Interactions/mitkSegmentationsProcessingTool.cpp Interactions/mitkSetRegionTool.cpp Interactions/mitkSegTool2D.cpp Interactions/mitkSubtractContourTool.cpp Interactions/mitkTool.cpp Interactions/mitkToolCommand.cpp Interactions/mitkWatershedTool.cpp Interactions/mitkPickingTool.cpp Interactions/mitkSegmentationInteractor.cpp #SO Rendering/mitkContourMapper2D.cpp Rendering/mitkContourSetMapper2D.cpp Rendering/mitkContourSetVtkMapper3D.cpp Rendering/mitkContourVtkMapper3D.cpp SegmentationUtilities/BooleanOperations/mitkBooleanOperation.cpp SegmentationUtilities/MorphologicalOperations/mitkMorphologicalOperations.cpp #Added from ML Controllers/mitkSliceBasedInterpolationController.cpp Algorithms/mitkSurfaceStampImageFilter.cpp ) set(RESOURCE_FILES Add_48x48.png Add_Cursor_32x32.png Correction_48x48.png Correction_Cursor_32x32.png Erase_48x48.png Erase_Cursor_32x32.png FastMarching_48x48.png FastMarching_Cursor_32x32.png Fill_48x48.png Fill_Cursor_32x32.png LiveWire_48x48.png LiveWire_Cursor_32x32.png Otsu_48x48.png Paint_48x48.png Paint_Cursor_32x32.png Pick_48x48.png RegionGrowing_48x48.png RegionGrowing_Cursor_32x32.png Subtract_48x48.png Subtract_Cursor_32x32.png Threshold_48x48.png TwoThresholds_48x48.png Watershed_48x48.png Watershed_Cursor_32x32.png Wipe_48x48.png Wipe_Cursor_32x32.png Interactions/dummy.xml Interactions/LiveWireTool.xml Interactions/FastMarchingTool.xml Interactions/PressMoveRelease.xml Interactions/PressMoveReleaseAndPointSetting.xml Interactions/PressMoveReleaseWithCTRLInversion.xml Interactions/PressMoveReleaseWithCTRLInversionAllMouseMoves.xml Interactions/SegmentationToolsConfig.xml Interactions/ContourModelModificationConfig.xml Interactions/ContourModelModificationInteractor.xml ) diff --git a/Modules/SegmentationUI/Qmitk/QmitkAdaptiveRegionGrowingToolGUI.cpp b/Modules/SegmentationUI/Qmitk/QmitkAdaptiveRegionGrowingToolGUI.cpp index 5ba6c5a14b..a20b0fcc50 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkAdaptiveRegionGrowingToolGUI.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkAdaptiveRegionGrowingToolGUI.cpp @@ -1,995 +1,995 @@ /*============================================================================ 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 "QmitkAdaptiveRegionGrowingToolGUI.h" #include #include "mitkITKImageImport.h" #include "mitkImageAccessByItk.h" #include "mitkImageTimeSelector.h" #include "mitkNodePredicateDataType.h" #include "mitkProperties.h" #include "mitkTransferFunctionProperty.h" #include "mitkImageStatisticsHolder.h" #include "itkMaskImageFilter.h" #include "itkNumericTraits.h" #include #include #include #include #include "QmitkConfirmSegmentationDialog.h" #include "itkOrImageFilter.h" #include "mitkImageCast.h" #include "mitkImagePixelReadAccessor.h" #include "mitkPixelTypeMultiplex.h" #include "mitkImageCast.h" MITK_TOOL_GUI_MACRO(, QmitkAdaptiveRegionGrowingToolGUI, "") QmitkAdaptiveRegionGrowingToolGUI::QmitkAdaptiveRegionGrowingToolGUI(QWidget *parent) : QmitkToolGUI(), m_DataStorage(nullptr), m_UseVolumeRendering(false), m_UpdateSuggestedThreshold(true), m_SuggestedThValue(0.0) { this->setParent(parent); m_Controls.setupUi(this); m_Controls.m_ThresholdSlider->setDecimals(1); m_Controls.m_ThresholdSlider->setSpinBoxAlignment(Qt::AlignVCenter); m_Controls.m_PreviewSlider->setEnabled(false); m_Controls.m_PreviewSlider->setSingleStep(0.5); // Not yet available // m_Controls.m_PreviewSlider->InvertedAppearance(true); //3D preview doesn't work: T24430. Postponed until reimplementation of segmentation m_Controls.m_cbVolumeRendering->setVisible(false); this->CreateConnections(); this->SetDataNodeNames("labeledRGSegmentation", "RGResult", "RGFeedbackSurface", "maskedSegmentation"); connect(this, SIGNAL(NewToolAssociated(mitk::Tool *)), this, SLOT(OnNewToolAssociated(mitk::Tool *))); } QmitkAdaptiveRegionGrowingToolGUI::~QmitkAdaptiveRegionGrowingToolGUI() { // Removing the observer of the PointSet node if (m_RegionGrow3DTool->GetPointSetNode().IsNotNull()) { m_RegionGrow3DTool->GetPointSetNode()->GetData()->RemoveObserver(m_PointSetAddObserverTag); m_RegionGrow3DTool->GetPointSetNode()->GetData()->RemoveObserver(m_PointSetMoveObserverTag); } this->RemoveHelperNodes(); } void QmitkAdaptiveRegionGrowingToolGUI::OnNewToolAssociated(mitk::Tool *tool) { m_RegionGrow3DTool = dynamic_cast(tool); if (m_RegionGrow3DTool.IsNotNull()) { SetInputImageNode(this->m_RegionGrow3DTool->GetReferenceData()); this->m_DataStorage = this->m_RegionGrow3DTool->GetDataStorage(); this->EnableControls(true); // Watch for point added or modified itk::SimpleMemberCommand::Pointer pointAddedCommand = itk::SimpleMemberCommand::New(); pointAddedCommand->SetCallbackFunction(this, &QmitkAdaptiveRegionGrowingToolGUI::OnPointAdded); m_PointSetAddObserverTag = m_RegionGrow3DTool->GetPointSetNode()->GetData()->AddObserver(mitk::PointSetAddEvent(), pointAddedCommand); m_PointSetMoveObserverTag = m_RegionGrow3DTool->GetPointSetNode()->GetData()->AddObserver(mitk::PointSetMoveEvent(), pointAddedCommand); } else { this->EnableControls(false); } } void QmitkAdaptiveRegionGrowingToolGUI::RemoveHelperNodes() { mitk::DataNode::Pointer imageNode = m_DataStorage->GetNamedNode(m_NAMEFORLABLEDSEGMENTATIONIMAGE); if (imageNode.IsNotNull()) { m_DataStorage->Remove(imageNode); } mitk::DataNode::Pointer maskedSegmentationNode = m_DataStorage->GetNamedNode(m_NAMEFORMASKEDSEGMENTATION); if (maskedSegmentationNode.IsNotNull()) { m_DataStorage->Remove(maskedSegmentationNode); } } void QmitkAdaptiveRegionGrowingToolGUI::CreateConnections() { // Connecting GUI components connect((QObject *)(m_Controls.m_pbRunSegmentation), SIGNAL(clicked()), this, SLOT(RunSegmentation())); connect(m_Controls.m_PreviewSlider, SIGNAL(valueChanged(double)), this, SLOT(ChangeLevelWindow(double))); connect((QObject *)(m_Controls.m_pbConfirmSegementation), SIGNAL(clicked()), this, SLOT(ConfirmSegmentation())); connect( m_Controls.m_ThresholdSlider, SIGNAL(maximumValueChanged(double)), this, SLOT(SetUpperThresholdValue(double))); connect( m_Controls.m_ThresholdSlider, SIGNAL(minimumValueChanged(double)), this, SLOT(SetLowerThresholdValue(double))); } void QmitkAdaptiveRegionGrowingToolGUI::SetDataNodeNames(std::string labledSegmentation, std::string binaryImage, std::string surface, std::string maskedSegmentation) { m_NAMEFORLABLEDSEGMENTATIONIMAGE = labledSegmentation; m_NAMEFORBINARYIMAGE = binaryImage; m_NAMEFORSURFACE = surface; m_NAMEFORMASKEDSEGMENTATION = maskedSegmentation; } void QmitkAdaptiveRegionGrowingToolGUI::SetDataStorage(mitk::DataStorage *dataStorage) { m_DataStorage = dataStorage; } void QmitkAdaptiveRegionGrowingToolGUI::SetInputImageNode(mitk::DataNode *node) { m_InputImageNode = node; mitk::Image *inputImage = dynamic_cast(m_InputImageNode->GetData()); if (inputImage) { mitk::ScalarType max = inputImage->GetStatistics()->GetScalarValueMax(); mitk::ScalarType min = inputImage->GetStatistics()->GetScalarValueMin(); m_Controls.m_ThresholdSlider->setMaximum(max); m_Controls.m_ThresholdSlider->setMinimum(min); // Just for initialization m_Controls.m_ThresholdSlider->setMaximumValue(max); m_Controls.m_ThresholdSlider->setMinimumValue(min); } } template static void AccessPixel(mitk::PixelType /*ptype*/, mitk::Image* im, mitk::Point3D p, int& val) { mitk::ImagePixelReadAccessor access(im); val = access.GetPixelByWorldCoordinates(p); } /**Overloaded const verison*/ template static void AccessPixel(mitk::PixelType /*ptype*/, const mitk::Image* im, mitk::Point3D p, int& val) { mitk::ImagePixelReadAccessor access(im); val = access.GetPixelByWorldCoordinates(p); } void QmitkAdaptiveRegionGrowingToolGUI::OnPointAdded() { if (m_RegionGrow3DTool.IsNull()) return; mitk::DataNode *node = m_RegionGrow3DTool->GetPointSetNode(); if (node != nullptr) { mitk::PointSet::Pointer pointSet = dynamic_cast(node->GetData()); if (pointSet.IsNull()) { QMessageBox::critical(nullptr, "QmitkAdaptiveRegionGrowingToolGUI", "PointSetNode does not contain a pointset"); return; } m_Controls.m_lblSetSeedpoint->setText(""); const mitk::Image *image = dynamic_cast(m_InputImageNode->GetData()); const auto timePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); - auto image3D = Get3DImageByTimePoint(image, timePoint); + auto image3D = GetImageByTimePoint(image, timePoint); if (nullptr == image3D) { MITK_WARN << "Cannot run segementation. Currently selected timepoint is not in the time bounds of the selected " "reference image. Time point: " << timePoint; return; } if (!pointSet->GetTimeGeometry()->IsValidTimePoint(timePoint)) return; mitk::Point3D seedPoint = pointSet ->GetPointSet(static_cast(pointSet->GetTimeGeometry()->TimePointToTimeStep(timePoint))) ->GetPoints() ->ElementAt(0); if (image3D->GetGeometry()->IsInside(seedPoint)) mitkPixelTypeMultiplex3( AccessPixel, image3D->GetChannelDescriptor().GetPixelType(), image3D, seedPoint, m_SeedpointValue) else return; /* In this case the seedpoint is placed e.g. in the lung or bronchialtree * The lowerFactor sets the windowsize depending on the regiongrowing direction */ m_CurrentRGDirectionIsUpwards = true; if (m_SeedpointValue < -500) { m_CurrentRGDirectionIsUpwards = false; } // Initializing the region by the area around the seedpoint m_SeedPointValueMean = 0; itk::Index<3> currentIndex, runningIndex; mitk::ScalarType pixelValues[125]; unsigned int pos(0); image3D->GetGeometry(0)->WorldToIndex(seedPoint, currentIndex); runningIndex = currentIndex; for (int i = runningIndex[0] - 2; i <= runningIndex[0] + 2; i++) { for (int j = runningIndex[1] - 2; j <= runningIndex[1] + 2; j++) { for (int k = runningIndex[2] - 2; k <= runningIndex[2] + 2; k++) { currentIndex[0] = i; currentIndex[1] = j; currentIndex[2] = k; if (image3D->GetGeometry()->IsIndexInside(currentIndex)) { int component = 0; m_InputImageNode->GetIntProperty("Image.Displayed Component", component); mitkPixelTypeMultiplex4(mitk::FastSinglePixelAccess, image3D->GetChannelDescriptor().GetPixelType(), image3D, nullptr, currentIndex, pixelValues[pos]); pos++; } else { pixelValues[pos] = std::numeric_limits::min(); pos++; } } } } // Now calculation mean of the pixelValues // Now calculation mean of the pixelValues unsigned int numberOfValues(0); for (auto &pixelValue : pixelValues) { if (pixelValue > std::numeric_limits::min()) { m_SeedPointValueMean += pixelValue; numberOfValues++; } } m_SeedPointValueMean = m_SeedPointValueMean / numberOfValues; mitk::ScalarType var = 0; if (numberOfValues > 1) { for (auto &pixelValue : pixelValues) { if (pixelValue > std::numeric_limits::min()) { var += (pixelValue - m_SeedPointValueMean) * (pixelValue - m_SeedPointValueMean); } } var /= numberOfValues - 1; } mitk::ScalarType stdDev = sqrt(var); /* * Here the upper- and lower threshold is calculated: * The windowSize is 20% of the maximum range of the intensity values existing in the current image * If the RG direction is upwards the lower TH is meanSeedValue-0.15*windowSize and upper TH is * meanSeedValue+0.85*windowsSize * if the RG direction is downwards the lower TH is meanSeedValue-0.85*windowSize and upper TH is * meanSeedValue+0.15*windowsSize */ const auto timeStepOfImage = image->GetTimeGeometry()->TimePointToTimeStep(timePoint); mitk::ScalarType min = image->GetStatistics()->GetScalarValueMin(timeStepOfImage); mitk::ScalarType max = image->GetStatistics()->GetScalarValueMax(timeStepOfImage); mitk::ScalarType windowSize = max - min; windowSize = 0.15 * windowSize; if (m_CurrentRGDirectionIsUpwards) { m_LOWERTHRESHOLD = m_SeedPointValueMean - stdDev; m_UPPERTHRESHOLD = m_SeedpointValue + windowSize; if (m_UPPERTHRESHOLD > max) m_UPPERTHRESHOLD = max; m_Controls.m_ThresholdSlider->setMaximumValue(m_UPPERTHRESHOLD); m_Controls.m_ThresholdSlider->setMinimumValue(m_LOWERTHRESHOLD); } else { m_UPPERTHRESHOLD = m_SeedPointValueMean; if (m_SeedpointValue > m_SeedPointValueMean) m_UPPERTHRESHOLD = m_SeedpointValue; m_LOWERTHRESHOLD = m_SeedpointValue - windowSize; if (m_LOWERTHRESHOLD < min) m_LOWERTHRESHOLD = min; m_Controls.m_ThresholdSlider->setMinimumValue(m_LOWERTHRESHOLD); m_Controls.m_ThresholdSlider->setMaximumValue(m_UPPERTHRESHOLD); } } } -mitk::Image::ConstPointer QmitkAdaptiveRegionGrowingToolGUI::Get3DImageByTimePoint(const mitk::Image *image, +mitk::Image::ConstPointer QmitkAdaptiveRegionGrowingToolGUI::GetImageByTimePoint(const mitk::Image *image, mitk::TimePointType timePoint) const { if (nullptr == image) return image; if (!image->GetTimeGeometry()->IsValidTimePoint(timePoint)) return nullptr; if (image->GetDimension() != 4) return image; auto imageTimeSelector = mitk::ImageTimeSelector::New(); imageTimeSelector->SetInput(image); imageTimeSelector->SetTimeNr(static_cast(image->GetTimeGeometry()->TimePointToTimeStep(timePoint))); imageTimeSelector->UpdateLargestPossibleRegion(); return imageTimeSelector->GetOutput(); } void QmitkAdaptiveRegionGrowingToolGUI::RunSegmentation() { if (m_InputImageNode.IsNull()) { QMessageBox::information(nullptr, "Adaptive Region Growing functionality", "Please specify the image in Datamanager!"); return; } mitk::DataNode::Pointer node = m_RegionGrow3DTool->GetPointSetNode(); if (node.IsNull()) { QMessageBox::information(nullptr, "Adaptive Region Growing functionality", "Please insert a seed point inside the " "image.\n\nFirst press the \"Define Seed " "Point\" button,\nthen click left mouse " "button inside the image."); return; } // safety if no pointSet or pointSet empty mitk::PointSet::Pointer seedPointSet = dynamic_cast(node->GetData()); if (seedPointSet.IsNull()) { m_Controls.m_pbRunSegmentation->setEnabled(true); QMessageBox::information( nullptr, "Adaptive Region Growing functionality", "The seed point is empty! Please choose a new seed point."); return; } mitk::Image::Pointer orgImage = dynamic_cast(m_InputImageNode->GetData()); if (orgImage.IsNotNull()) { const auto timePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); if (!seedPointSet->GetTimeGeometry()->IsValidTimePoint(timePoint)) mitkThrow() << "Point set is not defined for specified time point. Time point: " << timePoint; int timeStep = static_cast(seedPointSet->GetTimeGeometry()->TimePointToTimeStep(timePoint)); if (!(seedPointSet->GetSize(timeStep))) { m_Controls.m_pbRunSegmentation->setEnabled(true); QMessageBox::information( nullptr, "Adaptive Region Growing functionality", "The seed point is empty! Please choose a new seed point."); return; } QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); mitk::PointSet::PointType seedPoint = seedPointSet->GetPointSet(timeStep)->GetPoints()->Begin().Value(); - auto image3D = Get3DImageByTimePoint(orgImage, timePoint); + auto image3D = GetImageByTimePoint(orgImage, timePoint); if (image3D.IsNotNull()) { // QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); //set the cursor to waiting AccessByItk_2(image3D, StartRegionGrowing, image3D->GetGeometry(), seedPoint); // QApplication::restoreOverrideCursor();//reset cursor } else { QApplication::restoreOverrideCursor(); // reset cursor QMessageBox::information( nullptr, "Adaptive Region Growing functionality", "Only images of dimension 3 or 4 can be processed!"); return; } } EnableControls(true); // Segmentation ran successfully, so enable all controls. node->SetVisibility(true); QApplication::restoreOverrideCursor(); // reset cursor } template void QmitkAdaptiveRegionGrowingToolGUI::StartRegionGrowing(const itk::Image *itkImage, const mitk::BaseGeometry *imageGeometry, const mitk::PointSet::PointType seedPoint) { typedef itk::Image InputImageType; typedef typename InputImageType::IndexType IndexType; typedef itk::ConnectedAdaptiveThresholdImageFilter RegionGrowingFilterType; typename RegionGrowingFilterType::Pointer regionGrower = RegionGrowingFilterType::New(); typedef itk::BinaryThresholdImageFilter ThresholdFilterType; typedef itk::MaskImageFilter MaskImageFilterType; if (!imageGeometry->IsInside(seedPoint)) { QApplication::restoreOverrideCursor(); // reset cursor to be able to click ok with the regular mouse cursor QMessageBox::information(nullptr, "Segmentation functionality", "The seed point is outside of the image! Please choose a position inside the image!"); return; } IndexType seedIndex; imageGeometry->WorldToIndex(seedPoint, seedIndex); // convert world coordinates to image indices if (m_SeedpointValue > m_UPPERTHRESHOLD || m_SeedpointValue < m_LOWERTHRESHOLD) { QApplication::restoreOverrideCursor(); // reset cursor to be able to click ok with the regular mouse cursor QMessageBox::information( nullptr, "Segmentation functionality", "The seed point is outside the defined thresholds! Please set a new seed point or adjust the thresholds."); MITK_INFO << "Mean: " << m_SeedPointValueMean; return; } // Setting the direction of the regiongrowing. For dark structures e.g. the lung the regiongrowing // is performed starting at the upper value going to the lower one regionGrower->SetGrowingDirectionIsUpwards(m_CurrentRGDirectionIsUpwards); regionGrower->SetInput(itkImage); regionGrower->AddSeed(seedIndex); // In some cases we have to subtract 1 for the lower threshold and add 1 to the upper. // Otherwise no region growing is done. Maybe a bug in the ConnectiveAdaptiveThresholdFilter regionGrower->SetLower(m_LOWERTHRESHOLD - 1); regionGrower->SetUpper(m_UPPERTHRESHOLD + 1); try { regionGrower->Update(); } catch (itk::ExceptionObject &exc) { QMessageBox errorInfo; errorInfo.setWindowTitle("Adaptive RG Segmentation Functionality"); errorInfo.setIcon(QMessageBox::Critical); errorInfo.setText("An error occurred during region growing!"); errorInfo.setDetailedText(exc.what()); errorInfo.exec(); return; // can't work } catch (...) { QMessageBox::critical(nullptr, "Adaptive RG Segmentation Functionality", "An error occurred during region growing!"); return; } mitk::Image::Pointer resultImage = mitk::ImportItkImage(regionGrower->GetOutput())->Clone(); // initialize slider m_Controls.m_PreviewSlider->setMinimum(m_LOWERTHRESHOLD); mitk::ScalarType max = m_SeedpointValue + resultImage->GetStatistics()->GetScalarValueMax(); if (max < m_UPPERTHRESHOLD) m_Controls.m_PreviewSlider->setMaximum(max); else m_Controls.m_PreviewSlider->setMaximum(m_UPPERTHRESHOLD); this->m_DetectedLeakagePoint = regionGrower->GetLeakagePoint(); if (m_CurrentRGDirectionIsUpwards) { m_Controls.m_PreviewSlider->setValue(m_SeedPointValueMean - 1); } else { m_Controls.m_PreviewSlider->setValue(m_SeedPointValueMean + 1); } this->m_SliderInitialized = true; // create new node and then delete the old one if there is one mitk::DataNode::Pointer newNode = mitk::DataNode::New(); newNode->SetData(resultImage); // set some properties newNode->SetProperty("name", mitk::StringProperty::New(m_NAMEFORLABLEDSEGMENTATIONIMAGE)); newNode->SetProperty("helper object", mitk::BoolProperty::New(true)); newNode->SetProperty("color", mitk::ColorProperty::New(0.0, 1.0, 0.0)); newNode->SetProperty("layer", mitk::IntProperty::New(1)); newNode->SetProperty("opacity", mitk::FloatProperty::New(0.7)); // delete the old image, if there was one: mitk::DataNode::Pointer binaryNode = m_DataStorage->GetNamedNode(m_NAMEFORLABLEDSEGMENTATIONIMAGE); m_DataStorage->Remove(binaryNode); // now add result to data tree m_DataStorage->Add(newNode, m_InputImageNode); typename InputImageType::Pointer inputImageItk; mitk::CastToItkImage(resultImage, inputImageItk); // volume rendering preview masking typename ThresholdFilterType::Pointer thresholdFilter = ThresholdFilterType::New(); thresholdFilter->SetInput(inputImageItk); thresholdFilter->SetInsideValue(1); thresholdFilter->SetOutsideValue(0); double sliderVal = this->m_Controls.m_PreviewSlider->value(); if (m_CurrentRGDirectionIsUpwards) { thresholdFilter->SetLowerThreshold(sliderVal); thresholdFilter->SetUpperThreshold(itk::NumericTraits::max()); } else { thresholdFilter->SetLowerThreshold(itk::NumericTraits::min()); thresholdFilter->SetUpperThreshold(sliderVal); } thresholdFilter->SetInPlace(false); typename MaskImageFilterType::Pointer maskFilter = MaskImageFilterType::New(); maskFilter->SetInput(inputImageItk); maskFilter->SetInPlace(false); maskFilter->SetMaskImage(thresholdFilter->GetOutput()); maskFilter->SetOutsideValue(0); maskFilter->UpdateLargestPossibleRegion(); mitk::Image::Pointer mitkMask; mitk::CastToMitkImage(maskFilter->GetOutput(), mitkMask); mitk::DataNode::Pointer maskedNode = mitk::DataNode::New(); maskedNode->SetData(mitkMask); // set some properties maskedNode->SetProperty("name", mitk::StringProperty::New(m_NAMEFORMASKEDSEGMENTATION)); maskedNode->SetProperty("helper object", mitk::BoolProperty::New(true)); maskedNode->SetProperty("color", mitk::ColorProperty::New(0.0, 1.0, 0.0)); maskedNode->SetProperty("layer", mitk::IntProperty::New(1)); maskedNode->SetProperty("opacity", mitk::FloatProperty::New(0.0)); // delete the old image, if there was one: mitk::DataNode::Pointer deprecatedMask = m_DataStorage->GetNamedNode(m_NAMEFORMASKEDSEGMENTATION); m_DataStorage->Remove(deprecatedMask); // now add result to data tree m_DataStorage->Add(maskedNode, m_InputImageNode); this->InitializeLevelWindow(); if (m_UseVolumeRendering) this->EnableVolumeRendering(true); m_UpdateSuggestedThreshold = true; // reset first stored threshold value // Setting progress to finished mitk::ProgressBar::GetInstance()->Progress(357); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkAdaptiveRegionGrowingToolGUI::InitializeLevelWindow() { // get the preview from the datatree mitk::DataNode::Pointer newNode = m_DataStorage->GetNamedNode(m_NAMEFORLABLEDSEGMENTATIONIMAGE); mitk::LevelWindow tempLevelWindow; newNode->GetLevelWindow(tempLevelWindow, nullptr, "levelwindow"); mitk::ScalarType *level = new mitk::ScalarType(0.0); mitk::ScalarType *window = new mitk::ScalarType(1.0); int upper; if (m_CurrentRGDirectionIsUpwards) { upper = m_UPPERTHRESHOLD - m_SeedpointValue; } else { upper = m_SeedpointValue - m_LOWERTHRESHOLD; } tempLevelWindow.SetRangeMinMax(mitk::ScalarType(0), mitk::ScalarType(upper)); // get the suggested threshold from the detected leakage-point and adjust the slider if (m_CurrentRGDirectionIsUpwards) { this->m_Controls.m_PreviewSlider->setValue(m_SeedpointValue); *level = m_UPPERTHRESHOLD - (m_SeedpointValue) + 0.5; } else { this->m_Controls.m_PreviewSlider->setValue(m_SeedpointValue); *level = (m_SeedpointValue)-m_LOWERTHRESHOLD + 0.5; } tempLevelWindow.SetLevelWindow(*level, *window); newNode->SetLevelWindow(tempLevelWindow, nullptr, "levelwindow"); // update the widgets mitk::RenderingManager::GetInstance()->RequestUpdateAll(); m_SliderInitialized = true; // inquiry need to fix bug#1828 static int lastSliderPosition = 0; if ((this->m_SeedpointValue + this->m_DetectedLeakagePoint - 1) == lastSliderPosition) { this->ChangeLevelWindow(lastSliderPosition); } lastSliderPosition = this->m_SeedpointValue + this->m_DetectedLeakagePoint - 1; if (m_UseVolumeRendering) this->UpdateVolumeRenderingThreshold((int)(*level + 0.5)); // lower threshold for labeled image } void QmitkAdaptiveRegionGrowingToolGUI::ChangeLevelWindow(double newValue) { if (m_SliderInitialized) { // do nothing, if no preview exists mitk::DataNode::Pointer newNode = m_DataStorage->GetNamedNode(m_NAMEFORLABLEDSEGMENTATIONIMAGE); if (newNode.IsNull()) return; mitk::LevelWindow tempLevelWindow; newNode->GetLevelWindow(tempLevelWindow, nullptr, "levelwindow"); // get the levelWindow associated with the preview mitk::ScalarType level; // = this->m_UPPERTHRESHOLD - newValue + 0.5; mitk::ScalarType *window = new mitk::ScalarType(1); // adjust the levelwindow according to the position of the slider (newvalue) if (m_CurrentRGDirectionIsUpwards) { level = m_UPPERTHRESHOLD - newValue + 0.5; tempLevelWindow.SetLevelWindow(level, *window); } else { level = newValue - m_LOWERTHRESHOLD + 0.5; tempLevelWindow.SetLevelWindow(level, *window); } newNode->SetLevelWindow(tempLevelWindow, nullptr, "levelwindow"); if (m_UseVolumeRendering) this->UpdateVolumeRenderingThreshold((int)(level - 0.5)); // lower threshold for labeled image newNode->SetVisibility(true); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkAdaptiveRegionGrowingToolGUI::DecreaseSlider() { // moves the slider one step to the left, when the "-"-button is pressed if (this->m_Controls.m_PreviewSlider->value() != this->m_Controls.m_PreviewSlider->minimum()) { int newValue = this->m_Controls.m_PreviewSlider->value() - 1; this->ChangeLevelWindow(newValue); this->m_Controls.m_PreviewSlider->setValue(newValue); } } void QmitkAdaptiveRegionGrowingToolGUI::IncreaseSlider() { // moves the slider one step to the right, when the "+"-button is pressed if (this->m_Controls.m_PreviewSlider->value() != this->m_Controls.m_PreviewSlider->maximum()) { int newValue = this->m_Controls.m_PreviewSlider->value() + 1; this->ChangeLevelWindow(newValue); this->m_Controls.m_PreviewSlider->setValue(newValue); } } void QmitkAdaptiveRegionGrowingToolGUI::ConfirmSegmentation() { // get image node if (m_InputImageNode.IsNull()) { QMessageBox::critical(nullptr, "Adaptive region growing functionality", "Please specify the image in Datamanager!"); return; } // get image data mitk::Image::Pointer orgImage = dynamic_cast(m_InputImageNode->GetData()); if (orgImage.IsNull()) { QMessageBox::critical(nullptr, "Adaptive region growing functionality", "No Image found!"); return; } // get labeled segmentation mitk::Image::Pointer labeledSeg = (mitk::Image *)m_DataStorage->GetNamedObject(m_NAMEFORLABLEDSEGMENTATIONIMAGE); if (labeledSeg.IsNull()) { QMessageBox::critical(nullptr, "Adaptive region growing functionality", "No Segmentation Preview found!"); return; } mitk::DataNode::Pointer newNode = m_DataStorage->GetNamedNode(m_NAMEFORLABLEDSEGMENTATIONIMAGE); if (newNode.IsNull()) return; QmitkConfirmSegmentationDialog dialog; QString segName = QString::fromStdString(m_RegionGrow3DTool->GetCurrentSegmentationName()); dialog.SetSegmentationName(segName); int result = dialog.exec(); switch (result) { case QmitkConfirmSegmentationDialog::CREATE_NEW_SEGMENTATION: m_RegionGrow3DTool->SetOverwriteExistingSegmentation(false); break; case QmitkConfirmSegmentationDialog::OVERWRITE_SEGMENTATION: m_RegionGrow3DTool->SetOverwriteExistingSegmentation(true); break; case QmitkConfirmSegmentationDialog::CANCEL_SEGMENTATION: return; } mitk::Image::Pointer img = dynamic_cast(newNode->GetData()); AccessByItk(img, ITKThresholding); // disable volume rendering preview after the segmentation node was created this->EnableVolumeRendering(false); newNode->SetVisibility(false); m_Controls.m_cbVolumeRendering->setChecked(false); // TODO disable slider etc... if (m_RegionGrow3DTool.IsNotNull()) { m_RegionGrow3DTool->ConfirmSegmentation(); } } template void QmitkAdaptiveRegionGrowingToolGUI::ITKThresholding(itk::Image *itkImage) { mitk::Image::Pointer originalSegmentation = dynamic_cast(this->m_RegionGrow3DTool->GetTargetSegmentationNode()->GetData()); const auto timePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); if (!originalSegmentation->GetTimeGeometry()->IsValidTimePoint(timePoint)) mitkThrow() << "Segmentation is not defined for specified time point. Time point: " << timePoint; int timeStep = static_cast(originalSegmentation->GetTimeGeometry()->TimePointToTimeStep(timePoint)); if (originalSegmentation) { typedef itk::Image InputImageType; typedef itk::Image SegmentationType; // select single 3D volume if we have more than one time step typename SegmentationType::Pointer originalSegmentationInITK = SegmentationType::New(); if (originalSegmentation->GetTimeGeometry()->CountTimeSteps() > 1) { mitk::ImageTimeSelector::Pointer timeSelector = mitk::ImageTimeSelector::New(); timeSelector->SetInput(originalSegmentation); timeSelector->SetTimeNr(timeStep); timeSelector->UpdateLargestPossibleRegion(); CastToItkImage(timeSelector->GetOutput(), originalSegmentationInITK); } else // use original { CastToItkImage(originalSegmentation, originalSegmentationInITK); } // Fill current preiview image in segmentation image originalSegmentationInITK->FillBuffer(0); itk::ImageRegionIterator itOutput(originalSegmentationInITK, originalSegmentationInITK->GetLargestPossibleRegion()); itk::ImageRegionIterator itInput(itkImage, itkImage->GetLargestPossibleRegion()); itOutput.GoToBegin(); itInput.GoToBegin(); // calculate threhold from slider value int currentTreshold = 0; if (m_CurrentRGDirectionIsUpwards) { currentTreshold = m_UPPERTHRESHOLD - m_Controls.m_PreviewSlider->value() + 1; } else { currentTreshold = m_Controls.m_PreviewSlider->value() - m_LOWERTHRESHOLD; } // iterate over image and set pixel in segmentation according to thresholded labeled image while (!itOutput.IsAtEnd() && !itInput.IsAtEnd()) { // Use threshold slider to determine if pixel is set to 1 if (itInput.Value() != 0 && itInput.Value() >= static_cast::PixelType>(currentTreshold)) { itOutput.Set(1); } ++itOutput; ++itInput; } // combine current working segmentation image with our region growing result originalSegmentation->SetVolume((void *)(originalSegmentationInITK->GetPixelContainer()->GetBufferPointer()), timeStep); originalSegmentation->Modified(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkAdaptiveRegionGrowingToolGUI::EnableControls(bool enable) { if (m_RegionGrow3DTool.IsNull()) return; // Check if seed point is already set, if not leave RunSegmentation disabled // if even m_DataStorage is nullptr leave node nullptr mitk::DataNode::Pointer node = m_RegionGrow3DTool->GetPointSetNode(); if (node.IsNull()) { this->m_Controls.m_pbRunSegmentation->setEnabled(false); } else { this->m_Controls.m_pbRunSegmentation->setEnabled(enable); } // Check if a segmentation exists, if not leave segmentation dependent disabled. // if even m_DataStorage is nullptr leave node nullptr node = m_DataStorage ? m_DataStorage->GetNamedNode(m_NAMEFORLABLEDSEGMENTATIONIMAGE) : nullptr; if (node.IsNull()) { this->m_Controls.m_PreviewSlider->setEnabled(false); this->m_Controls.m_pbConfirmSegementation->setEnabled(false); } else { this->m_Controls.m_PreviewSlider->setEnabled(enable); this->m_Controls.m_pbConfirmSegementation->setEnabled(enable); } this->m_Controls.m_cbVolumeRendering->setEnabled(enable); } void QmitkAdaptiveRegionGrowingToolGUI::EnableVolumeRendering(bool enable) { mitk::DataNode::Pointer node = m_DataStorage->GetNamedNode(m_NAMEFORMASKEDSEGMENTATION); if (node.IsNull()) return; if (enable) { node->SetBoolProperty("volumerendering", enable); node->SetBoolProperty("volumerendering.uselod", true); } else { node->SetBoolProperty("volumerendering", enable); } double val = this->m_Controls.m_PreviewSlider->value(); this->ChangeLevelWindow(val); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkAdaptiveRegionGrowingToolGUI::UpdateVolumeRenderingThreshold(int) { typedef short PixelType; typedef itk::Image InputImageType; typedef itk::BinaryThresholdImageFilter ThresholdFilterType; typedef itk::MaskImageFilter MaskImageFilterType; mitk::DataNode::Pointer grownImageNode = m_DataStorage->GetNamedNode(m_NAMEFORLABLEDSEGMENTATIONIMAGE); mitk::Image::Pointer grownImage = dynamic_cast(grownImageNode->GetData()); if (!grownImage) { MITK_ERROR << "Missing data node for labeled segmentation image."; return; } InputImageType::Pointer itkGrownImage; mitk::CastToItkImage(grownImage, itkGrownImage); ThresholdFilterType::Pointer thresholdFilter = ThresholdFilterType::New(); thresholdFilter->SetInput(itkGrownImage); thresholdFilter->SetInPlace(false); double sliderVal = this->m_Controls.m_PreviewSlider->value(); PixelType threshold = itk::NumericTraits::min(); if (m_CurrentRGDirectionIsUpwards) { threshold = static_cast(m_UPPERTHRESHOLD - sliderVal + 0.5); thresholdFilter->SetLowerThreshold(threshold); thresholdFilter->SetUpperThreshold(itk::NumericTraits::max()); } else { threshold = sliderVal - m_LOWERTHRESHOLD + 0.5; thresholdFilter->SetLowerThreshold(itk::NumericTraits::min()); thresholdFilter->SetUpperThreshold(threshold); } thresholdFilter->UpdateLargestPossibleRegion(); MaskImageFilterType::Pointer maskFilter = MaskImageFilterType::New(); maskFilter->SetInput(itkGrownImage); maskFilter->SetInPlace(false); maskFilter->SetMaskImage(thresholdFilter->GetOutput()); maskFilter->SetOutsideValue(0); maskFilter->UpdateLargestPossibleRegion(); mitk::Image::Pointer mitkMaskedImage; mitk::CastToMitkImage(maskFilter->GetOutput(), mitkMaskedImage); mitk::DataNode::Pointer maskNode = m_DataStorage->GetNamedNode(m_NAMEFORMASKEDSEGMENTATION); maskNode->SetData(mitkMaskedImage); } void QmitkAdaptiveRegionGrowingToolGUI::UseVolumeRendering(bool on) { m_UseVolumeRendering = on; this->EnableVolumeRendering(on); } void QmitkAdaptiveRegionGrowingToolGUI::SetLowerThresholdValue(double lowerThreshold) { m_LOWERTHRESHOLD = lowerThreshold; } void QmitkAdaptiveRegionGrowingToolGUI::SetUpperThresholdValue(double upperThreshold) { m_UPPERTHRESHOLD = upperThreshold; } void QmitkAdaptiveRegionGrowingToolGUI::Deactivated() { // make the segmentation preview node invisible mitk::DataNode::Pointer node = m_DataStorage->GetNamedNode(m_NAMEFORLABLEDSEGMENTATIONIMAGE); if (node.IsNotNull()) { node->SetVisibility(false); } // disable volume rendering preview after the segmentation node was created this->EnableVolumeRendering(false); m_Controls.m_cbVolumeRendering->setChecked(false); } void QmitkAdaptiveRegionGrowingToolGUI::Activated() { } diff --git a/Modules/SegmentationUI/Qmitk/QmitkAdaptiveRegionGrowingToolGUI.h b/Modules/SegmentationUI/Qmitk/QmitkAdaptiveRegionGrowingToolGUI.h index 12d71f09b4..c0d645772d 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkAdaptiveRegionGrowingToolGUI.h +++ b/Modules/SegmentationUI/Qmitk/QmitkAdaptiveRegionGrowingToolGUI.h @@ -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. ============================================================================*/ #ifndef QMITK_QmitkAdaptiveRegionGrowingToolGUI_H #define QMITK_QmitkAdaptiveRegionGrowingToolGUI_H #include "itkImage.h" #include "mitkDataStorage.h" #include "mitkGeometry3D.h" #include "mitkPointSet.h" #include "qwidget.h" #include "ui_QmitkAdaptiveRegionGrowingToolGUIControls.h" #include #include "QmitkToolGUI.h" #include "mitkAdaptiveRegionGrowingTool.h" class DataNode; class QmitkAdaptiveRegionGrowingToolGUIControls; /*! * * \brief QmitkAdaptiveRegionGrowingToolGUI * * Adaptive Region Growing View class of the segmentation. * */ class MITKSEGMENTATIONUI_EXPORT QmitkAdaptiveRegionGrowingToolGUI : public QmitkToolGUI { Q_OBJECT public: /** * @brief mitkClassMacro */ mitkClassMacro(QmitkAdaptiveRegionGrowingToolGUI, QmitkToolGUI); itkFactorylessNewMacro(Self); itkCloneMacro(Self); QmitkAdaptiveRegionGrowingToolGUI(QWidget *parent = nullptr); /** \brief Method to create the connections for the component. This Method is obligatory even if no connections is * needed*/ virtual void CreateConnections(); ///** \brief Method to set the default data storage.*/ virtual void SetDataStorage(mitk::DataStorage *dataStorage); /** * @brief Method to set the name of a data node. * @param labledSegmentation Name of the labeled segmentation * @param binaryImage Name of the binary image * @param surface Name of the surface */ void SetDataNodeNames(std::string labledSegmentation, std::string binaryImage, /*std::string vesselTree,*/ std::string surface, std::string maskedSegmentation); /** * @brief Method to enable/disable controls for region growing * * This method checks if a seed point is set and a segmentation exists. * @param enable/disable controls */ void EnableControls(bool enable); /** * @brief Method to set the input image node * @param data node */ void SetInputImageNode(mitk::DataNode *node); void Deactivated(); void Activated(); /** * @brief The created GUI from the .ui-File. This Attribute is obligatory */ Ui::QmitkAdaptiveRegionGrowingToolGUIControls m_Controls; protected slots: /** * @brief Method to start the segmentation * * This method is called, when the "Start Segmentation" button is clicked. */ void RunSegmentation(); /** * @brief Method to change the level window * * This method is called, when the level window slider is changed via the slider in the control widget * @param new value */ void ChangeLevelWindow(double newValue); /** * @brief Method to increase the preview slider * * This method is called, when the + button is clicked and increases the value by 1 */ void IncreaseSlider(); /** * @brief Method to decrease the preview slider * * This method is called, when the - button is clicked and decreases the value by 1 */ void DecreaseSlider(); /** * @brief Method to confirm the preview segmentation * * This method is called, when the "Confirm Segmentation" button is clicked. */ void ConfirmSegmentation(); /** * @brief Method to switch the volume rendering on/off * @param on/off */ void UseVolumeRendering(bool on); /** * @brief Method to set the lower threshold * * This method is called, when the minimum threshold slider has changed * @param lower threshold */ void SetLowerThresholdValue(double lowerThreshold); /** * @brief Method to set upper threshold * * This Method is called, when the maximum threshold slider has changed * @param upper threshold */ void SetUpperThresholdValue(double upperThreshold); /** * @brief Method to determine which tool to activate * * This method listens to the tool manager and activates this tool if requested otherwise disables this view */ void OnNewToolAssociated(mitk::Tool *); protected: mitk::AdaptiveRegionGrowingTool::Pointer m_RegionGrow3DTool; /** \brief Destructor. */ ~QmitkAdaptiveRegionGrowingToolGUI() override; mitk::DataStorage *m_DataStorage; mitk::DataNode::Pointer m_InputImageNode; /** * @brief Method to calculate parameter settings, when a seed point is set */ void OnPointAdded(); /** * @brief Method to extract a 3D image based on a given time point that can be taken from the SliceNavigationController * * This ensures that the seed point is taken from the current selected 3D image */ - mitk::Image::ConstPointer Get3DImageByTimePoint(const mitk::Image *image, + mitk::Image::ConstPointer GetImageByTimePoint(const mitk::Image *image, mitk::TimePointType timePoint) const; private: std::string m_NAMEFORORGIMAGE; std::string m_NAMEFORLABLEDSEGMENTATIONIMAGE; std::string m_NAMEFORBINARYIMAGE; std::string m_NAMEFORSURFACE; std::string m_NAMEFORMASKEDSEGMENTATION; mitk::ScalarType m_LOWERTHRESHOLD; // Hounsfield value mitk::ScalarType m_UPPERTHRESHOLD; // Hounsfield value mitk::ScalarType m_SeedPointValueMean; void RemoveHelperNodes(); int m_DetectedLeakagePoint; bool m_CurrentRGDirectionIsUpwards; // defines fixed threshold (true = LOWERTHRESHOLD fixed, false = UPPERTHRESHOLD // fixed) int m_SeedpointValue; bool m_SliderInitialized; bool m_UseVolumeRendering; bool m_UpdateSuggestedThreshold; float m_SuggestedThValue; long m_PointSetAddObserverTag; long m_PointSetMoveObserverTag; template void StartRegionGrowing(const itk::Image *itkImage, const mitk::BaseGeometry *imageGeometry, const mitk::PointSet::PointType seedPoint); template void ITKThresholding(itk::Image *inputImage); void InitializeLevelWindow(); void EnableVolumeRendering(bool enable); void UpdateVolumeRenderingThreshold(int thValue); }; #endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdToolGUI.cpp b/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdToolGUI.cpp index d26c729fa2..7c720d4a58 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdToolGUI.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdToolGUI.cpp @@ -1,144 +1,168 @@ /*============================================================================ 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 "QmitkBinaryThresholdToolGUI.h" #include #include #include #include +#include MITK_TOOL_GUI_MACRO(MITKSEGMENTATIONUI_EXPORT, QmitkBinaryThresholdToolGUI, "") QmitkBinaryThresholdToolGUI::QmitkBinaryThresholdToolGUI() : QmitkToolGUI() { // create the visible widgets QBoxLayout *mainLayout = new QVBoxLayout(this); QLabel *label = new QLabel("Threshold :", this); QFont f = label->font(); f.setBold(false); label->setFont(f); mainLayout->addWidget(label); QBoxLayout *layout = new QHBoxLayout(); m_ThresholdSlider = new ctkSliderWidget(); connect( m_ThresholdSlider, SIGNAL(valueChanged(double)), this, SLOT(OnSliderValueChanged(double))); layout->addWidget(m_ThresholdSlider); mainLayout->addLayout(layout); m_ThresholdSlider->setSingleStep(0.01); QPushButton *okButton = new QPushButton("Confirm Segmentation", this); connect(okButton, SIGNAL(clicked()), this, SLOT(OnAcceptThresholdPreview())); okButton->setFont(f); mainLayout->addWidget(okButton); m_CheckProcessAll = new QCheckBox("Process all time steps", this); m_CheckProcessAll->setChecked(false); + m_CheckProcessAll->setToolTip("Process/overwrite all time steps of the dynamic segmentation and not just the currently visible time step."); + mainLayout->addWidget(m_CheckProcessAll); m_CheckCreateNew = new QCheckBox("Create as new segmentation", this); m_CheckCreateNew->setChecked(false); + m_CheckCreateNew->setToolTip("Add the confirmed segmentation as a new segmentation instead of overwriting the currently selected."); mainLayout->addWidget(m_CheckCreateNew); connect(this, SIGNAL(NewToolAssociated(mitk::Tool *)), this, SLOT(OnNewToolAssociated(mitk::Tool *))); } QmitkBinaryThresholdToolGUI::~QmitkBinaryThresholdToolGUI() { if (m_BinaryThresholdTool.IsNotNull()) { + m_BinaryThresholdTool->CurrentlyBusy -= + mitk::MessageDelegate1(this, &QmitkBinaryThresholdToolGUI::BusyStateChanged); m_BinaryThresholdTool->IntervalBordersChanged -= mitk::MessageDelegate3( this, &QmitkBinaryThresholdToolGUI::OnThresholdingIntervalBordersChanged); m_BinaryThresholdTool->ThresholdingValuesChanged -= mitk::MessageDelegate2( this, &QmitkBinaryThresholdToolGUI::OnThresholdingValuesChanged); } } void QmitkBinaryThresholdToolGUI::OnNewToolAssociated(mitk::Tool *tool) { if (m_BinaryThresholdTool.IsNotNull()) { + m_BinaryThresholdTool->CurrentlyBusy -= + mitk::MessageDelegate1(this, &QmitkBinaryThresholdToolGUI::BusyStateChanged); m_BinaryThresholdTool->IntervalBordersChanged -= mitk::MessageDelegate3( this, &QmitkBinaryThresholdToolGUI::OnThresholdingIntervalBordersChanged); m_BinaryThresholdTool->ThresholdingValuesChanged -= mitk::MessageDelegate2( this, &QmitkBinaryThresholdToolGUI::OnThresholdingValuesChanged); } m_BinaryThresholdTool = dynamic_cast(tool); if (m_BinaryThresholdTool.IsNotNull()) { + m_BinaryThresholdTool->CurrentlyBusy += + mitk::MessageDelegate1(this, &QmitkBinaryThresholdToolGUI::BusyStateChanged); m_BinaryThresholdTool->IntervalBordersChanged += mitk::MessageDelegate3( this, &QmitkBinaryThresholdToolGUI::OnThresholdingIntervalBordersChanged); m_BinaryThresholdTool->ThresholdingValuesChanged += mitk::MessageDelegate2( this, &QmitkBinaryThresholdToolGUI::OnThresholdingValuesChanged); m_BinaryThresholdTool->SetOverwriteExistingSegmentation(true); m_CheckProcessAll->setVisible(m_BinaryThresholdTool->GetTargetSegmentationNode()->GetData()->GetTimeSteps()>1); } } void QmitkBinaryThresholdToolGUI::OnAcceptThresholdPreview() { if (m_BinaryThresholdTool.IsNotNull()) { if (m_CheckCreateNew->isChecked()) { m_BinaryThresholdTool->SetOverwriteExistingSegmentation(false); } else { m_BinaryThresholdTool->SetOverwriteExistingSegmentation(true); } m_BinaryThresholdTool->SetCreateAllTimeSteps(m_CheckProcessAll->isChecked()); this->thresholdAccepted(); - m_BinaryThresholdTool->AcceptCurrentThresholdValue(); + m_BinaryThresholdTool->ConfirmSegmentation(); } } void QmitkBinaryThresholdToolGUI::OnThresholdingIntervalBordersChanged(double lower, double upper, bool isFloat) { m_InternalUpdate = true; if (!isFloat) { m_ThresholdSlider->setRange(int(lower), int(upper)); m_ThresholdSlider->setSingleStep(1); m_ThresholdSlider->setDecimals(0); } else { m_ThresholdSlider->setRange(lower, upper); } m_InternalUpdate = false; } void QmitkBinaryThresholdToolGUI::OnThresholdingValuesChanged(mitk::ScalarType lower, mitk::ScalarType /*upper*/) { m_ThresholdSlider->setValue(lower); } void QmitkBinaryThresholdToolGUI::OnSliderValueChanged(double value) { if (m_BinaryThresholdTool.IsNotNull() && !m_InternalUpdate) { m_BinaryThresholdTool->SetThresholdValue(value); } } + +void QmitkBinaryThresholdToolGUI::BusyStateChanged(bool value) +{ + if (value) + { + QApplication::setOverrideCursor(QCursor(Qt::BusyCursor)); + } + else + { + QApplication::restoreOverrideCursor(); + } + + m_ThresholdSlider->setEnabled(!value); +} diff --git a/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdToolGUI.h b/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdToolGUI.h index 3d1e396cd2..5696c1ff98 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdToolGUI.h +++ b/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdToolGUI.h @@ -1,78 +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. ============================================================================*/ #ifndef QmitkBinaryThresholdToolGUI_h_Included #define QmitkBinaryThresholdToolGUI_h_Included #include "QmitkToolGUI.h" #include "mitkBinaryThresholdTool.h" #include #include "ctkSliderWidget.h" #include /** \ingroup org_mitk_gui_qt_interactivesegmentation_internal \brief GUI for mitk::BinaryThresholdTool. This GUI shows a slider to change the tool's threshold and an OK button to accept a preview for actual thresholding. There is only a slider for INT values in QT. So, if the working image has a float/double pixeltype, we need to convert the original float intensity into a respective int value for the slider. The slider range is then between 0 and 99. If the pixeltype is INT, then we do not need any conversion. Last contributor: $Author$ */ class MITKSEGMENTATIONUI_EXPORT QmitkBinaryThresholdToolGUI : public QmitkToolGUI { Q_OBJECT public: mitkClassMacro(QmitkBinaryThresholdToolGUI, QmitkToolGUI); itkFactorylessNewMacro(Self); itkCloneMacro(Self); void OnThresholdingIntervalBordersChanged(double lower, double upper, bool isFloat); void OnThresholdingValuesChanged(mitk::ScalarType lower, mitk::ScalarType upper); signals: /// \brief Emitted when threshold Accepted void thresholdAccepted(); /// \brief Emitted when threshold Canceled void thresholdCanceled(); public slots: protected slots: void OnNewToolAssociated(mitk::Tool *); void OnAcceptThresholdPreview(); void OnSliderValueChanged(double value); protected: QmitkBinaryThresholdToolGUI(); ~QmitkBinaryThresholdToolGUI() override; + void BusyStateChanged(bool) override; + ctkSliderWidget* m_ThresholdSlider = nullptr; QCheckBox* m_CheckProcessAll = nullptr; QCheckBox* m_CheckCreateNew = nullptr; bool m_InternalUpdate = false; mitk::BinaryThresholdTool::Pointer m_BinaryThresholdTool; }; #endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdULToolGUI.cpp b/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdULToolGUI.cpp index 8f283ae403..5954acff4c 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdULToolGUI.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdULToolGUI.cpp @@ -1,141 +1,163 @@ /*============================================================================ 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 "QmitkBinaryThresholdULToolGUI.h" #include #include #include #include +#include MITK_TOOL_GUI_MACRO(MITKSEGMENTATIONUI_EXPORT, QmitkBinaryThresholdULToolGUI, "") QmitkBinaryThresholdULToolGUI::QmitkBinaryThresholdULToolGUI() : QmitkToolGUI() { // create the visible widgets QBoxLayout *mainLayout = new QVBoxLayout(this); QLabel *label = new QLabel("Threshold :", this); QFont f = label->font(); f.setBold(false); label->setFont(f); mainLayout->addWidget(label); QBoxLayout *layout = new QHBoxLayout(); m_DoubleThresholdSlider = new ctkRangeWidget(); connect( m_DoubleThresholdSlider, SIGNAL(valuesChanged(double, double)), this, SLOT(OnThresholdsChanged(double, double))); layout->addWidget(m_DoubleThresholdSlider); mainLayout->addLayout(layout); m_DoubleThresholdSlider->setSingleStep(0.01); QPushButton *okButton = new QPushButton("Confirm Segmentation", this); connect(okButton, SIGNAL(clicked()), this, SLOT(OnAcceptThresholdPreview())); okButton->setFont(f); mainLayout->addWidget(okButton); m_CheckProcessAll = new QCheckBox("Process all time steps", this); m_CheckProcessAll->setChecked(false); + m_CheckProcessAll->setToolTip("Process/overwrite all time steps of the dynamic segmentation and not just the currently visible time step."); mainLayout->addWidget(m_CheckProcessAll); m_CheckCreateNew = new QCheckBox("Create as new segmentation", this); m_CheckCreateNew->setChecked(false); + m_CheckCreateNew->setToolTip("Add the confirmed segmentation as a new segmentation instead of overwriting the currently selected."); mainLayout->addWidget(m_CheckCreateNew); connect(this, SIGNAL(NewToolAssociated(mitk::Tool *)), this, SLOT(OnNewToolAssociated(mitk::Tool *))); } QmitkBinaryThresholdULToolGUI::~QmitkBinaryThresholdULToolGUI() { - // !!! if (m_BinaryThresholdULTool.IsNotNull()) { + m_BinaryThresholdULTool->CurrentlyBusy -= + mitk::MessageDelegate1(this, &QmitkBinaryThresholdULToolGUI::BusyStateChanged); m_BinaryThresholdULTool->IntervalBordersChanged -= mitk::MessageDelegate3( this, &QmitkBinaryThresholdULToolGUI::OnThresholdingIntervalBordersChanged); m_BinaryThresholdULTool->ThresholdingValuesChanged -= mitk::MessageDelegate2( this, &QmitkBinaryThresholdULToolGUI::OnThresholdingValuesChanged); } } void QmitkBinaryThresholdULToolGUI::OnNewToolAssociated(mitk::Tool *tool) { if (m_BinaryThresholdULTool.IsNotNull()) { + m_BinaryThresholdULTool->CurrentlyBusy -= + mitk::MessageDelegate1(this, &QmitkBinaryThresholdULToolGUI::BusyStateChanged); m_BinaryThresholdULTool->IntervalBordersChanged -= mitk::MessageDelegate3( this, &QmitkBinaryThresholdULToolGUI::OnThresholdingIntervalBordersChanged); m_BinaryThresholdULTool->ThresholdingValuesChanged -= mitk::MessageDelegate2( this, &QmitkBinaryThresholdULToolGUI::OnThresholdingValuesChanged); } m_BinaryThresholdULTool = dynamic_cast(tool); if (m_BinaryThresholdULTool.IsNotNull()) { + m_BinaryThresholdULTool->CurrentlyBusy += + mitk::MessageDelegate1(this, &QmitkBinaryThresholdULToolGUI::BusyStateChanged); m_BinaryThresholdULTool->IntervalBordersChanged += mitk::MessageDelegate3( this, &QmitkBinaryThresholdULToolGUI::OnThresholdingIntervalBordersChanged); m_BinaryThresholdULTool->ThresholdingValuesChanged += mitk::MessageDelegate2( this, &QmitkBinaryThresholdULToolGUI::OnThresholdingValuesChanged); m_BinaryThresholdULTool->SetOverwriteExistingSegmentation(true); m_CheckProcessAll->setVisible(m_BinaryThresholdULTool->GetTargetSegmentationNode()->GetData()->GetTimeSteps() > 1); } } void QmitkBinaryThresholdULToolGUI::OnAcceptThresholdPreview() { if (m_BinaryThresholdULTool.IsNotNull()) { if (m_CheckCreateNew->isChecked()) { m_BinaryThresholdULTool->SetOverwriteExistingSegmentation(false); } else { m_BinaryThresholdULTool->SetOverwriteExistingSegmentation(true); } m_BinaryThresholdULTool->SetCreateAllTimeSteps(m_CheckProcessAll->isChecked()); - m_BinaryThresholdULTool->AcceptCurrentThresholdValue(); + m_BinaryThresholdULTool->ConfirmSegmentation(); } } void QmitkBinaryThresholdULToolGUI::OnThresholdingIntervalBordersChanged(double lower, double upper, bool isFloat) { if (!isFloat) { m_DoubleThresholdSlider->setRange(int(lower), int(upper)); m_DoubleThresholdSlider->setSingleStep(1); m_DoubleThresholdSlider->setDecimals(0); } else { m_DoubleThresholdSlider->setRange(lower, upper); } } void QmitkBinaryThresholdULToolGUI::OnThresholdingValuesChanged(mitk::ScalarType lower, mitk::ScalarType upper) { m_DoubleThresholdSlider->setValues(lower, upper); } void QmitkBinaryThresholdULToolGUI::OnThresholdsChanged(double min, double max) { m_BinaryThresholdULTool->SetThresholdValues(min, max); } + +void QmitkBinaryThresholdULToolGUI::BusyStateChanged(bool value) +{ + if (value) + { + QApplication::setOverrideCursor(QCursor(Qt::BusyCursor)); + } + else + { + QApplication::restoreOverrideCursor(); + } + + m_DoubleThresholdSlider->setEnabled(!value); +} diff --git a/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdULToolGUI.h b/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdULToolGUI.h index 3e178be366..ec0c130821 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdULToolGUI.h +++ b/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdULToolGUI.h @@ -1,66 +1,68 @@ /*============================================================================ 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 QmitkBinaryThresholdULToolGUI_h_Included #define QmitkBinaryThresholdULToolGUI_h_Included #include "QmitkToolGUI.h" #include "ctkRangeWidget.h" #include #include "mitkBinaryThresholdULTool.h" #include /** \ingroup org_mitk_gui_qt_interactivesegmentation_internal \brief GUI for mitk::BinaryThresholdTool. This GUI shows a slider to change the tool's threshold and an OK button to accept a preview for actual thresholding. Last contributor: $Author$ */ class MITKSEGMENTATIONUI_EXPORT QmitkBinaryThresholdULToolGUI : public QmitkToolGUI { Q_OBJECT public: mitkClassMacro(QmitkBinaryThresholdULToolGUI, QmitkToolGUI); itkFactorylessNewMacro(Self); itkCloneMacro(Self); void OnThresholdingIntervalBordersChanged(double lower, double upper, bool isFloat); void OnThresholdingValuesChanged(mitk::ScalarType lower, mitk::ScalarType upper); signals: public slots: protected slots: void OnNewToolAssociated(mitk::Tool *); void OnAcceptThresholdPreview(); void OnThresholdsChanged(double min, double max); protected: QmitkBinaryThresholdULToolGUI(); ~QmitkBinaryThresholdULToolGUI() override; + void BusyStateChanged(bool) override; + ctkRangeWidget *m_DoubleThresholdSlider; QCheckBox* m_CheckProcessAll = nullptr; QCheckBox* m_CheckCreateNew = nullptr; mitk::BinaryThresholdULTool::Pointer m_BinaryThresholdULTool; }; #endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkFastMarchingTool3DGUI.cpp b/Modules/SegmentationUI/Qmitk/QmitkFastMarchingTool3DGUI.cpp index 855a67e49c..b83f50a5eb 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkFastMarchingTool3DGUI.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkFastMarchingTool3DGUI.cpp @@ -1,370 +1,361 @@ /*============================================================================ 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 "QmitkFastMarchingTool3DGUI.h" #include "QmitkConfirmSegmentationDialog.h" #include "mitkBaseRenderer.h" #include "mitkStepper.h" #include #include #include #include #include #include #include #include +#include MITK_TOOL_GUI_MACRO(MITKSEGMENTATIONUI_EXPORT, QmitkFastMarchingTool3DGUI, "") -QmitkFastMarchingTool3DGUI::QmitkFastMarchingTool3DGUI() : QmitkToolGUI(), m_TimeIsConnected(false) +QmitkFastMarchingTool3DGUI::QmitkFastMarchingTool3DGUI() : QmitkToolGUI() { this->setContentsMargins(0, 0, 0, 0); // create the visible widgets QVBoxLayout *widgetLayout = new QVBoxLayout(this); widgetLayout->setContentsMargins(0, 0, 0, 0); QFont fntHelp; fntHelp.setBold(true); QLabel *lblHelp = new QLabel(this); lblHelp->setText("Press shift-click to add seeds repeatedly."); lblHelp->setFont(fntHelp); widgetLayout->addWidget(lblHelp); // Sigma controls { QHBoxLayout *hlayout = new QHBoxLayout(); hlayout->setSpacing(2); QLabel *lbl = new QLabel(this); lbl->setText("Sigma: "); hlayout->addWidget(lbl); QSpacerItem *sp2 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); hlayout->addItem(sp2); widgetLayout->addItem(hlayout); } m_slSigma = new ctkSliderWidget(this); m_slSigma->setMinimum(0.1); m_slSigma->setMaximum(5.0); m_slSigma->setPageStep(0.1); m_slSigma->setSingleStep(0.01); m_slSigma->setValue(1.0); m_slSigma->setTracking(false); m_slSigma->setToolTip("The \"sigma\" parameter in the Gradient Magnitude filter."); connect(m_slSigma, SIGNAL(valueChanged(double)), this, SLOT(OnSigmaChanged(double))); widgetLayout->addWidget(m_slSigma); // Alpha controls { QHBoxLayout *hlayout = new QHBoxLayout(); hlayout->setSpacing(2); QLabel *lbl = new QLabel(this); lbl->setText("Alpha: "); hlayout->addWidget(lbl); QSpacerItem *sp2 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); hlayout->addItem(sp2); widgetLayout->addItem(hlayout); } m_slAlpha = new ctkSliderWidget(this); m_slAlpha->setMinimum(-10); m_slAlpha->setMaximum(0); m_slAlpha->setPageStep(0.1); m_slAlpha->setSingleStep(0.01); m_slAlpha->setValue(-2.5); m_slAlpha->setTracking(false); m_slAlpha->setToolTip("The \"alpha\" parameter in the Sigmoid mapping filter."); connect(m_slAlpha, SIGNAL(valueChanged(double)), this, SLOT(OnAlphaChanged(double))); widgetLayout->addWidget(m_slAlpha); // Beta controls { QHBoxLayout *hlayout = new QHBoxLayout(); hlayout->setSpacing(2); QLabel *lbl = new QLabel(this); lbl->setText("Beta: "); hlayout->addWidget(lbl); QSpacerItem *sp2 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); hlayout->addItem(sp2); widgetLayout->addLayout(hlayout); } m_slBeta = new ctkSliderWidget(this); m_slBeta->setMinimum(0); m_slBeta->setMaximum(100); m_slBeta->setPageStep(0.1); m_slBeta->setSingleStep(0.01); m_slBeta->setValue(3.5); m_slBeta->setTracking(false); m_slBeta->setToolTip("The \"beta\" parameter in the Sigmoid mapping filter."); connect(m_slBeta, SIGNAL(valueChanged(double)), this, SLOT(OnBetaChanged(double))); widgetLayout->addWidget(m_slBeta); // stopping value controls { QHBoxLayout *hlayout = new QHBoxLayout(); hlayout->setSpacing(2); QLabel *lbl = new QLabel(this); lbl->setText("Stopping value: "); hlayout->addWidget(lbl); QSpacerItem *sp2 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); hlayout->addItem(sp2); widgetLayout->addLayout(hlayout); } m_slStoppingValue = new ctkSliderWidget(this); m_slStoppingValue->setMinimum(0); m_slStoppingValue->setMaximum(10000); m_slStoppingValue->setPageStep(10); m_slStoppingValue->setSingleStep(1); m_slStoppingValue->setValue(2000); m_slStoppingValue->setDecimals(0); m_slStoppingValue->setTracking(false); m_slStoppingValue->setToolTip("The \"stopping value\" parameter in the fast marching 3D algorithm"); connect(m_slStoppingValue, SIGNAL(valueChanged(double)), this, SLOT(OnStoppingValueChanged(double))); widgetLayout->addWidget(m_slStoppingValue); // threshold controls { QHBoxLayout *hlayout = new QHBoxLayout(); hlayout->setSpacing(2); QLabel *lbl = new QLabel(this); lbl->setText("Threshold: "); hlayout->addWidget(lbl); QSpacerItem *sp2 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); hlayout->addItem(sp2); widgetLayout->addLayout(hlayout); } m_slwThreshold = new ctkRangeWidget(this); m_slwThreshold->setMinimum(-100); m_slwThreshold->setMaximum(5000); m_slwThreshold->setMinimumValue(-100); m_slwThreshold->setMaximumValue(2000); m_slwThreshold->setDecimals(0); m_slwThreshold->setTracking(false); m_slwThreshold->setToolTip("The lower and upper thresholds for the final thresholding"); connect(m_slwThreshold, SIGNAL(valuesChanged(double, double)), this, SLOT(OnThresholdChanged(double, double))); widgetLayout->addWidget(m_slwThreshold); m_btClearSeeds = new QPushButton("Clear"); m_btClearSeeds->setToolTip("Clear current result and start over again"); m_btClearSeeds->setEnabled(false); widgetLayout->addWidget(m_btClearSeeds); connect(m_btClearSeeds, SIGNAL(clicked()), this, SLOT(OnClearSeeds())); m_btConfirm = new QPushButton("Confirm Segmentation"); m_btConfirm->setToolTip("Incorporate current result in your working session."); m_btConfirm->setEnabled(false); widgetLayout->addWidget(m_btConfirm); connect(m_btConfirm, SIGNAL(clicked()), this, SLOT(OnConfirmSegmentation())); + m_CheckProcessAll = new QCheckBox("Process all time steps", this); + m_CheckProcessAll->setChecked(false); + m_CheckProcessAll->setToolTip("Process/overwrite all time steps of the dynamic segmentation and not just the currently visible time step."); + widgetLayout->addWidget(m_CheckProcessAll); + + m_CheckCreateNew = new QCheckBox("Create as new segmentation", this); + m_CheckCreateNew->setChecked(false); + m_CheckCreateNew->setToolTip("Add the confirmed segmentation as a new segmentation instead of overwriting the currently selected."); + widgetLayout->addWidget(m_CheckCreateNew); + connect(this, SIGNAL(NewToolAssociated(mitk::Tool *)), this, SLOT(OnNewToolAssociated(mitk::Tool *))); m_slSigma->setDecimals(2); m_slBeta->setDecimals(2); m_slAlpha->setDecimals(2); this->EnableWidgets(false); } QmitkFastMarchingTool3DGUI::~QmitkFastMarchingTool3DGUI() { if (m_FastMarchingTool.IsNotNull()) { m_FastMarchingTool->CurrentlyBusy -= mitk::MessageDelegate1(this, &QmitkFastMarchingTool3DGUI::BusyStateChanged); - m_FastMarchingTool->RemoveReadyListener( - mitk::MessageDelegate(this, &QmitkFastMarchingTool3DGUI::OnFastMarchingToolReady)); } } void QmitkFastMarchingTool3DGUI::OnNewToolAssociated(mitk::Tool *tool) { if (m_FastMarchingTool.IsNotNull()) { m_FastMarchingTool->CurrentlyBusy -= mitk::MessageDelegate1(this, &QmitkFastMarchingTool3DGUI::BusyStateChanged); - m_FastMarchingTool->RemoveReadyListener( - mitk::MessageDelegate(this, &QmitkFastMarchingTool3DGUI::OnFastMarchingToolReady)); } m_FastMarchingTool = dynamic_cast(tool); if (m_FastMarchingTool.IsNotNull()) { m_FastMarchingTool->CurrentlyBusy += mitk::MessageDelegate1(this, &QmitkFastMarchingTool3DGUI::BusyStateChanged); - m_FastMarchingTool->AddReadyListener( - mitk::MessageDelegate(this, &QmitkFastMarchingTool3DGUI::OnFastMarchingToolReady)); - // listen to timestep change events - mitk::BaseRenderer::Pointer renderer; - renderer = mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget0")); - if (renderer.IsNotNull() && !m_TimeIsConnected) - { - new QmitkStepperAdapter(this, renderer->GetSliceNavigationController()->GetTime(), "stepper"); - // connect(m_TimeStepper, SIGNAL(Refetch()), this, SLOT(Refetch())); - m_TimeIsConnected = true; - } + m_FastMarchingTool->SetLowerThreshold(m_slwThreshold->minimumValue()); + m_FastMarchingTool->SetUpperThreshold(m_slwThreshold->maximumValue()); + m_FastMarchingTool->SetStoppingValue(m_slStoppingValue->value()); + m_FastMarchingTool->SetSigma(m_slSigma->value()); + m_FastMarchingTool->SetAlpha(m_slAlpha->value()); + m_FastMarchingTool->SetBeta(m_slBeta->value()); + m_FastMarchingTool->SetOverwriteExistingSegmentation(true); + m_FastMarchingTool->ClearSeeds(); + m_CheckProcessAll->setVisible(m_FastMarchingTool->GetTargetSegmentationNode()->GetData()->GetTimeSteps() > 1); } } void QmitkFastMarchingTool3DGUI::Update() { if (m_FastMarchingTool.IsNotNull()) { - m_FastMarchingTool->SetLowerThreshold(this->m_slwThreshold->minimumValue()); - m_FastMarchingTool->SetUpperThreshold(this->m_slwThreshold->maximumValue()); - m_FastMarchingTool->SetStoppingValue(this->m_slStoppingValue->value()); - m_FastMarchingTool->SetSigma(this->m_slSigma->value()); - m_FastMarchingTool->SetAlpha(this->m_slAlpha->value()); - m_FastMarchingTool->SetBeta(this->m_slBeta->value()); - m_FastMarchingTool->Update(); + m_FastMarchingTool->SetLowerThreshold(m_slwThreshold->minimumValue()); + m_FastMarchingTool->SetUpperThreshold(m_slwThreshold->maximumValue()); + m_FastMarchingTool->SetStoppingValue(m_slStoppingValue->value()); + m_FastMarchingTool->SetSigma(m_slSigma->value()); + m_FastMarchingTool->SetAlpha(m_slAlpha->value()); + m_FastMarchingTool->SetBeta(m_slBeta->value()); + m_FastMarchingTool->UpdatePreview(); } } void QmitkFastMarchingTool3DGUI::OnThresholdChanged(double lower, double upper) { if (m_FastMarchingTool.IsNotNull()) { m_FastMarchingTool->SetLowerThreshold(lower); m_FastMarchingTool->SetUpperThreshold(upper); this->Update(); } } void QmitkFastMarchingTool3DGUI::OnBetaChanged(double value) { if (m_FastMarchingTool.IsNotNull()) { m_FastMarchingTool->SetBeta(value); this->Update(); } } void QmitkFastMarchingTool3DGUI::OnSigmaChanged(double value) { if (m_FastMarchingTool.IsNotNull()) { m_FastMarchingTool->SetSigma(value); this->Update(); } } void QmitkFastMarchingTool3DGUI::OnAlphaChanged(double value) { if (m_FastMarchingTool.IsNotNull()) { m_FastMarchingTool->SetAlpha(value); this->Update(); } } void QmitkFastMarchingTool3DGUI::OnStoppingValueChanged(double value) { if (m_FastMarchingTool.IsNotNull()) { m_FastMarchingTool->SetStoppingValue(value); this->Update(); } } void QmitkFastMarchingTool3DGUI::OnConfirmSegmentation() { - QmitkConfirmSegmentationDialog dialog; - QString segName = QString::fromStdString(m_FastMarchingTool->GetCurrentSegmentationName()); - - dialog.SetSegmentationName(segName); - int result = dialog.exec(); - - switch (result) + if (m_FastMarchingTool.IsNotNull()) { - case QmitkConfirmSegmentationDialog::CREATE_NEW_SEGMENTATION: + if (m_CheckCreateNew->isChecked()) + { m_FastMarchingTool->SetOverwriteExistingSegmentation(false); - break; - case QmitkConfirmSegmentationDialog::OVERWRITE_SEGMENTATION: + } + else + { m_FastMarchingTool->SetOverwriteExistingSegmentation(true); - break; - case QmitkConfirmSegmentationDialog::CANCEL_SEGMENTATION: - return; - } - if (m_FastMarchingTool.IsNotNull()) - { + } + + m_FastMarchingTool->SetCreateAllTimeSteps(m_CheckProcessAll->isChecked()); + m_btConfirm->setEnabled(false); m_FastMarchingTool->ConfirmSegmentation(); } } -void QmitkFastMarchingTool3DGUI::SetStepper(mitk::Stepper *stepper) -{ - this->m_TimeStepper = stepper; -} - -void QmitkFastMarchingTool3DGUI::Refetch() -{ - // event from image navigator recieved - timestep has changed - m_FastMarchingTool->SetCurrentTimeStep(m_TimeStepper->GetPos()); -} - void QmitkFastMarchingTool3DGUI::OnClearSeeds() { // event from image navigator recieved - timestep has changed m_FastMarchingTool->ClearSeeds(); m_btClearSeeds->setEnabled(false); m_btConfirm->setEnabled(false); this->EnableWidgets(false); this->Update(); } void QmitkFastMarchingTool3DGUI::BusyStateChanged(bool value) { if (value) + { QApplication::setOverrideCursor(QCursor(Qt::BusyCursor)); + this->EnableWidgets(false); + } else + { QApplication::restoreOverrideCursor(); -} - -void QmitkFastMarchingTool3DGUI::OnFastMarchingToolReady() -{ - this->EnableWidgets(true); - this->m_btClearSeeds->setEnabled(true); - this->m_btConfirm->setEnabled(true); + this->EnableWidgets(true); + } } void QmitkFastMarchingTool3DGUI::EnableWidgets(bool enable) { m_slSigma->setEnabled(enable); m_slAlpha->setEnabled(enable); m_slBeta->setEnabled(enable); m_slStoppingValue->setEnabled(enable); m_slwThreshold->setEnabled(enable); + m_btClearSeeds->setEnabled(enable); + m_btConfirm->setEnabled(enable); + m_CheckCreateNew->setEnabled(enable); + m_CheckProcessAll->setEnabled(enable); } diff --git a/Modules/SegmentationUI/Qmitk/QmitkFastMarchingTool3DGUI.h b/Modules/SegmentationUI/Qmitk/QmitkFastMarchingTool3DGUI.h index 67c8204881..a02b18de12 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkFastMarchingTool3DGUI.h +++ b/Modules/SegmentationUI/Qmitk/QmitkFastMarchingTool3DGUI.h @@ -1,84 +1,79 @@ /*============================================================================ 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 QmitkFastMarchingTool3DGUI_h_Included #define QmitkFastMarchingTool3DGUI_h_Included #include "QmitkToolGUI.h" #include "mitkFastMarchingTool3D.h" #include class ctkSliderWidget; class ctkRangeWidget; class QPushButton; - -#include "QmitkStepperAdapter.h" +class QCheckBox; /** \ingroup org_mitk_gui_qt_interactivesegmentation_internal \brief GUI for mitk::FastMarchingTool. \sa mitk::FastMarchingTool */ class MITKSEGMENTATIONUI_EXPORT QmitkFastMarchingTool3DGUI : public QmitkToolGUI { Q_OBJECT public: mitkClassMacro(QmitkFastMarchingTool3DGUI, QmitkToolGUI); itkFactorylessNewMacro(Self); itkCloneMacro(Self); void OnThresholdChanged(int current); protected slots: void OnNewToolAssociated(mitk::Tool *); void OnThresholdChanged(double, double); void OnAlphaChanged(double); void OnBetaChanged(double); void OnSigmaChanged(double); void OnStoppingValueChanged(double); void OnConfirmSegmentation(); - void Refetch(); - void SetStepper(mitk::Stepper *); void OnClearSeeds(); protected: QmitkFastMarchingTool3DGUI(); ~QmitkFastMarchingTool3DGUI() override; void BusyStateChanged(bool) override; void Update(); ctkRangeWidget *m_slwThreshold; ctkSliderWidget *m_slStoppingValue; ctkSliderWidget *m_slSigma; ctkSliderWidget *m_slAlpha; ctkSliderWidget *m_slBeta; QPushButton *m_btConfirm; QPushButton *m_btClearSeeds; - mitk::FastMarchingTool3D::Pointer m_FastMarchingTool; - - bool m_TimeIsConnected; - mitk::Stepper::Pointer m_TimeStepper; + QCheckBox* m_CheckProcessAll = nullptr; + QCheckBox* m_CheckCreateNew = nullptr; - void OnFastMarchingToolReady(); + mitk::FastMarchingTool3D::Pointer m_FastMarchingTool; private: void EnableWidgets(bool); }; #endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkOtsuTool3DGUI.cpp b/Modules/SegmentationUI/Qmitk/QmitkOtsuTool3DGUI.cpp index 2d9b8f8032..031b353dab 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkOtsuTool3DGUI.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkOtsuTool3DGUI.cpp @@ -1,184 +1,214 @@ /*============================================================================ 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 "QmitkOtsuTool3DGUI.h" #include "QmitkConfirmSegmentationDialog.h" #include #include #include #include #include #include MITK_TOOL_GUI_MACRO(MITKSEGMENTATIONUI_EXPORT, QmitkOtsuTool3DGUI, "") QmitkOtsuTool3DGUI::QmitkOtsuTool3DGUI() : QmitkToolGUI(), m_NumberOfRegions(0) { m_Controls.setupUi(this); connect(m_Controls.previewButton, SIGNAL(clicked()), this, SLOT(OnSpinboxValueAccept())); connect(m_Controls.m_selectionListWidget, SIGNAL(itemSelectionChanged()), this, SLOT(OnRegionSelectionChanged())); connect(m_Controls.m_Spinbox, SIGNAL(valueChanged(int)), this, SLOT(OnRegionSpinboxChanged(int))); connect(m_Controls.m_ConfSegButton, SIGNAL(clicked()), this, SLOT(OnSegmentationRegionAccept())); connect(this, SIGNAL(NewToolAssociated(mitk::Tool *)), this, SLOT(OnNewToolAssociated(mitk::Tool *))); connect(m_Controls.advancedSettingsButton, SIGNAL(toggled(bool)), this, SLOT(OnAdvancedSettingsButtonToggled(bool))); this->OnAdvancedSettingsButtonToggled(false); } QmitkOtsuTool3DGUI::~QmitkOtsuTool3DGUI() { + if (m_OtsuTool3DTool.IsNotNull()) + { + m_OtsuTool3DTool->CurrentlyBusy -= + mitk::MessageDelegate1(this, &QmitkOtsuTool3DGUI::BusyStateChanged); + } } void QmitkOtsuTool3DGUI::OnRegionSpinboxChanged(int numberOfRegions) { // we have to change to minimum number of histogram bins accordingly int curBinValue = m_Controls.m_BinsSpinBox->value(); if (curBinValue < numberOfRegions) m_Controls.m_BinsSpinBox->setValue(numberOfRegions); } void QmitkOtsuTool3DGUI::OnRegionSelectionChanged() { m_SelectedItems = m_Controls.m_selectionListWidget->selectedItems(); - if (m_SelectedItems.size() == 0) - { - m_Controls.m_ConfSegButton->setEnabled(false); - m_OtsuTool3DTool->ShowMultiLabelResultNode(true); - return; - } - if (m_OtsuTool3DTool.IsNotNull()) { // update preview of region QList::Iterator it; std::vector regionIDs; for (it = m_SelectedItems.begin(); it != m_SelectedItems.end(); ++it) regionIDs.push_back((*it)->text().toInt()); - m_OtsuTool3DTool->UpdateBinaryPreview(regionIDs); - m_Controls.m_ConfSegButton->setEnabled(true); + + m_OtsuTool3DTool->SetSelectedRegions(regionIDs); + m_OtsuTool3DTool->UpdatePreview(); + + m_Controls.m_ConfSegButton->setEnabled(!regionIDs.empty()); } } void QmitkOtsuTool3DGUI::OnAdvancedSettingsButtonToggled(bool toggled) { m_Controls.m_ValleyCheckbox->setVisible(toggled); m_Controls.binLabel->setVisible(toggled); m_Controls.m_BinsSpinBox->setVisible(toggled); if (toggled) { - int max = m_OtsuTool3DTool->GetNumberOfBins(); + int max = m_OtsuTool3DTool->GetMaxNumberOfBins(); if (max >= m_Controls.m_BinsSpinBox->minimum()) { m_Controls.m_BinsSpinBox->setMaximum(max); } } } void QmitkOtsuTool3DGUI::OnNewToolAssociated(mitk::Tool *tool) { + if (m_OtsuTool3DTool.IsNotNull()) + { + m_OtsuTool3DTool->CurrentlyBusy -= + mitk::MessageDelegate1(this, &QmitkOtsuTool3DGUI::BusyStateChanged); + } + m_OtsuTool3DTool = dynamic_cast(tool); + + if (m_OtsuTool3DTool.IsNotNull()) + { + m_OtsuTool3DTool->CurrentlyBusy += + mitk::MessageDelegate1(this, &QmitkOtsuTool3DGUI::BusyStateChanged); + + m_OtsuTool3DTool->SetOverwriteExistingSegmentation(true); + m_OtsuTool3DTool->IsTimePointChangeAwareOff(); + m_Controls.m_CheckProcessAll->setVisible(m_OtsuTool3DTool->GetTargetSegmentationNode()->GetData()->GetTimeSteps() > 1); + } } void QmitkOtsuTool3DGUI::OnSegmentationRegionAccept() { QmitkConfirmSegmentationDialog dialog; QString segName = QString::fromStdString(m_OtsuTool3DTool->GetCurrentSegmentationName()); - dialog.SetSegmentationName(segName); - int result = dialog.exec(); - - switch (result) + if (m_OtsuTool3DTool.IsNotNull()) { - case QmitkConfirmSegmentationDialog::CREATE_NEW_SEGMENTATION: + if (this->m_Controls.m_CheckCreateNew->isChecked()) + { m_OtsuTool3DTool->SetOverwriteExistingSegmentation(false); - break; - case QmitkConfirmSegmentationDialog::OVERWRITE_SEGMENTATION: + } + else + { m_OtsuTool3DTool->SetOverwriteExistingSegmentation(true); - break; - case QmitkConfirmSegmentationDialog::CANCEL_SEGMENTATION: - return; - } + } - if (m_OtsuTool3DTool.IsNotNull() && m_Controls.m_selectionListWidget->currentItem() != nullptr) - { + m_OtsuTool3DTool->SetCreateAllTimeSteps(this->m_Controls.m_CheckProcessAll->isChecked()); + + this->m_Controls.m_ConfSegButton->setEnabled(false); m_OtsuTool3DTool->ConfirmSegmentation(); } } void QmitkOtsuTool3DGUI::OnSpinboxValueAccept() { if (m_NumberOfRegions == m_Controls.m_Spinbox->value() && m_UseValleyEmphasis == m_Controls.m_ValleyCheckbox->isChecked() && m_NumberOfBins == m_Controls.m_BinsSpinBox->value()) return; if (m_OtsuTool3DTool.IsNotNull()) { try { int proceed; QMessageBox *messageBox = new QMessageBox(QMessageBox::Question, nullptr, "The otsu segmentation computation may take several minutes depending " "on the number of Regions you selected. Proceed anyway?", QMessageBox::Ok | QMessageBox::Cancel); if (m_Controls.m_Spinbox->value() >= 5) { proceed = messageBox->exec(); if (proceed != QMessageBox::Ok) return; } m_NumberOfRegions = m_Controls.m_Spinbox->value(); m_UseValleyEmphasis = m_Controls.m_ValleyCheckbox->isChecked(); m_NumberOfBins = m_Controls.m_BinsSpinBox->value(); + m_OtsuTool3DTool->SetNumberOfRegions(m_NumberOfRegions); + m_OtsuTool3DTool->SetUseValley(m_UseValleyEmphasis); + m_OtsuTool3DTool->SetNumberOfBins(m_NumberOfBins); - this->setCursor(Qt::WaitCursor); - m_OtsuTool3DTool->RunSegmentation(m_NumberOfRegions, m_UseValleyEmphasis, m_NumberOfBins); - this->setCursor(Qt::ArrowCursor); + m_OtsuTool3DTool->UpdatePreview(); } catch (...) { this->setCursor(Qt::ArrowCursor); QMessageBox *messageBox = new QMessageBox(QMessageBox::Critical, nullptr, "itkOtsuFilter error: image dimension must be in {2, 3} and no RGB images can be handled."); messageBox->exec(); delete messageBox; return; } + // insert regions into widget QString itemName; QListWidgetItem *item; m_Controls.m_selectionListWidget->clear(); for (int i = 0; i < m_Controls.m_Spinbox->value(); ++i) { itemName = QString::number(i); item = new QListWidgetItem(itemName); m_Controls.m_selectionListWidget->addItem(item); } // deactivate 'confirm segmentation'-button m_Controls.m_ConfSegButton->setEnabled(false); + m_OtsuTool3DTool->IsTimePointChangeAwareOn(); } } -void QmitkOtsuTool3DGUI::OnVolumePreviewChecked(int state) +void QmitkOtsuTool3DGUI::BusyStateChanged(bool value) { - if (state == 1) + if (value) + { + QApplication::setOverrideCursor(QCursor(Qt::BusyCursor)); + } + else { + QApplication::restoreOverrideCursor(); } + + m_Controls.m_ValleyCheckbox->setEnabled(!value); + m_Controls.binLabel->setEnabled(!value); + m_Controls.m_BinsSpinBox->setEnabled(!value); + m_Controls.m_ConfSegButton->setEnabled(!m_OtsuTool3DTool->GetSelectedRegions().empty() && !value); + m_Controls.m_CheckProcessAll->setEnabled(!value); + m_Controls.m_CheckCreateNew->setEnabled(!value); + m_Controls.previewButton->setEnabled(!value); } diff --git a/Modules/SegmentationUI/Qmitk/QmitkOtsuTool3DGUI.h b/Modules/SegmentationUI/Qmitk/QmitkOtsuTool3DGUI.h index f562f73560..ce2ad862fe 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkOtsuTool3DGUI.h +++ b/Modules/SegmentationUI/Qmitk/QmitkOtsuTool3DGUI.h @@ -1,83 +1,83 @@ /*============================================================================ 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 QmitkOtsuTool3DGUI_h_Included #define QmitkOtsuTool3DGUI_h_Included #include "QmitkToolGUI.h" #include "mitkOtsuTool3D.h" #include "ui_QmitkOtsuToolWidgetControls.h" #include #include #include class QSpinBox; class QLabel; /** \ingroup org_mitk_gui_qt_interactivesegmentation_internal \brief GUI for mitk::. \sa mitk:: This GUI shows ... Last contributor: $Author$ */ class MITKSEGMENTATIONUI_EXPORT QmitkOtsuTool3DGUI : public QmitkToolGUI { Q_OBJECT public: mitkClassMacro(QmitkOtsuTool3DGUI, QmitkToolGUI); itkFactorylessNewMacro(Self); itkCloneMacro(Self); signals : public slots : protected slots : void OnNewToolAssociated(mitk::Tool *); void OnSpinboxValueAccept(); void OnSegmentationRegionAccept(); void OnRegionSelectionChanged(); void OnRegionSpinboxChanged(int); - void OnVolumePreviewChecked(int); - private slots: void OnAdvancedSettingsButtonToggled(bool toggled); protected: QmitkOtsuTool3DGUI(); ~QmitkOtsuTool3DGUI() override; + void BusyStateChanged(bool value) override; + mitk::OtsuTool3D::Pointer m_OtsuTool3DTool; Ui_QmitkOtsuToolWidgetControls m_Controls; int m_NumberOfRegions; bool m_UseValleyEmphasis; int m_NumberOfBins; QList m_SelectedItems; }; #endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkOtsuToolWidgetControls.ui b/Modules/SegmentationUI/Qmitk/QmitkOtsuToolWidgetControls.ui index e5484bf97b..27e1d87ff8 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkOtsuToolWidgetControls.ui +++ b/Modules/SegmentationUI/Qmitk/QmitkOtsuToolWidgetControls.ui @@ -1,214 +1,234 @@ QmitkOtsuToolWidgetControls 0 0 192 - 293 + 300 0 0 100 0 100000 100000 QmitkOtsuToolWidget Move to adjust the segmentation QLayout::SetNoConstraint 0 0 Number of Regions: 0 0 40 16777215 2 32 0 0 0 32 Advanced settings Qt::ToolButtonTextBesideIcon true Use Valley Emphasis Number of Histogram Bins: 2 4096 128 0 0 10000000 100 0 QAbstractItemView::MultiSelection QListView::Adjust 0 0 100000 16777215 Preview false 0 0 100000 16777215 Confirm Segmentation + + + + Process/overwrite all time steps of the dynamic segmentation and not just the currently visible time step. + + + Process all time steps + + + + + + + Add the confirmed segmentation as a new segmentation instead of overwriting the currently selected. + + + Create as new segmentation + + + ctkExpandButton QToolButton
ctkExpandButton.h
diff --git a/Modules/SegmentationUI/Qmitk/QmitkToolSelectionBox.cpp b/Modules/SegmentationUI/Qmitk/QmitkToolSelectionBox.cpp index 2dcb1b21fe..43ca2e2c15 100755 --- a/Modules/SegmentationUI/Qmitk/QmitkToolSelectionBox.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkToolSelectionBox.cpp @@ -1,716 +1,722 @@ /*============================================================================ 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. ============================================================================*/ //#define MBILOG_ENABLE_DEBUG 1 #include #include "QmitkToolSelectionBox.h" #include "QmitkToolGUI.h" #include "mitkBaseRenderer.h" #include #include #include #include #include #include #include #include "usModuleResource.h" #include "usModuleResourceStream.h" #include "mitkToolManagerProvider.h" QmitkToolSelectionBox::QmitkToolSelectionBox(QWidget *parent, mitk::DataStorage *) : QWidget(parent), m_SelfCall(false), m_DisplayedGroups("default"), m_LayoutColumns(2), m_ShowNames(true), m_GenerateAccelerators(false), m_ToolGUIWidget(nullptr), m_LastToolGUI(nullptr), m_ToolButtonGroup(nullptr), m_ButtonLayout(nullptr), m_EnabledMode(EnabledWithReferenceAndWorkingDataVisible) { QFont currentFont = QWidget::font(); currentFont.setBold(true); QWidget::setFont(currentFont); m_ToolManager = mitk::ToolManagerProvider::GetInstance()->GetToolManager(); // muellerm // QButtonGroup m_ToolButtonGroup = new QButtonGroup(this); // some features of QButtonGroup m_ToolButtonGroup->setExclusive(false); // mutually exclusive toggle buttons RecreateButtons(); QWidget::setContentsMargins(0, 0, 0, 0); if (layout() != nullptr) { layout()->setContentsMargins(0, 0, 0, 0); } // reactions to signals connect(m_ToolButtonGroup, SIGNAL(buttonClicked(int)), this, SLOT(toolButtonClicked(int))); // reactions to ToolManager events m_ToolManager->ActiveToolChanged += mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerToolModified); m_ToolManager->ReferenceDataChanged += mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerReferenceDataModified); m_ToolManager->WorkingDataChanged += mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerWorkingDataModified); // show active tool SetOrUnsetButtonForActiveTool(); QWidget::setEnabled(false); } QmitkToolSelectionBox::~QmitkToolSelectionBox() { m_ToolManager->ActiveToolChanged -= mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerToolModified); m_ToolManager->ReferenceDataChanged -= mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerReferenceDataModified); m_ToolManager->WorkingDataChanged -= mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerWorkingDataModified); } void QmitkToolSelectionBox::SetEnabledMode(EnabledMode mode) { m_EnabledMode = mode; SetGUIEnabledAccordingToToolManagerState(); } mitk::ToolManager *QmitkToolSelectionBox::GetToolManager() { return m_ToolManager; } void QmitkToolSelectionBox::SetToolManager( mitk::ToolManager &newManager) // no nullptr pointer allowed here, a manager is required { // say bye to the old manager m_ToolManager->ActiveToolChanged -= mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerToolModified); m_ToolManager->ReferenceDataChanged -= mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerReferenceDataModified); m_ToolManager->WorkingDataChanged -= mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerWorkingDataModified); if (QWidget::isEnabled()) { m_ToolManager->UnregisterClient(); } m_ToolManager = &newManager; RecreateButtons(); // greet the new one m_ToolManager->ActiveToolChanged += mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerToolModified); m_ToolManager->ReferenceDataChanged += mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerReferenceDataModified); m_ToolManager->WorkingDataChanged += mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerWorkingDataModified); if (QWidget::isEnabled()) { m_ToolManager->RegisterClient(); } // ask the new one what the situation is like SetOrUnsetButtonForActiveTool(); } void QmitkToolSelectionBox::toolButtonClicked(int id) { if (!QWidget::isEnabled()) return; // this method could be triggered from the constructor, when we are still disabled MITK_DEBUG << "toolButtonClicked(" << id << "): id translates to tool ID " << m_ToolIDForButtonID[id]; // QToolButton* toolButton = dynamic_cast( Q3ButtonGroup::find(id) ); QToolButton *toolButton = dynamic_cast(m_ToolButtonGroup->buttons().at(id)); if (toolButton) { if ((m_ButtonIDForToolID.find(m_ToolManager->GetActiveToolID()) != m_ButtonIDForToolID.end()) // if we have this tool in our box && (m_ButtonIDForToolID[m_ToolManager->GetActiveToolID()] == id)) // the tool corresponding to this button is already active { // disable this button, disable all tools // mmueller toolButton->setChecked(false); m_ToolManager->ActivateTool(-1); // disable everything } else { // enable the corresponding tool m_SelfCall = true; m_ToolManager->ActivateTool(m_ToolIDForButtonID[id]); m_SelfCall = false; } } } void QmitkToolSelectionBox::OnToolManagerToolModified() { SetOrUnsetButtonForActiveTool(); } void QmitkToolSelectionBox::SetOrUnsetButtonForActiveTool() { // we want to emit a signal in any case, whether we selected ourselves or somebody else changes "our" tool manager. // --> emit before check on m_SelfCall int id = m_ToolManager->GetActiveToolID(); // don't emit signal for shape model tools bool emitSignal = true; mitk::Tool *tool = m_ToolManager->GetActiveTool(); if (tool && std::string(tool->GetGroup()) == "organ_segmentation") emitSignal = false; if (emitSignal) emit ToolSelected(id); // delete old GUI (if any) if (m_LastToolGUI && m_ToolGUIWidget) { if (m_ToolGUIWidget->layout()) { m_ToolGUIWidget->layout()->removeWidget(m_LastToolGUI); } // m_LastToolGUI->reparent(nullptr, QPoint(0,0)); // TODO: reparent <-> setParent, Daniel fragen m_LastToolGUI->setParent(nullptr); delete m_LastToolGUI; // will hopefully notify parent and layouts m_LastToolGUI = nullptr; QLayout *layout = m_ToolGUIWidget->layout(); if (layout) { layout->activate(); } } QToolButton *toolButton(nullptr); // mitk::Tool* tool = m_ToolManager->GetActiveTool(); if (m_ButtonIDForToolID.find(id) != m_ButtonIDForToolID.end()) // if this tool is in our box { // toolButton = dynamic_cast( Q3ButtonGroup::find( m_ButtonIDForToolID[id] ) ); toolButton = dynamic_cast(m_ToolButtonGroup->buttons().at(m_ButtonIDForToolID[id])); } if (toolButton) { // mmueller // uncheck all other buttons QAbstractButton *tmpBtn = nullptr; QList::iterator it; for (int i = 0; i < m_ToolButtonGroup->buttons().size(); ++i) { tmpBtn = m_ToolButtonGroup->buttons().at(i); if (tmpBtn != toolButton) dynamic_cast(tmpBtn)->setChecked(false); } toolButton->setChecked(true); if (m_ToolGUIWidget && tool) { // create and reparent new GUI (if any) itk::Object::Pointer possibleGUI = tool->GetGUI("Qmitk", "GUI").GetPointer(); // prefix and postfix if (possibleGUI.IsNull()) possibleGUI = tool->GetGUI("", "GUI").GetPointer(); QmitkToolGUI *gui = dynamic_cast(possibleGUI.GetPointer()); //! m_LastToolGUI = gui; if (gui) { gui->SetTool(tool); // mmueller // gui->reparent(m_ToolGUIWidget, gui->geometry().topLeft(), true ); gui->setParent(m_ToolGUIWidget); gui->move(gui->geometry().topLeft()); gui->show(); QLayout *layout = m_ToolGUIWidget->layout(); if (!layout) { layout = new QVBoxLayout(m_ToolGUIWidget); } if (layout) { // mmueller layout->addWidget(gui); // layout->add( gui ); layout->activate(); } } } } else { // disable all buttons QToolButton *selectedToolButton = dynamic_cast(m_ToolButtonGroup->checkedButton()); // QToolButton* selectedToolButton = dynamic_cast( Q3ButtonGroup::find( Q3ButtonGroup::selectedId() ) // ); if (selectedToolButton) { // mmueller selectedToolButton->setChecked(false); // selectedToolButton->setOn(false); } } } void QmitkToolSelectionBox::OnToolManagerReferenceDataModified() { if (m_SelfCall) return; MITK_DEBUG << "OnToolManagerReferenceDataModified()"; SetGUIEnabledAccordingToToolManagerState(); } void QmitkToolSelectionBox::OnToolManagerWorkingDataModified() { if (m_SelfCall) return; MITK_DEBUG << "OnToolManagerWorkingDataModified()"; SetGUIEnabledAccordingToToolManagerState(); } /** Implementes the logic, which decides, when tools are activated/deactivated. */ void QmitkToolSelectionBox::SetGUIEnabledAccordingToToolManagerState() { mitk::DataNode *referenceNode = m_ToolManager->GetReferenceData(0); mitk::DataNode *workingNode = m_ToolManager->GetWorkingData(0); // MITK_DEBUG << this->name() << ": SetGUIEnabledAccordingToToolManagerState: referenceNode " << (void*)referenceNode // << " workingNode " << (void*)workingNode << " isVisible() " << isVisible(); bool enabled = true; switch (m_EnabledMode) { default: case EnabledWithReferenceAndWorkingDataVisible: enabled = referenceNode && workingNode && referenceNode->IsVisible( mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget0"))) && workingNode->IsVisible( mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget0"))) && isVisible(); break; case EnabledWithReferenceData: enabled = referenceNode && isVisible(); break; case EnabledWithWorkingData: enabled = workingNode && isVisible(); break; case AlwaysEnabled: enabled = isVisible(); break; } if (QWidget::isEnabled() == enabled) return; // nothing to change QWidget::setEnabled(enabled); if (enabled) { m_ToolManager->RegisterClient(); int id = m_ToolManager->GetActiveToolID(); emit ToolSelected(id); } else { m_ToolManager->ActivateTool(-1); m_ToolManager->UnregisterClient(); emit ToolSelected(-1); } } /** External enableization... */ void QmitkToolSelectionBox::setEnabled(bool /*enable*/) { SetGUIEnabledAccordingToToolManagerState(); } void QmitkToolSelectionBox::RecreateButtons() { if (m_ToolManager.IsNull()) return; /* // remove all buttons that are there QObjectList *l = Q3ButtonGroup::queryList( "QButton" ); QObjectListIt it( *l ); // iterate over all buttons QObject *obj; while ( (obj = it.current()) != 0 ) { ++it; QButton* button = dynamic_cast(obj); if (button) { Q3ButtonGroup::remove(button); delete button; } } delete l; // delete the list, not the objects */ // mmueller Qt impl QList l = m_ToolButtonGroup->buttons(); // remove all buttons that are there QList::iterator it; QAbstractButton *btn; for (it = l.begin(); it != l.end(); ++it) { btn = *it; m_ToolButtonGroup->removeButton(btn); // this->removeChild(btn); delete btn; } // end mmueller Qt impl mitk::ToolManager::ToolVectorTypeConst allPossibleTools = m_ToolManager->GetTools(); mitk::ToolManager::ToolVectorTypeConst allTools; typedef std::pair SortPairType; typedef std::priority_queue SortedToolQueueType; SortedToolQueueType toolPositions; // clear and sort all tools // step one: find name/group of all tools in m_DisplayedGroups string. remember these positions for all tools. for (mitk::ToolManager::ToolVectorTypeConst::const_iterator iter = allPossibleTools.begin(); iter != allPossibleTools.end(); ++iter) { const mitk::Tool *tool = *iter; std::string::size_type namePos = m_DisplayedGroups.find(std::string("'") + tool->GetName() + "'"); std::string::size_type groupPos = m_DisplayedGroups.find(std::string("'") + tool->GetGroup() + "'"); if (!m_DisplayedGroups.empty() && namePos == std::string::npos && groupPos == std::string::npos) continue; // skip if (m_DisplayedGroups.empty() && std::string(tool->GetName()).length() > 0) { namePos = static_cast(tool->GetName()[0]); } SortPairType thisPair = std::make_pair(namePos < groupPos ? namePos : groupPos, *iter); toolPositions.push(thisPair); } // step two: sort tools according to previously found positions in m_DisplayedGroups MITK_DEBUG << "Sorting order of tools (lower number --> earlier in button group)"; while (!toolPositions.empty()) { SortPairType thisPair = toolPositions.top(); MITK_DEBUG << "Position " << thisPair.first << " : " << thisPair.second->GetName(); allTools.push_back(thisPair.second); toolPositions.pop(); } std::reverse(allTools.begin(), allTools.end()); MITK_DEBUG << "Sorted tools:"; for (mitk::ToolManager::ToolVectorTypeConst::const_iterator iter = allTools.begin(); iter != allTools.end(); ++iter) { MITK_DEBUG << (*iter)->GetName(); } // try to change layout... bad? // Q3GroupBox::setColumnLayout ( m_LayoutColumns, Qt::Horizontal ); // mmueller using gridlayout instead of Q3GroupBox // this->setLayout(0); if (m_ButtonLayout == nullptr) m_ButtonLayout = new QGridLayout; /*else delete m_ButtonLayout;*/ int row(0); int column(-1); int currentButtonID(0); m_ButtonIDForToolID.clear(); m_ToolIDForButtonID.clear(); QToolButton *button = nullptr; MITK_DEBUG << "Creating buttons for tools"; // fill group box with buttons for (mitk::ToolManager::ToolVectorTypeConst::const_iterator iter = allTools.begin(); iter != allTools.end(); ++iter) { const mitk::Tool *tool = *iter; int currentToolID(m_ToolManager->GetToolID(tool)); ++column; // new line if we are at the maximum columns if (column == m_LayoutColumns) { ++row; column = 0; } button = new QToolButton; button->setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum)); // add new button to the group MITK_DEBUG << "Adding button with ID " << currentToolID; m_ToolButtonGroup->addButton(button, currentButtonID); // ... and to the layout MITK_DEBUG << "Adding button in row/column " << row << "/" << column; m_ButtonLayout->addWidget(button, row, column); if (m_LayoutColumns == 1) { // button->setTextPosition( QToolButton::BesideIcon ); // mmueller button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); } else { // button->setTextPosition( QToolButton::BelowIcon ); // mmueller button->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); } // button->setToggleButton( true ); // mmueller button->setCheckable(true); if (currentToolID == m_ToolManager->GetActiveToolID()) button->setChecked(true); QString label; if (m_GenerateAccelerators) { label += "&"; } label += tool->GetName(); QString tooltip = tool->GetName(); MITK_DEBUG << tool->GetName() << ", " << label.toLocal8Bit().constData() << ", '" << tooltip.toLocal8Bit().constData(); if (m_ShowNames) { /* button->setUsesTextLabel(true); button->setTextLabel( label ); // a label QToolTip::add( button, tooltip ); */ // mmueller Qt button->setText(label); // a label button->setToolTip(tooltip); // mmueller QFont currentFont = button->font(); currentFont.setBold(false); button->setFont(currentFont); } us::ModuleResource iconResource = tool->GetIconResource(); if (!iconResource.IsValid()) { button->setIcon(QIcon(QPixmap(tool->GetXPM()))); } else { auto isSVG = "svg" == iconResource.GetSuffix(); auto openmode = isSVG ? std::ios_base::in : std::ios_base::binary; us::ModuleResourceStream resourceStream(iconResource, openmode); resourceStream.seekg(0, std::ios::end); std::ios::pos_type length = resourceStream.tellg(); resourceStream.seekg(0, std::ios::beg); char *data = new char[length]; resourceStream.read(data, length); if (isSVG) { button->setIcon(QmitkStyleManager::ThemeIcon(QByteArray::fromRawData(data, length))); } else { QPixmap pixmap; pixmap.loadFromData(QByteArray::fromRawData(data, length)); button->setIcon(QIcon(pixmap)); } delete[] data; if (m_ShowNames) { if (m_LayoutColumns == 1) button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); else button->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); button->setIconSize(QSize(24, 24)); } else { button->setToolButtonStyle(Qt::ToolButtonIconOnly); button->setIconSize(QSize(32, 32)); button->setToolTip(tooltip); } } if (m_GenerateAccelerators) { QString firstLetter = QString(tool->GetName()); firstLetter.truncate(1); button->setShortcut( firstLetter); // a keyboard shortcut (just the first letter of the given name w/o any CTRL or something) } mitk::DataNode *dataNode = m_ToolManager->GetReferenceData(0); + const auto workingDataNode = m_ToolManager->GetWorkingData(0); + const mitk::BaseData* workingData = nullptr; + if (nullptr != workingDataNode) + { + workingData = workingDataNode->GetData(); + } - if (dataNode != nullptr && !tool->CanHandle(dataNode->GetData())) + if (nullptr == dataNode || !tool->CanHandle(dataNode->GetData(), workingData)) button->setEnabled(false); m_ButtonIDForToolID[currentToolID] = currentButtonID; m_ToolIDForButtonID[currentButtonID] = currentToolID; MITK_DEBUG << "m_ButtonIDForToolID[" << currentToolID << "] == " << currentButtonID; MITK_DEBUG << "m_ToolIDForButtonID[" << currentButtonID << "] == " << currentToolID; tool->GUIProcessEventsMessage += mitk::MessageDelegate( this, &QmitkToolSelectionBox::OnToolGUIProcessEventsMessage); // will never add a listener twice, so we don't have // to check here tool->ErrorMessage += mitk::MessageDelegate1( this, &QmitkToolSelectionBox::OnToolErrorMessage); // will never add a listener twice, so we don't have to check here tool->GeneralMessage += mitk::MessageDelegate1(this, &QmitkToolSelectionBox::OnGeneralToolMessage); ++currentButtonID; } // setting grid layout for this groupbox this->setLayout(m_ButtonLayout); // this->update(); } void QmitkToolSelectionBox::OnToolGUIProcessEventsMessage() { qApp->processEvents(); } void QmitkToolSelectionBox::OnToolErrorMessage(std::string s) { QMessageBox::critical( this, "MITK", QString(s.c_str()), QMessageBox::Ok, QMessageBox::NoButton, QMessageBox::NoButton); } void QmitkToolSelectionBox::OnGeneralToolMessage(std::string s) { QMessageBox::information( this, "MITK", QString(s.c_str()), QMessageBox::Ok, QMessageBox::NoButton, QMessageBox::NoButton); } void QmitkToolSelectionBox::SetDisplayedToolGroups(const std::string &toolGroups) { if (m_DisplayedGroups != toolGroups) { QString q_DisplayedGroups = toolGroups.c_str(); // quote all unquoted single words q_DisplayedGroups = q_DisplayedGroups.replace(QRegExp("\\b(\\w+)\\b|'([^']+)'"), "'\\1\\2'"); MITK_DEBUG << "m_DisplayedGroups was \"" << toolGroups << "\""; m_DisplayedGroups = q_DisplayedGroups.toLocal8Bit().constData(); MITK_DEBUG << "m_DisplayedGroups is \"" << m_DisplayedGroups << "\""; RecreateButtons(); SetOrUnsetButtonForActiveTool(); } } void QmitkToolSelectionBox::SetLayoutColumns(int columns) { if (columns > 0 && columns != m_LayoutColumns) { m_LayoutColumns = columns; RecreateButtons(); } } void QmitkToolSelectionBox::SetShowNames(bool show) { if (show != m_ShowNames) { m_ShowNames = show; RecreateButtons(); } } void QmitkToolSelectionBox::SetGenerateAccelerators(bool accel) { if (accel != m_GenerateAccelerators) { m_GenerateAccelerators = accel; RecreateButtons(); } } void QmitkToolSelectionBox::SetToolGUIArea(QWidget *parentWidget) { m_ToolGUIWidget = parentWidget; } void QmitkToolSelectionBox::setTitle(const QString & /*title*/) { } void QmitkToolSelectionBox::showEvent(QShowEvent *e) { QWidget::showEvent(e); SetGUIEnabledAccordingToToolManagerState(); } void QmitkToolSelectionBox::hideEvent(QHideEvent *e) { QWidget::hideEvent(e); SetGUIEnabledAccordingToToolManagerState(); }