diff --git a/Modules/Segmentation/Interactions/mitkTotalSegmentatorTool.cpp b/Modules/Segmentation/Interactions/mitkTotalSegmentatorTool.cpp new file mode 100644 index 0000000000..797c624ac8 --- /dev/null +++ b/Modules/Segmentation/Interactions/mitkTotalSegmentatorTool.cpp @@ -0,0 +1,340 @@ +/*============================================================================ + +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 "mitkTotalSegmentatorTool.h" + +#include "mitkIOUtil.h" +#include +#include +#include + +// us +#include +#include +#include +#include +#include + +namespace mitk +{ + MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, TotalSegmentatorTool, "Total Segmentator"); +} + +mitk::TotalSegmentatorTool::~TotalSegmentatorTool() +{ + std::filesystem::remove_all(this->GetMitkTempDir()); +} + +mitk::TotalSegmentatorTool::TotalSegmentatorTool() +{ + this->IsTimePointChangeAwareOff(); +} + +void mitk::TotalSegmentatorTool::Activated() +{ + Superclass::Activated(); + this->SetLabelTransferMode(LabelTransferMode::AllLabels); +} + +const char **mitk::TotalSegmentatorTool::GetXPM() const +{ + return nullptr; +} + +us::ModuleResource mitk::TotalSegmentatorTool::GetIconResource() const +{ + us::Module *module = us::GetModuleContext()->GetModule(); + us::ModuleResource resource = module->GetResource("AI.svg"); + return resource; +} + +const char *mitk::TotalSegmentatorTool::GetName() const +{ + return "TotalSegmentator"; +} + +void mitk::TotalSegmentatorTool::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; + } +} + +void mitk::TotalSegmentatorTool::DoUpdatePreview(const Image *inputAtTimeStep, + const Image * /*oldSegAtTimeStep*/, + LabelSetImage *previewImage, + TimeStepType timeStep) +{ + if (this->m_MitkTempDir.empty()) + { + this->SetMitkTempDir(IOUtil::CreateTemporaryDirectory("mitk-XXXXXX")); + } + if (m_LabelMapTotal.empty()) + { + this->ParseLabelNames(this->GetLabelMapPath()); + } + ProcessExecutor::Pointer spExec = ProcessExecutor::New(); + itk::CStyleCommand::Pointer spCommand = itk::CStyleCommand::New(); + spCommand->SetCallback(&onPythonProcessEvent); + spExec->AddObserver(ExternalProcessOutputEvent(), spCommand); + + std::string inDir, outDir, inputImagePath, outputImagePath, scriptPath; + inDir = IOUtil::CreateTemporaryDirectory("totalseg-in-XXXXXX", this->GetMitkTempDir()); + std::ofstream tmpStream; + inputImagePath = IOUtil::CreateTemporaryFile(tmpStream, TEMPLATE_FILENAME, 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("_")); + outDir = IOUtil::CreateTemporaryDirectory("totalseg-out-XXXXXX", this->GetMitkTempDir()); + LabelSetImage::Pointer outputBuffer; + + IOUtil::Save(inputAtTimeStep, inputImagePath); + + outputImagePath = outDir + IOUtil::GetDirectorySeparator() + token + "_000.nii.gz"; + const bool isSubTask = (this->GetSubTask() != DEFAULT_TOTAL_TASK); + if (isSubTask) + { + outputImagePath = outDir; + } + + this->run_totalsegmentator( + spExec, inputImagePath, outputImagePath, this->GetFast(), !isSubTask, this->GetGpuId(), DEFAULT_TOTAL_TASK); + + if (isSubTask) + { // Run total segmentator again + this->run_totalsegmentator( + spExec, inputImagePath, outputImagePath, !isSubTask, !isSubTask, this->GetGpuId(), this->GetSubTask()); + // Construct Label Id map + std::vector files = SUBTASKS_MAP.at(this->GetSubTask()); + std::map labelMapSubtask; + mitk::Label::PixelType labelId = 1; + for (auto const& file : files) + { + std::string labelName = file.substr(0, file.find('.')); + labelMapSubtask[labelId] = labelName; + labelId++; + } + // Agglomerate individual mask files into one multi-label image. + std::for_each(files.begin(), + files.end(), + [&](std::string &fileName) { fileName = (outDir + IOUtil::GetDirectorySeparator() + fileName); }); + outputBuffer = AgglomerateLabelFiles(files, inputAtTimeStep->GetDimensions(), inputAtTimeStep->GetGeometry()); + // Assign label names to the agglomerated LabelSetImage + this->MapLabelsToSegmentation(outputBuffer, labelMapSubtask); + } + else + { + Image::Pointer outputImage = IOUtil::Load(outputImagePath); + outputBuffer = mitk::LabelSetImage::New(); + outputBuffer->InitializeByLabeledImage(outputImage); + outputBuffer->SetGeometry(inputAtTimeStep->GetGeometry()); + this->MapLabelsToSegmentation(outputBuffer, m_LabelMapTotal); + } + this->TransferLabelSetImageContent(outputBuffer, previewImage, timeStep); +} + +mitk::LabelSetImage::Pointer mitk::TotalSegmentatorTool::AgglomerateLabelFiles(std::vector &filePaths, + unsigned int *dimensions, + mitk::BaseGeometry *geometry) +{ + Label::PixelType labelId = 1; + auto aggloLabelImage = mitk::LabelSetImage::New(); + auto initImage = mitk::Image::New(); + initImage->Initialize(mitk::MakeScalarPixelType(), 3, dimensions); + aggloLabelImage->Initialize(initImage); + aggloLabelImage->SetGeometry(geometry); + mitk::LabelSet::Pointer newlayer = mitk::LabelSet::New(); + newlayer->SetLayer(0); + aggloLabelImage->AddLayer(newlayer); + + for (auto const &outputImagePath : filePaths) + { + double rgba[4]; + aggloLabelImage->GetActiveLabelSet()->GetLookupTable()->GetTableValue(labelId, rgba); + mitk::Color color; + color.SetRed(rgba[0]); + color.SetGreen(rgba[1]); + color.SetBlue(rgba[2]); + + auto label = mitk::Label::New(); + label->SetName("object-" + std::to_string(labelId)); + label->SetValue(labelId); + label->SetColor(color); + label->SetOpacity(rgba[3]); + + aggloLabelImage->GetActiveLabelSet()->AddLabel(label); + + Image::Pointer outputImage = IOUtil::Load(outputImagePath); + auto source = mitk::LabelSetImage::New(); + source->InitializeByLabeledImage(outputImage); + source->SetGeometry(geometry); + + auto labelSet = aggloLabelImage->GetActiveLabelSet(); + mitk::TransferLabelContent(source, aggloLabelImage, labelSet, 0, 0, false, {{1, labelId}}); + labelId++; + } + return aggloLabelImage; +} + +void mitk::TotalSegmentatorTool::run_totalsegmentator(ProcessExecutor::Pointer spExec, + const std::string &inputImagePath, + const std::string &outputImagePath, + bool isFast, + bool isMultiLabel, + unsigned int gpuId, + const std::string &subTask) +{ + ProcessExecutor::ArgumentListType args; + std::string command = "TotalSegmentator"; +#if defined(__APPLE__) || defined(_WIN32) + command = "python"; +#endif + + args.clear(); + +#ifdef _WIN32 + std::string ending = "Scripts"; + if (0 == this->GetPythonPath().compare(this->GetPythonPath().length() - ending.length(), ending.length(), ending)) + { + args.push_back("TotalSegmentator"); + } + else + { + args.push_back("Scripts/TotalSegmentator"); + } +#endif + +#if defined(__APPLE__) + args.push_back("TotalSegmentator"); +#endif + + args.push_back("-i"); + args.push_back(inputImagePath); + + args.push_back("-o"); + args.push_back(outputImagePath); + + if (subTask != DEFAULT_TOTAL_TASK) + { + args.push_back("-ta"); + args.push_back(subTask); + } + + if (isMultiLabel) + { + args.push_back("--ml"); + } + + if (isFast) + { + args.push_back("--fast"); + } + + try + { + std::string cudaEnv = "CUDA_VISIBLE_DEVICES=" + std::to_string(gpuId); + itksys::SystemTools::PutEnv(cudaEnv.c_str()); + + std::stringstream logStream; + for (const auto &arg : args) + logStream << arg << " "; + logStream << this->GetPythonPath(); + MITK_INFO << logStream.str(); + + spExec->Execute(this->GetPythonPath(), command, args); + } + catch (const mitk::Exception &e) + { + MITK_ERROR << e.GetDescription(); + return; + } +} + +void mitk::TotalSegmentatorTool::ParseLabelNames(const std::string &fileName) +{ + std::fstream newfile; + newfile.open(fileName, ios::in); + std::stringstream buffer; + if (newfile.is_open()) + { + int line = 0; + std::string temp; + while (std::getline(newfile, temp)) + { + if (line > 1 && line < 106) + { + buffer << temp; + } + ++line; + } + } + std::string key, val; + while (std::getline(std::getline(buffer, key, ':'), val, ',')) + { + m_LabelMapTotal[std::stoi(key)] = val; + } +} + +void mitk::TotalSegmentatorTool::MapLabelsToSegmentation(mitk::LabelSetImage::Pointer outputBuffer, + std::map &labelMap) +{ + for (auto const &[key, val] : labelMap) + { + mitk::Label *labelptr = outputBuffer->GetActiveLabelSet()->GetLabel(key); + if (nullptr != labelptr) + { + labelptr->SetName(val); + } + } +} + +std::string mitk::TotalSegmentatorTool::GetLabelMapPath() +{ + std::string pythonFileName; + std::filesystem::path pathToLabelMap(this->GetPythonPath()); + pathToLabelMap = pathToLabelMap.parent_path(); +#ifdef _WIN32 + pythonFileName = pathToLabelMap.string() + "/Lib/site-packages/totalsegmentator/map_to_binary.py"; +#else + pathToLabelMap.append("lib"); + for (auto const &dir_entry : std::filesystem::directory_iterator{pathToLabelMap}) + { + if (dir_entry.is_directory()) + { + auto dirName = dir_entry.path().filename().string(); + if (dirName.rfind("python", 0) == 0) + { + pathToLabelMap.append(dir_entry.path().filename().string()); + break; + } + } + } + pythonFileName = pathToLabelMap.string() + "/site-packages/totalsegmentator/map_to_binary.py"; +#endif + return pythonFileName; +} diff --git a/Modules/Segmentation/Interactions/mitkTotalSegmentatorTool.h b/Modules/Segmentation/Interactions/mitkTotalSegmentatorTool.h new file mode 100644 index 0000000000..0ffe9474fe --- /dev/null +++ b/Modules/Segmentation/Interactions/mitkTotalSegmentatorTool.h @@ -0,0 +1,150 @@ +/*============================================================================ + +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 MITKTOTALSEGMENTATORTOOL_H +#define MITKTOTALSEGMENTATORTOOL_H + +#include "mitkSegWithPreviewTool.h" +#include +#include "mitkProcessExecutor.h" + + +namespace us +{ + class ModuleResource; +} + +namespace mitk +{ + + /** + \brief TotalSegmentator segmentation tool. + + \ingroup Interaction + \ingroup ToolManagerEtAl + + \warning Only to be instantiated by mitk::ToolManager. + */ + class MITKSEGMENTATION_EXPORT TotalSegmentatorTool : public SegWithPreviewTool + { + public: + mitkClassMacro(TotalSegmentatorTool, SegWithPreviewTool); + itkFactorylessNewMacro(Self); + itkCloneMacro(Self); + + const char *GetName() const override; + const char **GetXPM() const override; + us::ModuleResource GetIconResource() const override; + void Activated() override; + + itkSetMacro(MitkTempDir, std::string); + itkGetConstMacro(MitkTempDir, std::string); + + itkSetMacro(SubTask, std::string); + itkGetConstMacro(SubTask, std::string); + + itkSetMacro(PythonPath, std::string); + itkGetConstMacro(PythonPath, std::string); + + itkSetMacro(GpuId, unsigned int); + itkGetConstMacro(GpuId, unsigned int); + + itkSetMacro(Fast, bool); + itkGetConstMacro(Fast, bool); + itkBooleanMacro(Fast); + + /** + * @brief Static function to print out everything from itk::EventObject. + * Used as callback in mitk::ProcessExecutor object. + * + */ + static void onPythonProcessEvent(itk::Object *, const itk::EventObject &e, void *); + + protected: + TotalSegmentatorTool(); + ~TotalSegmentatorTool(); + + /** + * @brief Overriden method from the tool manager to execute the segmentation + * Implementation: + * 1. Creates temp directory, if not done already. + * 2. Parses Label names from map_to_binary.py for using later on. + * 3. Calls "run_totalsegmentator" method. + * 4. Expects an output image to be saved in the temporary directory by the python proces. Loads it as + * LabelSetImage and sets to previewImage. + * + * @param inputAtTimeStep + * @param oldSegAtTimeStep + * @param previewImage + * @param timeStep + */ + void DoUpdatePreview(const Image* inputAtTimeStep, const Image* oldSegAtTimeStep, LabelSetImage* previewImage, TimeStepType timeStep) override; + + private: + + /** + * @brief Runs Totalsegmentator python process with desired arguments + * + */ + void run_totalsegmentator(ProcessExecutor::Pointer, const std::string&, const std::string&, bool, bool, unsigned int, const std::string&); + + /** + * @brief Applies the m_LabelMapTotal lookup table on the output segmentation LabelSetImage. + * + */ + void MapLabelsToSegmentation(mitk::LabelSetImage::Pointer, std::map &); + + /** + * @brief Parses map_to_binary.py file to extract label ids and names + * and stores as a map for reference in m_LabelMapTotal + * + * @param filePath + */ + void ParseLabelNames(const std::string&); + + /** + * @brief Get the Label Map Path from the virtual environment location + * + * @return std::string + */ + std::string GetLabelMapPath(); + + /** + * @brief Agglomerate many individual mask image files into one multi-label LabelSetImage in the + * given filePath order. + * + * @param filePaths + * @param dimension + * @param geometry + * @return LabelSetImage::Pointer + */ + LabelSetImage::Pointer AgglomerateLabelFiles(std::vector& filePaths, unsigned int* dimension, mitk::BaseGeometry* geometry); + + std::string m_MitkTempDir; + std::string m_PythonPath; + std::string m_SubTask = "total"; + unsigned int m_GpuId = 0; + std::map m_LabelMapTotal; + bool m_Fast = true; + const std::string TEMPLATE_FILENAME = "XXXXXX_000_0000.nii.gz"; + const std::string DEFAULT_TOTAL_TASK = "total"; + const std::unordered_map> SUBTASKS_MAP = + { + {"body", { "body.nii.gz", "body_trunc.nii.gz", "body_extremities.nii.gz", "skin.nii.gz"}}, + {"hip_implant", {"hip_implant.nii.gz"}}, + {"cerebral_bleed", {"intracerebral_hemorrhage.nii.gz"}}, + {"coronary_arteries", {"coronary_arteries.nii.gz"}}, + {"lung_vessels", {"lung_vessels.nii.gz", "lung_trachea_bronchia.nii.gz"}}, + {"pleural_pericard_effusion", {"pleural_effusion.nii.gz", "pericardial_effusion.nii.gz"}} + }; + }; // class +} // namespace +#endif diff --git a/Modules/Segmentation/files.cmake b/Modules/Segmentation/files.cmake index c00c78c696..11155f5509 100644 --- a/Modules/Segmentation/files.cmake +++ b/Modules/Segmentation/files.cmake @@ -1,116 +1,117 @@ set(CPP_FILES Algorithms/mitkCalculateSegmentationVolume.cpp Algorithms/mitkContourModelSetToImageFilter.cpp Algorithms/mitkContourSetToPointSetFilter.cpp Algorithms/mitkContourUtils.cpp Algorithms/mitkCorrectorAlgorithm.cpp Algorithms/mitkDiffImageApplier.cpp Algorithms/mitkDiffSliceOperation.cpp Algorithms/mitkDiffSliceOperationApplier.cpp Algorithms/mitkFeatureBasedEdgeDetectionFilter.cpp Algorithms/mitkGrowCutSegmentationFilter.cpp Algorithms/mitkImageLiveWireContourModelFilter.cpp Algorithms/mitkImageToContourFilter.cpp #Algorithms/mitkImageToContourModelFilter.cpp Algorithms/mitkImageToLiveWireContourFilter.cpp Algorithms/mitkManualSegmentationToSurfaceFilter.cpp Algorithms/mitkOtsuSegmentationFilter.cpp Algorithms/mitkSegmentationHelper.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/mitkAddContourTool.cpp Interactions/mitkAutoCropTool.cpp Interactions/mitkSegWithPreviewTool.cpp Interactions/mitkBinaryThresholdBaseTool.cpp Interactions/mitkBinaryThresholdTool.cpp Interactions/mitkBinaryThresholdULTool.cpp Interactions/mitkCloseRegionTool.cpp Interactions/mitkContourModelInteractor.cpp Interactions/mitkContourModelLiveWireInteractor.cpp Interactions/mitkEditableContourTool.cpp Interactions/mitkLiveWireTool2D.cpp Interactions/mitkLassoTool.cpp Interactions/mitkContourTool.cpp Interactions/mitkDrawPaintbrushTool.cpp Interactions/mitkErasePaintbrushTool.cpp Interactions/mitkEraseRegionTool.cpp Interactions/mitkFeedbackContourTool.cpp Interactions/mitkFillRegionBaseTool.cpp Interactions/mitkFillRegionTool.cpp Interactions/mitkGrowCutTool.cpp Interactions/mitkOtsuTool3D.cpp Interactions/mitkPaintbrushTool.cpp Interactions/mitkRegionGrowingTool.cpp Interactions/mitkSegmentationsProcessingTool.cpp Interactions/mitkSegTool2D.cpp Interactions/mitkSubtractContourTool.cpp Interactions/mitkTool.cpp Interactions/mitkToolCommand.cpp Interactions/mitkPickingTool.cpp Interactions/mitknnUnetTool.cpp Interactions/mitkProcessExecutor.cpp + Interactions/mitkTotalSegmentatorTool.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.svg Add_Cursor.svg AI.svg AI_Cursor.svg Close.svg Close_Cursor.svg Erase.svg Erase_Cursor.svg Fill.svg Fill_Cursor.svg LiveWire.svg LiveWire_Cursor.svg Lasso.svg GrowCut.svg Lasso_Cursor.svg Otsu.svg Paint.svg Paint_Cursor.svg Picking.svg RegionGrowing.svg RegionGrowing_Cursor.svg Subtract.svg Subtract_Cursor.svg Threshold.svg ULThreshold.svg Wipe.svg Wipe_Cursor.svg Interactions/dummy.xml Interactions/EditableContourTool.xml Interactions/PickingTool.xml Interactions/MouseReleaseOnly.xml Interactions/PressMoveRelease.xml Interactions/PressMoveReleaseAndPointSetting.xml Interactions/PressMoveReleaseWithCTRLInversion.xml Interactions/PressMoveReleaseWithCTRLInversionAllMouseMoves.xml Interactions/SegmentationConfig.xml Interactions/SegmentationInteraction.xml Interactions/SegmentationToolsConfig.xml Interactions/ContourModelModificationConfig.xml Interactions/ContourModelModificationInteractor.xml ) diff --git a/Modules/SegmentationUI/Qmitk/QmitkSetupVirtualEnvUtil.cpp b/Modules/SegmentationUI/Qmitk/QmitkSetupVirtualEnvUtil.cpp new file mode 100644 index 0000000000..8cc7ecb0e2 --- /dev/null +++ b/Modules/SegmentationUI/Qmitk/QmitkSetupVirtualEnvUtil.cpp @@ -0,0 +1,182 @@ +/*============================================================================ + +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 + +============================================================================*/ + +#include "QmitkSetupVirtualEnvUtil.h" + +#include "mitkLogMacros.h" +#include +#include + +QmitkSetupVirtualEnvUtil::QmitkSetupVirtualEnvUtil() +{ + m_BaseDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QDir::separator() + + qApp->organizationName() + QDir::separator(); +} + +QmitkSetupVirtualEnvUtil::QmitkSetupVirtualEnvUtil(const QString &baseDir) +{ + m_BaseDir = baseDir; +} + +QString& QmitkSetupVirtualEnvUtil::GetBaseDir() +{ + return m_BaseDir; +} + +QString QmitkSetupVirtualEnvUtil::GetVirtualEnvPath() +{ + return m_venvPath; +} + +QString& QmitkSetupVirtualEnvUtil::GetSystemPythonPath() +{ + return m_SysPythonPath; +} + +QString& QmitkSetupVirtualEnvUtil::GetPythonPath() +{ + return m_PythonPath; +} + +QString& QmitkSetupVirtualEnvUtil::GetPipPath() +{ + return m_PipPath; +} + +void QmitkSetupVirtualEnvUtil::SetVirtualEnvPath(const QString &path) +{ + m_venvPath = path; +} + +void QmitkSetupVirtualEnvUtil::SetPipPath(const QString &path) +{ + m_PipPath = path; +} + +void QmitkSetupVirtualEnvUtil::SetPythonPath(const QString &path) +{ + if (this->IsPythonPath(path)) + { + m_PythonPath = path; + } + else + { + MITK_INFO << "Python was not detected in " + path.toStdString(); + } +} + +void QmitkSetupVirtualEnvUtil::SetSystemPythonPath(const QString &path) +{ + if (this->IsPythonPath(path)) + { + m_SysPythonPath = path; + } + else + { + MITK_INFO << "Python was not detected in " + path.toStdString(); + } +} + +void QmitkSetupVirtualEnvUtil::PrintProcessEvent(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; + } +} + +void QmitkSetupVirtualEnvUtil::InstallPytorch(const std::string &workingDir, + void (*callback)(itk::Object *, const itk::EventObject &, void *)) +{ + mitk::ProcessExecutor::ArgumentListType args; + auto spExec = mitk::ProcessExecutor::New(); + auto spCommand = itk::CStyleCommand::New(); + spCommand->SetCallback(callback); + spExec->AddObserver(mitk::ExternalProcessOutputEvent(), spCommand); + args.push_back("-m"); + args.push_back("pip"); + args.push_back("install"); + args.push_back("light-the-torch"); + spExec->Execute(workingDir, "python", args); + PipInstall("torch", workingDir, callback, "ltt"); +} + +void QmitkSetupVirtualEnvUtil::InstallPytorch() +{ + this->InstallPytorch(GetPythonPath().toStdString(), &PrintProcessEvent); +} + +void QmitkSetupVirtualEnvUtil::PipInstall(const std::string &library, + const std::string &workingDir, + void (*callback)(itk::Object *, const itk::EventObject &, void *), + const std::string &command) +{ + mitk::ProcessExecutor::ArgumentListType args; + auto spExec = mitk::ProcessExecutor::New(); + auto spCommand = itk::CStyleCommand::New(); + spCommand->SetCallback(callback); + spExec->AddObserver(mitk::ExternalProcessOutputEvent(), spCommand); + args.push_back("install"); + args.push_back(library); + spExec->Execute(workingDir, command, args); +} + +void QmitkSetupVirtualEnvUtil::PipInstall(const std::string &library, + void (*callback)(itk::Object*, const itk::EventObject&, void*), + const std::string& command) +{ + this->PipInstall(library, this->GetPipPath().toStdString(), callback, command); +} + +void QmitkSetupVirtualEnvUtil::ExecutePython(const std::string &pythonCode, + const std::string &workingDir, + void (*callback)(itk::Object *, const itk::EventObject &, void *), + const std::string &command) +{ + mitk::ProcessExecutor::ArgumentListType args; + auto spExec = mitk::ProcessExecutor::New(); + auto spCommand = itk::CStyleCommand::New(); + spCommand->SetCallback(callback); + spExec->AddObserver(mitk::ExternalProcessOutputEvent(), spCommand); + args.push_back("-c"); + args.push_back(pythonCode); + spExec->Execute(workingDir, command, args); +} + +void QmitkSetupVirtualEnvUtil::ExecutePython(const std::string &args, + void (*callback)(itk::Object *, const itk::EventObject &, void *), + const std::string &command) +{ + this->ExecutePython(args, this->GetPythonPath().toStdString(), callback, command); +} + +bool QmitkSetupVirtualEnvUtil::IsPythonPath(const QString &pythonPath) +{ + QString fullPath = pythonPath; + bool isExists = +#ifdef _WIN32 + QFile::exists(fullPath + QDir::separator() + QString("python.exe")); +#else + QFile::exists(fullPath + QDir::separator() + QString("python3")); +#endif + return isExists; +} diff --git a/Modules/SegmentationUI/Qmitk/QmitkSetupVirtualEnvUtil.h b/Modules/SegmentationUI/Qmitk/QmitkSetupVirtualEnvUtil.h new file mode 100644 index 0000000000..748334bc45 --- /dev/null +++ b/Modules/SegmentationUI/Qmitk/QmitkSetupVirtualEnvUtil.h @@ -0,0 +1,195 @@ +/*============================================================================ + +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 QmitkSetupVirtualEnvUtil_h_Included +#define QmitkSetupVirtualEnvUtil_h_Included + +#include "mitkLogMacros.h" +#include "mitkProcessExecutor.h" +#include +#include +#include +#include + +/** + * @brief Abstract Class to Setup a python virtual environment and pip install required packages. + * Derive this class for creating installer for the respective tool. + */ +class MITKSEGMENTATIONUI_EXPORT QmitkSetupVirtualEnvUtil +{ +public: + QmitkSetupVirtualEnvUtil(const QString& baseDir); + QmitkSetupVirtualEnvUtil(); + + /** + * @brief Implement the method in child class + * to setup the virtual environment. + */ + virtual bool SetupVirtualEnv(const QString& venvName) = 0; + + /** + * @brief Get the Virtual Env Path object. Override this method in the respective + * tool installer class. + * + * @return QString + */ + virtual QString GetVirtualEnvPath(); + + /** + * @brief Function to Pip install a library package given the location of + * pip3 executable. + * Any callback function can be passed to process the output. + * + * @param library + * @param workingDir + * @param callback + * @param command + */ + void PipInstall(const std::string &library, + const std::string &workingDir, + void (*callback)(itk::Object *, const itk::EventObject &, void *), + const std::string &command = "pip3"); + + /** + * @brief Overloaded function to Pip install a library function. + * + * @param library + * @param callback + * @param command + */ + void PipInstall(const std::string &library, + void (*callback)(itk::Object *, const itk::EventObject &, void *), + const std::string &command = "pip3"); + + + /** + * @brief Function to execute any python code given a python path. + * Any callback function can be passed to process the output. + * + * @param args + * @param pythonPath + * @param callback + * @param command + */ + void ExecutePython(const std::string &args, + const std::string &pythonPath, + void (*callback)(itk::Object *, const itk::EventObject &, void *), + const std::string &command = "python"); + + /** + * @brief Overloaded function to Execute Python code. + * Any callback function can be passed to process the output. + * + * @param args + * @param callback + * @param command + */ + void ExecutePython(const std::string &args, + void (*callback)(itk::Object *, const itk::EventObject &, void *), + const std::string &command = "python"); + + /** + * @brief Installs pytorch using light-the-torch package, correctly identifying cuda version. + * Requires location of pip3 executable. + * Any callback function can be passed to process the output. + * + * @param workingDir + * @param callback + */ + void InstallPytorch(const std::string &workingDir, void (*callback)(itk::Object *, const itk::EventObject &, void *)); + + /** + * @brief Overloaded function to install pytorch using light-the-torch package, correctly + * identifying cuda version. + */ + void InstallPytorch(); + + /** + * @brief Get the Base Dir object + * + * @return QString& + */ + QString& GetBaseDir(); + + /** + * @brief Get the System Python Path object + * + * @return QString& + */ + QString& GetSystemPythonPath(); + + /** + * @brief Get the Python Path object + * + * @return QString& + */ + QString& GetPythonPath(); + + /** + * @brief Get the Pip Path object + * + * @return QString& + */ + QString& GetPipPath(); + + /** + * @brief Set the System Python Path object + * + * @param path + */ + void SetSystemPythonPath(const QString& path); + + /** + * @brief Set the Python Path object + * + * @param path + */ + void SetPythonPath(const QString& path); + + /** + * @brief Set the Pip Path object + * + * @param path + */ + void SetPipPath(const QString& path); + + /** + * @brief Set the Virtual Env Path object + * + * @param path + */ + void SetVirtualEnvPath(const QString &path); + + /** + * @brief Check if the path provide has python executable or not. + * + * @param pythonPath + * @return true + * @return false + */ + bool IsPythonPath(const QString &pythonPath); + + /** + * @brief Function can be used as callback to simply print out all the process execution output + * parsed out from itk::EventObject. + * + */ + static void PrintProcessEvent(itk::Object *, const itk::EventObject &e, void *); + +private: + QString m_PythonPath; + QString m_PipPath; + QString m_BaseDir; + QString m_venvPath; + QString m_SysPythonPath; +}; +#endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkTotalSegmentatorGUIControls.ui b/Modules/SegmentationUI/Qmitk/QmitkTotalSegmentatorGUIControls.ui new file mode 100644 index 0000000000..cd5b68ca9e --- /dev/null +++ b/Modules/SegmentationUI/Qmitk/QmitkTotalSegmentatorGUIControls.ui @@ -0,0 +1,357 @@ + + + QmitkTotalSegmentatorToolGUIControls + + + + 0 + 0 + 699 + 352 + + + + + 0 + 0 + + + + + 100 + 0 + + + + + 100000 + 100000 + + + + QmitkTotalSegmentatorToolWidget + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + 0 + 0 + + + + <html><head/><body><p>Welcome to TotalSegmentator tool in MITK. [Experimental]</p><p>Please note that this is only an interface to TotalSegmentator. MITK does not ship with TotalSegmentator. Make sure to have a working internet connection to install TotalSegmentator. Or, choose an python environment with TotalSegmentator in it before inferencing. + Currently, the tool is pretrained on CT images only. </p><p>Refer to <a href="https://github.com/wasserth/TotalSegmentator"><span style=" text-decoration: underline; color:#0000ff;">https://github.com/wasserth/TotalSegmentator</span></a> to learn everything about the TotalSegmentator.</p><p><br/></p></body></html> + + + Qt::RichText + + + true + + + + + + + + 0 + 0 + + + + + 100000 + 16777215 + + + + Install TotalSegmentator + + + + + + + + + 0 + 0 + + + + Install Options + + + true + + + 5 + + + true + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 6 + + + + + + 0 + 0 + + + + Use Custom Installation: + + + + + + + false + + + + + + + + 0 + 0 + + + + + 100000 + 16777215 + + + + Clear Install + + + + + + + + 0 + 0 + + + + Custom Env. Path: + + + + + + + + + + + 0 + 0 + + + + System Python: + + + + + + + + + + + + + + + + + 0 + 0 + + + + Task: + + + + + + + + + + + + + 0 + 0 + + + + Advanced + + + true + + + 5 + + + true + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 6 + + + + + + 0 + 0 + + + + Fast: + + + + + + + true + + + + + + + + 0 + 0 + + + + GPU Id: + + + + + + + + + + + + + + + + 0 + 0 + + + + + 100000 + 16777215 + + + + Run TotalSegmentator + + + + + + + + 0 + 0 + + + + true + + + + + + + + + ctkComboBox + QComboBox +
ctkComboBox.h
+ 1 +
+ + ctkCollapsibleGroupBox + QWidget +
ctkCollapsibleGroupBox.h
+
+ + ctkCheckBox + QWidget +
ctkCheckBox.h
+
+
+ + +
+ diff --git a/Modules/SegmentationUI/Qmitk/QmitkTotalSegmentatorToolGUI.cpp b/Modules/SegmentationUI/Qmitk/QmitkTotalSegmentatorToolGUI.cpp new file mode 100644 index 0000000000..a6a8c0a038 --- /dev/null +++ b/Modules/SegmentationUI/Qmitk/QmitkTotalSegmentatorToolGUI.cpp @@ -0,0 +1,499 @@ +#include "QmitkTotalSegmentatorToolGUI.h" + +#include "mitkProcessExecutor.h" +#include "mitkTotalSegmentatorTool.h" +#include +#include +#include +#include +#include +#include +#include + +MITK_TOOL_GUI_MACRO(MITKSEGMENTATIONUI_EXPORT, QmitkTotalSegmentatorToolGUI, "") + +QmitkTotalSegmentatorToolGUI::QmitkTotalSegmentatorToolGUI() + : QmitkMultiLabelSegWithPreviewToolGUIBase(), m_SuperclassEnableConfirmSegBtnFnc(m_EnableConfirmSegBtnFnc) +{ + // Nvidia-smi command returning zero doesn't always imply 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 TotalSegmentator tool can be very slow."; + this->ShowErrorMessage(warning); + } + m_EnableConfirmSegBtnFnc = [this](bool enabled) + { return !m_FirstPreviewComputation ? m_SuperclassEnableConfirmSegBtnFnc(enabled) : false; }; +} + +void QmitkTotalSegmentatorToolGUI::ConnectNewTool(mitk::SegWithPreviewTool *newTool) +{ + Superclass::ConnectNewTool(newTool); + m_FirstPreviewComputation = true; +} + +void QmitkTotalSegmentatorToolGUI::InitializeUI(QBoxLayout *mainLayout) +{ + m_Controls.setupUi(this); +#ifndef _WIN32 + m_Controls.sysPythonComboBox->addItem("/usr/bin"); +#endif + this->AutoParsePythonPaths(); + m_Controls.sysPythonComboBox->addItem("Select"); + m_Controls.sysPythonComboBox->setCurrentIndex(0); + m_Controls.pythonEnvComboBox->addItem("Select"); + m_Controls.pythonEnvComboBox->setDuplicatesEnabled(false); + m_Controls.pythonEnvComboBox->setDisabled(true); + m_Controls.previewButton->setDisabled(true); + m_Controls.statusLabel->setTextFormat(Qt::RichText); + m_Controls.subtaskComboBox->addItems(VALID_TASKS); + QString welcomeText; + this->SetGPUInfo(); + if (m_GpuLoader.GetGPUCount() != 0) + { + welcomeText = "STATUS: Welcome to TotalSegmentator tool. You're in luck: " + + QString::number(m_GpuLoader.GetGPUCount()) + " GPU(s) were detected."; + } + else + { + welcomeText = "STATUS: Welcome to TotalSegmentator tool. Sorry, " + + QString::number(m_GpuLoader.GetGPUCount()) + " GPUs were detected."; + } + + connect(m_Controls.previewButton, SIGNAL(clicked()), this, SLOT(OnPreviewBtnClicked())); + connect(m_Controls.clearButton, SIGNAL(clicked()), this, SLOT(OnClearInstall())); + connect(m_Controls.installButton, SIGNAL(clicked()), this, SLOT(OnInstallBtnClicked())); + connect(m_Controls.overrideBox, SIGNAL(stateChanged(int)), this, SLOT(OnOverrideChecked(int))); + connect(m_Controls.pythonEnvComboBox, + QOverload::of(&QComboBox::activated), + [=](int index) { OnPythonPathChanged(m_Controls.pythonEnvComboBox->itemText(index)); }); + connect(m_Controls.sysPythonComboBox, + QOverload::of(&QComboBox::activated), + [=](int index) { OnSystemPythonChanged(m_Controls.sysPythonComboBox->itemText(index)); }); + + QString lastSelectedPyEnv = m_Settings.value("TotalSeg/LastCustomPythonPath").toString(); + if (!lastSelectedPyEnv.isEmpty() && lastSelectedPyEnv!= "Select") + { + m_Controls.pythonEnvComboBox->insertItem(0, lastSelectedPyEnv); + } + const QString storageDir = m_Installer.GetVirtualEnvPath(); + m_IsInstalled = this->IsTotalSegmentatorInstalled(storageDir); + if (m_IsInstalled) + { + m_PythonPath = GetExactPythonPath(storageDir); + m_Installer.SetVirtualEnvPath(m_PythonPath); + this->EnableAll(m_IsInstalled); + welcomeText += " TotalSegmentator is already found installed."; + } + else + { + welcomeText += " TotalSegmentator is not installed. Please click on \"Install TotalSegmentator\" above."; + } + this->WriteStatusMessage(welcomeText); + + QIcon deleteIcon = + QmitkStyleManager::ThemeIcon(QStringLiteral(":/org_mitk_icons/icons/awesome/scalable/actions/edit-delete.svg")); + QIcon arrowIcon = + QmitkStyleManager::ThemeIcon(QStringLiteral(":/org_mitk_icons/icons/awesome/scalable/actions/go-next.svg")); + m_Controls.clearButton->setIcon(deleteIcon); + m_Controls.previewButton->setIcon(arrowIcon); + + mainLayout->addLayout(m_Controls.verticalLayout); + Superclass::InitializeUI(mainLayout); +} + +void QmitkTotalSegmentatorToolGUI::SetGPUInfo() +{ + std::vector specs = m_GpuLoader.GetAllGPUSpecs(); + for (const QmitkGPUSpec &gpuSpec : specs) + { + m_Controls.gpuComboBox->addItem(QString::number(gpuSpec.id) + ": " + gpuSpec.name + " (" + gpuSpec.memory + ")"); + } + if (specs.empty()) + { + m_Controls.gpuComboBox->setEditable(true); + m_Controls.gpuComboBox->addItem(QString::number(0)); + m_Controls.gpuComboBox->setValidator(new QIntValidator(0, 999, this)); + } +} + +unsigned int QmitkTotalSegmentatorToolGUI::FetchSelectedGPUFromUI() const +{ + QString gpuInfo = m_Controls.gpuComboBox->currentText(); + if (m_GpuLoader.GetGPUCount() == 0) + { + return static_cast(gpuInfo.toInt()); + } + else + { + QString gpuId = gpuInfo.split(":", QString::SplitBehavior::SkipEmptyParts).first(); + return static_cast(gpuId.toInt()); + } +} + +void QmitkTotalSegmentatorToolGUI::EnableAll(bool isEnable) +{ + m_Controls.previewButton->setEnabled(isEnable); + m_Controls.subtaskComboBox->setEnabled(isEnable); + m_Controls.installButton->setEnabled((!isEnable)); +} + +void QmitkTotalSegmentatorToolGUI::OnInstallBtnClicked() +{ + bool isInstalled = false; + QString systemPython = OnSystemPythonChanged(m_Controls.sysPythonComboBox->currentText()); + if (systemPython.isEmpty()) + { + this->WriteErrorMessage("ERROR: Couldn't find Python."); + } + else + { + this->WriteStatusMessage("STATUS: Installing TotalSegmentator..."); + m_Installer.SetSystemPythonPath(systemPython); + isInstalled = m_Installer.SetupVirtualEnv(m_Installer.VENV_NAME); + if (isInstalled) + { + m_PythonPath = this->GetExactPythonPath(m_Installer.GetVirtualEnvPath()); + this->WriteStatusMessage("STATUS: Successfully installed TotalSegmentator."); + } + else + { + this->WriteErrorMessage("ERROR: Couldn't install TotalSegmentator."); + } + } + this->EnableAll(isInstalled); +} + +void QmitkTotalSegmentatorToolGUI::OnPreviewBtnClicked() +{ + auto tool = this->GetConnectedToolAs(); + if (nullptr == tool) + { + return; + } + try + { + m_Controls.previewButton->setEnabled(false); + qApp->processEvents(); + if (!this->IsTotalSegmentatorInstalled(m_PythonPath)) + { + throw std::runtime_error(WARNING_TOTALSEG_NOT_FOUND); + } + bool isFast = m_Controls.fastBox->isChecked(); + QString subTask = m_Controls.subtaskComboBox->currentText(); + if (subTask != VALID_TASKS[0]) + { + isFast = true; + } + tool->SetPythonPath(m_PythonPath.toStdString()); + tool->SetGpuId(FetchSelectedGPUFromUI()); + tool->SetFast(isFast); + tool->SetSubTask(subTask.toStdString()); + this->WriteStatusMessage(QString("STATUS: Starting Segmentation task... This might take a while.")); + tool->UpdatePreview(); + m_Controls.previewButton->setEnabled(true); + m_FirstPreviewComputation = false; + } + catch (const std::exception &e) + { + std::stringstream errorMsg; + errorMsg << "STATUS: Error while processing parameters for TotalSegmentator segmentation. Reason: " + << e.what(); + this->ShowErrorMessage(errorMsg.str()); + this->WriteErrorMessage(QString::fromStdString(errorMsg.str())); + m_Controls.previewButton->setEnabled(true); + return; + } + catch (...) + { + std::string errorMsg = "Unkown error occured while generation TotalSegmentator segmentation."; + this->ShowErrorMessage(errorMsg); + m_Controls.previewButton->setEnabled(true); + return; + } + this->SetLabelSetPreview(tool->GetPreviewSegmentation()); + this->ActualizePreviewLabelVisibility(); + this->WriteStatusMessage("STATUS: Segmentation task finished successfully."); + QString pythonPathTextItem = m_Controls.pythonEnvComboBox->currentText(); + if (!pythonPathTextItem.isEmpty() && pythonPathTextItem != "Select") // only cache if the prediction ended without errors. + { + QString lastSelectedPyEnv = m_Settings.value("TotalSeg/LastCustomPythonPath").toString(); + if (lastSelectedPyEnv != pythonPathTextItem) + { + m_Settings.setValue("TotalSeg/LastCustomPythonPath", pythonPathTextItem); + } + } +} + +void QmitkTotalSegmentatorToolGUI::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 QmitkTotalSegmentatorToolGUI::WriteStatusMessage(const QString &message) +{ + m_Controls.statusLabel->setText(message); + m_Controls.statusLabel->setStyleSheet("font-weight: bold; color: white"); + qApp->processEvents(); +} + +void QmitkTotalSegmentatorToolGUI::WriteErrorMessage(const QString &message) +{ + m_Controls.statusLabel->setText(message); + m_Controls.statusLabel->setStyleSheet("font-weight: bold; color: red"); + qApp->processEvents(); +} + +bool QmitkTotalSegmentatorToolGUI::IsTotalSegmentatorInstalled(const QString &pythonPath) +{ + QString fullPath = pythonPath; + bool isPythonExists = false; +#ifdef _WIN32 + isPythonExists = QFile::exists(fullPath + QDir::separator() + QString("python.exe")); + if (!(fullPath.endsWith("Scripts", Qt::CaseInsensitive) || fullPath.endsWith("Scripts/", Qt::CaseInsensitive))) + { + fullPath += QDir::separator() + QString("Scripts"); + isPythonExists = + (!isPythonExists) ? QFile::exists(fullPath + QDir::separator() + QString("python.exe")) : isPythonExists; + } +#else + isPythonExists = QFile::exists(fullPath + QDir::separator() + QString("python3")); + if (!(fullPath.endsWith("bin", Qt::CaseInsensitive) || fullPath.endsWith("bin/", Qt::CaseInsensitive))) + { + fullPath += QDir::separator() + QString("bin"); + isPythonExists = + (!isPythonExists) ? QFile::exists(fullPath + QDir::separator() + QString("python3")) : isPythonExists; + } +#endif + bool isExists = QFile::exists(fullPath + QDir::separator() + QString("TotalSegmentator")) && isPythonExists; + return isExists; +} + +void QmitkTotalSegmentatorToolGUI::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.sysPythonComboBox->addItem("(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.sysPythonComboBox->addItem("(" + envName + "): " + subIt.filePath()); + } + } + } +} + +QString QmitkTotalSegmentatorToolGUI::OnSystemPythonChanged(const QString &pyEnv) +{ + QString pyPath; + if (pyEnv == QString("Select")) + { + m_Controls.previewButton->setDisabled(true); + QString path = + QFileDialog::getExistingDirectory(m_Controls.sysPythonComboBox->parentWidget(), "Python Path", "dir"); + if (!path.isEmpty()) + { + this->OnSystemPythonChanged(path); // recall same function for new path validation + bool oldState = m_Controls.sysPythonComboBox->blockSignals(true); // block signal firing while inserting item + m_Controls.sysPythonComboBox->insertItem(0, path); + m_Controls.sysPythonComboBox->setCurrentIndex(0); + m_Controls.sysPythonComboBox->blockSignals( + oldState); // unblock signal firing after inserting item. Remove this after Qt6 migration + } + } + else + { + QString uiPyPath = this->GetPythonPathFromUI(pyEnv); + pyPath = this->GetExactPythonPath(uiPyPath); + } + return pyPath; +} + +void QmitkTotalSegmentatorToolGUI::OnPythonPathChanged(const QString &pyEnv) +{ + if (pyEnv == QString("Select")) + { + m_Controls.previewButton->setDisabled(true); + QString path = + QFileDialog::getExistingDirectory(m_Controls.pythonEnvComboBox->parentWidget(), "Python Path", "dir"); + if (!path.isEmpty()) + { + this->OnPythonPathChanged(path); // recall same function for new path validation + bool oldState = m_Controls.pythonEnvComboBox->blockSignals(true); // block signal firing while inserting item + m_Controls.pythonEnvComboBox->insertItem(0, path); + m_Controls.pythonEnvComboBox->setCurrentIndex(0); + m_Controls.pythonEnvComboBox->blockSignals( + oldState); // unblock signal firing after inserting item. Remove this after Qt6 migration + } + } + else if (!this->IsTotalSegmentatorInstalled(pyEnv)) + { + this->ShowErrorMessage(WARNING_TOTALSEG_NOT_FOUND); + m_Controls.previewButton->setDisabled(true); + } + else + {// Show positive status meeage + m_Controls.previewButton->setDisabled(false); + QString uiPyPath = this->GetPythonPathFromUI(pyEnv); + m_PythonPath = this->GetExactPythonPath(uiPyPath); + } +} + +QString QmitkTotalSegmentatorToolGUI::GetPythonPathFromUI(const QString &pyUI) const +{ + QString fullPath = pyUI; + if (-1 != fullPath.indexOf(")")) + { + fullPath = fullPath.mid(fullPath.indexOf(")") + 2); + } + return fullPath.simplified(); +} + +QString QmitkTotalSegmentatorToolGUI::GetExactPythonPath(const QString &pyEnv) const +{ + QString fullPath = pyEnv; + bool isPythonExists = false; +#ifdef _WIN32 + isPythonExists = QFile::exists(fullPath + QDir::separator() + QString("python.exe")); + if (!isPythonExists && + !(fullPath.endsWith("Scripts", Qt::CaseInsensitive) || fullPath.endsWith("Scripts/", Qt::CaseInsensitive))) + { + fullPath += QDir::separator() + QString("Scripts"); + isPythonExists = QFile::exists(fullPath + QDir::separator() + QString("python.exe")); + } +#else + isPythonExists = QFile::exists(fullPath + QDir::separator() + QString("python3")); + if (!isPythonExists && !(fullPath.endsWith("bin", Qt::CaseInsensitive) || + fullPath.endsWith("bin/", Qt::CaseInsensitive))) + { + fullPath += QDir::separator() + QString("bin"); + isPythonExists = QFile::exists(fullPath + QDir::separator() + QString("python3")); + } +#endif + if (!isPythonExists) + { + fullPath.clear(); + } + return fullPath; +} + +void QmitkTotalSegmentatorToolGUI::OnOverrideChecked(int state) +{ + bool isEnabled = false; + if (state == Qt::Checked) + { + isEnabled = true; + m_Controls.previewButton->setDisabled(true); + m_PythonPath.clear(); + } + else + { + m_PythonPath.clear(); + m_Controls.previewButton->setDisabled(true); + if (m_IsInstalled) + { + const QString pythonPath = m_Installer.GetVirtualEnvPath(); + m_PythonPath = this->GetExactPythonPath(pythonPath); + this->EnableAll(m_IsInstalled); + } + } + m_Controls.pythonEnvComboBox->setEnabled(isEnabled); +} + +void QmitkTotalSegmentatorToolGUI::OnClearInstall() +{ + QDir folderPath(m_Installer.GetVirtualEnvPath()); + if (folderPath.removeRecursively()) + { + m_Controls.installButton->setEnabled(true); + if (!m_Controls.overrideBox->isChecked()) + { + m_Controls.previewButton->setEnabled(false); + } + } + else + { + MITK_ERROR + << "The virtual environment couldn't be removed. Please check if you have the required access privileges or, some other process is accessing the folders."; + } +} + +bool QmitkTotalSegmentatorToolInstaller::SetupVirtualEnv(const QString& venvName) +{ + if (GetSystemPythonPath().isEmpty()) + { + return false; + } + QDir folderPath(GetBaseDir()); + folderPath.mkdir(venvName); + if (!folderPath.cd(venvName)) + { + return false; // Check if directory creation was successful. + } + mitk::ProcessExecutor::ArgumentListType args; + auto spExec = mitk::ProcessExecutor::New(); + auto spCommand = itk::CStyleCommand::New(); + spCommand->SetCallback(&PrintProcessEvent); + spExec->AddObserver(mitk::ExternalProcessOutputEvent(), spCommand); + + args.push_back("-m"); + args.push_back("venv"); + args.push_back(venvName.toStdString()); +#ifdef _WIN32 + QString pythonFile = GetSystemPythonPath() + QDir::separator() + "python.exe"; + QString pythonExeFolder = "Scripts"; +#else + QString pythonFile = GetSystemPythonPath() + QDir::separator() + "python3"; + QString pythonExeFolder = "bin"; +#endif + spExec->Execute(GetBaseDir().toStdString(), pythonFile.toStdString(), args); // Setup local virtual environment + if (folderPath.cd(pythonExeFolder)) + { + this->SetPythonPath(folderPath.absolutePath()); + this->SetPipPath(folderPath.absolutePath()); + this->InstallPytorch(); + for (auto &package : PACKAGES) + { + this->PipInstall(package.toStdString(), &PrintProcessEvent); + } + std::string pythonCode; // python syntax to check if torch is installed with CUDA. + pythonCode.append("import torch;"); + pythonCode.append("print('Pytorch was installed with CUDA') if torch.cuda.is_available() else print('PyTorch was " + "installed WITHOUT CUDA');"); + this->ExecutePython(pythonCode, &PrintProcessEvent); + return true; + } + return false; +} + +QString QmitkTotalSegmentatorToolInstaller::GetVirtualEnvPath() +{ + return STORAGE_DIR + VENV_NAME; +} diff --git a/Modules/SegmentationUI/Qmitk/QmitkTotalSegmentatorToolGUI.h b/Modules/SegmentationUI/Qmitk/QmitkTotalSegmentatorToolGUI.h new file mode 100644 index 0000000000..c100fe87cc --- /dev/null +++ b/Modules/SegmentationUI/Qmitk/QmitkTotalSegmentatorToolGUI.h @@ -0,0 +1,176 @@ +#ifndef QmitkTotalSegmentatorToolGUI_h_Included +#define QmitkTotalSegmentatorToolGUI_h_Included + +#include "QmitkMultiLabelSegWithPreviewToolGUIBase.h" +#include "QmitkSetupVirtualEnvUtil.h" +#include "QmitknnUNetGPU.h" +#include "ui_QmitkTotalSegmentatorGUIControls.h" +#include +#include +#include +#include + +/** + * @brief Installer class for TotalSegmentator Tool. + * Class specifies the virtual environment name, install version, packages required to pip install + * and implements SetupVirtualEnv method. + * + */ +class QmitkTotalSegmentatorToolInstaller : public QmitkSetupVirtualEnvUtil +{ +public: + const QString VENV_NAME = ".totalsegmentator"; + const QString TOTALSEGMENTATOR_VERSION = "1.5.5"; + const std::vector PACKAGES = {QString("Totalsegmentator==") + TOTALSEGMENTATOR_VERSION, + QString("scipy==1.9.1")}; + const QString STORAGE_DIR; + inline QmitkTotalSegmentatorToolInstaller( + const QString baseDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QDir::separator() + + qApp->organizationName() + QDir::separator()) + : QmitkSetupVirtualEnvUtil(baseDir), STORAGE_DIR(baseDir){}; + bool SetupVirtualEnv(const QString &) override; + QString GetVirtualEnvPath() override; +}; + +/** + \ingroup org_mitk_gui_qt_interactivesegmentation_internal + \brief GUI for mitk::TotalSegmentatorTool. + \sa mitk:: +*/ +class MITKSEGMENTATIONUI_EXPORT QmitkTotalSegmentatorToolGUI : public QmitkMultiLabelSegWithPreviewToolGUIBase +{ + Q_OBJECT + +public: + mitkClassMacro(QmitkTotalSegmentatorToolGUI, QmitkMultiLabelSegWithPreviewToolGUIBase); + itkFactorylessNewMacro(Self); + itkCloneMacro(Self); + +protected slots: + + /** + * @brief Qt Slot + */ + void OnPreviewBtnClicked(); + + /** + * @brief Qt Slot + */ + void OnPythonPathChanged(const QString &); + + /** + * @brief Qt Slot + */ + QString OnSystemPythonChanged(const QString &); + + /** + * @brief Qt Slot + */ + void OnInstallBtnClicked(); + + /** + * @brief Qt Slot + */ + void OnOverrideChecked(int); + + /** + * @brief Qt Slot + */ + void OnClearInstall(); + +protected: + QmitkTotalSegmentatorToolGUI(); + ~QmitkTotalSegmentatorToolGUI() = default; + + void ConnectNewTool(mitk::SegWithPreviewTool *newTool) override; + void InitializeUI(QBoxLayout *mainLayout) override; + + /** + * @brief Enable (or Disable) GUI elements. + */ + void EnableAll(bool); + + /** + * @brief Searches and parses paths of python virtual enviroments + * from predefined lookout locations + */ + void AutoParsePythonPaths(); + + /** + * @brief Checks if TotalSegmentator command is valid in the selected python virtual environment. + * + * @return bool + */ + bool IsTotalSegmentatorInstalled(const QString &); + + /** + * @brief Creates a QMessage object and shows on screen. + */ + void ShowErrorMessage(const std::string &, QMessageBox::Icon = QMessageBox::Critical); + + /** + * @brief Writes any message in white on the tool pane. + */ + void WriteStatusMessage(const QString &); + + /** + * @brief Writes any message in red on the tool pane. + */ + void WriteErrorMessage(const QString &); + + /** + * @brief Adds GPU information to the gpu combo box. + * In case, there aren't any GPUs avaialble, the combo box will be + * rendered editable. + */ + void SetGPUInfo(); + + /** + * @brief Returns GPU id of the selected GPU from the Combo box. + * + * @return unsigned int + */ + unsigned int FetchSelectedGPUFromUI() const; + + /** + * @brief Get the virtual env path from UI combobox removing any + * extra special characters. + * + * @return QString + */ + QString GetPythonPathFromUI(const QString &) const; + + /** + * @brief Get the Exact Python Path for any OS + * from the virtual environment path. + * @return QString + */ + QString GetExactPythonPath(const QString &) const; + + /** + * @brief For storing values like Python path across sessions. + */ + QSettings m_Settings; + + QString m_PythonPath; + QmitkGPULoader m_GpuLoader; + Ui_QmitkTotalSegmentatorToolGUIControls m_Controls; + bool m_FirstPreviewComputation = true; + bool m_IsInstalled = false; + EnableConfirmSegBtnFunctionType m_SuperclassEnableConfirmSegBtnFnc; + + const std::string WARNING_TOTALSEG_NOT_FOUND = + "TotalSegmentator is not detected in the selected python environment.Please select a valid " + "python environment or install TotalSegmentator."; + const QStringList VALID_TASKS = { + "total", + "cerebral_bleed", + "hip_implant", + "coronary_arteries", + "body", + "lung_vessels", + "pleural_pericard_effusion" + }; + QmitkTotalSegmentatorToolInstaller m_Installer; +}; +#endif diff --git a/Modules/SegmentationUI/Qmitk/QmitknnUNetGPU.cpp b/Modules/SegmentationUI/Qmitk/QmitknnUNetGPU.cpp index 67217dd9ec..00837b995c 100644 --- a/Modules/SegmentationUI/Qmitk/QmitknnUNetGPU.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitknnUNetGPU.cpp @@ -1,55 +1,55 @@ /*============================================================================ 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 ============================================================================*/ #include "QmitknnUNetGPU.h" #include QmitkGPULoader::QmitkGPULoader() { QProcess process; process.start("nvidia-smi --query-gpu=name,memory.total --format=csv"); process.waitForFinished(-1); QStringList infoStringList; while (process.canReadLine()) { QString line = process.readLine(); if (!line.startsWith("name")) { infoStringList << line; } } unsigned int count = 0; foreach (QString infoString, infoStringList) { QmitkGPUSpec spec; QStringList gpuDetails; gpuDetails = infoString.split(","); if(gpuDetails.count() == 2) { spec.name = gpuDetails.at(0); spec.id = count; spec.memory = gpuDetails.at(1); this->m_Gpus.push_back(spec); ++count; } } } -int QmitkGPULoader::GetGPUCount() +int QmitkGPULoader::GetGPUCount() const { return static_cast(m_Gpus.size()); } std::vector QmitkGPULoader::GetAllGPUSpecs() { return m_Gpus; } diff --git a/Modules/SegmentationUI/Qmitk/QmitknnUNetGPU.h b/Modules/SegmentationUI/Qmitk/QmitknnUNetGPU.h index e161de2ddf..203ea91eb3 100644 --- a/Modules/SegmentationUI/Qmitk/QmitknnUNetGPU.h +++ b/Modules/SegmentationUI/Qmitk/QmitknnUNetGPU.h @@ -1,63 +1,63 @@ /*============================================================================ 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 QmitknnUNetGPU_h #define QmitknnUNetGPU_h #include #include #include /** * @brief Struct to store GPU info. * */ struct QmitkGPUSpec { QString name; QString memory; unsigned int id; }; /** * @brief Class to load and save GPU information * for further validation */ class MITKSEGMENTATIONUI_EXPORT QmitkGPULoader { 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(); ~QmitkGPULoader() = default; /** * @brief Returns the number of GPUs parsed and saved as QmitkGPUSpec objects. * * @return int */ - int GetGPUCount(); + int GetGPUCount() const; /** * @brief Returns all the parsed GPU information * * @return std::vector */ std::vector GetAllGPUSpecs(); }; #endif diff --git a/Modules/SegmentationUI/files.cmake b/Modules/SegmentationUI/files.cmake index 649fc755b7..49d4287df5 100644 --- a/Modules/SegmentationUI/files.cmake +++ b/Modules/SegmentationUI/files.cmake @@ -1,113 +1,118 @@ set(CPP_FILES Qmitk/QmitkSegWithPreviewToolGUIBase.cpp Qmitk/QmitkMultiLabelSegWithPreviewToolGUIBase.cpp Qmitk/QmitkBinaryThresholdToolGUIBase.cpp Qmitk/QmitkBinaryThresholdToolGUI.cpp Qmitk/QmitkBinaryThresholdULToolGUI.cpp Qmitk/QmitkConfirmSegmentationDialog.cpp Qmitk/QmitkCopyToClipBoardDialog.cpp Qmitk/QmitkDrawPaintbrushToolGUI.cpp Qmitk/QmitkErasePaintbrushToolGUI.cpp Qmitk/QmitkEditableContourToolGUIBase.cpp Qmitk/QmitkGrowCutToolGUI.cpp Qmitk/QmitkLiveWireTool2DGUI.cpp Qmitk/QmitkLassoToolGUI.cpp Qmitk/QmitkOtsuTool3DGUI.cpp Qmitk/QmitkPaintbrushToolGUI.cpp Qmitk/QmitkPickingToolGUI.cpp Qmitk/QmitkSlicesInterpolator.cpp Qmitk/QmitkToolGUI.cpp Qmitk/QmitkToolGUIArea.cpp Qmitk/QmitkToolSelectionBox.cpp Qmitk/QmitknnUNetFolderParser.cpp Qmitk/QmitknnUNetToolGUI.cpp Qmitk/QmitknnUNetWorker.cpp Qmitk/QmitknnUNetGPU.cpp Qmitk/QmitkSurfaceStampWidget.cpp Qmitk/QmitkMaskStampWidget.cpp Qmitk/QmitkStaticDynamicSegmentationDialog.cpp Qmitk/QmitkSimpleLabelSetListWidget.cpp Qmitk/QmitkSegmentationTaskListWidget.cpp + Qmitk/QmitkTotalSegmentatorToolGUI.cpp + Qmitk/QmitkSetupVirtualEnvUtil.cpp Qmitk/QmitkMultiLabelInspector.cpp Qmitk/QmitkMultiLabelManager.cpp Qmitk/QmitkMultiLabelTreeModel.cpp Qmitk/QmitkMultiLabelTreeView.cpp Qmitk/QmitkLabelColorItemDelegate.cpp Qmitk/QmitkLabelToggleItemDelegate.cpp SegmentationUtilities/QmitkBooleanOperationsWidget.cpp SegmentationUtilities/QmitkContourModelToImageWidget.cpp SegmentationUtilities/QmitkImageMaskingWidget.cpp SegmentationUtilities/QmitkMorphologicalOperationsWidget.cpp SegmentationUtilities/QmitkSurfaceToImageWidget.cpp SegmentationUtilities/QmitkSegmentationUtilityWidget.cpp SegmentationUtilities/QmitkDataSelectionWidget.cpp ) set(MOC_H_FILES Qmitk/QmitkSegWithPreviewToolGUIBase.h Qmitk/QmitkMultiLabelSegWithPreviewToolGUIBase.h Qmitk/QmitkBinaryThresholdToolGUIBase.h Qmitk/QmitkBinaryThresholdToolGUI.h Qmitk/QmitkBinaryThresholdULToolGUI.h Qmitk/QmitkConfirmSegmentationDialog.h Qmitk/QmitkCopyToClipBoardDialog.h Qmitk/QmitkDrawPaintbrushToolGUI.h Qmitk/QmitkErasePaintbrushToolGUI.h Qmitk/QmitkEditableContourToolGUIBase.h Qmitk/QmitkGrowCutToolGUI.h Qmitk/QmitkLiveWireTool2DGUI.h Qmitk/QmitkLassoToolGUI.h Qmitk/QmitkOtsuTool3DGUI.h Qmitk/QmitkPaintbrushToolGUI.h Qmitk/QmitkPickingToolGUI.h Qmitk/QmitkSlicesInterpolator.h Qmitk/QmitkToolGUI.h Qmitk/QmitkToolGUIArea.h Qmitk/QmitkToolSelectionBox.h Qmitk/QmitknnUNetFolderParser.h Qmitk/QmitknnUNetToolGUI.h Qmitk/QmitknnUNetGPU.h Qmitk/QmitknnUNetWorker.h Qmitk/QmitknnUNetEnsembleLayout.h Qmitk/QmitkSurfaceStampWidget.h Qmitk/QmitkMaskStampWidget.h Qmitk/QmitkStaticDynamicSegmentationDialog.h Qmitk/QmitkSimpleLabelSetListWidget.h Qmitk/QmitkSegmentationTaskListWidget.h + Qmitk/QmitkTotalSegmentatorToolGUI.h + Qmitk/QmitkSetupVirtualEnvUtil.h Qmitk/QmitkMultiLabelInspector.h Qmitk/QmitkMultiLabelManager.h Qmitk/QmitkMultiLabelTreeModel.h Qmitk/QmitkMultiLabelTreeView.h Qmitk/QmitkLabelColorItemDelegate.h Qmitk/QmitkLabelToggleItemDelegate.h SegmentationUtilities/QmitkBooleanOperationsWidget.h SegmentationUtilities/QmitkContourModelToImageWidget.h SegmentationUtilities/QmitkImageMaskingWidget.h SegmentationUtilities/QmitkMorphologicalOperationsWidget.h SegmentationUtilities/QmitkSurfaceToImageWidget.h SegmentationUtilities/QmitkSegmentationUtilityWidget.h SegmentationUtilities/QmitkDataSelectionWidget.h ) set(UI_FILES Qmitk/QmitkConfirmSegmentationDialog.ui Qmitk/QmitkGrowCutToolWidgetControls.ui Qmitk/QmitkOtsuToolWidgetControls.ui Qmitk/QmitkSurfaceStampWidgetGUIControls.ui Qmitk/QmitkMaskStampWidgetGUIControls.ui Qmitk/QmitknnUNetToolGUIControls.ui Qmitk/QmitkEditableContourToolGUIControls.ui Qmitk/QmitkSegmentationTaskListWidget.ui + Qmitk/QmitkTotalSegmentatorGUIControls.ui Qmitk/QmitkMultiLabelInspectorControls.ui Qmitk/QmitkMultiLabelManagerControls.ui SegmentationUtilities/QmitkBooleanOperationsWidgetControls.ui SegmentationUtilities/QmitkContourModelToImageWidgetControls.ui SegmentationUtilities/QmitkImageMaskingWidgetControls.ui SegmentationUtilities/QmitkMorphologicalOperationsWidgetControls.ui SegmentationUtilities/QmitkSurfaceToImageWidgetControls.ui SegmentationUtilities/QmitkDataSelectionWidgetControls.ui ) set(QRC_FILES resources/SegmentationUI.qrc ) 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 55c10b90ff..396b35a06b 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.cpp +++ b/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.cpp @@ -1,1034 +1,1034 @@ /*============================================================================ 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 #include #include #include #include #include // Qmitk #include #include #include #include // us #include #include // Qt #include #include #include // vtk #include #include namespace { QList Get2DWindows(const QList allWindows) { QList all2DWindows; for (auto* window : allWindows) { if (window->GetRenderer()->GetMapperID() == mitk::BaseRenderer::Standard2D) { all2DWindows.append(window); } } return all2DWindows; } } 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_DrawOutline(true) , m_SelectionMode(false) , m_MouseCursorSet(false) , m_DefaultLabelNaming(true) , m_SelectionChangeIsAlreadyBeingHandled(false) { auto 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"); auto validImages = mitk::NodePredicateOr::New(); validImages->AddPredicate(mitk::NodePredicateAnd::New(isImage, mitk::NodePredicateNot::New(isSegment))); validImages->AddPredicate(isDwi); validImages->AddPredicate(isDti); validImages->AddPredicate(isOdf); m_SegmentationPredicate = mitk::NodePredicateAnd::New(); m_SegmentationPredicate->AddPredicate(mitk::TNodePredicateDataType::New()); 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 (nullptr != m_Controls) { // deactivate all tools m_ToolManager->ActivateTool(-1); // removing all observers from working data for (NodeTagMapType::iterator dataIter = m_WorkingDataObserverTags.begin(); dataIter != m_WorkingDataObserverTags.end(); ++dataIter) { (*dataIter).first->GetProperty("visible")->RemoveObserver((*dataIter).second); } m_WorkingDataObserverTags.clear(); // removing all observers from reference data for (NodeTagMapType::iterator dataIter = m_ReferenceDataObserverTags.begin(); dataIter != m_ReferenceDataObserverTags.end(); ++dataIter) { (*dataIter).first->GetProperty("visible")->RemoveObserver((*dataIter).second); } m_ReferenceDataObserverTags.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); } m_ToolManager->ActiveToolChanged -= mitk::MessageDelegate(this, &QmitkSegmentationView::ActiveToolChanged); delete m_Controls; } /**********************************************************************/ /* private Q_SLOTS */ /**********************************************************************/ void QmitkSegmentationView::OnReferenceSelectionChanged(QList) { this->OnAnySelectionChanged(); } void QmitkSegmentationView::OnSegmentationSelectionChanged(QList) { this->OnAnySelectionChanged(); } void QmitkSegmentationView::OnAnySelectionChanged() { // When only a segmentation has been selected and the method is then called by a reference image selection, // the already selected segmentation may not match the geometry predicate of the new reference image anymore. // This will trigger a recursive call of this method further below. While it would be resolved gracefully, we // can spare the extra call with an early-out. The original call of this method will handle the segmentation // selection change afterwards anyway. if (m_SelectionChangeIsAlreadyBeingHandled) return; auto selectedReferenceNode = m_Controls->referenceNodeSelector->GetSelectedNode(); bool referenceNodeChanged = false; m_ToolManager->ActivateTool(-1); if (m_ReferenceNode != selectedReferenceNode) { referenceNodeChanged = true; // Remove visibility observer for the current reference node if (m_ReferenceDataObserverTags.find(m_ReferenceNode) != m_ReferenceDataObserverTags.end()) { m_ReferenceNode->GetProperty("visible")->RemoveObserver(m_ReferenceDataObserverTags[m_ReferenceNode]); m_ReferenceDataObserverTags.erase(m_ReferenceNode); } // Set new reference node m_ReferenceNode = selectedReferenceNode; m_ToolManager->SetReferenceData(m_ReferenceNode); // Prepare for a potential recursive call when changing node predicates of the working node selector m_SelectionChangeIsAlreadyBeingHandled = true; if (m_ReferenceNode.IsNull()) { // Without a reference image, allow all segmentations to be selected m_Controls->workingNodeSelector->SetNodePredicate(m_SegmentationPredicate); m_SelectionChangeIsAlreadyBeingHandled = false; } else { // With a reference image, only allow segmentations that fit the geometry of the reference image to be selected. m_Controls->workingNodeSelector->SetNodePredicate(mitk::NodePredicateAnd::New( mitk::NodePredicateSubGeometry::New(m_ReferenceNode->GetData()->GetGeometry()), m_SegmentationPredicate.GetPointer())); m_SelectionChangeIsAlreadyBeingHandled = false; this->ApplySelectionModeOnReferenceNode(); // Add visibility observer for the new reference node auto command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &QmitkSegmentationView::ValidateSelectionInput); m_ReferenceDataObserverTags[m_ReferenceNode] = m_ReferenceNode->GetProperty("visible")->AddObserver(itk::ModifiedEvent(), command); } } auto selectedWorkingNode = m_Controls->workingNodeSelector->GetSelectedNode(); bool workingNodeChanged = false; if (m_WorkingNode != selectedWorkingNode) { workingNodeChanged = true; // Remove visibility observer for the current working node if (m_WorkingDataObserverTags.find(m_WorkingNode) != m_WorkingDataObserverTags.end()) { m_WorkingNode->GetProperty("visible")->RemoveObserver(m_WorkingDataObserverTags[m_WorkingNode]); m_WorkingDataObserverTags.erase(m_WorkingNode); } // Set new working node m_WorkingNode = selectedWorkingNode; m_ToolManager->SetWorkingData(m_WorkingNode); if (m_WorkingNode.IsNotNull()) { this->ApplySelectionModeOnWorkingNode(); // Add visibility observer for the new segmentation node auto command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &QmitkSegmentationView::ValidateSelectionInput); m_WorkingDataObserverTags[m_WorkingNode] = m_WorkingNode->GetProperty("visible")->AddObserver(itk::ModifiedEvent(), command); } } // Reset camera if any selection changed but only if both reference node and working node are set if ((referenceNodeChanged || workingNodeChanged) && (m_ReferenceNode.IsNotNull() && m_WorkingNode.IsNotNull())) { if (nullptr != m_RenderWindowPart) { m_RenderWindowPart->InitializeViews(m_ReferenceNode->GetData()->GetTimeGeometry(), false); } } this->UpdateGUI(); } void QmitkSegmentationView::OnVisibilityShortcutActivated() { if (m_WorkingNode.IsNull()) { return; } bool isVisible = false; m_WorkingNode->GetBoolProperty("visible", isVisible); m_WorkingNode->SetVisibility(!isVisible); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSegmentationView::OnLabelToggleShortcutActivated() { if (m_WorkingNode.IsNull()) { return; } auto workingImage = dynamic_cast(m_WorkingNode->GetData()); if (nullptr == workingImage) { return; } this->WaitCursorOn(); workingImage->GetActiveLabelSet()->SetNextActiveLabel(); workingImage->Modified(); this->WaitCursorOff(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSegmentationView::OnNewSegmentation() { m_ToolManager->ActivateTool(-1); if (m_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(m_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; } auto segTemplateImage = referenceImage; if (referenceImage->GetDimension() > 3) { QmitkStaticDynamicSegmentationDialog dialog(m_Parent); dialog.SetReferenceImage(referenceImage.GetPointer()); dialog.exec(); segTemplateImage = dialog.GetSegmentationTemplate(); } mitk::DataNode::Pointer newSegmentationNode; try { this->WaitCursorOn(); newSegmentationNode = mitk::LabelSetImageHelper::CreateNewSegmentationNode(m_ReferenceNode, segTemplateImage); this->WaitCursorOff(); } catch (mitk::Exception& e) { this->WaitCursorOff(); MITK_ERROR << "Exception caught: " << e.GetDescription(); QMessageBox::warning(m_Parent, "New segmentation", "Could not create a new segmentation."); return; } auto newLabelSetImage = dynamic_cast(newSegmentationNode->GetData()); if (nullptr == newLabelSetImage) { // something went wrong return; } const auto labelSetPreset = this->GetDefaultLabelSetPreset(); if (labelSetPreset.empty() || !mitk::MultiLabelIOHelper::LoadLabelSetImagePreset(labelSetPreset, newLabelSetImage)) { auto newLabel = mitk::LabelSetImageHelper::CreateNewLabel(newLabelSetImage); if (!m_DefaultLabelNaming) { QmitkNewSegmentationDialog::DoRenameLabel(newLabel,nullptr,m_Parent); } newLabelSetImage->GetActiveLabelSet()->AddLabel(newLabel); } if (!this->GetDataStorage()->Exists(newSegmentationNode)) { this->GetDataStorage()->Add(newSegmentationNode, m_ReferenceNode); } if (m_ToolManager->GetWorkingData(0)) { m_ToolManager->GetWorkingData(0)->SetSelected(false); } newSegmentationNode->SetSelected(true); m_Controls->workingNodeSelector->SetCurrentSelectedNode(newSegmentationNode); } std::string QmitkSegmentationView::GetDefaultLabelSetPreset() const { auto labelSetPreset = mitk::BaseApplication::instance().config().getString(mitk::BaseApplication::ARG_SEGMENTATION_LABELSET_PRESET.toStdString(), ""); if (labelSetPreset.empty()) labelSetPreset = m_LabelSetPresetPreference.toStdString(); return labelSetPreset; } 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 (nullptr == manualSegmentationTool) { continue; } manualSegmentationTool->SetShowMarkerNodes(state); } } void QmitkSegmentationView::OnCurrentLabelSelectionChanged(QmitkMultiLabelManager::LabelValueVectorType labels) { auto segmentation = this->GetCurrentSegmentation(); const auto labelValue = labels.front(); const auto groupID = segmentation->GetGroupIndexOfLabel(labelValue); if (groupID != segmentation->GetActiveLayer()) segmentation->SetActiveLayer(groupID); if (labelValue != segmentation->GetActiveLabelSet()->GetActiveLabel()->GetValue()) segmentation->GetActiveLabelSet()->SetActiveLabel(labelValue); segmentation->Modified(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSegmentationView::OnGoToLabel(mitk::LabelSetImage::LabelValueType /*label*/, const mitk::Point3D& pos) { if (m_RenderWindowPart) { m_RenderWindowPart->SetSelectedPosition(pos); } } void QmitkSegmentationView::OnLabelRenameRequested(mitk::Label* label, bool rename) const { auto segmentation = this->GetCurrentSegmentation(); if (rename) { QmitkNewSegmentationDialog::DoRenameLabel(label, segmentation, this->m_Parent, QmitkNewSegmentationDialog::Mode::RenameLabel); } else { QmitkNewSegmentationDialog::DoRenameLabel(label, segmentation, this->m_Parent, QmitkNewSegmentationDialog::Mode::NewLabel); } } mitk::LabelSetImage* QmitkSegmentationView::GetCurrentSegmentation() const { auto workingNode = m_Controls->workingNodeSelector->GetSelectedNode(); if (workingNode.IsNull()) mitkThrow() << "Segmentation view is in an invalid state. Working node is null, but a label selection change has been triggered."; auto segmentation = dynamic_cast(workingNode->GetData()); if (nullptr == segmentation) mitkThrow() << "Segmentation view is in an invalid state. Working node contains no segmentation, but a label selection change has been triggered."; return segmentation; } /**********************************************************************/ /* private */ /**********************************************************************/ void QmitkSegmentationView::CreateQtPartControl(QWidget* parent) { m_Parent = parent; m_Controls = new Ui::QmitkSegmentationViewControls; m_Controls->setupUi(parent); // *------------------------ // * SHORTCUTS // *------------------------ QShortcut* visibilityShortcut = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key::Key_H), parent); connect(visibilityShortcut, &QShortcut::activated, this, &QmitkSegmentationView::OnVisibilityShortcutActivated); QShortcut* labelToggleShortcut = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key::Key_L, Qt::CTRL | Qt::Key::Key_I), parent); connect(labelToggleShortcut, &QShortcut::activated, this, &QmitkSegmentationView::OnLabelToggleShortcutActivated); // *------------------------ // * DATA SELECTION WIDGETS // *------------------------ m_Controls->referenceNodeSelector->SetDataStorage(GetDataStorage()); m_Controls->referenceNodeSelector->SetNodePredicate(m_ReferencePredicate); m_Controls->referenceNodeSelector->SetInvalidInfo("Select an image"); m_Controls->referenceNodeSelector->SetPopUpTitel("Select an image"); m_Controls->referenceNodeSelector->SetPopUpHint("Select an image that should be used to define the geometry and bounds of the segmentation."); m_Controls->workingNodeSelector->SetDataStorage(GetDataStorage()); m_Controls->workingNodeSelector->SetNodePredicate(m_SegmentationPredicate); m_Controls->workingNodeSelector->SetInvalidInfo("Select a segmentation"); m_Controls->workingNodeSelector->SetPopUpTitel("Select a segmentation"); m_Controls->workingNodeSelector->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->referenceNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkSegmentationView::OnReferenceSelectionChanged); connect(m_Controls->workingNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkSegmentationView::OnSegmentationSelectionChanged); // *------------------------ // * TOOLMANAGER // *------------------------ m_ToolManager = mitk::ToolManagerProvider::GetInstance()->GetToolManager(); m_ToolManager->SetDataStorage(*(this->GetDataStorage())); m_ToolManager->InitializeTools(); QString segTools2D = tr("Add Subtract Lasso Fill Erase Close Paint Wipe 'Region Growing' 'Live Wire'"); - QString segTools3D = tr("Threshold 'UL Threshold' Otsu 'Region Growing 3D' Picking GrowCut"); + QString segTools3D = tr("Threshold 'UL Threshold' Otsu 'Region Growing 3D' Picking GrowCut TotalSegmentator"); #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())); } } // setup 2D tools m_Controls->toolSelectionBox2D->SetToolManager(*m_ToolManager); m_Controls->toolSelectionBox2D->SetGenerateAccelerators(true); m_Controls->toolSelectionBox2D->SetToolGUIArea(m_Controls->toolGUIArea2D); m_Controls->toolSelectionBox2D->SetDisplayedToolGroups(segTools2D.toStdString()); connect(m_Controls->toolSelectionBox2D, &QmitkToolSelectionBox::ToolSelected, this, &QmitkSegmentationView::OnManualTool2DSelected); // setup 3D Tools m_Controls->toolSelectionBox3D->SetToolManager(*m_ToolManager); m_Controls->toolSelectionBox3D->SetGenerateAccelerators(true); m_Controls->toolSelectionBox3D->SetToolGUIArea(m_Controls->toolGUIArea3D); m_Controls->toolSelectionBox3D->SetDisplayedToolGroups(segTools3D.toStdString()); m_Controls->slicesInterpolator->SetDataStorage(this->GetDataStorage()); // create general signal / slot connections connect(m_Controls->newSegmentationButton, &QToolButton::clicked, this, &QmitkSegmentationView::OnNewSegmentation); connect(m_Controls->slicesInterpolator, &QmitkSlicesInterpolator::SignalShowMarkerNodes, this, &QmitkSegmentationView::OnShowMarkerNodes); connect(m_Controls->multiLabelWidget, &QmitkMultiLabelManager::CurrentSelectionChanged, this, &QmitkSegmentationView::OnCurrentLabelSelectionChanged); connect(m_Controls->multiLabelWidget, &QmitkMultiLabelManager::GoToLabel, this, &QmitkSegmentationView::OnGoToLabel); connect(m_Controls->multiLabelWidget, &QmitkMultiLabelManager::LabelRenameRequested, this, &QmitkSegmentationView::OnLabelRenameRequested); 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->referenceNodeSelector->SetAutoSelectNewNodes(true); m_Controls->workingNodeSelector->SetAutoSelectNewNodes(true); this->UpdateGUI(); } void QmitkSegmentationView::ActiveToolChanged() { if (nullptr == m_RenderWindowPart) { return; } mitk::TimeGeometry* interactionReferenceGeometry = nullptr; auto activeTool = m_ToolManager->GetActiveTool(); if (nullptr != activeTool && m_ReferenceNode.IsNotNull()) { mitk::Image::ConstPointer referenceImage = dynamic_cast(m_ReferenceNode->GetData()); if (referenceImage.IsNotNull()) { // tool activated, reference image available: set reference geometry interactionReferenceGeometry = m_ReferenceNode->GetData()->GetTimeGeometry(); } } // set the interaction reference geometry for the render window part (might be nullptr) m_RenderWindowPart->SetInteractionReferenceGeometry(interactionReferenceGeometry); } void QmitkSegmentationView::RenderWindowPartActivated(mitk::IRenderWindowPart* renderWindowPart) { if (m_RenderWindowPart != renderWindowPart) { m_RenderWindowPart = renderWindowPart; } if (nullptr != m_Parent) { m_Parent->setEnabled(true); } if (nullptr == m_Controls) { return; } if (nullptr != m_RenderWindowPart) { auto all2DWindows = Get2DWindows(m_RenderWindowPart->GetQmitkRenderWindows().values()); m_Controls->slicesInterpolator->Initialize(m_ToolManager, all2DWindows); if (!m_RenderWindowPart->HasCoupledRenderWindows()) { // react if the active tool changed, only if a render window part with decoupled render windows is used m_ToolManager->ActiveToolChanged += mitk::MessageDelegate(this, &QmitkSegmentationView::ActiveToolChanged); } } } void QmitkSegmentationView::RenderWindowPartDeactivated(mitk::IRenderWindowPart* /*renderWindowPart*/) { m_RenderWindowPart = nullptr; if (nullptr != m_Parent) { m_Parent->setEnabled(false); } // remove message-connection to make sure no message is processed if no render window part is available m_ToolManager->ActiveToolChanged -= mitk::MessageDelegate(this, &QmitkSegmentationView::ActiveToolChanged); m_Controls->slicesInterpolator->Uninitialize(); } void QmitkSegmentationView::RenderWindowPartInputChanged(mitk::IRenderWindowPart* /*renderWindowPart*/) { if (nullptr == m_RenderWindowPart) { return; } m_Controls->slicesInterpolator->Uninitialize(); auto all2DWindows = Get2DWindows(m_RenderWindowPart->GetQmitkRenderWindows().values()); m_Controls->slicesInterpolator->Initialize(m_ToolManager, all2DWindows); } void QmitkSegmentationView::OnPreferencesChanged(const mitk::IPreferences* prefs) { auto labelSuggestions = mitk::BaseApplication::instance().config().getString(mitk::BaseApplication::ARG_SEGMENTATION_LABEL_SUGGESTIONS.toStdString(), ""); m_DefaultLabelNaming = labelSuggestions.empty() ? prefs->GetBool("default label naming", true) : false; // No default label naming when label suggestions are enforced via command-line argument if (nullptr != m_Controls) { m_Controls->multiLabelWidget->SetDefaultLabelNaming(m_DefaultLabelNaming); bool compactView = prefs->GetBool("compact view", false); int numberOfColumns = compactView ? 6 : 4; m_Controls->toolSelectionBox2D->SetLayoutColumns(numberOfColumns); m_Controls->toolSelectionBox2D->SetShowNames(!compactView); m_Controls->toolSelectionBox3D->SetLayoutColumns(numberOfColumns); m_Controls->toolSelectionBox3D->SetShowNames(!compactView); } m_DrawOutline = prefs->GetBool("draw outline", true); m_SelectionMode = prefs->GetBool("selection mode", false); m_LabelSetPresetPreference = QString::fromStdString(prefs->Get("label set preset", "")); this->ApplyDisplayOptions(); this->ApplySelectionMode(); } void QmitkSegmentationView::NodeAdded(const mitk::DataNode* node) { if (m_SegmentationPredicate->CheckNode(node)) this->ApplyDisplayOptions(const_cast(node)); this->ApplySelectionMode(); } void QmitkSegmentationView::NodeRemoved(const mitk::DataNode* node) { if (!m_SegmentationPredicate->CheckNode(node)) { return; } // 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); } void QmitkSegmentationView::ApplyDisplayOptions() { if (nullptr == m_Parent) { return; } if (nullptr == m_Controls) { return; // might happen on initialization (preferences loaded) } 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 labelSetImage = dynamic_cast(node->GetData()); if (nullptr == labelSetImage) { return; } // the outline property can be set in the segmentation preference page node->SetProperty("labelset.contour.active", mitk::BoolProperty::New(m_DrawOutline)); // force render window update to show outline mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSegmentationView::ApplySelectionMode() { if (!m_SelectionMode) return; this->ApplySelectionModeOnReferenceNode(); this->ApplySelectionModeOnWorkingNode(); } void QmitkSegmentationView::ApplySelectionModeOnReferenceNode() { this->ApplySelectionMode(m_ReferenceNode, m_ReferencePredicate); } void QmitkSegmentationView::ApplySelectionModeOnWorkingNode() { this->ApplySelectionMode(m_WorkingNode, m_SegmentationPredicate); } void QmitkSegmentationView::ApplySelectionMode(mitk::DataNode* node, mitk::NodePredicateBase* predicate) { if (!m_SelectionMode || node == nullptr || predicate == nullptr) return; auto nodes = this->GetDataStorage()->GetSubset(predicate); for (auto iter = nodes->begin(); iter != nodes->end(); ++iter) (*iter)->SetVisibility(*iter == node); } 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* threeDRenderWindow = 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, threeDRenderWindow->GetRenderer())) { selectedRenderWindow = threeDRenderWindow; } // make node visible if (nullptr != 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 (0 == nodes.size()) { return; } 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->newSegmentationButton->setEnabled(false); if (hasReferenceNode) { m_Controls->newSegmentationButton->setEnabled(true); } if (hasWorkingNode && hasReferenceNode) { int layer = -1; referenceNode->GetIntProperty("layer", layer); workingNode->SetIntProperty("layer", layer + 1); } this->ValidateSelectionInput(); } void QmitkSegmentationView::ValidateSelectionInput() { auto referenceNode = m_Controls->referenceNodeSelector->GetSelectedNode(); auto workingNode = m_Controls->workingNodeSelector->GetSelectedNode(); bool hasReferenceNode = referenceNode.IsNotNull(); bool hasWorkingNode = workingNode.IsNotNull(); bool hasBothNodes = hasReferenceNode && hasWorkingNode; QString warning; bool toolSelectionBoxesEnabled = hasReferenceNode && hasWorkingNode; unsigned int numberOfLabels = 0; m_Controls->multiLabelWidget->setEnabled(hasWorkingNode); m_Controls->toolSelectionBox2D->setEnabled(hasBothNodes); m_Controls->toolSelectionBox3D->setEnabled(hasBothNodes); m_Controls->slicesInterpolator->setEnabled(false); m_Controls->interpolatorWarningLabel->hide(); if (hasReferenceNode) { if (nullptr != m_RenderWindowPart && m_RenderWindowPart->HasCoupledRenderWindows() && !referenceNode->IsVisible(nullptr)) { warning += tr("The selected reference image is currently not visible!"); toolSelectionBoxesEnabled = false; } } if (hasWorkingNode) { if (nullptr != m_RenderWindowPart && m_RenderWindowPart->HasCoupledRenderWindows() && !workingNode->IsVisible(nullptr)) { warning += (!warning.isEmpty() ? "
" : "") + tr("The selected segmentation is currently not visible!"); toolSelectionBoxesEnabled = false; } m_ToolManager->SetReferenceData(referenceNode); m_ToolManager->SetWorkingData(workingNode); m_Controls->multiLabelWidget->setEnabled(true); m_Controls->toolSelectionBox2D->setEnabled(true); m_Controls->toolSelectionBox3D->setEnabled(true); auto labelSetImage = dynamic_cast(workingNode->GetData()); auto activeLayer = labelSetImage->GetActiveLayer(); numberOfLabels = labelSetImage->GetNumberOfLabels(activeLayer); if (numberOfLabels > 0) m_Controls->slicesInterpolator->setEnabled(true); m_Controls->multiLabelWidget->SetMultiLabelSegmentation(dynamic_cast(workingNode->GetData())); } else { m_Controls->multiLabelWidget->SetMultiLabelSegmentation(nullptr); } toolSelectionBoxesEnabled &= numberOfLabels > 0; // Here we need to check whether the geometry of the selected segmentation image (working image geometry) // is aligned with the geometry of the 3D render window. // It is not allowed to use a geometry different from the working image geometry for segmenting. // We only need to this if the tool selection box would be enabled without this check. // Additionally this check only has to be performed for render window parts with coupled render windows. // For different render window parts the user is given the option to reinitialize each render window individually // (see QmitkRenderWindow::ShowOverlayMessage). if (toolSelectionBoxesEnabled && nullptr != m_RenderWindowPart && m_RenderWindowPart->HasCoupledRenderWindows()) { const mitk::BaseGeometry* workingNodeGeometry = workingNode->GetData()->GetGeometry(); const mitk::BaseGeometry* renderWindowGeometry = m_RenderWindowPart->GetQmitkRenderWindow("3d")->GetSliceNavigationController()->GetCurrentGeometry3D(); if (nullptr != workingNodeGeometry && nullptr != renderWindowGeometry) { if (!mitk::Equal(*workingNodeGeometry->GetBoundingBox(), *renderWindowGeometry->GetBoundingBox(), mitk::eps, true)) { warning += (!warning.isEmpty() ? "
" : "") + tr("Please reinitialize the selected segmentation image!"); toolSelectionBoxesEnabled = false; } } } m_Controls->toolSelectionBox2D->setEnabled(toolSelectionBoxesEnabled); m_Controls->toolSelectionBox3D->setEnabled(toolSelectionBoxesEnabled); this->UpdateWarningLabel(warning); m_ToolManager->SetReferenceData(referenceNode); m_ToolManager->SetWorkingData(workingNode); } void QmitkSegmentationView::UpdateWarningLabel(QString text) { if (text.isEmpty()) { m_Controls->selectionWarningLabel->hide(); } else { m_Controls->selectionWarningLabel->setText("" + text + ""); m_Controls->selectionWarningLabel->show(); } }