diff --git a/Modules/SegmentationUI/Qmitk/QmitkSegmentAnythingGUIControls.ui b/Modules/SegmentationUI/Qmitk/QmitkSegmentAnythingGUIControls.ui index 8299ff1d6a..f04eece6e1 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkSegmentAnythingGUIControls.ui +++ b/Modules/SegmentationUI/Qmitk/QmitkSegmentAnythingGUIControls.ui @@ -1,231 +1,169 @@ 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> + <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></body></html> Qt::RichText true 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 100000 16777215 Reset Picks 0 0 100000 16777215 Activate SAM 0 0 true - 1 - - - 0 + 1 + + + 0 false ctkComboBox QComboBox
ctkComboBox.h
1
ctkCollapsibleGroupBox QWidget
ctkCollapsibleGroupBox.h
diff --git a/Modules/SegmentationUI/Qmitk/QmitkSegmentAnythingToolGUI.cpp b/Modules/SegmentationUI/Qmitk/QmitkSegmentAnythingToolGUI.cpp index 9c416893da..9f5668f26f 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkSegmentAnythingToolGUI.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkSegmentAnythingToolGUI.cpp @@ -1,341 +1,312 @@ /*============================================================================ 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 #include #include MITK_TOOL_GUI_MACRO(MITKSEGMENTATIONUI_EXPORT, QmitkSegmentAnythingToolGUI, "") namespace { mitk::IPreferences *GetPreferences() { auto *preferencesService = mitk::CoreServices::GetPreferencesService(); return preferencesService->GetSystemPreferences()->Node("org.mitk.views.segmentation"); } } 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 SegmentAnything 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; }; m_Prefences = GetPreferences(); } void QmitkSegmentAnythingToolGUI::InitializeUI(QBoxLayout *mainLayout) { m_Controls.setupUi(this); m_Controls.statusLabel->setTextFormat(Qt::RichText); 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())); QIcon arrowIcon = QmitkStyleManager::ThemeIcon(QStringLiteral(":/org_mitk_icons/icons/awesome/scalable/actions/go-next.svg")); m_Controls.activateButton->setIcon(arrowIcon); bool isInstalled = this->ValidatePrefences(); if (isInstalled) { m_PythonPath = QString::fromStdString(m_Prefences->Get("sam python path", "")); - welcomeText += " SAM is already found installed."; + QString modelType = QString::fromStdString(m_Prefences->Get("sam modeltype", "")); + welcomeText += " SAM is already found installed. Model type '" + modelType + "' selected in Preferences."; } else { - welcomeText += " SAM is not configured correctly. Please go to Prefences (Cntl+P) to configure and/or install SAM."; + welcomeText += " SAM is not configured correctly. Please go to Preferences (Cntl+P) to configure and/or install SAM."; } this->EnableAll(isInstalled); this->WriteStatusMessage(welcomeText); this->ShowProgressBar(false); m_Controls.samProgressBar->setMaximum(0); mainLayout->addLayout(m_Controls.verticalLayout); Superclass::InitializeUI(mainLayout); } bool QmitkSegmentAnythingToolGUI::ValidatePrefences() { const QString storageDir = QString::fromStdString(m_Prefences->Get("sam python path", "")); bool isInstalled = QmitkSegmentAnythingToolGUI::IsSAMInstalled(storageDir); - std::string modelType = m_Prefences->Get("sam modeltype", ""); - std::string path = m_Prefences->Get("sam parent path", ""); + std::string &modelType = m_Prefences->Get("sam modeltype", ""); + std::string &path = m_Prefences->Get("sam parent path", ""); return (isInstalled && !modelType.empty() && !path.empty()); } void QmitkSegmentAnythingToolGUI::EnableAll(bool isEnable) { m_Controls.activateButton->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; } -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 (!QmitkSegmentAnythingToolGUI::IsSAMInstalled(m_PythonPath)) { throw std::runtime_error(WARNING_SAM_NOT_FOUND); } tool->SetIsAuto(false); tool->SetPythonPath(m_PythonPath.toStdString()); - tool->SetGpuId(FetchSelectedGPUFromUI()); + tool->SetGpuId(m_Prefences->GetInt("sam gpuid", 0)); const QString modelType = QString::fromStdString(m_Prefences->Get("sam modeltype", "")); 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->InitSAMPythonProcess(); while (!tool->IsPythonReady()) { qApp->processEvents(); } tool->IsReadyOn(); m_Controls.activateButton->setEnabled(true); this->ShowProgressBar(false); } bool QmitkSegmentAnythingToolGUI::DownloadModel(const QString &modelType) { QUrl url = QmitkSegmentAnythingToolGUI::VALID_MODELS_URL_MAP[modelType]; QString modelFileName = url.fileName(); const QString storageDir = QString::fromStdString(m_Prefences->Get("sam parent path", "")); 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 = QString::fromStdString(m_Prefences->Get("sam parent path", "")); 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 *))); this->WriteStatusMessage(QString("STATUS: Model successfully downloaded. Activating SAM....")); 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); } 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; bool samExists = 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 QDir folderPath(fullPath); folderPath.cdUp(); if (folderPath.cd("Lib/site-packages/segment_anything")) { samExists = true; } bool isExists = samExists && 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(); } } diff --git a/Modules/SegmentationUI/Qmitk/QmitkSegmentAnythingToolGUI.h b/Modules/SegmentationUI/Qmitk/QmitkSegmentAnythingToolGUI.h index 0ecad9672f..a01b616766 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkSegmentAnythingToolGUI.h +++ b/Modules/SegmentationUI/Qmitk/QmitkSegmentAnythingToolGUI.h @@ -1,138 +1,125 @@ /*============================================================================ 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 QmitkSegmentAnythingToolGUI_h #define QmitkSegmentAnythingToolGUI_h #include "QmitkSegWithPreviewToolGUIBase.h" #include #include "ui_QmitkSegmentAnythingGUIControls.h" #include "QmitknnUNetGPU.h" #include "QmitkSetupVirtualEnvUtil.h" #include #include #include #include #include /** \ingroup org_mitk_gui_qt_interactivesegmentation_internal \brief GUI for mitk::SegmentAnythingTool. \sa mitk::PickingTool */ class MITKSEGMENTATIONUI_EXPORT QmitkSegmentAnythingToolGUI : public QmitkSegWithPreviewToolGUIBase { Q_OBJECT public: mitkClassMacro(QmitkSegmentAnythingToolGUI, QmitkSegWithPreviewToolGUIBase); itkFactorylessNewMacro(Self); itkCloneMacro(Self); inline static const QMap VALID_MODELS_URL_MAP = { {"vit_b", QUrl("https://dl.fbaipublicfiles.com/segment_anything/sam_vit_b_01ec64.pth")}, //{"vit_b", QUrl("http://www.google.ru/images/srpr/logo3w.png")}, {"vit_l", QUrl("https://dl.fbaipublicfiles.com/segment_anything/sam_vit_l_0b3195.pth")}, {"vit_h", QUrl("https://dl.fbaipublicfiles.com/segment_anything/sam_vit_h_4b8939.pth")}}; /** * @brief Checks if SegmentAnything is found inside the selected python virtual environment. * @return bool */ static bool IsSAMInstalled(const QString &); protected slots: /** * @brief Qt Slot */ void OnResetPicksClicked(); /** * @brief Qt Slot */ void OnActivateBtnClicked(); /** * @brief Qt Slot */ void FileDownloaded(QNetworkReply *); /** * @brief Qt Slot */ void OnParametersChanged(const QString&); protected: QmitkSegmentAnythingToolGUI(); ~QmitkSegmentAnythingToolGUI() = default; void InitializeUI(QBoxLayout *mainLayout) override; /** * @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 Creates a QMessage object and shows on screen. */ void ShowErrorMessage(const std::string &, QMessageBox::Icon = QMessageBox::Critical); - /** - * @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 Enable (or Disable) GUI elements. */ void EnableAll(bool); /** * */ bool DownloadModel(const QString &); /** * */ void ShowProgressBar(bool); void ActivateSAMDaemon(); bool ValidatePrefences(); private: mitk::IPreferences *m_Prefences; QNetworkAccessManager m_Manager; Ui_QmitkSegmentAnythingGUIControls m_Controls; QString m_PythonPath; QmitkGPULoader m_GpuLoader; bool m_FirstPreviewComputation = true; const std::string WARNING_SAM_NOT_FOUND = "SAM is not detected in the selected python environment. Please reinstall SAM."; }; #endif diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/QmitkSegmentationPreferencePage.cpp b/Plugins/org.mitk.gui.qt.segmentation/src/QmitkSegmentationPreferencePage.cpp index 9e7f6a193e..96baff9346 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/src/QmitkSegmentationPreferencePage.cpp +++ b/Plugins/org.mitk.gui.qt.segmentation/src/QmitkSegmentationPreferencePage.cpp @@ -1,419 +1,427 @@ /*============================================================================ 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 "QmitkSegmentationPreferencePage.h" #include #include #include #include #include #include #include #include #include #include #include #include namespace { mitk::IPreferences* GetPreferences() { auto* preferencesService = mitk::CoreServices::GetPreferencesService(); return preferencesService->GetSystemPreferences()->Node("org.mitk.views.segmentation"); } } QmitkSegmentationPreferencePage::QmitkSegmentationPreferencePage() : m_Ui(new Ui::QmitkSegmentationPreferencePageControls), m_Control(nullptr), m_Initializing(false) { } QmitkSegmentationPreferencePage::~QmitkSegmentationPreferencePage() { } void QmitkSegmentationPreferencePage::Init(berry::IWorkbench::Pointer) { } void QmitkSegmentationPreferencePage::CreateQtControl(QWidget* parent) { m_Initializing = true; m_Control = new QWidget(parent); m_Ui->setupUi(m_Control); #ifndef _WIN32 m_Ui->sysPythonComboBox->addItem("/usr/bin"); #endif this->AutoParsePythonPaths(); m_Ui->sysPythonComboBox->addItem("Select..."); m_Ui->sysPythonComboBox->setCurrentIndex(0); connect(m_Ui->labelSetPresetToolButton, SIGNAL(clicked()), this, SLOT(OnLabelSetPresetButtonClicked())); connect(m_Ui->suggestionsToolButton, SIGNAL(clicked()), this, SLOT(OnSuggestionsButtonClicked())); connect(m_Ui->installSAMButton, SIGNAL(clicked()), this, SLOT(OnInstallBtnClicked())); connect(m_Ui->clearSAMButton, SIGNAL(clicked()), this, SLOT(OnClearInstall())); connect(m_Ui->sysPythonComboBox, QOverload::of(&QComboBox::activated), [=](int index) { OnSystemPythonChanged(m_Ui->sysPythonComboBox->itemText(index)); }); QIcon deleteIcon = QmitkStyleManager::ThemeIcon(QStringLiteral(":/org_mitk_icons/icons/awesome/scalable/actions/edit-delete.svg")); m_Ui->clearSAMButton->setIcon(deleteIcon); const QString storageDir = m_Installer.GetVirtualEnvPath(); m_IsInstalled = QmitkSegmentAnythingToolGUI::IsSAMInstalled(storageDir); QString welcomeText; 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->WriteStatusMessage(welcomeText); m_Ui->samModelTypeComboBox->addItems(QmitkSegmentAnythingToolGUI::VALID_MODELS_URL_MAP.keys()); - + this->SetGPUInfo(); this->Update(); m_Initializing = false; } QWidget* QmitkSegmentationPreferencePage::GetQtControl() const { return m_Control; } bool QmitkSegmentationPreferencePage::PerformOk() { auto* prefs = GetPreferences(); prefs->PutBool("compact view", m_Ui->compactViewCheckBox->isChecked()); prefs->PutBool("draw outline", m_Ui->outlineRadioButton->isChecked()); prefs->PutBool("selection mode", m_Ui->selectionModeCheckBox->isChecked()); prefs->Put("label set preset", m_Ui->labelSetPresetLineEdit->text().toStdString()); prefs->PutBool("default label naming", m_Ui->defaultNameRadioButton->isChecked()); prefs->Put("label suggestions", m_Ui->suggestionsLineEdit->text().toStdString()); prefs->PutBool("replace standard suggestions", m_Ui->replaceStandardSuggestionsCheckBox->isChecked()); prefs->PutBool("suggest once", m_Ui->suggestOnceCheckBox->isChecked()); prefs->Put("sam parent path", m_Installer.STORAGE_DIR.toStdString()); prefs->Put("sam python path", m_PythonPath.toStdString()); prefs->Put("sam modeltype", m_Ui->samModelTypeComboBox->currentText().toStdString()); + prefs->PutInt("sam gpuid", FetchSelectedGPUFromUI()); + return true; } void QmitkSegmentationPreferencePage::PerformCancel() { } void QmitkSegmentationPreferencePage::Update() { auto* prefs = GetPreferences(); m_Ui->compactViewCheckBox->setChecked(prefs->GetBool("compact view", false)); if (prefs->GetBool("draw outline", true)) { m_Ui->outlineRadioButton->setChecked(true); } else { m_Ui->overlayRadioButton->setChecked(true); } m_Ui->selectionModeCheckBox->setChecked(prefs->GetBool("selection mode", false)); auto labelSetPreset = mitk::BaseApplication::instance().config().getString(mitk::BaseApplication::ARG_SEGMENTATION_LABELSET_PRESET.toStdString(), ""); bool isOverriddenByCmdLineArg = !labelSetPreset.empty(); if (!isOverriddenByCmdLineArg) labelSetPreset = prefs->Get("label set preset", ""); m_Ui->labelSetPresetLineEdit->setDisabled(isOverriddenByCmdLineArg); m_Ui->labelSetPresetToolButton->setDisabled(isOverriddenByCmdLineArg); m_Ui->labelSetPresetCmdLineArgLabel->setVisible(isOverriddenByCmdLineArg); m_Ui->labelSetPresetLineEdit->setText(QString::fromStdString(labelSetPreset)); if (prefs->GetBool("default label naming", true)) { m_Ui->defaultNameRadioButton->setChecked(true); } else { m_Ui->askForNameRadioButton->setChecked(true); } auto labelSuggestions = mitk::BaseApplication::instance().config().getString(mitk::BaseApplication::ARG_SEGMENTATION_LABEL_SUGGESTIONS.toStdString(), ""); isOverriddenByCmdLineArg = !labelSuggestions.empty(); if (!isOverriddenByCmdLineArg) labelSuggestions = prefs->Get("label suggestions", ""); m_Ui->defaultNameRadioButton->setDisabled(isOverriddenByCmdLineArg); m_Ui->askForNameRadioButton->setDisabled(isOverriddenByCmdLineArg); m_Ui->suggestionsLineEdit->setDisabled(isOverriddenByCmdLineArg); m_Ui->suggestionsToolButton->setDisabled(isOverriddenByCmdLineArg); m_Ui->suggestionsCmdLineArgLabel->setVisible(isOverriddenByCmdLineArg); m_Ui->suggestionsLineEdit->setText(QString::fromStdString(labelSuggestions)); m_Ui->replaceStandardSuggestionsCheckBox->setChecked(prefs->GetBool("replace standard suggestions", true)); m_Ui->suggestOnceCheckBox->setChecked(prefs->GetBool("suggest once", true)); + + m_Ui->samModelTypeComboBox->setCurrentText(QString::fromStdString(prefs->Get("sam modeltype", "vit_b"))); } void QmitkSegmentationPreferencePage::OnLabelSetPresetButtonClicked() { const auto filename = QFileDialog::getOpenFileName(m_Control, QStringLiteral("Load Label Set Preset"), QString(), QStringLiteral("Label set preset (*.lsetp)")); if (!filename.isEmpty()) m_Ui->labelSetPresetLineEdit->setText(filename); } void QmitkSegmentationPreferencePage::OnSuggestionsButtonClicked() { const auto filename = QFileDialog::getOpenFileName(m_Control, QStringLiteral("Load Label Suggestions"), QString(), QStringLiteral("Label suggestions (*.json)")); if (!filename.isEmpty()) m_Ui->suggestionsLineEdit->setText(filename); } QString QmitkSegmentationPreferencePage::OnSystemPythonChanged(const QString &pyEnv) { QString pyPath; if (pyEnv == QString("Select...")) { QString path = QFileDialog::getExistingDirectory(m_Ui->sysPythonComboBox->parentWidget(), "Python Path", "dir"); if (!path.isEmpty()) { this->OnSystemPythonChanged(path); // recall same function for new path validation bool oldState = m_Ui->sysPythonComboBox->blockSignals(true); // block signal firing while inserting item m_Ui->sysPythonComboBox->insertItem(0, path); m_Ui->sysPythonComboBox->setCurrentIndex(0); m_Ui->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; } QString QmitkSegmentationPreferencePage::GetPythonPathFromUI(const QString &pyUI) const { QString fullPath = pyUI; if (-1 != fullPath.indexOf(")")) { fullPath = fullPath.mid(fullPath.indexOf(")") + 2); } return fullPath.simplified(); } QString QmitkSegmentationPreferencePage::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 QmitkSegmentationPreferencePage::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() + "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_Ui->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_Ui->sysPythonComboBox->addItem("(" + envName + "): " + subIt.filePath()); } } } } -/* bool QmitkSegmentationPreferencePage::IsSAMInstalled(const QString &pythonPath) +void QmitkSegmentationPreferencePage::SetGPUInfo() { - 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))) + std::vector specs = m_GpuLoader.GetAllGPUSpecs(); + for (const QmitkGPUSpec &gpuSpec : specs) { - fullPath += QDir::separator() + QString("Scripts"); - isPythonExists = - (!isPythonExists) ? QFile::exists(fullPath + QDir::separator() + QString("python.exe")) : isPythonExists; + m_Ui->gpuComboBox->addItem(QString::number(gpuSpec.id) + ": " + gpuSpec.name + " (" + gpuSpec.memory + ")"); } -#else - isPythonExists = QFile::exists(fullPath + QDir::separator() + QString("python3")); - if (!(fullPath.endsWith("bin", Qt::CaseInsensitive) || fullPath.endsWith("bin/", Qt::CaseInsensitive))) + if (specs.empty()) { - fullPath += QDir::separator() + QString("bin"); - isPythonExists = - (!isPythonExists) ? QFile::exists(fullPath + QDir::separator() + QString("python3")) : isPythonExists; + m_Ui->gpuComboBox->setEditable(true); + m_Ui->gpuComboBox->addItem(QString::number(0)); + m_Ui->gpuComboBox->setValidator(new QIntValidator(0, 999, this)); } -#endif*/ -// bool isExists = /*QFile::exists(fullPath + QDir::separator() + QString("MITK_SAM"))*/ true && isPythonExists; -// return isExists; -//} +} + +unsigned int QmitkSegmentationPreferencePage::FetchSelectedGPUFromUI() const +{ + QString gpuInfo = m_Ui->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 QmitkSegmentationPreferencePage::OnInstallBtnClicked() { MITK_INFO << "Install clicked"; QString systemPython = OnSystemPythonChanged(m_Ui->sysPythonComboBox->currentText()); if (!systemPython.isEmpty()) { this->WriteStatusMessage("STATUS: Installing SAM..."); m_Installer.SetSystemPythonPath(systemPython); m_IsInstalled = m_Installer.SetupVirtualEnv(m_Installer.VENV_NAME); if (m_IsInstalled) { m_PythonPath = this->GetExactPythonPath(m_Installer.GetVirtualEnvPath()); this->WriteStatusMessage("STATUS: Successfully installed SAM."); auto *prefs = GetPreferences(); prefs->Put("sam python path", m_PythonPath.toStdString()); } else { this->WriteErrorMessage("ERROR: Couldn't install SAM."); } } } void QmitkSegmentationPreferencePage::OnClearInstall() { MITK_INFO << "OnClearInstall clicked"; QDir folderPath(m_Installer.GetVirtualEnvPath()); m_IsInstalled = folderPath.removeRecursively(); if (!m_IsInstalled) { 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."; } } void QmitkSegmentationPreferencePage::WriteStatusMessage(const QString &message) { m_Ui->samInstallStatusLabel->setText(message); m_Ui->samInstallStatusLabel->setStyleSheet("font-weight: bold; color: white"); qApp->processEvents(); } void QmitkSegmentationPreferencePage::WriteErrorMessage(const QString &message) { m_Ui->samInstallStatusLabel->setText(message); m_Ui->samInstallStatusLabel->setStyleSheet("font-weight: bold; color: red"); qApp->processEvents(); } QString QmitkSAMInstaller::GetVirtualEnvPath() { return STORAGE_DIR + VENV_NAME; } bool QmitkSAMInstaller::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; } diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/QmitkSegmentationPreferencePage.h b/Plugins/org.mitk.gui.qt.segmentation/src/QmitkSegmentationPreferencePage.h index a4af736897..2e7a833f1c 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/src/QmitkSegmentationPreferencePage.h +++ b/Plugins/org.mitk.gui.qt.segmentation/src/QmitkSegmentationPreferencePage.h @@ -1,114 +1,130 @@ /*============================================================================ 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 QmitkSegmentationPreferencePage_h #define QmitkSegmentationPreferencePage_h #include "org_mitk_gui_qt_segmentation_Export.h" #include #include #include #include #include +#include class QWidget; namespace Ui { class QmitkSegmentationPreferencePageControls; } class QmitkSAMInstaller : public QmitkSetupVirtualEnvUtil { public: const QString VENV_NAME = ".sam"; const QString SAM_VERSION = "1.0"; // currently, unused const std::vector PACKAGES = {QString("numpy"), QString("opencv-python"), QString("git+https://github.com/facebookresearch/segment-anything.git"), QString("SimpleITK")}; const QString STORAGE_DIR; inline QmitkSAMInstaller( 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; }; class MITK_QT_SEGMENTATION QmitkSegmentationPreferencePage : public QObject, public berry::IQtPreferencePage { Q_OBJECT Q_INTERFACES(berry::IPreferencePage) public: QmitkSegmentationPreferencePage(); ~QmitkSegmentationPreferencePage() override; void Init(berry::IWorkbench::Pointer workbench) override; void CreateQtControl(QWidget* widget) override; QWidget* GetQtControl() const override; bool PerformOk() override; void PerformCancel() override; void Update() override; protected Q_SLOTS: void OnLabelSetPresetButtonClicked(); void OnSuggestionsButtonClicked(); void OnInstallBtnClicked(); void OnClearInstall(); QString OnSystemPythonChanged(const QString&); protected: /** * @brief Searches and parses paths of python virtual enviroments * from predefined lookout locations */ void AutoParsePythonPaths(); /** * @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 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; + void WriteStatusMessage(const QString&); void WriteErrorMessage(const QString&); QNetworkAccessManager m_Manager; + QmitkGPULoader m_GpuLoader; Ui::QmitkSegmentationPreferencePageControls* m_Ui; QmitkSAMInstaller m_Installer; QWidget* m_Control; QString m_PythonPath; bool m_IsInstalled = false; bool m_Initializing; }; #endif diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/QmitkSegmentationPreferencePageControls.ui b/Plugins/org.mitk.gui.qt.segmentation/src/QmitkSegmentationPreferencePageControls.ui index 4e94cc1b70..f11dc36c2d 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/src/QmitkSegmentationPreferencePageControls.ui +++ b/Plugins/org.mitk.gui.qt.segmentation/src/QmitkSegmentationPreferencePageControls.ui @@ -1,302 +1,317 @@ QmitkSegmentationPreferencePageControls 0 0 656 779 Form Compact view Hide tool button texts and increase icon size 2D display Draw as outline true displayButtonGroup Draw as transparent overlay displayButtonGroup Data node selection mode If checked the segmentation plugin ensures that only the selected segmentation and the reference image are visible at one time. Show only selected nodes Default label set preset true ... <html><head/><body><p><span style=" color:#ff0000;">The default label set preset is currently overriden by the </span><span style=" font-family:'Courier New'; color:#ff0000;">Segmentation.labelSetPreset</span><span style=" color:#ff0000;"> command-line argument.</span></p></body></html> Qt::RichText true Label creation Assign default name and color true labelCreationButtonGroup Ask for name and color labelCreationButtonGroup Label suggestions true ... <html><head/><body><p><span style=" color:#ff0000;">Suggestions are currently enforced by the </span><span style=" font-family:'Courier New'; color:#ff0000;">Segmentation.labelSuggestions</span><span style=" color:#ff0000;"> command-line argument.</span></p></body></html> Qt::RichText true Replace standard organ suggestions true Suggest once per segmentation true - - - - - SAM Tool - - - - - - - - - - 0 - 0 - - - - Install SAM - - - - - - - - 0 - 0 - - - - Clear Install - - - - - - - System Python - - - - - - - - - - Model Type: - - - - - - - - - - Qt::RichText - - - true - - - - - + + + + SAM Tool + + + + + + + + + + 0 + 0 + + + + Install SAM + + + + + + + + 0 + 0 + + + + Clear Install + + + + + + + System Python + + + + + + + + + + Model Type: + + + + + + + + + + + + + + 0 + 0 + + + + GPU Id: + + + + + + + Qt::RichText + + + true + + + + + Qt::Vertical 20 40