diff --git a/Modules/SegmentationUI/Qmitk/QmitkSegmentAnythingGUIControls.ui b/Modules/SegmentationUI/Qmitk/QmitkSegmentAnythingGUIControls.ui
index 7435fd9d8a..6497cf24fb 100644
--- a/Modules/SegmentationUI/Qmitk/QmitkSegmentAnythingGUIControls.ui
+++ b/Modules/SegmentationUI/Qmitk/QmitkSegmentAnythingGUIControls.ui
@@ -1,388 +1,347 @@
QmitkSegmentAnythingGUIControls
0
0
699
490
0
0
100
0
100000
100000
QmitkSegmentAnythingToolWidget
0
0
0
0
-
-
0
0
<html><head/><body><p>Welcome to Segment Anything Model (SAM) tool in MITK. [Experimental]</p><p>Please note that this is only an interface to SAM. MITK does not ship with SAM. Make sure to have a working internet connection to install SAM via MITK.
</p><p>Refer to <a href="https://segment-anything.com/"><span style=" text-decoration: underline; color:#0000ff;">https://segment-anything.com</span></a> to learn everything about the SAM.</p><p><br/></p></body></html>
Qt::RichText
true
-
0
0
100000
16777215
Install SAM
-
0
0
Install Options
true
5
true
-
0
0
0
0
6
-
-
0
0
System Python:
-
0
0
100000
16777215
Clear Install
-
-
-
-
- 0
- 0
-
-
-
- Interactive
-
-
-
- -
-
-
- true
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Auto
-
-
-
- -
-
-
- false
-
-
-
-
- -
0
0
Press SHIFT+Left-click for positive seeds.
Press SHIFT+Right-click for negative seeds.
Press DEL to remove last seed.
-
0
0
Preferences
true
5
true
-
0
0
0
0
6
-
0
0
GPU Id:
-
-
0
0
Model Type:
-
-
0
0
100000
16777215
Reset Picks
-
0
0
100000
16777215
Activate SAM
-
0
0
true
- -
-
-
- 1
-
-
- 0
-
-
- false
-
-
-
+ -
+
+
+ 1
+
+
+ 0
+
+
+ false
+
+
+
ctkComboBox
QComboBox
1
ctkCollapsibleGroupBox
QWidget
diff --git a/Modules/SegmentationUI/Qmitk/QmitkSegmentAnythingToolGUI.cpp b/Modules/SegmentationUI/Qmitk/QmitkSegmentAnythingToolGUI.cpp
index 987291ec2e..bae8d65a2c 100644
--- a/Modules/SegmentationUI/Qmitk/QmitkSegmentAnythingToolGUI.cpp
+++ b/Modules/SegmentationUI/Qmitk/QmitkSegmentAnythingToolGUI.cpp
@@ -1,531 +1,531 @@
/*============================================================================
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 "QmitkSegmentAnythingToolGUI.h"
#include "mitkSegmentAnythingTool.h"
#include "mitkProcessExecutor.h"
#include
#include
#include
#include
#include
#include
#include
MITK_TOOL_GUI_MACRO(MITKSEGMENTATIONUI_EXPORT, QmitkSegmentAnythingToolGUI, "")
QmitkSegmentAnythingToolGUI::QmitkSegmentAnythingToolGUI() : QmitkSegWithPreviewToolGUIBase(true)
{
// 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)
{
bool result = false;
auto tool = this->GetConnectedToolAs();
if (nullptr != tool)
{
result = enabled && tool->HasPicks();
}
return result;
};
}
void QmitkSegmentAnythingToolGUI::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.statusLabel->setTextFormat(Qt::RichText);
m_Controls.modelTypeComboBox->addItems(VALID_MODELS_URL_MAP.keys());
QString welcomeText;
this->SetGPUInfo();
if (m_GpuLoader.GetGPUCount() != 0)
{
welcomeText = "STATUS: Welcome to Segment Anything tool. You're in luck: " +
QString::number(m_GpuLoader.GetGPUCount()) + " GPU(s) were detected.";
}
else
{
welcomeText = "STATUS: Welcome to Segment Anything tool. Sorry, " +
QString::number(m_GpuLoader.GetGPUCount()) + " GPUs were detected.";
}
connect(m_Controls.activateButton, SIGNAL(clicked()), this, SLOT(OnActivateBtnClicked()));
connect(m_Controls.resetButton, SIGNAL(clicked()), this, SLOT(OnResetPicksClicked()));
connect(m_Controls.installButton, SIGNAL(clicked()), this, SLOT(OnInstallBtnClicked()));
connect(m_Controls.clearButton, SIGNAL(clicked()), this, SLOT(OnClearInstall()));
connect(m_Controls.sysPythonComboBox,
QOverload::of(&QComboBox::activated),
[=](int index) { OnSystemPythonChanged(m_Controls.sysPythonComboBox->itemText(index)); });
connect(m_Controls.gpuComboBox, SIGNAL(currentTextChanged(const QString &)), this,
SLOT(OnParametersChanged(const QString &)));
connect(m_Controls.modelTypeComboBox, SIGNAL(currentTextChanged(const QString &)), this,
SLOT(OnParametersChanged(const QString &)));
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.activateButton->setIcon(arrowIcon);
const QString storageDir = m_Installer.GetVirtualEnvPath();
m_IsInstalled = this->IsSAMInstalled(storageDir);
if (m_IsInstalled)
{
m_PythonPath = GetExactPythonPath(storageDir);
m_Installer.SetVirtualEnvPath(m_PythonPath);
welcomeText += " SAM is already found installed.";
}
else
{
welcomeText += " SAM is not installed. Please click on \"Install SAM\" above.";
}
this->EnableAll(m_IsInstalled);
this->WriteStatusMessage(welcomeText);
this->ShowProgressBar(false);
m_Controls.samProgressBar->setMaximum(0);
mainLayout->addLayout(m_Controls.verticalLayout);
Superclass::InitializeUI(mainLayout);
}
QString QmitkSegmentAnythingToolGUI::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 QmitkSegmentAnythingToolGUI::EnableAll(bool isEnable)
{
m_Controls.activateButton->setEnabled(isEnable);
m_Controls.installButton->setEnabled((!isEnable));
}
void QmitkSegmentAnythingToolGUI::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));
}
}
void QmitkSegmentAnythingToolGUI::WriteStatusMessage(const QString &message)
{
m_Controls.statusLabel->setText(message);
m_Controls.statusLabel->setStyleSheet("font-weight: bold; color: white");
qApp->processEvents();
}
void QmitkSegmentAnythingToolGUI::WriteErrorMessage(const QString &message)
{
m_Controls.statusLabel->setText(message);
m_Controls.statusLabel->setStyleSheet("font-weight: bold; color: red");
qApp->processEvents();
}
void QmitkSegmentAnythingToolGUI::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 QmitkSegmentAnythingToolGUI::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());
}
}
}
}
unsigned int QmitkSegmentAnythingToolGUI::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 QmitkSegmentAnythingToolGUI::OnActivateBtnClicked()
{
auto tool = this->GetConnectedToolAs();
if (nullptr == tool)
{
return;
}
try
{
m_Controls.activateButton->setEnabled(false);
qApp->processEvents();
if (!this->IsSAMInstalled(m_PythonPath))
{
throw std::runtime_error(WARNING_SAM_NOT_FOUND);
}
- tool->SetIsAuto(m_Controls.autoRButton->isChecked());
+ tool->SetIsAuto(false);
tool->SetPythonPath(m_PythonPath.toStdString());
tool->SetGpuId(FetchSelectedGPUFromUI());
QString modelType = m_Controls.modelTypeComboBox->currentText();
tool->SetModelType(modelType.toStdString());
this->WriteStatusMessage(
QString("STATUS: Checking if model is already downloaded... This might take a while."));
if (this->DownloadModel(modelType))
{
this->ActivateSAMDaemon();
this->WriteStatusMessage(QString("STATUS: Model found. SAM Activated."));
}
else
{
tool->IsReadyOff();
this->WriteStatusMessage(QString("STATUS: Model not found. Starting download..."));
}
}
catch (const std::exception &e)
{
std::stringstream errorMsg;
errorMsg << "STATUS: Error while processing parameters for SAM segmentation. Reason: " << e.what();
this->ShowErrorMessage(errorMsg.str());
this->WriteErrorMessage(QString::fromStdString(errorMsg.str()));
m_Controls.activateButton->setEnabled(true);
return;
}
catch (...)
{
std::string errorMsg = "Unkown error occured while generation SAM segmentation.";
this->ShowErrorMessage(errorMsg);
m_Controls.activateButton->setEnabled(true);
return;
}
}
void QmitkSegmentAnythingToolGUI::ActivateSAMDaemon()
{
auto tool = this->GetConnectedToolAs();
if (nullptr == tool)
{
return;
}
this->ShowProgressBar(true);
tool->StartAsyncProcess();
while (!tool->IsPythonReady)
{
qApp->processEvents();
}
tool->IsReadyOn();
m_Controls.activateButton->setEnabled(true);
this->ShowProgressBar(false);
}
QString QmitkSegmentAnythingToolGUI::GetPythonPathFromUI(const QString &pyUI) const
{
QString fullPath = pyUI;
if (-1 != fullPath.indexOf(")"))
{
fullPath = fullPath.mid(fullPath.indexOf(")") + 2);
}
return fullPath.simplified();
}
bool QmitkSegmentAnythingToolGUI::DownloadModel(const QString &modelType)
{
QUrl url = VALID_MODELS_URL_MAP[modelType];
QString modelFileName = url.fileName();
const QString& storageDir = m_Installer.STORAGE_DIR;
QString checkPointPath = storageDir + QDir::separator() + modelFileName;
if (QFile::exists(checkPointPath))
{
auto tool = this->GetConnectedToolAs();
if (nullptr != tool)
{
tool->SetCheckpointPath(checkPointPath.toStdString());
}
return true;
}
connect(&m_Manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(FileDownloaded(QNetworkReply*)));
QNetworkRequest request(url);
m_Manager.get(request);
this->ShowProgressBar(true);
return false;
}
void QmitkSegmentAnythingToolGUI::FileDownloaded(QNetworkReply *reply)
{
const QString &storageDir = m_Installer.STORAGE_DIR;
const QString &modelFileName = reply->url().fileName();
QFile file(storageDir + QDir::separator() + modelFileName);
if (file.open(QIODevice::WriteOnly))
{
file.write(reply->readAll());
file.close();
disconnect(&m_Manager, SIGNAL(finished(QNetworkReply *)), this, SLOT(FileDownloaded(QNetworkReply *)));
auto tool = this->GetConnectedToolAs();
if (nullptr != tool)
{
tool->SetCheckpointPath(file.fileName().toStdString());
this->ActivateSAMDaemon();
this->WriteStatusMessage(QString("STATUS: Model successfully downloaded. SAM Activated."));
}
}
else
{
this->WriteErrorMessage("STATUS: Model couldn't be downloaded. SAM not activated.");
}
this->EnableAll(true);
this->ShowProgressBar(false);
}
QString QmitkSegmentAnythingToolGUI::OnSystemPythonChanged(const QString &pyEnv)
{
QString pyPath;
if (pyEnv == QString("Select..."))
{
m_Controls.activateButton->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 QmitkSegmentAnythingToolGUI::ShowProgressBar(bool enabled)
{
m_Controls.samProgressBar->setEnabled(enabled);
m_Controls.samProgressBar->setVisible(enabled);
}
bool QmitkSegmentAnythingToolGUI::IsSAMInstalled(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("MITK_SAM"))*/ true && isPythonExists;
return isExists;
}
void QmitkSegmentAnythingToolGUI::OnParametersChanged(const QString &)
{
if (m_Controls.activateButton->isEnabled())
{
this->WriteStatusMessage("STATUS: Please Reactivate SAM.");
}
}
void QmitkSegmentAnythingToolGUI::OnResetPicksClicked()
{
auto tool = this->GetConnectedToolAs();
if (nullptr != tool)
{
tool->ClearPicks();
tool->StopAsyncProcess();
}
}
void QmitkSegmentAnythingToolGUI::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 SAM...");
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 SAM.");
}
else
{
this->WriteErrorMessage("ERROR: Couldn't install SAM.");
}
}
this->EnableAll(isInstalled);
}
void QmitkSegmentAnythingToolGUI::OnClearInstall()
{
QDir folderPath(m_Installer.GetVirtualEnvPath());
if (folderPath.removeRecursively())
{
m_Controls.installButton->setEnabled(true);
m_IsInstalled = 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.";
}
this->EnableAll(m_IsInstalled);
}
bool QmitkSegmentAnythingToolInstaller::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 QmitkSegmentAnythingToolInstaller::GetVirtualEnvPath()
{
return STORAGE_DIR + VENV_NAME;
}