diff --git a/Modules/Segmentation/Interactions/mitkSegmentAnythingTool.h b/Modules/Segmentation/Interactions/mitkSegmentAnythingTool.h index a200b2c09b..5385dca185 100644 --- a/Modules/Segmentation/Interactions/mitkSegmentAnythingTool.h +++ b/Modules/Segmentation/Interactions/mitkSegmentAnythingTool.h @@ -1,217 +1,216 @@ /*============================================================================ 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 mitkSegmentAnythingTool_h #define mitkSegmentAnythingTool_h #include "mitkSegWithPreviewTool.h" #include "mitkPointSet.h" #include "mitkProcessExecutor.h" #include "mitkSegmentAnythingPythonService.h" #include #include #include namespace us { class ModuleResource; } namespace mitk { /** \brief Segment Anything Model interactive 2D tool class. \ingroup ToolManagerEtAl \sa mitk::Tool \sa QmitkInteractiveSegmentation */ class MITKSEGMENTATION_EXPORT SegmentAnythingTool : public SegWithPreviewTool { public: mitkClassMacro(SegmentAnythingTool, SegWithPreviewTool); itkFactorylessNewMacro(Self); itkCloneMacro(Self); const char **GetXPM() const override; const char *GetName() const override; us::ModuleResource GetIconResource() const override; void Activated() override; void Deactivated() override; /** * @brief Clears all picks and updates the preview. */ void ClearPicks(); /** * @brief Checks if any point exists in the either * of the pointsets * * @return bool */ bool HasPicks() const; itkSetMacro(MitkTempDir, std::string); itkGetConstMacro(MitkTempDir, std::string); itkSetMacro(PythonPath, std::string); itkGetConstMacro(PythonPath, std::string); itkSetMacro(ModelType, std::string); itkGetConstMacro(ModelType, std::string); itkSetMacro(CheckpointPath, std::string); itkGetConstMacro(CheckpointPath, std::string); itkSetMacro(GpuId, int); itkGetConstMacro(GpuId, int); itkSetMacro(TimeOutLimit, long); itkGetConstMacro(TimeOutLimit, long); itkSetMacro(IsReady, bool); itkGetConstMacro(IsReady, bool); itkBooleanMacro(IsReady); /** * @brief Initializes python service and * starts async python daemon of SegmentAnything model. * */ void InitSAMPythonProcess(); /** * @brief Checks if Python daemon is ready to accept inputs. * * @return bool */ bool IsPythonReady() const; Message1 SAMStatusMessageEvent; protected: SegmentAnythingTool(); ~SegmentAnythingTool() = default; void ConnectActionsAndFunctions() override; /* * @brief Add positive seed point action of StateMachine pattern */ virtual void OnAddPositivePoint(StateMachineAction*, InteractionEvent *interactionEvent); /* * @brief Add negative seed point action of StateMachine pattern */ virtual void OnAddNegativePoint(StateMachineAction*, InteractionEvent *interactionEvent); /* * @brief Delete action of StateMachine pattern. The function deletes positive or negative points in the reverse order of creation. This is done by finding & deleting the Point having the highest PointIdentifier value from either of the PointSets m_PointSetPositive & m_PointSetNegative. */ virtual void OnDelete(StateMachineAction*, InteractionEvent*); /* * @brief Clear all seed points and call UpdatePreview to reset the segmentation Preview */ void ClearSeeds(); /** * @brief Overriden method from the tool manager to execute the segmentation * Implementation: * 1. Creates Hash for input image from current plane geometry. * 2. Transfers image pointer to python service along with the hash code. * 3. Creates seed points as CSV string & transfers to python service * 3. Retrieves resulting segmentation Image pointer from python service 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; /** * @brief Get the Points from positive and negative pointsets as std::vector. * - * @param baseGeometry of Image * @return std::vector> */ std::vector> GetPointsAsVector(const mitk::BaseGeometry*); /** * @brief Get the Points from positive and negative pointsets as csv string. * * @param baseGeometry * @return std::stringstream */ std::stringstream GetPointsAsCSVString(const mitk::BaseGeometry *baseGeometry); /** * @brief Get the Hash For Current Plane from current working plane geometry. * * @return std::string */ std::string GetHashForCurrentPlane(const mitk::LevelWindow&); /** * @brief Emits message to connected Listnerers. * */ void EmitSAMStatusMessageEvent(const std::string&); /** * @brief Cleans up segmentation preview and clears all seeds. * */ void ConfirmCleanUp() override; /** * @brief Applies ITK IntensityWindowing Filter to input image; * */ template void ITKWindowing(const itk::Image*, mitk::Image*, ScalarType, ScalarType); private: /** * @brief Convert 3D world coordinates to 2D indices. * * @param baseGeometry * @param mitk::Point3D * @return mitk::Point2D */ static mitk::Point2D Get2DIndicesfrom3DWorld(const mitk::BaseGeometry*, const mitk::Point3D&); std::string m_MitkTempDir; std::string m_PythonPath; std::string m_ModelType; std::string m_CheckpointPath; int m_GpuId = 0; PointSet::Pointer m_PointSetPositive; PointSet::Pointer m_PointSetNegative; DataNode::Pointer m_PointSetNodePositive; DataNode::Pointer m_PointSetNodeNegative; bool m_IsGenerateEmbeddings = true; bool m_IsReady = false; int m_PointSetCount = 0; long m_TimeOutLimit = -1; std::unique_ptr m_PythonService; const Label::PixelType MASK_VALUE = 1; }; } // namespace #endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkSetupVirtualEnvUtil.cpp b/Modules/SegmentationUI/Qmitk/QmitkSetupVirtualEnvUtil.cpp index 326bbc522f..b871e15128 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkSetupVirtualEnvUtil.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkSetupVirtualEnvUtil.cpp @@ -1,274 +1,238 @@ /*============================================================================ 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 "mitkLog.h" #include #include #include -#include #include #include -#include +#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==0.7.5"); spExec->Execute(workingDir, "python", args); PipInstall("torch==2.0.0", workingDir, callback, "ltt"); PipInstall("torchvision==0.15.0", 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; } -namespace -{ - std::mutex mutex; - std::string pyVersionCaptured; - - void CapturePyVersion(itk::Object * /*pCaller*/, const itk::EventObject &e, void *) - { - std::string testCOUT; - const auto *pEvent = dynamic_cast(&e); - if (pEvent) - { - pyVersionCaptured = pEvent->GetOutput(); - } - } - - std::vector SplitVersionString(const std::string& version) - { - std::vector splits; - std::string part; - std::istringstream tokenStream(version); - while (std::getline(tokenStream, part, '.')) - { - splits.push_back(std::stoi(part)); - } - return splits; - } - - bool IsSupported(const std::string& version, const std::string& low, const std::string& high) - { - std::vector inHandVersion = SplitVersionString(version); - std::vector targetLowVersion = SplitVersionString(low); - std::vector targetHighVersion = SplitVersionString(high); - if (inHandVersion.size() > 1 && targetLowVersion.size() > 1 && targetHighVersion.size() > 1) - { // comparing second part of the version - return (inHandVersion[1] > targetLowVersion[1] && inHandVersion[1] < targetHighVersion[1]); - } - return false; - } -} - std::pair QmitkSetupVirtualEnvUtil::GetExactPythonPath(const QString &pyEnv) { QString fullPath = pyEnv; bool pythonDoesExist = false; bool isSupportedVersion = false; #ifdef _WIN32 const std::string PYTHON_EXE = "python.exe"; // check if python exist in given folder. pythonDoesExist = QFile::exists(fullPath + QDir::separator() + QString::fromStdString(PYTHON_EXE)); if (!pythonDoesExist && // check if in Scripts already, if not go there !(fullPath.endsWith("Scripts", Qt::CaseInsensitive) || fullPath.endsWith("Scripts/", Qt::CaseInsensitive))) { fullPath += QDir::separator() + QString("Scripts"); pythonDoesExist = QFile::exists(fullPath + QDir::separator() + QString("python.exe")); } #else const std::string PYTHON_EXE = "python3"; pythonDoesExist = QFile::exists(fullPath + QDir::separator() + QString::fromStdString(PYTHON_EXE)); if (!pythonDoesExist && !(fullPath.endsWith("bin", Qt::CaseInsensitive) || fullPath.endsWith("bin/", Qt::CaseInsensitive))) { fullPath += QDir::separator() + QString("bin"); pythonDoesExist = QFile::exists(fullPath + QDir::separator() + QString("python3")); } #endif std::pair pythonPath; if (pythonDoesExist) { - ::mutex.lock(); - std::regex sanitizer(R"([^3\.(\d+)])"); - mitk::ProcessExecutor::ArgumentListType args; - auto spExec = mitk::ProcessExecutor::New(); - auto spCommand = itk::CStyleCommand::New(); - spCommand->SetCallback(&::CapturePyVersion); - spExec->AddObserver(mitk::ExternalProcessOutputEvent(), spCommand); - args.push_back("--version"); - spExec->Execute(fullPath.toStdString(), PYTHON_EXE, args); - std::string pyVersionNumber = std::regex_replace(::pyVersionCaptured, sanitizer, ""); - isSupportedVersion = ::IsSupported(pyVersionNumber, "3.8", "3.13"); - pythonPath.second = QString::fromStdString(pyVersionNumber); - ::mutex.unlock(); + std::regex sanitizer(R"(3\.(\d+))"); + QProcess pyProcess; + pyProcess.start(fullPath + QDir::separator() + QString::fromStdString(PYTHON_EXE), + QStringList() << "--version", + QProcess::ReadOnly); + if (pyProcess.waitForFinished()) + { + auto pyVersionCaptured = QString(QStringDecoder(QStringDecoder::Utf8)(pyProcess.readAllStandardOutput())).toStdString(); + std::smatch match; // Expecting "Python 3.xx.xx" or "Python 3.xx" + if (std::regex_search(pyVersionCaptured, match, sanitizer) && !match.empty()) + { + std::string pyVersionNumber = match[0]; + int pySubVersion = std::stoi(match[1]); + isSupportedVersion = (pySubVersion > 8) ? (pySubVersion < 13) : false; + pythonPath.second = QString::fromStdString(pyVersionNumber); + } + } } pythonPath.first = pythonDoesExist &&isSupportedVersion ? fullPath : ""; return pythonPath; }