diff --git a/Modules/Segmentation/Interactions/mitkAutoMLSegmentationWithPreviewTool.cpp b/Modules/Segmentation/Interactions/mitkAutoMLSegmentationWithPreviewTool.cpp index 63c827db0c..881f688fc3 100644 --- a/Modules/Segmentation/Interactions/mitkAutoMLSegmentationWithPreviewTool.cpp +++ b/Modules/Segmentation/Interactions/mitkAutoMLSegmentationWithPreviewTool.cpp @@ -1,205 +1,209 @@ /*============================================================================ 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 "mitkAutoMLSegmentationWithPreviewTool.h" #include "mitkImageAccessByItk.h" #include "mitkToolManager.h" #include #include #include #include #include #include #include // ITK #include #include mitk::AutoMLSegmentationWithPreviewTool::AutoMLSegmentationWithPreviewTool() : AutoSegmentationWithPreviewTool(true) { } void mitk::AutoMLSegmentationWithPreviewTool::SetSelectedLabels(const SelectedLabelVectorType& regions) { if (m_SelectedLabels != regions) { m_SelectedLabels = 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. } } const mitk::LabelSetImage* mitk::AutoMLSegmentationWithPreviewTool::GetMLPreview() const { if (m_MLPreviewNode.IsNotNull()) { const auto mlPreviewImage = dynamic_cast(this->m_MLPreviewNode->GetData()); return mlPreviewImage; } return nullptr; } mitk::AutoMLSegmentationWithPreviewTool::SelectedLabelVectorType mitk::AutoMLSegmentationWithPreviewTool::GetSelectedLabels() const { return this->m_SelectedLabels; } void mitk::AutoMLSegmentationWithPreviewTool::Activated() { Superclass::Activated(); m_SelectedLabels = {}; m_MLPreviewNode = mitk::DataNode::New(); m_MLPreviewNode->SetProperty("name", StringProperty::New(std::string(this->GetName()) + "ML preview")); m_MLPreviewNode->SetProperty("helper object", BoolProperty::New(true)); m_MLPreviewNode->SetVisibility(true); m_MLPreviewNode->SetOpacity(1.0); this->GetToolManager()->GetDataStorage()->Add(m_MLPreviewNode); } void mitk::AutoMLSegmentationWithPreviewTool::Deactivated() { this->GetToolManager()->GetDataStorage()->Remove(m_MLPreviewNode); m_MLPreviewNode = nullptr; Superclass::Deactivated(); } void mitk::AutoMLSegmentationWithPreviewTool::UpdateCleanUp() { if (m_MLPreviewNode.IsNotNull()) m_MLPreviewNode->SetVisibility(m_SelectedLabels.empty()); if (nullptr != this->GetPreviewSegmentationNode()) this->GetPreviewSegmentationNode()->SetVisibility(!m_SelectedLabels.empty()); if (m_SelectedLabels.empty()) { this->ResetPreviewNode(); } } -void mitk::AutoMLSegmentationWithPreviewTool::DoUpdatePreview(const Image* inputAtTimeStep, const Image* /*oldSegAtTimeStep*/, Image* previewImage, TimeStepType timeStep) +void mitk::AutoMLSegmentationWithPreviewTool::SetNodeProperties(LabelSetImage::Pointer newMLPreview) { - const auto timePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); - - if (nullptr == m_MLPreviewNode->GetData() - || this->GetMTime() > m_MLPreviewNode->GetData()->GetMTime() - || this->m_LastMLTimeStep != 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 computations - ) - { - if (nullptr == inputAtTimeStep) - { - MITK_WARN << "Cannot run segementation. Currently selected input image is not set."; - return; - } - - this->m_LastMLTimeStep = timeStep; - - auto newMLPreview = ComputeMLPreview(inputAtTimeStep, timeStep); - - if (newMLPreview.IsNotNull()) + if (newMLPreview.IsNotNull()) { this->m_MLPreviewNode->SetData(newMLPreview); this->m_MLPreviewNode->SetProperty("binary", mitk::BoolProperty::New(false)); mitk::RenderingModeProperty::Pointer renderingMode = mitk::RenderingModeProperty::New(); renderingMode->SetValue(mitk::RenderingModeProperty::LOOKUPTABLE_LEVELWINDOW_COLOR); this->m_MLPreviewNode->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_MLPreviewNode->SetProperty("LookupTable", prop); mitk::LevelWindowProperty::Pointer levWinProp = mitk::LevelWindowProperty::New(); mitk::LevelWindow levelwindow; levelwindow.SetRangeMinMax(0, newMLPreview->GetStatistics()->GetScalarValueMax()); levWinProp->SetLevelWindow(levelwindow); this->m_MLPreviewNode->SetProperty("levelwindow", levWinProp); } +} + +void mitk::AutoMLSegmentationWithPreviewTool::DoUpdatePreview(const Image* inputAtTimeStep, const Image* /*oldSegAtTimeStep*/, Image* previewImage, TimeStepType timeStep) +{ + const auto timePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); + + if (nullptr == m_MLPreviewNode->GetData() + || this->GetMTime() > m_MLPreviewNode->GetData()->GetMTime() + || this->m_LastMLTimeStep != 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 computations + ) + { + if (nullptr == inputAtTimeStep) + { + MITK_WARN << "Cannot run segementation. Currently selected input image is not set."; + return; + } + + this->m_LastMLTimeStep = timeStep; + + auto newMLPreview = ComputeMLPreview(inputAtTimeStep, timeStep); + this->SetNodeProperties(newMLPreview); } if (!m_SelectedLabels.empty()) { const auto mlPreviewImage = this->GetMLPreview(); if (nullptr != mlPreviewImage) { AccessByItk_n(mlPreviewImage, CalculateMergedSimplePreview, (previewImage, timeStep)); } } } template void mitk::AutoMLSegmentationWithPreviewTool::CalculateMergedSimplePreview(const 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 itkBinaryResultImage; filter->SetInput(itkImage); filter->SetLowerThreshold(m_SelectedLabels[0]); filter->SetUpperThreshold(m_SelectedLabels[0]); filter->SetInsideValue(1); filter->SetOutsideValue(0); filter->AddObserver(itk::ProgressEvent(), m_ProgressCommand); filter->Update(); itkBinaryResultImage = filter->GetOutput(); itkBinaryResultImage->DisconnectPipeline(); // if more than one region id is used compute the union of all given binary regions for (const auto labelID : m_SelectedLabels) { if (labelID != m_SelectedLabels[0]) { filter->SetLowerThreshold(labelID); filter->SetUpperThreshold(labelID); 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(); } } //---------------------------------------------------------------------------------------------------- segmentation->SetVolume((void*)(itkBinaryResultImage->GetPixelContainer()->GetBufferPointer()), timeStep); } diff --git a/Modules/Segmentation/Interactions/mitkAutoMLSegmentationWithPreviewTool.h b/Modules/Segmentation/Interactions/mitkAutoMLSegmentationWithPreviewTool.h index 878fd5592c..60921765c6 100644 --- a/Modules/Segmentation/Interactions/mitkAutoMLSegmentationWithPreviewTool.h +++ b/Modules/Segmentation/Interactions/mitkAutoMLSegmentationWithPreviewTool.h @@ -1,82 +1,82 @@ /*============================================================================ 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 MITK_AUTO_ML_SEGMENTATION_WITH_PREVIEW_TOOL_H #define MITK_AUTO_ML_SEGMENTATION_WITH_PREVIEW_TOOL_H #include "mitkAutoSegmentationWithPreviewTool.h" #include "mitkDataNode.h" #include "mitkLabelSetImage.h" #include namespace mitk { /** \brief Base class for any auto segmentation tool that provides a preview of the new segmentation and generates segmentations with multiple labels. This tool class implements the basic logic to handle previews of multi label segmentations and to allow to pick arbitrary labels as selected and merge them to a single segmentation to store this segmentation as confirmed segmentation. \ingroup ToolManagerEtAl \sa mitk::Tool \sa QmitkInteractiveSegmentation */ class MITKSEGMENTATION_EXPORT AutoMLSegmentationWithPreviewTool : public AutoSegmentationWithPreviewTool { public: mitkClassMacro(AutoMLSegmentationWithPreviewTool, AutoSegmentationWithPreviewTool); void Activated() override; void Deactivated() override; using SelectedLabelVectorType = std::vector; void SetSelectedLabels(const SelectedLabelVectorType& regions); SelectedLabelVectorType GetSelectedLabels() const; const LabelSetImage* GetMLPreview() const; protected: AutoMLSegmentationWithPreviewTool(); ~AutoMLSegmentationWithPreviewTool() = default; void UpdateCleanUp() override; void DoUpdatePreview(const Image* inputAtTimeStep, const Image* oldSegAtTimeStep, Image* previewImage, TimeStepType timeStep) override; - + virtual void SetNodeProperties(LabelSetImage::Pointer); /** Function to generate the new multi lable preview for a given time step input image. * The function must be implemented by derived tools. * This function is called by DoUpdatePreview if needed. * Reasons are: * - ML preview does not exist * - Modify time of tools is newer then of ML preview * - ML preview was not generated for the current selected timestep of input image or for the current selected timepoint.*/ virtual LabelSetImage::Pointer ComputeMLPreview(const Image* inputAtTimeStep, TimeStepType timeStep) = 0; private: /** Function to generate a simple (single lable) preview by merging all labels of the ML preview that are selected and * copies that single label preview to the passed previewImage. * This function is called by DoUpdatePreview if needed. * @param mlPreviewImage Multi label preview that is the source. * @param previewImage Pointer to the single label preview image that should receive the merged selected labels. * @timeStep Time step of the previewImage that should be filled.*/ template void CalculateMergedSimplePreview(const itk::Image* mlImage, mitk::Image* segmentation, unsigned int timeStep); SelectedLabelVectorType m_SelectedLabels = {}; // holds the multilabel result as a preview image mitk::DataNode::Pointer m_MLPreviewNode; TimeStepType m_LastMLTimeStep = 0; }; } #endif diff --git a/Modules/Segmentation/Interactions/mitkProcessExecutor.cpp b/Modules/Segmentation/Interactions/mitkProcessExecutor.cpp new file mode 100644 index 0000000000..d65969bb8a --- /dev/null +++ b/Modules/Segmentation/Interactions/mitkProcessExecutor.cpp @@ -0,0 +1,153 @@ +/*============================================================================ + +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 "mitkProcessExecutor.h" + +#include +#include +#include +#include + +namespace mitk +{ + std::string ProcessExecutor::GetOSDependendExecutableName(const std::string &name) + { +#if defined(_WIN32) + + if (itksys::SystemTools::GetFilenameLastExtension(name).empty()) + { + return name + ".exe"; + } + + return name; + +#else + auto result = itksys::SystemTools::GetFilenamePath(name); + if (EnsureCorrectOSPathSeparator(result).empty()) + { + return "./" + name; + } + else + { + return name; + } + +#endif + } + + std::string ProcessExecutor::EnsureCorrectOSPathSeparator(const std::string &path) + { + std::string ret = path; + +#ifdef _WIN32 + const std::string curSep = "\\"; + const char wrongSep = '/'; +#else + const std::string curSep = "/"; + const char wrongSep = '\\'; +#endif + + std::string::size_type pos = ret.find_first_of(wrongSep); + + while (pos != std::string::npos) + { + ret.replace(pos, 1, curSep); + + pos = ret.find_first_of(wrongSep); + } + + return ret; + } + + int ProcessExecutor::GetExitValue() { return this->m_ExitValue; }; + + bool ProcessExecutor::Execute(const std::string &executionPath, const ArgumentListType &argumentList) + { + std::vector pArguments_(argumentList.size() + 1); + + for (ArgumentListType::size_type index = 0; index < argumentList.size(); ++index) + { + pArguments_[index] = argumentList[index].c_str(); + } + pArguments_.push_back(nullptr); //terminating null element as required by ITK + + bool normalExit = false; + + try + { + itksysProcess *processID = itksysProcess_New(); + itksysProcess_SetCommand(processID, pArguments_.data()); + + itksysProcess_SetWorkingDirectory(processID, executionPath.c_str()); + + if (this->m_SharedOutputPipes) + { + itksysProcess_SetPipeShared(processID, itksysProcess_Pipe_STDOUT, 1); + itksysProcess_SetPipeShared(processID, itksysProcess_Pipe_STDERR, 1); + } + + itksysProcess_Execute(processID); + + char *rawOutput = nullptr; + int outputLength = 0; + while (true) + { + int dataStatus = itksysProcess_WaitForData(processID, &rawOutput, &outputLength, nullptr); + + if (dataStatus == itksysProcess_Pipe_STDOUT) + { + std::string data(rawOutput, outputLength); + this->InvokeEvent(ExternalProcessStdOutEvent(data)); + } + else if (dataStatus == itksysProcess_Pipe_STDERR) + { + std::string data(rawOutput, outputLength); + this->InvokeEvent(ExternalProcessStdErrEvent(data)); + } + else + { + break; + } + } + + itksysProcess_WaitForExit(processID, nullptr); + + auto state = static_cast(itksysProcess_GetState(processID)); + + normalExit = (state == itksysProcess_State_Exited); + this->m_ExitValue = itksysProcess_GetExitValue(processID); + } + catch (...) + { + throw; + } + return normalExit; + }; + + bool ProcessExecutor::Execute(const std::string &executionPath, + const std::string &executableName, + ArgumentListType argumentList) + { + std::string executableName_OS = GetOSDependendExecutableName(executableName); + argumentList.insert(argumentList.begin(), executableName_OS); + + return Execute(executionPath, argumentList); + } + + ProcessExecutor::ProcessExecutor() + { + this->m_ExitValue = 0; + this->m_SharedOutputPipes = false; + } + + ProcessExecutor::~ProcessExecutor() = default; +} // namespace mitk diff --git a/Modules/Segmentation/Interactions/mitkProcessExecutor.h b/Modules/Segmentation/Interactions/mitkProcessExecutor.h new file mode 100644 index 0000000000..79856889b4 --- /dev/null +++ b/Modules/Segmentation/Interactions/mitkProcessExecutor.h @@ -0,0 +1,110 @@ +/*============================================================================ + +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. + +============================================================================*/ + +// Class is adapted from MatchPoint ProcessExecutor + +#ifndef __MITK_PROCESS_EXECUTOR_H +#define __MITK_PROCESS_EXECUTOR_H + +#include +#include +#include + +namespace mitk +{ + class ExternalProcessOutputEvent : public itk::AnyEvent + { + public: + typedef ExternalProcessOutputEvent Self; + typedef itk::AnyEvent Superclass; + + explicit ExternalProcessOutputEvent(const std::string &output = "") : m_Output(output) {} + ~ExternalProcessOutputEvent() override {} + + const char *GetEventName() const override { return "ExternalProcessOutputEvent"; } + bool CheckEvent(const ::itk::EventObject *e) const override { return dynamic_cast(e); } + itk::EventObject *MakeObject() const override { return new Self(m_Output); } + std::string GetOutput() const { return m_Output; } + + private: + std::string m_Output; + }; + +#define mitkProcessExecutorEventMacro(classname) \ + class classname : public ExternalProcessOutputEvent \ + { \ + public: \ + typedef classname Self; \ + typedef ExternalProcessOutputEvent Superclass; \ + \ + explicit classname(const std::string &output) : Superclass(output) {} \ + ~classname() override {} \ + \ + virtual const char *GetEventName() const { return #classname; } \ + virtual bool CheckEvent(const ::itk::EventObject *e) const { return dynamic_cast(e); } \ + virtual ::itk::EventObject *MakeObject() const { return new Self(this->GetOutput()); } \ + }; + + mitkProcessExecutorEventMacro(ExternalProcessStdOutEvent); + mitkProcessExecutorEventMacro(ExternalProcessStdErrEvent); + + /** + * @brief You may register an observer for an ExternalProcessOutputEvent, ExternalProcessStdOutEvent or + * ExternalProcessStdErrEvent in order to get notified of any output. + * @remark The events will only be invoked if the pipes are NOT(!) shared. By default the pipes are not shared. + * + */ + class MITKSEGMENTATION_EXPORT ProcessExecutor : public itk::Object + { + public: + using Self = ProcessExecutor; + using Superclass = ::itk::Object; + using Pointer = ::itk::SmartPointer; + using ConstPointer = ::itk::SmartPointer; + + itkTypeMacro(ProcessExecutor, ::itk::Object); + itkFactorylessNewMacro(Self); + + itkSetMacro(SharedOutputPipes, bool); + itkGetConstMacro(SharedOutputPipes, bool); + + using ArgumentListType = std::vector; + + bool Execute(const std::string &executionPath, const std::string &executableName, ArgumentListType argumentList); + + /** + * @brief Executes the process. This version assumes that the executable name is the first argument in the argument + * list and has already been converted to its OS dependent name via the static convert function of this class. + */ + bool Execute(const std::string &executionPath, const ArgumentListType &argumentList); + + int GetExitValue(); + static std::string EnsureCorrectOSPathSeparator(const std::string &); + + static std::string GetOSDependendExecutableName(const std::string &name); + + protected: + ProcessExecutor(); + ~ProcessExecutor() override; + + int m_ExitValue; + + /** + * @brief Specifies if the child process should share the output pipes (true) or not (false). + * If pipes are not shared the output will be passed by invoking ExternalProcessOutputEvents + * @remark The events will only be invoked if the pipes are NOT(!) shared. + */ + bool m_SharedOutputPipes; + }; + +} // namespace mitk +#endif diff --git a/Modules/Segmentation/Interactions/mitknnUnetTool.cpp b/Modules/Segmentation/Interactions/mitknnUnetTool.cpp new file mode 100644 index 0000000000..c2db0bcca3 --- /dev/null +++ b/Modules/Segmentation/Interactions/mitknnUnetTool.cpp @@ -0,0 +1,322 @@ +/*============================================================================ + +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 "mitknnUnetTool.h" + +#include "mitkIOUtil.h" +#include "mitkProcessExecutor.h" +#include +#include +#include +#include +#include + +namespace mitk +{ + MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, nnUNetTool, "nnUNet tool"); +} + +mitk::nnUNetTool::nnUNetTool() +{ + this->SetMitkTempDir(IOUtil::CreateTemporaryDirectory("mitk-XXXXXX")); +} + +mitk::nnUNetTool::~nnUNetTool() +{ + itksys::SystemTools::RemoveADirectory(this->GetMitkTempDir()); +} + +void mitk::nnUNetTool::Activated() +{ + Superclass::Activated(); + m_InputOutputPair = std::make_pair(nullptr, nullptr); +} + +void mitk::nnUNetTool::UpdateCleanUp() +{ + // This overriden method is intentionally left out for setting later upon demand + // in the `RenderOutputBuffer` method. +} + +void mitk::nnUNetTool::RenderOutputBuffer() +{ + if (this->m_OutputBuffer != nullptr) + { + Superclass::SetNodeProperties(this->m_OutputBuffer); + this->ClearOutputBuffer(); + try + { + if (nullptr != this->GetPreviewSegmentationNode()) + { + this->GetPreviewSegmentationNode()->SetVisibility(!this->GetSelectedLabels().empty()); + } + if (this->GetSelectedLabels().empty()) + { + this->ResetPreviewNode(); + } + } + catch (const mitk::Exception &e) + { + MITK_INFO << e.GetDescription(); + } + } +} + +void mitk::nnUNetTool::SetNodeProperties(LabelSetImage::Pointer segmentation) +{ + // This overriden method doesn't set node properties. Intentionally left out for setting later upon demand + // in the `RenderOutputBuffer` method. + this->m_OutputBuffer = segmentation; +} + +mitk::LabelSetImage::Pointer mitk::nnUNetTool::GetOutputBuffer() +{ + return this->m_OutputBuffer; +} + +void mitk::nnUNetTool::ClearOutputBuffer() +{ + this->m_OutputBuffer = nullptr; +} + +us::ModuleResource mitk::nnUNetTool::GetIconResource() const +{ + us::Module *module = us::GetModuleContext()->GetModule(); + us::ModuleResource resource = module->GetResource("Watershed_48x48.png"); + return resource; +} + +const char **mitk::nnUNetTool::GetXPM() const +{ + return nullptr; +} + +const char *mitk::nnUNetTool::GetName() const +{ + return "nnUNet"; +} + +mitk::DataStorage *mitk::nnUNetTool::GetDataStorage() +{ + return this->GetToolManager()->GetDataStorage(); +} + +mitk::DataNode *mitk::nnUNetTool::GetRefNode() +{ + return this->GetToolManager()->GetReferenceData(0); +} + +namespace +{ + void onPythonProcessEvent(itk::Object * /*pCaller*/, const itk::EventObject &e, void *) + { + std::string testCOUT; + std::string testCERR; + const auto *pEvent = dynamic_cast(&e); + + if (pEvent) + { + testCOUT = testCOUT + pEvent->GetOutput(); + MITK_INFO << testCOUT; + } + + const auto *pErrEvent = dynamic_cast(&e); + + if (pErrEvent) + { + testCERR = testCERR + pErrEvent->GetOutput(); + MITK_ERROR << testCERR; + } + } +} // namespace + +mitk::LabelSetImage::Pointer mitk::nnUNetTool::ComputeMLPreview(const Image *inputAtTimeStep, TimeStepType /*timeStep*/) +{ + if (m_InputOutputPair.first == inputAtTimeStep) + { + return m_InputOutputPair.second; + } + std::string inDir, outDir, inputImagePath, outputImagePath, scriptPath; + std::string templateFilename = "XXXXXX_000_0000.nii.gz"; + + ProcessExecutor::Pointer spExec = ProcessExecutor::New(); + itk::CStyleCommand::Pointer spCommand = itk::CStyleCommand::New(); + spCommand->SetCallback(&onPythonProcessEvent); + spExec->AddObserver(ExternalProcessOutputEvent(), spCommand); + ProcessExecutor::ArgumentListType args; + + inDir = IOUtil::CreateTemporaryDirectory("nnunet-in-XXXXXX", this->GetMitkTempDir()); + std::ofstream tmpStream; + inputImagePath = IOUtil::CreateTemporaryFile(tmpStream, templateFilename, inDir + IOUtil::GetDirectorySeparator()); + tmpStream.close(); + std::size_t found = inputImagePath.find_last_of(IOUtil::GetDirectorySeparator()); + std::string fileName = inputImagePath.substr(found + 1); + std::string token = fileName.substr(0, fileName.find("_")); + + if (this->GetNoPip()) + { + scriptPath = this->GetnnUNetDirectory() + IOUtil::GetDirectorySeparator() + "nnunet" + + IOUtil::GetDirectorySeparator() + "inference" + IOUtil::GetDirectorySeparator() + "predict_simple.py"; + } + + try + { + IOUtil::Save(inputAtTimeStep, inputImagePath); + + if (this->GetMultiModal()) + { + for (size_t i = 0; i < this->m_OtherModalPaths.size(); ++i) + { + mitk::Image::ConstPointer modalImage = this->m_OtherModalPaths[i]; + std::string outModalFile = + inDir + IOUtil::GetDirectorySeparator() + token + "_000_000" + std::to_string(i + 1) + ".nii.gz"; + IOUtil::Save(modalImage.GetPointer(), outModalFile); + } + } + } + catch (const mitk::Exception &e) + { + /* + Can't throw mitk exception to the caller. Refer: T28691 + */ + MITK_ERROR << e.GetDescription(); + return nullptr; + } + // Code calls external process + std::string command = "nnUNet_predict"; + if (this->GetNoPip()) + { +#ifdef _WIN32 + command = "python"; +#else + command = "python3"; +#endif + } + for (ModelParams &modelparam : m_ParamQ) + { + outDir = IOUtil::CreateTemporaryDirectory("nnunet-out-XXXXXX", this->GetMitkTempDir()); + outputImagePath = outDir + IOUtil::GetDirectorySeparator() + token + "_000.nii.gz"; + modelparam.outputDir = outDir; + args.clear(); + if (this->GetNoPip()) + { + args.push_back(scriptPath); + } + args.push_back("-i"); + args.push_back(inDir); + + args.push_back("-o"); + args.push_back(outDir); + + args.push_back("-t"); + args.push_back(modelparam.task); + + if (modelparam.model.find("cascade") != std::string::npos) + { + args.push_back("-ctr"); + } + else + { + args.push_back("-tr"); + } + args.push_back(modelparam.trainer); + + args.push_back("-m"); + args.push_back(modelparam.model); + + args.push_back("-p"); + args.push_back(modelparam.planId); + + if (!modelparam.folds.empty()) + { + args.push_back("-f"); + for (auto fold : modelparam.folds) + { + args.push_back(fold); + } + } + + args.push_back("--num_threads_nifti_save"); + args.push_back("1"); // fixing to 1 + + if (!this->GetMirror()) + { + args.push_back("--disable_tta"); + } + + if (!this->GetMixedPrecision()) + { + args.push_back("--disable_mixed_precision"); + } + + if (this->GetEnsemble() && !this->GetPostProcessingJsonDirectory().empty()) + { + args.push_back("--save_npz"); + } + + try + { + std::string resultsFolderEnv = "RESULTS_FOLDER=" + this->GetModelDirectory(); + itksys::SystemTools::PutEnv(resultsFolderEnv.c_str()); + std::string cudaEnv = "CUDA_VISIBLE_DEVICES=" + std::to_string(this->GetGpuId()); + itksys::SystemTools::PutEnv(cudaEnv.c_str()); + + spExec->Execute(this->GetPythonPath(), command, args); + } + catch (const mitk::Exception &e) + { + /* + Can't throw mitk exception to the caller. Refer: T28691 + */ + MITK_ERROR << e.GetDescription(); + return nullptr; + } + } + if (this->GetEnsemble() && !this->GetPostProcessingJsonDirectory().empty()) + { + args.clear(); + command = "nnUNet_ensemble"; + outDir = IOUtil::CreateTemporaryDirectory("nnunet-ensemble-out-XXXXXX", this->GetMitkTempDir()); + outputImagePath = outDir + IOUtil::GetDirectorySeparator() + token + "_000.nii.gz"; + + args.push_back("-f"); + for (ModelParams &modelparam : m_ParamQ) + { + args.push_back(modelparam.outputDir); + } + + args.push_back("-o"); + args.push_back(outDir); + + args.push_back("-pp"); + args.push_back(this->GetPostProcessingJsonDirectory()); + + spExec->Execute(this->GetPythonPath(), command, args); + } + try + { + LabelSetImage::Pointer resultImage = LabelSetImage::New(); + Image::Pointer outputImage = IOUtil::Load(outputImagePath); + resultImage->InitializeByLabeledImage(outputImage); + resultImage->SetGeometry(inputAtTimeStep->GetGeometry()); + m_InputOutputPair = std::make_pair(inputAtTimeStep, resultImage); + return resultImage; + } + catch (const mitk::Exception &e) + { + /* + Can't throw mitk exception to the caller. Refer: T28691 + */ + MITK_ERROR << e.GetDescription(); + return nullptr; + } +} diff --git a/Modules/Segmentation/Interactions/mitknnUnetTool.h b/Modules/Segmentation/Interactions/mitknnUnetTool.h new file mode 100644 index 0000000000..9e5a31cac3 --- /dev/null +++ b/Modules/Segmentation/Interactions/mitknnUnetTool.h @@ -0,0 +1,196 @@ +/*============================================================================ + +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 mitknnUnetTool_h_Included +#define mitknnUnetTool_h_Included + +#include "mitkAutoMLSegmentationWithPreviewTool.h" +#include "mitkCommon.h" +#include "mitkToolManager.h" +#include +#include +#include + +namespace us +{ + class ModuleResource; +} + +namespace mitk +{ + /** + * @brief nnUNet parameter request object holding all model parameters for input. + * Also holds output temporary directory path. + */ + struct ModelParams + { + std::string task; + std::vector folds; + std::string model; + std::string trainer; + std::string planId; + std::string outputDir; + }; + + /** + \brief nnUNet segmentation tool. + + \ingroup Interaction + \ingroup ToolManagerEtAl + + \warning Only to be instantiated by mitk::ToolManager. + */ + class MITKSEGMENTATION_EXPORT nnUNetTool : public AutoMLSegmentationWithPreviewTool + { + public: + mitkClassMacro(nnUNetTool, AutoMLSegmentationWithPreviewTool); + itkFactorylessNewMacro(Self); + itkCloneMacro(Self); + + const char **GetXPM() const override; + const char *GetName() const override; + us::ModuleResource GetIconResource() const override; + + void Activated() override; + + itkSetMacro(nnUNetDirectory, std::string); + itkGetConstMacro(nnUNetDirectory, std::string); + + itkSetMacro(ModelDirectory, std::string); + itkGetConstMacro(ModelDirectory, std::string); + + itkSetMacro(PythonPath, std::string); + itkGetConstMacro(PythonPath, std::string); + + itkSetMacro(MitkTempDir, std::string); + itkGetConstMacro(MitkTempDir, std::string); + + itkSetMacro(PostProcessingJsonDirectory, std::string); + itkGetConstMacro(PostProcessingJsonDirectory, std::string); + + itkSetMacro(MixedPrecision, bool); + itkGetConstMacro(MixedPrecision, bool); + itkBooleanMacro(MixedPrecision); + + itkSetMacro(Mirror, bool); + itkGetConstMacro(Mirror, bool); + itkBooleanMacro(Mirror); + + itkSetMacro(MultiModal, bool); + itkGetConstMacro(MultiModal, bool); + itkBooleanMacro(MultiModal); + + itkSetMacro(NoPip, bool); + itkGetConstMacro(NoPip, bool); + itkBooleanMacro(NoPip); + + itkSetMacro(Ensemble, bool); + itkGetConstMacro(Ensemble, bool); + itkBooleanMacro(Ensemble); + + itkSetMacro(Predict, bool); + itkGetConstMacro(Predict, bool); + itkBooleanMacro(Predict); + + itkSetMacro(GpuId, unsigned int); + itkGetConstMacro(GpuId, unsigned int); + + /** + * @brief vector of ModelParams. + * Size > 1 only for ensemble prediction. + */ + std::vector m_ParamQ; + + /** + * @brief Holds paths to other input image modalities. + * + */ + std::vector m_OtherModalPaths; + + std::pair m_InputOutputPair; + + /** + * @brief Renders the output LabelSetImage. + * To called in the main thread. + */ + void RenderOutputBuffer(); + + /** + * @brief Get the Output Buffer object + * + * @return LabelSetImage::Pointer + */ + LabelSetImage::Pointer GetOutputBuffer(); + + /** + * @brief Sets the outputBuffer to nullptr + * + */ + void ClearOutputBuffer(); + + /** + * @brief Returns the DataStorage from the ToolManager + */ + mitk::DataStorage *GetDataStorage(); + + mitk::DataNode *GetRefNode(); + + protected: + /** + * @brief Construct a new nnUNet Tool object and temp directory. + * + */ + nnUNetTool(); + + /** + * @brief Destroy the nnUNet Tool object and deletes the temp directory. + * + */ + ~nnUNetTool(); + + /** + * @brief Overriden method from the tool manager to execute the segmentation + * Implementation: + * 1. Saves the inputAtTimeStep in a temporary directory. + * 2. Copies other modalities, renames and saves in the temporary directory, if required. + * 3. Sets RESULTS_FOLDER and CUDA_VISIBLE_DEVICES variables in the environment. + * 3. Iterates through the parameter queue (m_ParamQ) and executes "nnUNet_predict" command with the parameters + * 4. Expects an output image to be saved in the temporary directory by the python proces. Loads it as + * LabelSetImage and returns. + * + * @param inputAtTimeStep + * @param timeStep + * @return LabelSetImage::Pointer + */ + LabelSetImage::Pointer ComputeMLPreview(const Image *inputAtTimeStep, TimeStepType timeStep) override; + void UpdateCleanUp() override; + void SetNodeProperties(LabelSetImage::Pointer) override; + + private: + std::string m_MitkTempDir; + std::string m_nnUNetDirectory; + std::string m_ModelDirectory; + std::string m_PythonPath; + std::string m_PostProcessingJsonDirectory; + // bool m_UseGPU; kept for future + // bool m_AllInGPU; + bool m_MixedPrecision; + bool m_Mirror; + bool m_NoPip; + bool m_MultiModal; + bool m_Ensemble = false; + bool m_Predict; + LabelSetImage::Pointer m_OutputBuffer; + unsigned int m_GpuId; + }; +} // namespace mitk +#endif diff --git a/Modules/Segmentation/files.cmake b/Modules/Segmentation/files.cmake index c4c3643e85..02b759d7b4 100644 --- a/Modules/Segmentation/files.cmake +++ b/Modules/Segmentation/files.cmake @@ -1,115 +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/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/mitkAutoMLSegmentationWithPreviewTool.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/mitkCreateSurfaceTool.cpp Interactions/mitkDrawPaintbrushTool.cpp Interactions/mitkErasePaintbrushTool.cpp Interactions/mitkEraseRegionTool.cpp Interactions/mitkFastMarchingBaseTool.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/mitknnUnetTool.cpp Interactions/mitkSegmentationInteractor.cpp #SO + Interactions/mitkProcessExecutor.cpp 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 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/PickingTool.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/QmitknnUNetEnsembleLayout.h b/Modules/SegmentationUI/Qmitk/QmitknnUNetEnsembleLayout.h new file mode 100644 index 0000000000..881cb52567 --- /dev/null +++ b/Modules/SegmentationUI/Qmitk/QmitknnUNetEnsembleLayout.h @@ -0,0 +1,86 @@ +/*============================================================================ + +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.s + +============================================================================*/ + +#ifndef QmitknnUNetEnsembleLayout_h_Included +#define QmitknnUNetEnsembleLayout_h_Included + + +#include +#include +#include +#include +#include +#include +#include + + +QT_BEGIN_NAMESPACE + +class MITKSEGMENTATIONUI_EXPORT QmitknnUNetTaskParamsUITemplate +{ + +public: + QLabel* trainerLabel; + ctkComboBox* trainerBox; + QLabel* plannerLabel; + ctkComboBox* plannerBox; + QLabel* foldLabel; + ctkCheckableComboBox* foldBox; + QLabel* modelLabel; + ctkComboBox* modelBox; + QWidget* parent; + + QmitknnUNetTaskParamsUITemplate(QWidget* inputGroupBox_1) + { + this->parent = inputGroupBox_1; + QVBoxLayout* verticalLayout_x = new QVBoxLayout(inputGroupBox_1); + verticalLayout_x->setObjectName(QString::fromUtf8("verticalLayout_x")); + QGridLayout* g_x = new QGridLayout(); +#ifndef Q_OS_MAC + g_x->setSpacing(6); +#endif +#ifndef Q_OS_MAC + g_x->setContentsMargins(0, 0, 0, 0); +#endif + g_x->setObjectName(QString::fromUtf8("g_2")); + + modelLabel = new QLabel("Configuration", inputGroupBox_1); + g_x->addWidget(modelLabel, 0, 0, 1, 1); + trainerLabel = new QLabel("Trainer", inputGroupBox_1); + g_x->addWidget(trainerLabel, 0, 1, 1, 1); + + modelBox = new ctkComboBox(inputGroupBox_1); + modelBox->setObjectName(QString::fromUtf8("modelBox_1")); + g_x->addWidget(modelBox, 1, 0, 1, 1); + trainerBox = new ctkComboBox(inputGroupBox_1); + trainerBox->setObjectName(QString::fromUtf8("trainerBox_1")); + g_x->addWidget(trainerBox, 1, 1, 1, 1); + + plannerLabel = new QLabel("Planner", inputGroupBox_1); + g_x->addWidget(plannerLabel, 2, 0, 1, 1); + foldLabel = new QLabel("Fold", inputGroupBox_1); + g_x->addWidget(foldLabel, 2, 1, 1, 1); + + plannerBox = new ctkComboBox(inputGroupBox_1); + plannerBox->setObjectName(QString::fromUtf8("plannerBox_1")); + g_x->addWidget(plannerBox, 3, 0, 1, 1); + foldBox = new ctkCheckableComboBox(inputGroupBox_1); + foldBox->setObjectName(QString::fromUtf8("foldBox_1")); + g_x->addWidget(foldBox, 3, 1, 1, 1); + + verticalLayout_x->addLayout(g_x); + } +}; +QT_END_NAMESPACE + +#endif + diff --git a/Modules/SegmentationUI/Qmitk/QmitknnUNetFolderParser.h b/Modules/SegmentationUI/Qmitk/QmitknnUNetFolderParser.h new file mode 100644 index 0000000000..d005f62eb3 --- /dev/null +++ b/Modules/SegmentationUI/Qmitk/QmitknnUNetFolderParser.h @@ -0,0 +1,281 @@ +/*============================================================================ + +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.s + +============================================================================*/ + +#ifndef QmitknnUNetFolderParser_h_Included +#define QmitknnUNetFolderParser_h_Included + +#include "QmitknnUNetToolGUI.h" +#include +#include +#include + +/** + * @brief Struct to store each (Folder) Node of the hierarchy tree structure. + * + */ +struct FolderNode +{ + QString name; + QString path; // parent + std::vector> subFolders; +}; + +/** + * @brief Class to store and retreive folder hierarchy information + * of RESULTS_FOLDER. Only Root node is explicitly stored in m_RootNode. + * No. of sub levels in the hierachry is defined in the LEVEL constant. + * + */ +class MITKSEGMENTATIONUI_EXPORT QmitknnUNetFolderParser +{ +public: + + /** + * @brief Construct a new QmitknnUNetFolderParser object + * Initializes root folder node object pointer calls + * @param parentFolder + */ + QmitknnUNetFolderParser(const QString parentFolder) + { + m_RootNode = std::make_shared(); + m_RootNode->path = parentFolder; + m_RootNode->name = QString("nnUNet"); + m_RootNode->subFolders.clear(); + InitDirs(m_RootNode, 0); + } + + /** + * @brief Destroy the QmitknnUNetFolderParser object + * + */ + ~QmitknnUNetFolderParser() = default; /*{ DeleteDirs(m_RootNode, LEVEL); }*/ + + /** + * @brief Returns the "Results Folder" string which is parent path of the root node. + * + * @return QString + */ + QString getResultsFolder() { return m_RootNode->path; } + + /** + * @brief Returns the Model Names from root node. Template function, + * type can be any of stl or Qt containers which supports push_back call. + * + * @tparam T + * @return T (any of stl or Qt containers which supports push_back call) + */ + template + T getModelNames() + { + auto models = GetSubFolderNamesFromNode(m_RootNode); + return models; + } + + /** + * @brief Returns the task names for a given model. Template function, + * type can be any of stl or Qt containers which supports push_back call. + * + * @tparam T + * @param modelName + * @return T (any of stl or Qt containers which supports push_back call) + */ + template + T getTasksForModel(const QString &modelName) + { + std::shared_ptr modelNode = GetSubNodeMatchingNameCrietria(modelName, m_RootNode); + auto tasks = GetSubFolderNamesFromNode(modelNode); + return tasks; + } + + /** + * @brief Returns the models names for a given task. Template function, + * type can be any of stl or Qt containers which supports push_back call. + * + * @tparam T + * @param taskName + * @return T (any of stl or Qt containers which supports push_back call) + */ + template + T getModelsForTask(const QString &taskName) + { + T modelsForTask; + auto models = GetSubFolderNamesFromNode(m_RootNode); + foreach (QString model, models) + { + QStringList taskList = getTasksForModel(model); + if (taskList.contains(taskName, Qt::CaseInsensitive)) + { + modelsForTask << model; + } + } + return modelsForTask; + } + + /** + * @brief Returns the trainer / planner names for a given task & model. Template function, + * type can be any of stl or Qt containers which supports push_back call. + * + * @tparam T + * @param taskName + * @param modelName + * @return T (any of stl or Qt containers which supports push_back call) + */ + template + T getTrainerPlannersForTask(const QString &taskName, const QString &modelName) + { + std::shared_ptr modelNode = GetSubNodeMatchingNameCrietria(modelName, m_RootNode); + std::shared_ptr taskNode = GetSubNodeMatchingNameCrietria(taskName, modelNode); + auto tps = GetSubFolderNamesFromNode(taskNode); + return tps; + } + + /** + * @brief Returns the Folds names for a given trainer,planner,task & model name. Template function, + * type can be any of stl or Qt containers which supports push_back call. + * + * @tparam T + * @param trainer + * @param planner + * @param taskName + * @param modelName + * @return T (any of stl or Qt containers which supports push_back call) + */ + template + T getFoldsForTrainerPlanner(const QString &trainer, + const QString &planner, + const QString &taskName, + const QString &modelName) + { + std::shared_ptr modelNode = GetSubNodeMatchingNameCrietria(modelName, m_RootNode); + std::shared_ptr taskNode = GetSubNodeMatchingNameCrietria(taskName, modelNode); + QString trainerPlanner = trainer + QString("__") + planner; + std::shared_ptr tpNode = GetSubNodeMatchingNameCrietria(trainerPlanner, taskNode); + auto folds = GetSubFolderNamesFromNode(tpNode); + return folds; + } + +private: + const int m_LEVEL = 4; + std::shared_ptr m_RootNode; + + /** + * @brief Iterates through the root node and returns the sub FolderNode object Matching Name Crietria + * + * @param queryName + * @param parentNode + * @return std::shared_ptr + */ + std::shared_ptr GetSubNodeMatchingNameCrietria(const QString &queryName, + std::shared_ptr parentNode) + { + std::shared_ptr retNode; + std::vector> subNodes = parentNode->subFolders; + for (std::shared_ptr node : subNodes) + { + if (node->name == queryName) + { + retNode = node; + break; + } + } + return retNode; + } + + /** + * @brief Returns the sub folder names for a folder node object. Template function, + * type can be any of stl or Qt containers which supports push_back call. + * + * @tparam T + * @param std::shared_ptr + * @return T (any of stl or Qt containers which supports push_back call) + */ + template + T GetSubFolderNamesFromNode(const std::shared_ptr parent) + { + T folders; + std::vector> subNodes = parent->subFolders; + for (std::shared_ptr folder : subNodes) + { + folders.push_back(folder->name); + } + return folders; + } + + /** + * @brief Iterates through the sub folder hierarchy upto a level provided + * and create a tree structure. + * + * @param parent + * @param level + */ + void InitDirs(std::shared_ptr parent, int level) + { + QString searchFolder = parent->path + QDir::separator() + parent->name; + auto subFolders = FetchFoldersFromDir(searchFolder); + level++; + foreach (QString folder, subFolders) + { + std::shared_ptr fp = std::make_shared(); + fp->path = searchFolder; + fp->name = folder; + if (level < this->m_LEVEL) + { + InitDirs(fp, level); + } + parent->subFolders.push_back(fp); + } + } + + /** + * @brief Iterates through the sub folder hierarchy upto a level provided + * and clears the sub folder std::vector from each node. + * + * @param parent + * @param level + */ + void DeleteDirs(std::shared_ptr parent, int level) + { + level++; + for (std::shared_ptr subFolder : parent->subFolders) + { + if (level < m_LEVEL) + { + DeleteDirs(subFolder, level); + } + parent->subFolders.clear(); + } + } + + /** + * @brief Template function to fetch all folders inside a given path. + * The type can be any of stl or Qt containers which supports push_back call. + * + * @tparam T + * @param path + * @return T + */ + template + T FetchFoldersFromDir(const QString &path) + { + T folders; + for (QDirIterator it(path, QDir::AllDirs, QDirIterator::NoIteratorFlags); it.hasNext();) + { + it.next(); + if (!it.fileName().startsWith('.')) + { + folders.push_back(it.fileName()); + } + } + return folders; + } +}; +#endif diff --git a/Modules/SegmentationUI/Qmitk/QmitknnUNetGPU.h b/Modules/SegmentationUI/Qmitk/QmitknnUNetGPU.h new file mode 100644 index 0000000000..6cd9532f46 --- /dev/null +++ b/Modules/SegmentationUI/Qmitk/QmitknnUNetGPU.h @@ -0,0 +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.s + +============================================================================*/ + +#ifndef QmitknnUNetToolGPU_h_Included +#define QmitknnUNetToolGPU_h_Included + +#include +#include +#include +#include + +/** + * @brief Struct to store GPU info. + * + */ +struct QmitkGPUSpec +{ + QString name; + int memoryFree; + unsigned int id; +}; + +/** + * @brief Class to load and save GPU information + * for further validation + */ +class QmitkGPULoader : public QObject +{ + Q_OBJECT + +private: + std::vector m_Gpus; + +public: + /** + * @brief Construct a new Qmitk GPU Loader object. + * Parses GPU info using `nvidia-smi` command and saves it as QmitkGPUSpec objects. + */ + QmitkGPULoader() + { + QProcess process; + process.start("nvidia-smi --query-gpu=name,memory.free --format=csv"); + process.waitForFinished(-1); + QStringList infoStringList; + while (process.canReadLine()) + { + QString line = process.readLine(); + if (!line.startsWith("name")) + { + infoStringList << line; + } + } + foreach (QString infoString, infoStringList) + { + QmitkGPUSpec spec; + QStringList gpuDetails; + gpuDetails = infoString.split(","); + spec.name = gpuDetails.at(0); + // spec.id = id; + spec.memoryFree = gpuDetails.at(1).split(" ")[0].toInt(); + this->m_Gpus.push_back(spec); + } + } + ~QmitkGPULoader() = default; + + /** + * @brief Returns the number of GPUs parsed and saved as QmitkGPUSpec objects. + * + * @return int + */ + int GetGPUCount() { return static_cast(m_Gpus.size()); } +}; + +#endif diff --git a/Modules/SegmentationUI/Qmitk/QmitknnUNetToolGUI.cpp b/Modules/SegmentationUI/Qmitk/QmitknnUNetToolGUI.cpp new file mode 100644 index 0000000000..ea0b15c38b --- /dev/null +++ b/Modules/SegmentationUI/Qmitk/QmitknnUNetToolGUI.cpp @@ -0,0 +1,328 @@ +/*============================================================================ + +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 "QmitknnUNetToolGUI.h" + +#include "mitknnUnetTool.h" +#include +#include +#include +#include + +MITK_TOOL_GUI_MACRO(MITKSEGMENTATIONUI_EXPORT, QmitknnUNetToolGUI, "") + +QmitknnUNetToolGUI::QmitknnUNetToolGUI() : QmitkAutoMLSegmentationToolGUIBase() +{ + // Nvidia-smi command returning zero doesn't alway mean lack of GPUs. + // Pytorch uses its own libraries to communicate to the GPUs. Hence, only a warning can be given. + if (m_GpuLoader.GetGPUCount() == 0) + { + std::string warning = "WARNING: No GPUs were detected on your machine. The nnUNet tool might not work."; + ShowErrorMessage(warning); + } + + // define predicates for multi modal data selection combobox + auto imageType = mitk::TNodePredicateDataType::New(); + auto labelSetImageType = mitk::NodePredicateNot::New(mitk::TNodePredicateDataType::New()); + this->m_MultiModalPredicate = mitk::NodePredicateAnd::New(imageType, labelSetImageType).GetPointer(); +} + +void QmitknnUNetToolGUI::ConnectNewTool(mitk::AutoSegmentationWithPreviewTool *newTool) +{ + Superclass::ConnectNewTool(newTool); + newTool->IsTimePointChangeAwareOff(); +} + +void QmitknnUNetToolGUI::InitializeUI(QBoxLayout *mainLayout) +{ + m_Controls.setupUi(this); +#ifndef _WIN32 + m_Controls.pythonEnvComboBox->addItem("/usr/bin"); +#endif + m_Controls.pythonEnvComboBox->addItem("Select"); + AutoParsePythonPaths(); + connect(m_Controls.previewButton, SIGNAL(clicked()), this, SLOT(OnPreviewRequested())); + connect(m_Controls.modeldirectoryBox, + SIGNAL(directoryChanged(const QString &)), + this, + SLOT(OnDirectoryChanged(const QString &))); + connect( + m_Controls.modelBox, SIGNAL(currentTextChanged(const QString &)), this, SLOT(OnModelChanged(const QString &))); + connect(m_Controls.taskBox, SIGNAL(currentTextChanged(const QString &)), this, SLOT(OnTaskChanged(const QString &))); + connect( + m_Controls.plannerBox, SIGNAL(currentTextChanged(const QString &)), this, SLOT(OnTrainerChanged(const QString &))); + connect(m_Controls.nopipBox, SIGNAL(stateChanged(int)), this, SLOT(OnCheckBoxChanged(int))); + connect(m_Controls.multiModalBox, SIGNAL(stateChanged(int)), this, SLOT(OnCheckBoxChanged(int))); + connect(m_Controls.multiModalSpinBox, SIGNAL(valueChanged(int)), this, SLOT(OnModalitiesNumberChanged(int))); + connect(m_Controls.posSpinBox, SIGNAL(valueChanged(int)), this, SLOT(OnModalPositionChanged(int))); + connect(m_Controls.pythonEnvComboBox, +#if QT_VERSION >= 0x050F00 // 5.15 + SIGNAL(textActivated(const QString &)), +#elif QT_VERSION >= 0x050C00 // 5.12 + SIGNAL(activated(const QString &)), +#endif + this, + SLOT(OnPythonPathChanged(const QString &))); + connect(m_Controls.refreshdirectoryBox, SIGNAL(clicked()), this, SLOT(OnRefreshPresssed())); + + m_Controls.codedirectoryBox->setVisible(false); + m_Controls.nnUnetdirLabel->setVisible(false); + m_Controls.multiModalSpinBox->setVisible(false); + m_Controls.multiModalSpinLabel->setVisible(false); + m_Controls.posSpinBoxLabel->setVisible(false); + m_Controls.posSpinBox->setVisible(false); + m_Controls.previewButton->setEnabled(false); + + QSize sz(16, 16); + m_Controls.stopButton->setVisible(false); + + QIcon refreshIcon; + refreshIcon.addPixmap(style()->standardIcon(QStyle::SP_BrowserReload).pixmap(sz), QIcon::Normal, QIcon::Off); + m_Controls.refreshdirectoryBox->setIcon(refreshIcon); + m_Controls.refreshdirectoryBox->setEnabled(true); + + m_Controls.statusLabel->setTextFormat(Qt::RichText); + m_Controls.statusLabel->setText("STATUS: Welcome to nnUNet. " + QString::number(m_GpuLoader.GetGPUCount()) + + " GPUs were detected."); + + if (m_GpuLoader.GetGPUCount() != 0) + { + m_Controls.gpuSpinBox->setMaximum(m_GpuLoader.GetGPUCount() - 1); + } + mainLayout->addLayout(m_Controls.verticalLayout); + Superclass::InitializeUI(mainLayout); + m_UI_ROWS = m_Controls.advancedSettingsLayout->rowCount(); // Must do. Row count is correct only here. +} + +void QmitknnUNetToolGUI::OnPreviewRequested() +{ + mitk::nnUNetTool::Pointer tool = this->GetConnectedToolAs(); + if (nullptr != tool) + { + try + { + m_Controls.previewButton->setEnabled(false); // To prevent misclicked back2back prediction. + qApp->processEvents(); + tool->PredictOn(); // purposefully placed to make tool->GetMTime different than before. + QString modelName = m_Controls.modelBox->currentText(); + if (modelName.startsWith("ensemble", Qt::CaseInsensitive)) + { + ProcessEnsembleModelsParams(tool); + } + else + { + ProcessModelParams(tool); + } + QString pythonPathTextItem = m_Controls.pythonEnvComboBox->currentText(); + QString pythonPath = pythonPathTextItem.mid(pythonPathTextItem.indexOf(" ") + 1); + bool isNoPip = m_Controls.nopipBox->isChecked(); +#ifdef _WIN32 + if (!isNoPip && !(pythonPath.endsWith("Scripts", Qt::CaseInsensitive) || + pythonPath.endsWith("Scripts/", Qt::CaseInsensitive))) + { + pythonPath += QDir::separator() + QString("Scripts"); + } +#else + if (!(pythonPath.endsWith("bin", Qt::CaseInsensitive) || pythonPath.endsWith("bin/", Qt::CaseInsensitive))) + { + pythonPath += QDir::separator() + QString("bin"); + } +#endif + std::string nnUNetDirectory; + if (isNoPip) + { + nnUNetDirectory = m_Controls.codedirectoryBox->directory().toStdString(); + } + else if (!IsNNUNetInstalled(pythonPath)) + { + throw std::runtime_error("nnUNet is not detected in the selected python environment. Please select a valid " + "python environment or install nnUNet."); + } + + tool->SetnnUNetDirectory(nnUNetDirectory); + tool->SetPythonPath(pythonPath.toStdString()); + tool->SetModelDirectory(m_ParentFolder->getResultsFolder().toStdString()); + // checkboxes + tool->SetMirror(m_Controls.mirrorBox->isChecked()); + tool->SetMixedPrecision(m_Controls.mixedPrecisionBox->isChecked()); + tool->SetNoPip(isNoPip); + tool->SetMultiModal(m_Controls.multiModalBox->isChecked()); + // Spinboxes + tool->SetGpuId(static_cast(m_Controls.gpuSpinBox->value())); + // Multi-Modal + tool->MultiModalOff(); + if (m_Controls.multiModalBox->isChecked()) + { + tool->m_OtherModalPaths.clear(); + tool->m_OtherModalPaths = FetchMultiModalImagesFromUI(); + tool->MultiModalOn(); + } + tool->m_InputOutputPair = std::make_pair(nullptr, nullptr); + m_Controls.statusLabel->setText("STATUS: Starting Segmentation task... This might take a while."); + tool->UpdatePreview(); + if (tool->GetOutputBuffer() == nullptr) + { + SegmentationProcessFailed(); + } + SegmentationResultHandler(tool); + tool->PredictOff(); // purposefully placed to make tool->GetMTime different than before. + m_Controls.previewButton->setEnabled(true); + } + catch (const std::exception &e) + { + std::stringstream errorMsg; + errorMsg << "Error while processing parameters for nnUNet segmentation. Reason: " << e.what(); + ShowErrorMessage(errorMsg.str()); + m_Controls.previewButton->setEnabled(true); + tool->PredictOff(); + return; + } + catch (...) + { + std::string errorMsg = "Unkown error occured while generation nnUNet segmentation."; + ShowErrorMessage(errorMsg); + m_Controls.previewButton->setEnabled(true); + tool->PredictOff(); + return; + } + } +} + +std::vector QmitknnUNetToolGUI::FetchMultiModalImagesFromUI() +{ + std::vector paths; + if (m_Controls.multiModalBox->isChecked() && !m_Modalities.empty()) + { + for (QmitkDataStorageComboBox *modality : m_Modalities) + { + if (modality->objectName() != "multiModal_0") + { + paths.push_back(dynamic_cast(modality->GetSelectedNode()->GetData())); + } + } + } + return paths; +} + +bool QmitknnUNetToolGUI::IsNNUNetInstalled(const QString &pythonPath) +{ + QString fullPath = pythonPath; +#ifdef _WIN32 + if (!(fullPath.endsWith("Scripts", Qt::CaseInsensitive) || fullPath.endsWith("Scripts/", Qt::CaseInsensitive))) + { + fullPath += QDir::separator() + QString("Scripts"); + } +#else + if (!(fullPath.endsWith("bin", Qt::CaseInsensitive) || fullPath.endsWith("bin/", Qt::CaseInsensitive))) + { + fullPath += QDir::separator() + QString("bin"); + } +#endif + fullPath = fullPath.mid(fullPath.indexOf(" ") + 1); + return QFile::exists(fullPath + QDir::separator() + QString("nnUNet_predict")); +} + +void QmitknnUNetToolGUI::ShowErrorMessage(const std::string &message, QMessageBox::Icon icon) +{ + this->setCursor(Qt::ArrowCursor); + QMessageBox *messageBox = new QMessageBox(icon, nullptr, message.c_str()); + messageBox->exec(); + delete messageBox; + MITK_WARN << message; +} + +void QmitknnUNetToolGUI::ProcessEnsembleModelsParams(mitk::nnUNetTool::Pointer tool) +{ + if (m_EnsembleParams[0]->modelBox->currentText() == m_EnsembleParams[1]->modelBox->currentText()) + { + throw std::runtime_error("Both models you have selected for ensembling are the same."); + } + QString taskName = m_Controls.taskBox->currentText(); + std::vector requestQ; + QString ppDirFolderNamePart1 = "ensemble_"; + QStringList ppDirFolderNameParts; + for (std::unique_ptr &layout : m_EnsembleParams) + { + QStringList ppDirFolderName; + QString modelName = layout->modelBox->currentText(); + ppDirFolderName << modelName; + ppDirFolderName << "__"; + QString trainer = layout->trainerBox->currentText(); + ppDirFolderName << trainer; + ppDirFolderName << "__"; + QString planId = layout->plannerBox->currentText(); + ppDirFolderName << planId; + + if (!IsModelExists(modelName, taskName, QString(trainer + "__" + planId))) + { + std::string errorMsg = "The configuration " + modelName.toStdString() + + " you have selected doesn't exist. Check your Results Folder again."; + throw std::runtime_error(errorMsg); + } + std::vector testfold; // empty vector to consider all folds for inferencing. + mitk::ModelParams modelObject = MapToRequest(modelName, taskName, trainer, planId, testfold); + requestQ.push_back(modelObject); + ppDirFolderNameParts << ppDirFolderName.join(QString("")); + } + tool->EnsembleOn(); + + QString ppJsonFilePossibility1 = QDir::cleanPath( + m_ParentFolder->getResultsFolder() + QDir::separator() + "nnUNet" + QDir::separator() + "ensembles" + + QDir::separator() + taskName + QDir::separator() + ppDirFolderNamePart1 + ppDirFolderNameParts.first() + "--" + + ppDirFolderNameParts.last() + QDir::separator() + "postprocessing.json"); + QString ppJsonFilePossibility2 = QDir::cleanPath( + m_ParentFolder->getResultsFolder() + QDir::separator() + "nnUNet" + QDir::separator() + "ensembles" + + QDir::separator() + taskName + QDir::separator() + ppDirFolderNamePart1 + ppDirFolderNameParts.last() + "--" + + ppDirFolderNameParts.first() + QDir::separator() + "postprocessing.json"); + + if (QFile(ppJsonFilePossibility1).exists()) + { + tool->SetPostProcessingJsonDirectory(ppJsonFilePossibility1.toStdString()); + } + else if (QFile(ppJsonFilePossibility2).exists()) + { + tool->SetPostProcessingJsonDirectory(ppJsonFilePossibility2.toStdString()); + } + else + { + // warning message + } + tool->m_ParamQ.clear(); + tool->m_ParamQ = requestQ; +} + +void QmitknnUNetToolGUI::ProcessModelParams(mitk::nnUNetTool::Pointer tool) +{ + tool->EnsembleOff(); + std::vector requestQ; + QString modelName = m_Controls.modelBox->currentText(); + QString taskName = m_Controls.taskBox->currentText(); + QString trainer = m_Controls.trainerBox->currentText(); + QString planId = m_Controls.plannerBox->currentText(); + std::vector fetchedFolds = FetchSelectedFoldsFromUI(); + mitk::ModelParams modelObject = MapToRequest(modelName, taskName, trainer, planId, fetchedFolds); + requestQ.push_back(modelObject); + tool->m_ParamQ.clear(); + tool->m_ParamQ = requestQ; +} + +bool QmitknnUNetToolGUI::IsModelExists(const QString &modelName, const QString &taskName, const QString &trainerPlanner) +{ + QString modelSearchPath = + QDir::cleanPath(m_ParentFolder->getResultsFolder() + QDir::separator() + "nnUNet" + QDir::separator() + modelName + + QDir::separator() + taskName + QDir::separator() + trainerPlanner); + if (QDir(modelSearchPath).exists()) + { + return true; + } + return false; +} diff --git a/Modules/SegmentationUI/Qmitk/QmitknnUNetToolGUI.h b/Modules/SegmentationUI/Qmitk/QmitknnUNetToolGUI.h new file mode 100644 index 0000000000..583151c47a --- /dev/null +++ b/Modules/SegmentationUI/Qmitk/QmitknnUNetToolGUI.h @@ -0,0 +1,222 @@ +/*============================================================================ + +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.s + +============================================================================*/ + +#ifndef QmitknnUNetToolGUI_h_Included +#define QmitknnUNetToolGUI_h_Included + +#include "QmitkAutoMLSegmentationToolGUIBase.h" +#include "QmitknnUNetFolderParser.h" +#include "QmitknnUNetGPU.h" +#include "mitknnUnetTool.h" +#include "ui_QmitknnUNetToolGUIControls.h" +#include +#include +#include +#include + +class MITKSEGMENTATIONUI_EXPORT QmitknnUNetToolGUI : public QmitkAutoMLSegmentationToolGUIBase +{ + Q_OBJECT + +public: + mitkClassMacro(QmitknnUNetToolGUI, QmitkAutoMLSegmentationToolGUIBase); + itkFactorylessNewMacro(Self); + itkCloneMacro(Self); + +protected slots: + + /** + * @brief Qt slot + * + */ + void OnPreviewRequested(); + + /** + * @brief Qt slot + * + */ + void OnDirectoryChanged(const QString &); + + /** + * @brief Qt slot + * + */ + void OnModelChanged(const QString &); + + /** + * @brief Qt slot + * + */ + void OnTaskChanged(const QString &); + + /** + * @brief Qt slot + * + */ + void OnTrainerChanged(const QString &); + + /** + * @brief Qt slot + * + */ + void OnCheckBoxChanged(int); + + /** + * @brief Qthread slot to capture failures from thread worker and + * shows error message + * + */ + void SegmentationProcessFailed(); + + /** + * @brief Qthread to capture sucessfull nnUNet segmentation. + * Further, renders the LabelSet image + */ + void SegmentationResultHandler(mitk::nnUNetTool *); + + /** + * @brief Qt Slot + * + */ + void OnModalitiesNumberChanged(int); + + /** + * @brief Qt Slot + * + */ + void OnPythonPathChanged(const QString &); + + /** + * @brief Qt Slot + * + */ + void OnModalPositionChanged(int); + + /** + * @brief Qt slot + * + */ + void OnRefreshPresssed(); + +protected: + QmitknnUNetToolGUI(); + ~QmitknnUNetToolGUI() = default; + + void ConnectNewTool(mitk::AutoSegmentationWithPreviewTool *newTool) override; + void InitializeUI(QBoxLayout *mainLayout) override; + void EnableWidgets(bool enabled) override; + +private: + /** + * @brief Parses the ensemble UI elements and sets to nnUNetTool object pointer. + * + */ + void ProcessEnsembleModelsParams(mitk::nnUNetTool::Pointer); + + /** + * @brief Parses the UI elements and sets to nnUNetTool object pointer. + * + */ + void ProcessModelParams(mitk::nnUNetTool::Pointer); + + /** + * @brief Creates and renders QmitknnUNetTaskParamsUITemplate layout for ensemble input. + */ + void ShowEnsembleLayout(bool visible = true); + + /** + * @brief Creates a QMessage object and shows on screen. + */ + void ShowErrorMessage(const std::string &, QMessageBox::Icon = QMessageBox::Critical); + + /** + * @brief Searches and parses paths of python virtual enviroments + * from predefined lookout locations + */ + void AutoParsePythonPaths(); + + /** + * @brief Check if pretrained model sub folder inside RESULTS FOLDER exist. + * + */ + bool IsModelExists(const QString&, const QString&, const QString&); + + /** + * @brief Clears all combo boxes + * Any new combo box added in the future can be featured here for clearance. + * + */ + void ClearAllComboBoxes(); + + /** + * @brief Checks if nnUNet_predict command is valid in the selected python virtual environment. + * + * @return bool + */ + bool IsNNUNetInstalled(const QString &); + + /** + * @brief Mapper function to map QString entries from UI to ModelParam attributes. + * + * @return mitk::ModelParams + */ + mitk::ModelParams MapToRequest( + const QString &, const QString &, const QString &, const QString &, const std::vector &); + + /** + * @brief Returns checked fold names from the ctk-Checkable-ComboBox. + * + * @return std::vector + */ + std::vector FetchSelectedFoldsFromUI(); + + /** + * @brief Returns all paths from the dynamically generated ctk-path-line-edit boxes. + * + * @return std::vector + */ + std::vector FetchMultiModalImagesFromUI(); + + Ui_QmitknnUNetToolGUIControls m_Controls; + QmitkGPULoader m_GpuLoader; + + /** + * @brief Stores all dynamically added ctk-path-line-edit UI elements. + * + */ + std::vector m_Modalities; + + std::vector> m_EnsembleParams; + + mitk::NodePredicateBase::Pointer m_MultiModalPredicate; + + /** + * @brief Stores row count of the "advancedSettingsLayout" layout element. This value helps dynamically add + * ctk-path-line-edit UI elements at the right place. Forced to initialize in the InitializeUI method since there is + * no guarantee of retrieving exact row count anywhere else. + * + */ + int m_UI_ROWS; + + /** + * @brief Stores path of the model director (RESULTS_FOLDER appended by "nnUNet"). + * + */ + std::shared_ptr m_ParentFolder = nullptr; + + /** + * @brief Valid list of models supported by nnUNet + * + */ + const QStringList m_VALID_MODELS = {"2d", "3d_lowres", "3d_fullres", "3d_cascade_fullres", "ensembles"}; +}; +#endif diff --git a/Modules/SegmentationUI/Qmitk/QmitknnUNetToolGUIControls.ui b/Modules/SegmentationUI/Qmitk/QmitknnUNetToolGUIControls.ui new file mode 100644 index 0000000000..534b398ddf --- /dev/null +++ b/Modules/SegmentationUI/Qmitk/QmitknnUNetToolGUIControls.ui @@ -0,0 +1,440 @@ + + + QmitknnUNetToolGUIControls + + + + 0 + 0 + 192 + 352 + + + + + 0 + 0 + + + + + 100 + 0 + + + + + 100000 + 100000 + + + + QmitknnUNetToolWidget + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + 0 + 0 + + + + nnUNet Results Folder: + + + + + + + + + + + + Refresh Results Folder + + + + + + + + + Configuration: + + + + + + + + + + + 0 + 0 + + + + Task: + + + + + + + + + + + + + 0 + 0 + + + + Trainer: + + + + + + + + + + + + 0 + 0 + + + + Plan: + + + + + + + + + + + Fold: + + + + + + + + + + Stop + + + + + + + + + 0 + 0 + + + + Multi-Modal: + + + + + + + + + + + 0 + 0 + + + + No. of Extra Modalities: + + + + + + + + + + + + + 0 + 0 + + + + Reference Image Position + + + + + + + + + + + + + + + + 0 + 0 + + + + 5 + + + Advanced + + + true + + + true + + + Qt::AlignRight + + + + + + + 6 + + + 0 + + + + + + + 0 + 0 + + + + No Pip + + + + + + + + + + + 0 + 0 + + + + Mixed Precision + + + + + + + true + + + + + + + + + 0 + 0 + + + + GPU Id: + + + + + + + + + + + + 0 + 0 + + + + Enable Mirroring + + + + + + + true + + + + + + + + + 0 + 0 + + + + Python Path: + + + + + + + + + + + + 0 + 0 + + + + nnUNet Path: + + + + + + + + + + + + + + + + + + + + 0 + 0 + + + + + 100000 + 16777215 + + + + Preview + + + + + + + + 0 + 0 + + + + + + + + + + + ctkDirectoryButton + QWidget +
ctkDirectoryButton.h
+ 1 +
+ + ctkComboBox + QComboBox +
ctkComboBox.h
+ 1 +
+ + ctkCheckableComboBox + QComboBox +
ctkCheckableComboBox.h
+ 1 +
+ + ctkCheckBox + QCheckBox +
ctkCheckBox.h
+ 1 +
+ + ctkCollapsibleGroupBox + QGroupBox +
ctkCollapsibleGroupBox.h
+ 1 +
+
+ + +
diff --git a/Modules/SegmentationUI/Qmitk/QmitknnUNetToolSlots.cpp b/Modules/SegmentationUI/Qmitk/QmitknnUNetToolSlots.cpp new file mode 100644 index 0000000000..b04034d042 --- /dev/null +++ b/Modules/SegmentationUI/Qmitk/QmitknnUNetToolSlots.cpp @@ -0,0 +1,434 @@ +#include "QmitknnUNetToolGUI.h" +#include +#include +#include +#include +#include + +void QmitknnUNetToolGUI::EnableWidgets(bool enabled) +{ + Superclass::EnableWidgets(enabled); +} + +void QmitknnUNetToolGUI::ClearAllComboBoxes() +{ + m_Controls.modelBox->clear(); + m_Controls.taskBox->clear(); + m_Controls.foldBox->clear(); + m_Controls.trainerBox->clear(); + for (std::unique_ptr &layout : m_EnsembleParams) + { + layout->modelBox->clear(); + layout->trainerBox->clear(); + layout->plannerBox->clear(); + } +} + +void QmitknnUNetToolGUI::OnRefreshPresssed() +{ + const QString resultsFolder = m_Controls.modeldirectoryBox->directory(); + OnDirectoryChanged(resultsFolder); +} + +void QmitknnUNetToolGUI::OnDirectoryChanged(const QString &resultsFolder) +{ + m_Controls.previewButton->setEnabled(false); + this->ClearAllComboBoxes(); + m_ParentFolder = std::make_shared(resultsFolder); + auto models = m_ParentFolder->getModelNames(); + std::for_each(models.begin(), + models.end(), + [this](QString model) + { + if (m_VALID_MODELS.contains(model, Qt::CaseInsensitive)) + m_Controls.modelBox->addItem(model); + }); +} + +void QmitknnUNetToolGUI::OnModelChanged(const QString &model) +{ + if (model.isEmpty()) + { + return; + } + m_Controls.taskBox->clear(); + auto tasks = m_ParentFolder->getTasksForModel(model); + std::for_each(tasks.begin(), tasks.end(), [this](QString task) { m_Controls.taskBox->addItem(task); }); +} + +void QmitknnUNetToolGUI::OnTaskChanged(const QString &task) +{ + if (task.isEmpty()) + { + return; + } + m_Controls.trainerBox->clear(); + m_Controls.plannerBox->clear(); + QStringList trainerPlanners = + m_ParentFolder->getTrainerPlannersForTask(task, m_Controls.modelBox->currentText()); + if (m_Controls.modelBox->currentText() == "ensembles") + { + m_Controls.trainerBox->setVisible(false); + m_Controls.trainerLabel->setVisible(false); + m_Controls.plannerBox->setVisible(false); + m_Controls.plannerLabel->setVisible(false); + m_Controls.foldBox->setVisible(false); + m_Controls.foldLabel->setVisible(false); + ShowEnsembleLayout(true); + QString splitterString = "--"; + QStringList models, trainers, planners; + foreach (QString trainerPlanner, trainerPlanners) + { + QStringList trainerSplitParts = trainerPlanner.split(splitterString, QString::SplitBehavior::SkipEmptyParts); + foreach (QString modelSet, trainerSplitParts) + { + modelSet.remove("ensemble_", Qt::CaseInsensitive); + QStringList splitParts = modelSet.split("__", QString::SplitBehavior::SkipEmptyParts); + QString modelName = splitParts.first(); + QString trainer = splitParts.at(1); + QString planId = splitParts.at(2); + models << modelName; + trainers << trainer; + planners << planId; + } + } + trainers.removeDuplicates(); + planners.removeDuplicates(); + models.removeDuplicates(); + + for (std::unique_ptr &layout : m_EnsembleParams) + { + layout->modelBox->clear(); + layout->trainerBox->clear(); + layout->plannerBox->clear(); + std::for_each(models.begin(), + models.end(), + [&layout, this](QString model) + { + if (m_VALID_MODELS.contains(model, Qt::CaseInsensitive)) + layout->modelBox->addItem(model); + }); + std::for_each( + trainers.begin(), trainers.end(), [&layout](QString trainer) { layout->trainerBox->addItem(trainer); }); + std::for_each( + planners.begin(), planners.end(), [&layout](QString planner) { layout->plannerBox->addItem(planner); }); + } + m_Controls.previewButton->setEnabled(true); + } + else + { + m_Controls.trainerBox->setVisible(true); + m_Controls.trainerLabel->setVisible(true); + m_Controls.plannerBox->setVisible(true); + m_Controls.plannerLabel->setVisible(true); + m_Controls.foldBox->setVisible(true); + m_Controls.foldLabel->setVisible(true); + m_Controls.previewButton->setEnabled(false); + ShowEnsembleLayout(false); + QString splitterString = "__"; + QStringList trainers, planners; + foreach (QString trainerPlanner, trainerPlanners) + { + trainers << trainerPlanner.split(splitterString, QString::SplitBehavior::SkipEmptyParts).first(); + planners << trainerPlanner.split(splitterString, QString::SplitBehavior::SkipEmptyParts).last(); + } + trainers.removeDuplicates(); + planners.removeDuplicates(); + std::for_each( + trainers.begin(), trainers.end(), [this](QString trainer) { m_Controls.trainerBox->addItem(trainer); }); + std::for_each( + planners.begin(), planners.end(), [this](QString planner) { m_Controls.plannerBox->addItem(planner); }); + } +} + +void QmitknnUNetToolGUI::OnTrainerChanged(const QString &plannerSelected) +{ + if (plannerSelected.isEmpty()) + { + return; + } + m_Controls.foldBox->clear(); + if (m_Controls.modelBox->currentText() != "ensembles") + { + auto selectedTrainer = m_Controls.trainerBox->currentText(); + auto selectedTask = m_Controls.taskBox->currentText(); + auto selectedModel = m_Controls.modelBox->currentText(); + auto folds = m_ParentFolder->getFoldsForTrainerPlanner( + selectedTrainer, plannerSelected, selectedTask, selectedModel); + std::for_each(folds.begin(), + folds.end(), + [this](QString fold) + { + if (fold.startsWith("fold_", Qt::CaseInsensitive)) // imposed by nnUNet + m_Controls.foldBox->addItem(fold); + }); + if (m_Controls.foldBox->count() != 0) + { + // Now recalling all added items to check-mark it. + const QAbstractItemModel *qaim = m_Controls.foldBox->checkableModel(); + for (int i = 0; i < folds.size(); ++i) + { + const QModelIndex mi = qaim->index(i, 0); + m_Controls.foldBox->setCheckState(mi, Qt::Checked); + } + m_Controls.previewButton->setEnabled(true); + } + } + else + { + m_Controls.previewButton->setEnabled(true); + } +} + +void QmitknnUNetToolGUI::OnPythonPathChanged(const QString &pyEnv) +{ + if (pyEnv == QString("Select")) + { + QString path = + QFileDialog::getExistingDirectory(m_Controls.pythonEnvComboBox->parentWidget(), "Python Path", "dir"); + if (!path.isEmpty()) + { + m_Controls.pythonEnvComboBox->insertItem(0, path); + m_Controls.pythonEnvComboBox->setCurrentIndex(0); + } + } + else if (!IsNNUNetInstalled(pyEnv)) + { + std::string warning = + "WARNING: nnUNet is not detected on the Python environment you selected. Please select another " + "environment or create one. For more info refer https://github.com/MIC-DKFZ/nnUNet"; + ShowErrorMessage(warning); + } +} + +void QmitknnUNetToolGUI::OnCheckBoxChanged(int state) +{ + bool visibility = false; + if (state == Qt::Checked) + { + visibility = true; + } + ctkCheckBox *box = qobject_cast(sender()); + if (box != nullptr) + { + if (box->objectName() == QString("nopipBox")) + { + m_Controls.codedirectoryBox->setVisible(visibility); + m_Controls.nnUnetdirLabel->setVisible(visibility); + } + else if (box->objectName() == QString("multiModalBox")) + { + m_Controls.multiModalSpinLabel->setVisible(visibility); + m_Controls.multiModalSpinBox->setVisible(visibility); + m_Controls.posSpinBoxLabel->setVisible(visibility); + m_Controls.posSpinBox->setVisible(visibility); + if (visibility) + { + QmitkDataStorageComboBox *defaultImage = new QmitkDataStorageComboBox(this, true); + defaultImage->setObjectName(QString("multiModal_" + QString::number(0))); + defaultImage->SetPredicate(this->m_MultiModalPredicate); + defaultImage->setDisabled(true); + mitk::nnUNetTool::Pointer tool = this->GetConnectedToolAs(); + if (tool != nullptr) + { + defaultImage->SetDataStorage(tool->GetDataStorage()); + defaultImage->SetSelectedNode(tool->GetRefNode()); + } + m_Controls.advancedSettingsLayout->addWidget(defaultImage, this->m_UI_ROWS + m_Modalities.size() + 1, 1, 1, 3); + m_Modalities.push_back(defaultImage); + m_Controls.posSpinBox->setMaximum(this->m_Modalities.size() - 1); + m_UI_ROWS++; + } + else + { + OnModalitiesNumberChanged(0); + m_Controls.multiModalSpinBox->setValue(0); + m_Controls.posSpinBox->setMaximum(0); + delete this->m_Modalities[0]; + m_Modalities.pop_back(); + } + } + } +} + +void QmitknnUNetToolGUI::OnModalitiesNumberChanged(int num) +{ + while (num > static_cast(this->m_Modalities.size() - 1)) + { + QmitkDataStorageComboBox *multiModalBox = new QmitkDataStorageComboBox(this, true); + mitk::nnUNetTool::Pointer tool = this->GetConnectedToolAs(); + multiModalBox->SetDataStorage(tool->GetDataStorage()); + multiModalBox->SetPredicate(this->m_MultiModalPredicate); + multiModalBox->setObjectName(QString("multiModal_" + QString::number(m_Modalities.size() + 1))); + m_Controls.advancedSettingsLayout->addWidget(multiModalBox, this->m_UI_ROWS + m_Modalities.size() + 1, 1, 1, 3); + m_Modalities.push_back(multiModalBox); + } + while (num < static_cast(this->m_Modalities.size() - 1) && !m_Modalities.empty()) + { + QmitkDataStorageComboBox *child = m_Modalities.back(); + if (child->objectName() == "multiModal_0") + { + std::iter_swap(this->m_Modalities.end() - 2, this->m_Modalities.end() - 1); + child = m_Modalities.back(); + } + delete child; // delete the layout item + m_Modalities.pop_back(); + } + m_Controls.posSpinBox->setMaximum(this->m_Modalities.size() - 1); + m_Controls.advancedSettingsLayout->update(); +} + +void QmitknnUNetToolGUI::OnModalPositionChanged(int posIdx) +{ + if (posIdx < static_cast(m_Modalities.size())) + { + int currPos = 0; + bool stopCheck = false; + // for-loop clears all widgets from the QGridLayout and also, finds the position of loaded-image widget. + for (QmitkDataStorageComboBox *multiModalBox : m_Modalities) + { + m_Controls.advancedSettingsLayout->removeWidget(multiModalBox); + multiModalBox->setParent(nullptr); + if (multiModalBox->objectName() != "multiModal_0" && !stopCheck) + { + currPos++; + } + else + { + stopCheck = true; + } + } + // moving the loaded-image widget to the required position + std::iter_swap(this->m_Modalities.begin() + currPos, m_Modalities.begin() + posIdx); + // re-adding all widgets in the order + for (int i = 0; i < static_cast(m_Modalities.size()); ++i) + { + QmitkDataStorageComboBox *multiModalBox = m_Modalities[i]; + m_Controls.advancedSettingsLayout->addWidget(multiModalBox, m_UI_ROWS + i + 1, 1, 1, 3); + } + m_Controls.advancedSettingsLayout->update(); + } +} + +void QmitknnUNetToolGUI::AutoParsePythonPaths() +{ + QString homeDir = QDir::homePath(); + std::vector searchDirs; +#ifdef _WIN32 + searchDirs.push_back(QString("C:") + QDir::separator() + QString("ProgramData") + QDir::separator() + + QString("anaconda3")); +#else + // Add search locations for possible standard python paths here + searchDirs.push_back(homeDir + QDir::separator() + "environments"); + searchDirs.push_back(homeDir + QDir::separator() + "anaconda3"); + searchDirs.push_back(homeDir + QDir::separator() + "miniconda3"); + searchDirs.push_back(homeDir + QDir::separator() + "opt" + QDir::separator() + "miniconda3"); + searchDirs.push_back(homeDir + QDir::separator() + "opt" + QDir::separator() + "anaconda3"); +#endif + for (QString searchDir : searchDirs) + { + if (searchDir.endsWith("anaconda3", Qt::CaseInsensitive)) + { + if (QDir(searchDir).exists()) + { + m_Controls.pythonEnvComboBox->insertItem(0, "(base): " + searchDir); + searchDir.append((QDir::separator() + QString("envs"))); + } + } + for (QDirIterator subIt(searchDir, QDir::AllDirs, QDirIterator::NoIteratorFlags); subIt.hasNext();) + { + subIt.next(); + QString envName = subIt.fileName(); + if (!envName.startsWith('.')) // Filter out irrelevent hidden folders, if any. + { + m_Controls.pythonEnvComboBox->insertItem(0, "(" + envName + "): " + subIt.filePath()); + } + } + } + m_Controls.pythonEnvComboBox->setCurrentIndex(-1); +} + +std::vector QmitknnUNetToolGUI::FetchSelectedFoldsFromUI() +{ + std::vector folds; + if (!(m_Controls.foldBox->allChecked() || m_Controls.foldBox->noneChecked())) + { + QModelIndexList foldList = m_Controls.foldBox->checkedIndexes(); + foreach (QModelIndex index, foldList) + { + QString foldQString = + m_Controls.foldBox->itemText(index.row()).split("_", QString::SplitBehavior::SkipEmptyParts).last(); + folds.push_back(foldQString.toStdString()); + } + } + return folds; +} + +mitk::ModelParams QmitknnUNetToolGUI::MapToRequest(const QString &modelName, + const QString &taskName, + const QString &trainer, + const QString &planId, + const std::vector &folds) +{ + mitk::ModelParams requestObject; + requestObject.model = modelName.toStdString(); + requestObject.trainer = trainer.toStdString(); + requestObject.planId = planId.toStdString(); + requestObject.task = taskName.toStdString(); + requestObject.folds = folds; + return requestObject; +} + +void QmitknnUNetToolGUI::SegmentationProcessFailed() +{ + m_Controls.statusLabel->setText( + "STATUS: Error in the segmentation process. No resulting segmentation can be loaded."); + this->setCursor(Qt::ArrowCursor); + std::stringstream stream; + stream << "Error in the segmentation process. No resulting segmentation can be loaded."; + ShowErrorMessage(stream.str()); + m_Controls.stopButton->setEnabled(false); +} + +void QmitknnUNetToolGUI::SegmentationResultHandler(mitk::nnUNetTool *tool) +{ + tool->RenderOutputBuffer(); + this->SetLabelSetPreview(tool->GetMLPreview()); + m_Controls.statusLabel->setText("STATUS: Segmentation task finished successfully. Please Confirm the " + "segmentation else will result in data loss"); + m_Controls.stopButton->setEnabled(false); +} + +void QmitknnUNetToolGUI::ShowEnsembleLayout(bool visible) +{ + if (m_EnsembleParams.empty()) + { + ctkCollapsibleGroupBox *groupBoxModel1 = new ctkCollapsibleGroupBox(this); + auto lay1 = std::make_unique(groupBoxModel1); + m_EnsembleParams.push_back(std::move(lay1)); + groupBoxModel1->setObjectName(QString::fromUtf8("model_1_Box")); + groupBoxModel1->setTitle(QString::fromUtf8("Model 1")); + groupBoxModel1->setMinimumSize(QSize(0, 0)); + groupBoxModel1->setCollapsedHeight(5); + groupBoxModel1->setCollapsed(false); + groupBoxModel1->setFlat(true); + groupBoxModel1->setAlignment(Qt::AlignRight); + m_Controls.advancedSettingsLayout->addWidget(groupBoxModel1, 3, 0, 1, 2); + + ctkCollapsibleGroupBox *groupBoxModel2 = new ctkCollapsibleGroupBox(this); + auto lay2 = std::make_unique(groupBoxModel2); + m_EnsembleParams.push_back(std::move(lay2)); + groupBoxModel2->setObjectName(QString::fromUtf8("model_2_Box")); + groupBoxModel2->setTitle(QString::fromUtf8("Model 2")); + groupBoxModel2->setMinimumSize(QSize(0, 0)); + groupBoxModel2->setCollapsedHeight(5); + groupBoxModel2->setCollapsed(false); + groupBoxModel2->setFlat(true); + groupBoxModel2->setAlignment(Qt::AlignLeft); + m_Controls.advancedSettingsLayout->addWidget(groupBoxModel2, 3, 2, 1, 2); + } + for (std::unique_ptr &layout : m_EnsembleParams) + { + layout->parent->setVisible(visible); + } +} diff --git a/Modules/SegmentationUI/files.cmake b/Modules/SegmentationUI/files.cmake index e71eb77792..63a50b2923 100644 --- a/Modules/SegmentationUI/files.cmake +++ b/Modules/SegmentationUI/files.cmake @@ -1,90 +1,97 @@ set( CPP_FILES Qmitk/QmitkAdaptiveRegionGrowingToolGUI.cpp Qmitk/QmitkAutoSegmentationToolGUIBase.cpp Qmitk/QmitkAutoMLSegmentationToolGUIBase.cpp Qmitk/QmitkBinaryThresholdToolGUIBase.cpp Qmitk/QmitkBinaryThresholdToolGUI.cpp Qmitk/QmitkBinaryThresholdULToolGUI.cpp Qmitk/QmitkCalculateGrayValueStatisticsToolGUI.cpp Qmitk/QmitkConfirmSegmentationDialog.cpp Qmitk/QmitkCopyToClipBoardDialog.cpp Qmitk/QmitkDrawPaintbrushToolGUI.cpp Qmitk/QmitkErasePaintbrushToolGUI.cpp Qmitk/QmitkFastMarchingToolGUIBase.cpp Qmitk/QmitkFastMarchingTool3DGUI.cpp Qmitk/QmitkFastMarchingToolGUI.cpp Qmitk/QmitkLiveWireTool2DGUI.cpp Qmitk/QmitkNewSegmentationDialog.cpp Qmitk/QmitkOtsuTool3DGUI.cpp Qmitk/QmitkPaintbrushToolGUI.cpp Qmitk/QmitkPickingToolGUI.cpp Qmitk/QmitkPixelManipulationToolGUI.cpp Qmitk/QmitkSlicesInterpolator.cpp Qmitk/QmitkToolGUI.cpp Qmitk/QmitkToolGUIArea.cpp Qmitk/QmitkToolSelectionBox.cpp Qmitk/QmitkWatershedToolGUI.cpp +Qmitk/QmitknnUNetToolGUI.cpp +Qmitk/QmitknnUNetToolSlots.cpp #Added from ML Qmitk/QmitkLabelSetWidget.cpp Qmitk/QmitkSurfaceStampWidget.cpp Qmitk/QmitkMaskStampWidget.cpp Qmitk/QmitkSliceBasedInterpolatorWidget.cpp Qmitk/QmitkSurfaceBasedInterpolatorWidget.cpp Qmitk/QmitkSearchLabelDialog.cpp Qmitk/QmitkSimpleLabelSetListWidget.cpp ) set(MOC_H_FILES Qmitk/QmitkAdaptiveRegionGrowingToolGUI.h Qmitk/QmitkAutoSegmentationToolGUIBase.h Qmitk/QmitkAutoMLSegmentationToolGUIBase.h Qmitk/QmitkBinaryThresholdToolGUIBase.h Qmitk/QmitkBinaryThresholdToolGUI.h Qmitk/QmitkBinaryThresholdULToolGUI.h Qmitk/QmitkCalculateGrayValueStatisticsToolGUI.h Qmitk/QmitkConfirmSegmentationDialog.h Qmitk/QmitkCopyToClipBoardDialog.h Qmitk/QmitkDrawPaintbrushToolGUI.h Qmitk/QmitkErasePaintbrushToolGUI.h Qmitk/QmitkFastMarchingToolGUIBase.h Qmitk/QmitkFastMarchingTool3DGUI.h Qmitk/QmitkFastMarchingToolGUI.h Qmitk/QmitkLiveWireTool2DGUI.h Qmitk/QmitkNewSegmentationDialog.h Qmitk/QmitkOtsuTool3DGUI.h Qmitk/QmitkPaintbrushToolGUI.h Qmitk/QmitkPickingToolGUI.h Qmitk/QmitkPixelManipulationToolGUI.h Qmitk/QmitkSlicesInterpolator.h Qmitk/QmitkToolGUI.h Qmitk/QmitkToolGUIArea.h Qmitk/QmitkToolSelectionBox.h Qmitk/QmitkWatershedToolGUI.h +Qmitk/QmitknnUNetToolGUI.h +Qmitk/QmitknnUNetGPU.h +Qmitk/QmitknnUNetEnsembleLayout.h +Qmitk/QmitknnUNetFolderParser.h #Added from ML Qmitk/QmitkLabelSetWidget.h Qmitk/QmitkSurfaceStampWidget.h Qmitk/QmitkMaskStampWidget.h Qmitk/QmitkSliceBasedInterpolatorWidget.h Qmitk/QmitkSurfaceBasedInterpolatorWidget.h Qmitk/QmitkSearchLabelDialog.h Qmitk/QmitkSimpleLabelSetListWidget.h ) set(UI_FILES Qmitk/QmitkAdaptiveRegionGrowingToolGUIControls.ui Qmitk/QmitkConfirmSegmentationDialog.ui Qmitk/QmitkOtsuToolWidgetControls.ui Qmitk/QmitkLiveWireTool2DGUIControls.ui Qmitk/QmitkWatershedToolGUIControls.ui #Added from ML Qmitk/QmitkLabelSetWidgetControls.ui Qmitk/QmitkSurfaceStampWidgetGUIControls.ui Qmitk/QmitkMaskStampWidgetGUIControls.ui Qmitk/QmitkSliceBasedInterpolatorWidgetGUIControls.ui Qmitk/QmitkSurfaceBasedInterpolatorWidgetGUIControls.ui Qmitk/QmitkSearchLabelDialogGUI.ui +Qmitk/QmitknnUNetToolGUIControls.ui ) set(QRC_FILES resources/SegmentationUI.qrc ) diff --git a/Plugins/org.mitk.gui.qt.segmentation/plugin.xml b/Plugins/org.mitk.gui.qt.segmentation/plugin.xml index a72db4b004..a628b56818 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/plugin.xml +++ b/Plugins/org.mitk.gui.qt.segmentation/plugin.xml @@ -1,80 +1,81 @@ Allows the segmentation of images using different tools. Edit segmentations using standard operations. + diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.cpp b/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.cpp index 1c1181fdf1..8fdf537329 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.cpp +++ b/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.cpp @@ -1,822 +1,824 @@ /*============================================================================ 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 "QmitkSegmentationView.h" #include "mitkPluginActivator.h" // blueberry #include // mitk #include #include #include #include #include #include #include #include #include #include #include // Qmitk #include #include #include // us #include #include // Qt #include #include const std::string QmitkSegmentationView::VIEW_ID = "org.mitk.views.segmentation"; QmitkSegmentationView::QmitkSegmentationView() : m_Parent(nullptr) , m_Controls(nullptr) , m_RenderWindowPart(nullptr) , m_ToolManager(nullptr) , m_ReferenceNode(nullptr) , m_WorkingNode(nullptr) , m_AutoSelectionEnabled(false) , m_MouseCursorSet(false) { mitk::TNodePredicateDataType::Pointer isImage = mitk::TNodePredicateDataType::New(); auto isDwi = mitk::NodePredicateDataType::New("DiffusionImage"); auto isDti = mitk::NodePredicateDataType::New("TensorImage"); auto isOdf = mitk::NodePredicateDataType::New("OdfImage"); auto isSegment = mitk::NodePredicateDataType::New("Segment"); mitk::NodePredicateOr::Pointer validImages = mitk::NodePredicateOr::New(); validImages->AddPredicate(mitk::NodePredicateAnd::New(isImage, mitk::NodePredicateNot::New(isSegment))); validImages->AddPredicate(isDwi); validImages->AddPredicate(isDti); validImages->AddPredicate(isOdf); auto isBinary = mitk::NodePredicateProperty::New("binary", mitk::BoolProperty::New(true)); auto isMask = mitk::NodePredicateAnd::New(isBinary, isImage); auto validSegmentations = mitk::NodePredicateOr::New(); validSegmentations->AddPredicate(mitk::TNodePredicateDataType::New()); validSegmentations->AddPredicate(isMask); m_SegmentationPredicate = mitk::NodePredicateAnd::New(); m_SegmentationPredicate->AddPredicate(validSegmentations); m_SegmentationPredicate->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object"))); m_SegmentationPredicate->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("hidden object"))); m_ReferencePredicate = mitk::NodePredicateAnd::New(); m_ReferencePredicate->AddPredicate(validImages); m_ReferencePredicate->AddPredicate(mitk::NodePredicateNot::New(m_SegmentationPredicate)); m_ReferencePredicate->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object"))); m_ReferencePredicate->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("hidden object"))); } QmitkSegmentationView::~QmitkSegmentationView() { if (m_Controls) { // deactivate all tools m_ToolManager->ActivateTool(-1); // removing all observers for (NodeTagMapType::iterator dataIter = m_WorkingDataObserverTags.begin(); dataIter != m_WorkingDataObserverTags.end(); ++dataIter) { (*dataIter).first->GetProperty("visible")->RemoveObserver((*dataIter).second); } m_WorkingDataObserverTags.clear(); mitk::RenderingManager::GetInstance()->RemoveObserver(m_RenderingManagerObserverTag); ctkPluginContext* context = mitk::PluginActivator::getContext(); ctkServiceReference ppmRef = context->getServiceReference(); mitk::PlanePositionManagerService* service = context->getService(ppmRef); service->RemoveAllPlanePositions(); context->ungetService(ppmRef); m_ToolManager->SetReferenceData(nullptr); m_ToolManager->SetWorkingData(nullptr); } delete m_Controls; } /**********************************************************************/ /* private Q_SLOTS */ /**********************************************************************/ void QmitkSegmentationView::OnReferenceSelectionChanged(QList nodes) { m_ToolManager->ActivateTool(-1); if (nodes.empty()) { m_Controls->segImageSelector->SetNodePredicate(m_SegmentationPredicate); m_ReferenceNode = nullptr; m_ToolManager->SetReferenceData(m_ReferenceNode); this->UpdateGUI(); return; } m_ReferenceNode = nodes.first(); m_ToolManager->SetReferenceData(m_ReferenceNode); if (m_ReferenceNode.IsNotNull()) { // set a predicate such that a segmentation fits the selected reference image geometry auto segPredicate = mitk::NodePredicateAnd::New(m_SegmentationPredicate.GetPointer(), mitk::NodePredicateSubGeometry::New(m_ReferenceNode->GetData()->GetGeometry())); m_Controls->segImageSelector->SetNodePredicate(segPredicate); if (m_AutoSelectionEnabled) { // hide all image nodes to later show only the automatically selected ones mitk::DataStorage::SetOfObjects::ConstPointer imageNodes = this->GetDataStorage()->GetSubset(m_ReferencePredicate); for (mitk::DataStorage::SetOfObjects::const_iterator iter = imageNodes->begin(); iter != imageNodes->end(); ++iter) { (*iter)->SetVisibility(false); } } m_ReferenceNode->SetVisibility(true); } this->UpdateGUI(); } void QmitkSegmentationView::OnSegmentationSelectionChanged(QList nodes) { m_ToolManager->ActivateTool(-1); if (nodes.empty()) { m_WorkingNode = nullptr; m_ToolManager->SetWorkingData(m_WorkingNode); this->UpdateGUI(); return; } if (m_ReferenceNode.IsNull()) { this->UpdateGUI(); return; } mitk::Image::ConstPointer referenceImage = dynamic_cast(m_ReferenceNode->GetData()); if (referenceImage.IsNull()) { this->UpdateGUI(); return; } m_WorkingNode = nodes.first(); m_ToolManager->SetWorkingData(m_WorkingNode); if (m_WorkingNode.IsNotNull()) { if (m_AutoSelectionEnabled) { // hide all segmentation nodes to later show only the automatically selected ones mitk::DataStorage::SetOfObjects::ConstPointer segmentationNodes = this->GetDataStorage()->GetSubset(m_SegmentationPredicate); for (mitk::DataStorage::SetOfObjects::const_iterator iter = segmentationNodes->begin(); iter != segmentationNodes->end(); ++iter) { (*iter)->SetVisibility(false); } } m_WorkingNode->SetVisibility(true); this->InitializeRenderWindows(referenceImage->GetTimeGeometry(), mitk::RenderingManager::REQUEST_UPDATE_ALL, false); } this->UpdateGUI(); } void QmitkSegmentationView::CreateNewSegmentation() { mitk::DataNode::Pointer referenceNode = m_ToolManager->GetReferenceData(0); if (referenceNode.IsNull()) { MITK_ERROR << "'Create new segmentation' button should never be clickable unless a reference image is selected."; return; } mitk::Image::ConstPointer referenceImage = dynamic_cast(referenceNode->GetData()); if (referenceImage.IsNull()) { QMessageBox::information( m_Parent, "New segmentation", "Please load and select an image before starting some action."); return; } if (referenceImage->GetDimension() <= 1) { QMessageBox::information(m_Parent, "New segmentation", "Segmentation is currently not supported for 2D images"); return; } m_ToolManager->ActivateTool(-1); const auto currentTimePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); unsigned int imageTimeStep = 0; if (referenceImage->GetTimeGeometry()->IsValidTimePoint(currentTimePoint)) { imageTimeStep = referenceImage->GetTimeGeometry()->TimePointToTimeStep(currentTimePoint); } auto segTemplateImage = referenceImage; if (referenceImage->GetDimension() > 3) { auto result = QMessageBox::question(m_Parent, tr("Create a static or dynamic segmentation?"), tr("The selected image has multiple time steps.\n\nDo you want to create a static " "segmentation that is identical for all time steps or do you want to create a " "dynamic segmentation to segment individual time steps?"), tr("Create static segmentation"), tr("Create dynamic segmentation"), QString(), 0, 0); if (result == 0) { auto selector = mitk::ImageTimeSelector::New(); selector->SetInput(referenceImage); selector->SetTimeNr(0); selector->Update(); const auto refTimeGeometry = referenceImage->GetTimeGeometry(); auto newTimeGeometry = mitk::ProportionalTimeGeometry::New(); newTimeGeometry->SetFirstTimePoint(refTimeGeometry->GetMinimumTimePoint()); newTimeGeometry->SetStepDuration(refTimeGeometry->GetMaximumTimePoint() - refTimeGeometry->GetMinimumTimePoint()); mitk::Image::Pointer newImage = selector->GetOutput(); newTimeGeometry->SetTimeStepGeometry(referenceImage->GetGeometry(imageTimeStep), 0); newImage->SetTimeGeometry(newTimeGeometry); segTemplateImage = newImage; } } QString newName = QString::fromStdString(referenceNode->GetName()); newName.append("-labels"); // ask about the name and organ type of the new segmentation auto dialog = new QmitkNewSegmentationDialog(m_Parent); QStringList organColors = mitk::OrganNamesHandling::GetDefaultOrganColorString(); dialog->SetSuggestionList(organColors); dialog->SetSegmentationName(newName); int dialogReturnValue = dialog->exec(); if (dialogReturnValue == QDialog::Rejected) { return; } std::string newNodeName = dialog->GetSegmentationName().toStdString(); if (newNodeName.empty()) { newNodeName = "Unnamed"; } // create a new image of the same dimensions and smallest possible pixel type auto firstTool = m_ToolManager->GetToolById(0); if (nullptr == firstTool) { return; } mitk::DataNode::Pointer emptySegmentation = nullptr; try { emptySegmentation = firstTool->CreateEmptySegmentationNode(segTemplateImage, newNodeName, dialog->GetColor()); } catch (const std::bad_alloc &) { QMessageBox::warning(m_Parent, tr("New segmentation"), tr("Could not allocate memory for new segmentation")); } if (nullptr == emptySegmentation) { return; // could have been aborted by user } // initialize "showVolume"-property to false to prevent recalculating the volume while working on the segmentation emptySegmentation->SetProperty("showVolume", mitk::BoolProperty::New(false)); mitk::OrganNamesHandling::UpdateOrganList(organColors, dialog->GetSegmentationName(), dialog->GetColor()); // escape ';' here (replace by '\;') QString stringForStorage = organColors.replaceInStrings(";", "\\;").join(";"); MITK_DEBUG << "Will store: " << stringForStorage; this->GetPreferences()->Put("Organ-Color-List", stringForStorage); this->GetPreferences()->Flush(); this->GetDataStorage()->Add(emptySegmentation, referenceNode); if (m_ToolManager->GetWorkingData(0)) { m_ToolManager->GetWorkingData(0)->SetSelected(false); } emptySegmentation->SetSelected(true); m_Controls->segImageSelector->SetCurrentSelectedNode(emptySegmentation); this->InitializeRenderWindows(referenceImage->GetTimeGeometry(), mitk::RenderingManager::REQUEST_UPDATE_ALL, false); } void QmitkSegmentationView::OnManualTool2DSelected(int id) { this->ResetMouseCursor(); mitk::StatusBar::GetInstance()->DisplayText(""); if (id >= 0) { std::string text = "Active Tool: \""; text += m_ToolManager->GetToolById(id)->GetName(); text += "\""; mitk::StatusBar::GetInstance()->DisplayText(text.c_str()); us::ModuleResource resource = m_ToolManager->GetToolById(id)->GetCursorIconResource(); this->SetMouseCursor(resource, 0, 0); } } void QmitkSegmentationView::OnShowMarkerNodes(bool state) { mitk::SegTool2D::Pointer manualSegmentationTool; unsigned int numberOfExistingTools = m_ToolManager->GetTools().size(); for (unsigned int i = 0; i < numberOfExistingTools; i++) { manualSegmentationTool = dynamic_cast(m_ToolManager->GetToolById(i)); if (manualSegmentationTool) { if (state == true) { manualSegmentationTool->SetShowMarkerNodes(true); } else { manualSegmentationTool->SetShowMarkerNodes(false); } } } } /**********************************************************************/ /* private */ /**********************************************************************/ void QmitkSegmentationView::CreateQtPartControl(QWidget* parent) { m_Parent = parent; m_Controls = new Ui::QmitkSegmentationControls; m_Controls->setupUi(parent); m_Controls->patImageSelector->SetDataStorage(GetDataStorage()); m_Controls->patImageSelector->SetNodePredicate(m_ReferencePredicate); m_Controls->patImageSelector->SetInvalidInfo("Select an image"); m_Controls->patImageSelector->SetPopUpTitel("Select an image"); m_Controls->patImageSelector->SetPopUpHint("Select an image that should be used to define the geometry and bounds of the segmentation."); m_Controls->segImageSelector->SetDataStorage(GetDataStorage()); m_Controls->segImageSelector->SetNodePredicate(m_SegmentationPredicate); m_Controls->segImageSelector->SetInvalidInfo("Select a segmentation"); m_Controls->segImageSelector->SetPopUpTitel("Select a segmentation"); m_Controls->segImageSelector->SetPopUpHint("Select a segmentation that should be modified. Only segmentation with the same geometry and within the bounds of the reference image are selected."); connect(m_Controls->patImageSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkSegmentationView::OnReferenceSelectionChanged); connect(m_Controls->segImageSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkSegmentationView::OnSegmentationSelectionChanged); m_ToolManager = mitk::ToolManagerProvider::GetInstance()->GetToolManager(); m_ToolManager->SetDataStorage(*(this->GetDataStorage())); m_ToolManager->InitializeTools(); QString segTools2D = tr("Add Subtract Fill Erase Paint Wipe 'Region Growing' 'Live Wire' '2D Fast Marching'"); QString segTools3D = tr("Threshold 'UL Threshold' Otsu 'Fast Marching 3D' 'Region Growing 3D' Watershed Picking"); - +#ifdef __linux__ + segTools3D.append(" nnUNet"); // plugin not enabled for MacOS / Windows +#endif std::regex extSegTool2DRegEx("SegTool2D$"); std::regex extSegTool3DRegEx("SegTool3D$"); auto tools = m_ToolManager->GetTools(); for (const auto &tool : tools) { if (std::regex_search(tool->GetNameOfClass(), extSegTool2DRegEx)) { segTools2D.append(QString(" '%1'").arg(tool->GetName())); } else if (std::regex_search(tool->GetNameOfClass(), extSegTool3DRegEx)) { segTools3D.append(QString(" '%1'").arg(tool->GetName())); } } // all part of open source MITK m_Controls->m_ManualToolSelectionBox2D->SetToolManager(*m_ToolManager); m_Controls->m_ManualToolSelectionBox2D->SetGenerateAccelerators(true); m_Controls->m_ManualToolSelectionBox2D->SetToolGUIArea(m_Controls->m_ManualToolGUIContainer2D); m_Controls->m_ManualToolSelectionBox2D->SetDisplayedToolGroups(segTools2D.toStdString()); m_Controls->m_ManualToolSelectionBox2D->SetLayoutColumns(3); m_Controls->m_ManualToolSelectionBox2D->SetEnabledMode( QmitkToolSelectionBox::EnabledWithReferenceAndWorkingDataVisible); connect(m_Controls->m_ManualToolSelectionBox2D, &QmitkToolSelectionBox::ToolSelected, this, &QmitkSegmentationView::OnManualTool2DSelected); //setup 3D Tools m_Controls->m_ManualToolSelectionBox3D->SetToolManager(*m_ToolManager); m_Controls->m_ManualToolSelectionBox3D->SetGenerateAccelerators(true); m_Controls->m_ManualToolSelectionBox3D->SetToolGUIArea(m_Controls->m_ManualToolGUIContainer3D); m_Controls->m_ManualToolSelectionBox3D->SetDisplayedToolGroups(segTools3D.toStdString()); m_Controls->m_ManualToolSelectionBox3D->SetLayoutColumns(3); m_Controls->m_ManualToolSelectionBox3D->SetEnabledMode( QmitkToolSelectionBox::EnabledWithReferenceAndWorkingDataVisible); // create signal/slot connections connect(m_Controls->btnNewSegmentation, &QToolButton::clicked, this, &QmitkSegmentationView::CreateNewSegmentation); connect(m_Controls->m_SlicesInterpolator, &QmitkSlicesInterpolator::SignalShowMarkerNodes, this, &QmitkSegmentationView::OnShowMarkerNodes); // set callback function for already existing segmentation nodes mitk::DataStorage::SetOfObjects::ConstPointer allSegmentations = GetDataStorage()->GetSubset(m_SegmentationPredicate); for (mitk::DataStorage::SetOfObjects::const_iterator iter = allSegmentations->begin(); iter != allSegmentations->end(); ++iter) { mitk::DataNode* node = *iter; auto command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &QmitkSegmentationView::ValidateSelectionInput); m_WorkingDataObserverTags.insert(std::pair( node, node->GetProperty("visible")->AddObserver(itk::ModifiedEvent(), command))); } auto command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &QmitkSegmentationView::ValidateSelectionInput); m_RenderingManagerObserverTag = mitk::RenderingManager::GetInstance()->AddObserver(mitk::RenderingManagerViewsInitializedEvent(), command); m_RenderWindowPart = this->GetRenderWindowPart(); if (nullptr != m_RenderWindowPart) { this->RenderWindowPartActivated(m_RenderWindowPart); } // Make sure the GUI notices if appropriate data is already present on creation. // Should be done last, if everything else is configured because it triggers the autoselection of data. m_Controls->patImageSelector->SetAutoSelectNewNodes(true); m_Controls->segImageSelector->SetAutoSelectNewNodes(true); this->UpdateGUI(); } void QmitkSegmentationView::RenderWindowPartActivated(mitk::IRenderWindowPart* renderWindowPart) { if (m_RenderWindowPart != renderWindowPart) { m_RenderWindowPart = renderWindowPart; } if (m_Parent) { m_Parent->setEnabled(true); } // tell the interpolation about tool manager, data storage and render window part if (m_Controls) { m_Controls->m_SlicesInterpolator->SetDataStorage(this->GetDataStorage()); QList controllers; controllers.push_back(renderWindowPart->GetQmitkRenderWindow("axial")->GetSliceNavigationController()); controllers.push_back(renderWindowPart->GetQmitkRenderWindow("sagittal")->GetSliceNavigationController()); controllers.push_back(renderWindowPart->GetQmitkRenderWindow("coronal")->GetSliceNavigationController()); m_Controls->m_SlicesInterpolator->Initialize(m_ToolManager, controllers); } } void QmitkSegmentationView::RenderWindowPartDeactivated(mitk::IRenderWindowPart* /*renderWindowPart*/) { m_RenderWindowPart = nullptr; if (m_Parent) { m_Parent->setEnabled(false); } } void QmitkSegmentationView::OnPreferencesChanged(const berry::IBerryPreferences* prefs) { if (m_Controls != nullptr) { bool slimView = prefs->GetBool("slim view", false); m_Controls->m_ManualToolSelectionBox2D->SetShowNames(!slimView); m_Controls->m_ManualToolSelectionBox3D->SetShowNames(!slimView); } m_AutoSelectionEnabled = prefs->GetBool("auto selection", false); this->ApplyDisplayOptions(); } void QmitkSegmentationView::NodeAdded(const mitk::DataNode* node) { if (!m_SegmentationPredicate->CheckNode(node)) { return; } auto command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &QmitkSegmentationView::ValidateSelectionInput); m_WorkingDataObserverTags.insert(std::pair( const_cast(node), node->GetProperty("visible")->AddObserver(itk::ModifiedEvent(), command))); this->ApplyDisplayOptions(const_cast(node)); } void QmitkSegmentationView::NodeRemoved(const mitk::DataNode* node) { if (m_SegmentationPredicate->CheckNode(node)) { // remove all possible contour markers of the segmentation mitk::DataStorage::SetOfObjects::ConstPointer allContourMarkers = this->GetDataStorage()->GetDerivations( node, mitk::NodePredicateProperty::New("isContourMarker", mitk::BoolProperty::New(true))); ctkPluginContext* context = mitk::PluginActivator::getContext(); ctkServiceReference ppmRef = context->getServiceReference(); mitk::PlanePositionManagerService* service = context->getService(ppmRef); for (mitk::DataStorage::SetOfObjects::ConstIterator it = allContourMarkers->Begin(); it != allContourMarkers->End(); ++it) { std::string nodeName = node->GetName(); unsigned int t = nodeName.find_last_of(" "); unsigned int id = atof(nodeName.substr(t + 1).c_str()) - 1; service->RemovePlanePosition(id); this->GetDataStorage()->Remove(it->Value()); } context->ungetService(ppmRef); service = nullptr; mitk::Image* image = dynamic_cast(node->GetData()); mitk::SurfaceInterpolationController::GetInstance()->RemoveInterpolationSession(image); } mitk::DataNode* tempNode = const_cast(node); //Remove observer if one was registered auto finding = m_WorkingDataObserverTags.find(tempNode); if (finding != m_WorkingDataObserverTags.end()) { node->GetProperty("visible")->RemoveObserver(m_WorkingDataObserverTags[tempNode]); m_WorkingDataObserverTags.erase(tempNode); } } void QmitkSegmentationView::ApplyDisplayOptions() { if (!m_Parent) { return; } if (!m_Controls) { return; // might happen on initialization (preferences loaded) } mitk::DataNode::Pointer workingData = m_ToolManager->GetWorkingData(0); mitk::DataStorage::SetOfObjects::ConstPointer allImages = this->GetDataStorage()->GetSubset(m_SegmentationPredicate); for (mitk::DataStorage::SetOfObjects::const_iterator iter = allImages->begin(); iter != allImages->end(); ++iter) { this->ApplyDisplayOptions(*iter); } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSegmentationView::ApplyDisplayOptions(mitk::DataNode* node) { if (nullptr == node) { return; } auto drawOutline = mitk::BoolProperty::New(GetPreferences()->GetBool("draw outline", true)); auto labelSetImage = dynamic_cast(node->GetData()); if (nullptr != labelSetImage) { // node is actually a multi label segmentation, // but its outline property can be set in the 'single label' segmentation preference page as well node->SetProperty("labelset.contour.active", drawOutline); // force render window update to show outline node->GetData()->Modified(); } else { // node is a 'single label' segmentation bool isBinary = false; node->GetBoolProperty("binary", isBinary); if (isBinary) { node->SetProperty("outline binary", drawOutline); node->SetProperty("outline width", mitk::FloatProperty::New(2.0)); // force render window update to show outline node->GetData()->Modified(); } } } void QmitkSegmentationView::OnContourMarkerSelected(const mitk::DataNode* node) { QmitkRenderWindow* selectedRenderWindow = nullptr; auto* renderWindowPart = this->GetRenderWindowPart(mitk::WorkbenchUtil::OPEN); auto* axialRenderWindow = renderWindowPart->GetQmitkRenderWindow("axial"); auto* sagittalRenderWindow = renderWindowPart->GetQmitkRenderWindow("sagittal"); auto* coronalRenderWindow = renderWindowPart->GetQmitkRenderWindow("coronal"); auto* _3DRenderWindow = renderWindowPart->GetQmitkRenderWindow("3d"); bool PlanarFigureInitializedWindow = false; // find initialized renderwindow if (node->GetBoolProperty("PlanarFigureInitializedWindow", PlanarFigureInitializedWindow, axialRenderWindow->GetRenderer())) { selectedRenderWindow = axialRenderWindow; } if (!selectedRenderWindow && node->GetBoolProperty( "PlanarFigureInitializedWindow", PlanarFigureInitializedWindow, sagittalRenderWindow->GetRenderer())) { selectedRenderWindow = sagittalRenderWindow; } if (!selectedRenderWindow && node->GetBoolProperty( "PlanarFigureInitializedWindow", PlanarFigureInitializedWindow, coronalRenderWindow->GetRenderer())) { selectedRenderWindow = coronalRenderWindow; } if (!selectedRenderWindow && node->GetBoolProperty( "PlanarFigureInitializedWindow", PlanarFigureInitializedWindow, _3DRenderWindow->GetRenderer())) { selectedRenderWindow = _3DRenderWindow; } // make node visible if (selectedRenderWindow) { std::string nodeName = node->GetName(); unsigned int t = nodeName.find_last_of(" "); unsigned int id = atof(nodeName.substr(t + 1).c_str()) - 1; { ctkPluginContext* context = mitk::PluginActivator::getContext(); ctkServiceReference ppmRef = context->getServiceReference(); mitk::PlanePositionManagerService* service = context->getService(ppmRef); selectedRenderWindow->GetSliceNavigationController()->ExecuteOperation(service->GetPlanePosition(id)); context->ungetService(ppmRef); } selectedRenderWindow->GetRenderer()->GetCameraController()->Fit(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkSegmentationView::OnSelectionChanged(berry::IWorkbenchPart::Pointer /*part*/, const QList& nodes) { if (nodes.size() != 0) { std::string markerName = "Position"; unsigned int numberOfNodes = nodes.size(); std::string nodeName = nodes.at(0)->GetName(); if ((numberOfNodes == 1) && (nodeName.find(markerName) == 0)) { this->OnContourMarkerSelected(nodes.at(0)); return; } } } void QmitkSegmentationView::ResetMouseCursor() { if (m_MouseCursorSet) { mitk::ApplicationCursor::GetInstance()->PopCursor(); m_MouseCursorSet = false; } } void QmitkSegmentationView::SetMouseCursor(const us::ModuleResource& resource, int hotspotX, int hotspotY) { // Remove previously set mouse cursor if (m_MouseCursorSet) this->ResetMouseCursor(); if (resource) { us::ModuleResourceStream cursor(resource, std::ios::binary); mitk::ApplicationCursor::GetInstance()->PushCursor(cursor, hotspotX, hotspotY); m_MouseCursorSet = true; } } void QmitkSegmentationView::UpdateGUI() { mitk::DataNode* referenceNode = m_ToolManager->GetReferenceData(0); bool hasReferenceNode = referenceNode != nullptr; mitk::DataNode* workingNode = m_ToolManager->GetWorkingData(0); bool hasWorkingNode = workingNode != nullptr; m_Controls->btnNewSegmentation->setEnabled(false); m_Controls->m_SlicesInterpolator->setEnabled(false); if (hasReferenceNode) { m_Controls->btnNewSegmentation->setEnabled(true); } if (hasWorkingNode && hasReferenceNode) { m_Controls->m_SlicesInterpolator->setEnabled(true); int layer = -1; referenceNode->GetIntProperty("layer", layer); workingNode->SetIntProperty("layer", layer + 1); } this->ValidateSelectionInput(); } void QmitkSegmentationView::ValidateSelectionInput() { this->UpdateWarningLabel(""); // the argument is actually not used // enable status depends on the tool manager selection m_Controls->m_ManualToolSelectionBox2D->setEnabled(false); m_Controls->m_ManualToolSelectionBox3D->setEnabled(false); mitk::DataNode* referenceNode = m_Controls->patImageSelector->GetSelectedNode(); mitk::DataNode* workingNode = m_Controls->segImageSelector->GetSelectedNode(); if (nullptr == referenceNode) { return; } if (nullptr == workingNode) { return; } mitk::IRenderWindowPart* renderWindowPart = this->GetRenderWindowPart(); auto workingNodeIsVisible = renderWindowPart && workingNode->IsVisible(renderWindowPart->GetQmitkRenderWindow("axial")->GetRenderer()); if (!workingNodeIsVisible) { this->UpdateWarningLabel(tr("The selected segmentation is currently not visible!")); return; } /* * Here we check whether the geometry of the selected segmentation image is aligned with the worldgeometry. * At the moment it is not supported to use a geometry different from the selected image for reslicing. * For further information see Bug 16063 */ const mitk::BaseGeometry *workingNodeGeo = workingNode->GetData()->GetGeometry(); const mitk::BaseGeometry *worldGeo = renderWindowPart->GetQmitkRenderWindow("3d")->GetSliceNavigationController()->GetCurrentGeometry3D(); if (nullptr != workingNodeGeo && nullptr != worldGeo) { if (mitk::Equal(*workingNodeGeo->GetBoundingBox(), *worldGeo->GetBoundingBox(), mitk::eps, true)) { m_ToolManager->SetReferenceData(referenceNode); m_ToolManager->SetWorkingData(workingNode); m_Controls->m_ManualToolSelectionBox2D->setEnabled(true); m_Controls->m_ManualToolSelectionBox3D->setEnabled(true); return; } } m_ToolManager->SetReferenceData(referenceNode); m_ToolManager->SetWorkingData(nullptr); this->UpdateWarningLabel(tr("Please perform a reinit on the segmentation image!")); } void QmitkSegmentationView::UpdateWarningLabel(QString text) { if (text.size() == 0) { m_Controls->lblSegmentationWarnings->hide(); } else { m_Controls->lblSegmentationWarnings->show(); } m_Controls->lblSegmentationWarnings->setText("" + text + ""); }