diff --git a/Modules/Segmentation/Interactions/mitkMonaiLabelTool.cpp b/Modules/Segmentation/Interactions/mitkMonaiLabelTool.cpp index b1b1c8f614..87700b8c5d 100644 --- a/Modules/Segmentation/Interactions/mitkMonaiLabelTool.cpp +++ b/Modules/Segmentation/Interactions/mitkMonaiLabelTool.cpp @@ -1,660 +1,662 @@ /*============================================================================ 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 "mitkMonaiLabelTool.h" #ifndef CPPHTTPLIB_OPENSSL_SUPPORT #define CPPHTTPLIB_OPENSSL_SUPPORT #endif #include #include #include #include #include #include #include #include #include "mitkImageAccessByItk.h" mitk::MonaiLabelTool::MonaiLabelTool() : SegWithPreviewTool(true, "PressMoveReleaseAndPointSetting") { this->ResetsToEmptyPreviewOn(); this->IsTimePointChangeAwareOff(); } mitk::MonaiLabelTool::~MonaiLabelTool() { fs::remove_all(this->GetTempDir()); } void mitk::MonaiLabelTool::ConnectActionsAndFunctions() { CONNECT_FUNCTION("ShiftSecondaryButtonPressed", OnAddNegativePoint); CONNECT_FUNCTION("ShiftPrimaryButtonPressed", OnAddPositivePoint); CONNECT_FUNCTION("DeletePoint", OnDelete); } void mitk::MonaiLabelTool::Activated() { Superclass::Activated(); m_PointSetPositive = mitk::PointSet::New(); m_PointSetNodePositive = mitk::DataNode::New(); m_PointSetNodePositive->SetData(m_PointSetPositive); m_PointSetNodePositive->SetName(std::string(this->GetName()) + "_PointSetPositive"); m_PointSetNodePositive->SetBoolProperty("helper object", true); m_PointSetNodePositive->SetColor(0.0, 1.0, 0.0); m_PointSetNodePositive->SetVisibility(true); m_PointSetNodePositive->SetProperty("Pointset.2D.shape", mitk::PointSetShapeProperty::New(mitk::PointSetShapeProperty::CIRCLE)); m_PointSetNodePositive->SetProperty("Pointset.2D.fill shape", mitk::BoolProperty::New(true)); this->GetDataStorage()->Add(m_PointSetNodePositive, this->GetToolManager()->GetWorkingData(0)); m_PointSetNegative = mitk::PointSet::New(); m_PointSetNodeNegative = mitk::DataNode::New(); m_PointSetNodeNegative->SetData(m_PointSetNegative); m_PointSetNodeNegative->SetName(std::string(this->GetName()) + "_PointSetNegative"); m_PointSetNodeNegative->SetBoolProperty("helper object", true); m_PointSetNodeNegative->SetColor(1.0, 0.0, 0.0); m_PointSetNodeNegative->SetVisibility(true); m_PointSetNodeNegative->SetProperty("Pointset.2D.shape", mitk::PointSetShapeProperty::New(mitk::PointSetShapeProperty::CIRCLE)); m_PointSetNodeNegative->SetProperty("Pointset.2D.fill shape", mitk::BoolProperty::New(true)); this->GetDataStorage()->Add(m_PointSetNodeNegative, this->GetToolManager()->GetWorkingData(0)); } void mitk::MonaiLabelTool::Deactivated() { this->ClearSeeds(); this->GetDataStorage()->Remove(m_PointSetNodePositive); this->GetDataStorage()->Remove(m_PointSetNodeNegative); m_PointSetNodePositive = nullptr; m_PointSetNodeNegative = nullptr; m_PointSetPositive = nullptr; m_PointSetNegative = nullptr; Superclass::Deactivated(); } void mitk::MonaiLabelTool::UpdatePrepare() { Superclass::UpdatePrepare(); auto preview = this->GetPreviewSegmentation(); preview->RemoveLabels(preview->GetAllLabelValues()); } void mitk::MonaiLabelTool::OnAddPositivePoint(StateMachineAction *, InteractionEvent *interactionEvent) { - if (m_RequestParameters->model.IsInteractive()) + if (nullptr == m_RequestParameters || !m_RequestParameters->model.IsInteractive()) { - if (m_RequestParameters->model.Is2D() && ((nullptr == this->GetWorkingPlaneGeometry()) || - !mitk::Equal(*(interactionEvent->GetSender()->GetCurrentWorldPlaneGeometry()), - *(this->GetWorkingPlaneGeometry())))) - { - this->ClearSeeds(); - this->SetWorkingPlaneGeometry(interactionEvent->GetSender()->GetCurrentWorldPlaneGeometry()->Clone()); - this->ResetPreviewContent(); - } - if (!this->IsUpdating() && m_PointSetPositive.IsNotNull()) + return; + } + + if (m_RequestParameters->model.Is2D() && ((nullptr == this->GetWorkingPlaneGeometry()) || + !mitk::Equal(*(interactionEvent->GetSender()->GetCurrentWorldPlaneGeometry()), + *(this->GetWorkingPlaneGeometry())))) + { + this->ClearSeeds(); + this->SetWorkingPlaneGeometry(interactionEvent->GetSender()->GetCurrentWorldPlaneGeometry()->Clone()); + this->ResetPreviewContent(); + } + if (!this->IsUpdating() && m_PointSetPositive.IsNotNull()) + { + const auto positionEvent = dynamic_cast(interactionEvent); + if (positionEvent != nullptr) { - const auto positionEvent = dynamic_cast(interactionEvent); - if (positionEvent != nullptr) - { - m_PointSetPositive->InsertPoint(m_PointSetCount, positionEvent->GetPositionInWorld()); - ++m_PointSetCount; - this->UpdatePreview(); - } + m_PointSetPositive->InsertPoint(m_PointSetCount, positionEvent->GetPositionInWorld()); + ++m_PointSetCount; + this->UpdatePreview(); } } } void mitk::MonaiLabelTool::OnAddNegativePoint(StateMachineAction *, InteractionEvent *interactionEvent) { - if ("deepgrow" != m_RequestParameters->model.type) + if (nullptr == m_RequestParameters || "deepgrow" != m_RequestParameters->model.type) { return; } if (m_RequestParameters->model.Is2D() && (m_PointSetPositive->GetSize() == 0 || nullptr == this->GetWorkingPlaneGeometry() || !mitk::Equal(*(interactionEvent->GetSender()->GetCurrentWorldPlaneGeometry()), *(this->GetWorkingPlaneGeometry())))) { return; } if (!this->IsUpdating() && m_PointSetNegative.IsNotNull()) { const auto positionEvent = dynamic_cast(interactionEvent); if (positionEvent != nullptr) { m_PointSetNegative->InsertPoint(m_PointSetCount, positionEvent->GetPositionInWorld()); m_PointSetCount++; this->UpdatePreview(); } } } void mitk::MonaiLabelTool::OnDelete(StateMachineAction *, InteractionEvent *) { if (!this->IsUpdating() && m_PointSetPositive.IsNotNull()) { PointSet::Pointer removeSet = m_PointSetPositive; itk::IdentifierType maxId = 0; if (m_PointSetPositive->GetSize() > 0) { maxId = m_PointSetPositive->GetMaxId().Index(); } if (m_PointSetNegative->GetSize() > 0 && (maxId < m_PointSetNegative->GetMaxId().Index())) { removeSet = m_PointSetNegative; } removeSet->RemovePointAtEnd(0); --m_PointSetCount; this->UpdatePreview(); } } void mitk::MonaiLabelTool::ClearPicks() { this->ClearSeeds(); this->UpdatePreview(); } bool mitk::MonaiLabelTool::HasPicks() const { return this->m_PointSetPositive.IsNotNull() && this->m_PointSetPositive->GetSize() > 0; } void mitk::MonaiLabelTool::ClearSeeds() { if (this->m_PointSetPositive.IsNotNull()) { m_PointSetCount -= m_PointSetPositive->GetSize(); this->m_PointSetPositive = mitk::PointSet::New(); // renew pointset this->m_PointSetNodePositive->SetData(this->m_PointSetPositive); } if (this->m_PointSetNegative.IsNotNull()) { m_PointSetCount -= m_PointSetNegative->GetSize(); this->m_PointSetNegative = mitk::PointSet::New(); // renew pointset this->m_PointSetNodeNegative->SetData(this->m_PointSetNegative); } } bool mitk::MonaiLabelTool::IsMonaiServerOn(const std::string &hostName, const int &port) const { httplib::SSLClient cli(hostName, port); cli.enable_server_certificate_verification(false); while (cli.is_socket_open()); return cli.Get("/info/"); } namespace mitk { // Converts the json GET response from the MonaiLabel server to MonaiAppMetadata object. void from_json(const nlohmann::json &jsonObj, mitk::MonaiAppMetadata &appData) { jsonObj["name"].get_to(appData.name); jsonObj["description"].get_to(appData.description); jsonObj["labels"].get_to(appData.labels); auto modelJsonMap = jsonObj["models"].get>(); for (const auto &[_name, _jsonObj] : modelJsonMap) { if (_jsonObj.is_discarded() || !_jsonObj.is_object()) { MITK_ERROR << "Could not parse JSON object."; } mitk::MonaiModelInfo modelInfo; modelInfo.name = _name; try { auto labels = _jsonObj["labels"].get>(); modelInfo.labels = labels; } catch (const std::exception &) { auto labels = _jsonObj["labels"].get>(); for (const auto &label : labels) { modelInfo.labels[label] = -1; // Hardcode -1 as label id } } _jsonObj["type"].get_to(modelInfo.type); _jsonObj["dimension"].get_to(modelInfo.dimension); _jsonObj["description"].get_to(modelInfo.description); appData.models.push_back(modelInfo); } } } namespace { /** * @brief Returns boundary string from Httplib response. */ std::string GetBoundaryString(const httplib::Result &response) { httplib::Headers headers = response->headers; std::string contentType = headers.find("content-type")->second; std::string delimiter = "boundary="; std::string boundaryString = contentType.substr(contentType.find(delimiter) + delimiter.length(), std::string::npos); boundaryString.insert(0, "--"); return boundaryString; } /** * @brief Returns image data string from monai label overall response. */ std::string GetResponseImageString(const std::string &imagePart) { std::string contentTypeStream = "Content-Type: application/octet-stream"; size_t ctPos = imagePart.find(contentTypeStream) + contentTypeStream.length(); std::string imageDataPart = imagePart.substr(ctPos); std::string whitespaces = " \n\r"; imageDataPart.erase(0, imageDataPart.find_first_not_of(whitespaces)); // clean up return imageDataPart; } /** * @brief Helper function to get the Parts of the POST response. */ std::vector GetPartsBetweenBoundary(const std::string &body, const std::string &boundary) { std::vector retVal; std::string master = body; size_t boundaryPos = master.find(boundary); size_t beginPos = 0; while (boundaryPos != std::string::npos) { std::string part = master.substr(beginPos, boundaryPos); if (!part.empty()) { retVal.push_back(part); } master.erase(beginPos, boundaryPos + boundary.length()); boundaryPos = master.find(boundary); } return retVal; } /** * @brief Applies the give std::map lookup table on the preview segmentation LabelSetImage. */ void MapLabelsToSegmentation(const mitk::LabelSetImage *source, mitk::LabelSetImage *dest, const std::map &labelMap) { if (labelMap.empty()) { auto label = mitk::LabelSetImageHelper::CreateNewLabel(dest, "object"); label->SetValue(1); dest->AddLabel(label, false); return; } std::map flippedLabelMap; for (auto const &[key, val] : labelMap) { flippedLabelMap[val] = key; } auto lookupTable = mitk::LookupTable::New(); lookupTable->SetType(mitk::LookupTable::LookupTableType::MULTILABEL); for (auto const &[key, val] : flippedLabelMap) { if (source->ExistLabel(key, source->GetActiveLayer())) { auto label = mitk::Label::New(key, val); std::array lookupTableColor; lookupTable->GetColor(key, lookupTableColor.data()); mitk::Color color; color.SetRed(lookupTableColor[0]); color.SetGreen(lookupTableColor[1]); color.SetBlue(lookupTableColor[2]); label->SetColor(color); dest->AddLabel(label, false); } else { MITK_INFO << "Label not found for " << val; } } } template void ITKWindowing(const itk::Image *inputImage, mitk::Image *mitkImage, mitk::ScalarType min, mitk::ScalarType max) { typedef itk::Image ImageType; typedef itk::IntensityWindowingImageFilter IntensityFilterType; typename IntensityFilterType::Pointer filter = IntensityFilterType::New(); filter->SetInput(inputImage); filter->SetWindowMinimum(min); filter->SetWindowMaximum(max); filter->SetOutputMinimum(min); filter->SetOutputMaximum(max); filter->Update(); mitkImage->SetImportVolume( (void *)(filter->GetOutput()->GetPixelContainer()->GetBufferPointer()), 0, 0, mitk::Image::ManageMemory); filter->GetOutput()->GetPixelContainer()->ContainerManageMemoryOff(); } } mitk::Image::Pointer mitk::MonaiLabelTool::ApplyLevelWindowEffect(const Image *inputAtTimeStep) const { mitk::LevelWindow levelWindow; this->GetToolManager()->GetReferenceData(0)->GetLevelWindow(levelWindow); auto filteredImage = mitk::Image::New(); filteredImage->Initialize(inputAtTimeStep); AccessByItk_n(inputAtTimeStep, ::ITKWindowing, // apply level window filter (filteredImage, levelWindow.GetLowerWindowBound(), levelWindow.GetUpperWindowBound())); return filteredImage; } void mitk::MonaiLabelTool::DoUpdatePreview(const Image *inputAtTimeStep, const Image * /*oldSegAtTimeStep*/, LabelSetImage *previewImage, TimeStepType timeStep) { if (nullptr == m_RequestParameters || (m_RequestParameters->model.IsInteractive() && !this->HasPicks())) { this->ResetPreviewContentAtTimeStep(timeStep); RenderingManager::GetInstance()->ForceImmediateUpdateAll(); return; } const std::string &hostName = m_RequestParameters->hostName; const int port = m_RequestParameters->port; if (!IsMonaiServerOn(hostName, port)) { mitkThrow() << m_SERVER_503_ERROR_TEXT; } if (this->m_TempDir.empty()) { this->SetTempDir(IOUtil::CreateTemporaryDirectory("mitk-XXXXXX")); } std::string inputImagePath, outputImagePath; std::tie(inputImagePath, outputImagePath) = this->CreateTempDirs(m_TEMPLATE_FILENAME); try { mitk::Image::Pointer filteredImage = this->ApplyLevelWindowEffect(inputAtTimeStep); this->WriteImage(filteredImage, inputImagePath); this->PostInferRequest(hostName, port, inputImagePath, outputImagePath, inputAtTimeStep->GetGeometry()); auto outputImage = IOUtil::Load(outputImagePath); auto outputBuffer = mitk::LabelSetImage::New(); outputBuffer->InitializeByLabeledImage(outputImage); std::map labelMap; // empty map if (m_RequestParameters->model.IsInteractive()) { this->SetLabelTransferMode(LabelTransferMode::MapLabel); this->SetSelectedLabels({MASK_VALUE}); } else { outputBuffer->SetGeometry(inputAtTimeStep->GetGeometry()); labelMap = m_ResultMetadata["label_names"]; this->SetLabelTransferMode(LabelTransferMode::AddLabel); } ::MapLabelsToSegmentation(outputBuffer, previewImage, labelMap); this->WriteBackResults(previewImage, outputBuffer.GetPointer(), timeStep); MonaiStatusEvent.Send(true); } catch (const mitk::Exception &e) { MITK_ERROR << e.GetDescription(); mitkThrow() << e.GetDescription(); MonaiStatusEvent.Send(false); } } void mitk::MonaiLabelTool::FetchOverallInfo(const std::string &hostName, const int &port) { m_InfoParameters.reset(); if (!IsMonaiServerOn(hostName, port)) { Tool::ErrorMessage.Send(m_SERVER_503_ERROR_TEXT); mitkThrow() << m_SERVER_503_ERROR_TEXT; } httplib::SSLClient cli(hostName, port); cli.enable_server_certificate_verification(false); if (auto response = cli.Get("/info/")) { if (response->status == 200) { auto jsonObj = nlohmann::json::parse(response->body); if (jsonObj.is_discarded() || !jsonObj.is_object()) { MITK_ERROR << "Could not parse response from MONAILabel server as JSON object!"; return; } auto appData = jsonObj.template get(); m_InfoParameters = std::make_unique(appData); if (nullptr != m_InfoParameters) { m_InfoParameters->hostName = hostName; m_InfoParameters->port = port; } } } else { Tool::ErrorMessage.Send(httplib::to_string(response.error()) + " error occured."); } } void mitk::MonaiLabelTool::PostInferRequest(const std::string &hostName, const int &port, const std::string &filePath, const std::string &outFile, const mitk::BaseGeometry *baseGeometry) { std::string &modelName = m_RequestParameters->model.name; // Get this from args as well. std::string postPath = "/infer/"; // make this separate class of constants postPath.append(modelName); std::ifstream input(filePath, std::ios::binary); if (!input) { MITK_WARN << "could not read file to POST"; } std::stringstream buffer_lf_img; buffer_lf_img << input.rdbuf(); input.close(); httplib::MultipartFormDataItems items; if (m_RequestParameters->model.IsInteractive()) { std::string foreground = this->ConvertPointsAsListString(baseGeometry, m_PointSetPositive); std::string background = this->ConvertPointsAsListString(baseGeometry, m_PointSetNegative); std::stringstream paramString; paramString << "{" << "\"foreground\":" << foreground << ",\"background\":" << background << "}"; MITK_DEBUG << paramString.str(); items.push_back({"params", paramString.str(), "", ""}); } else // Auto models { items.push_back({"params", "{\"restore_label_idx\": true}", "", ""}); } items.push_back({"file", buffer_lf_img.str(), "post_from_mitk.nii.gz", "application/octet-stream"}); httplib::SSLClient cli(hostName, port); - cli.set_read_timeout(60); // arbitary 1 minute time-out to avoid corner cases. + cli.set_read_timeout(m_Timeout); cli.enable_server_certificate_verification(false); if (auto response = cli.Post(postPath, items)) { if (response->status == 200) { // Find boundary std::string boundaryString = ::GetBoundaryString(response); MITK_DEBUG << "boundary hash: " << boundaryString; // Parse metadata JSON std::string resBody = response->body; std::vector multiPartResponse = ::GetPartsBetweenBoundary(resBody, boundaryString); std::string metaData = multiPartResponse[0]; std::string contentTypeJson = "Content-Type: application/json"; size_t ctPos = metaData.find(contentTypeJson) + contentTypeJson.length(); std::string metaDataPart = metaData.substr(ctPos); MITK_DEBUG << metaDataPart; auto jsonObj = nlohmann::json::parse(metaDataPart); if (jsonObj.is_discarded() || !jsonObj.is_object()) { MITK_ERROR << "Could not parse response from MONAILabel server as JSON object!"; return; } else // use the metadata { m_ResultMetadata = jsonObj; } // Parse response image string std::string imagePart = multiPartResponse[1]; std::string imageData = ::GetResponseImageString(imagePart); std::ofstream output(outFile, std::ios::out | std::ios::app | std::ios::binary); output << imageData; output.unsetf(std::ios::skipws); output.flush(); } else { auto err = response.error(); MITK_ERROR << "An HTTP POST error: " << httplib::to_string(err) << " occured."; mitkThrow() << "An HTTP POST error: " << httplib::to_string(err) << " occured."; } } } const std::vector mitk::MonaiLabelTool::GetAutoSegmentationModels(const int dim) const { std::vector autoModels; if (nullptr != m_InfoParameters) { for (mitk::MonaiModelInfo &model : m_InfoParameters->models) { if (m_AUTO_SEG_TYPE_NAME.find(model.type) != m_AUTO_SEG_TYPE_NAME.end() && (!(dim > -1) || model.dimension == dim)) { autoModels.push_back(model); } } } return autoModels; } const std::vector mitk::MonaiLabelTool::GetInteractiveSegmentationModels(const int dim) const { std::vector interactiveModels; if (nullptr != m_InfoParameters) { for (mitk::MonaiModelInfo &model : m_InfoParameters->models) { if (m_INTERACTIVE_SEG_TYPE_NAME.find(model.type) != m_INTERACTIVE_SEG_TYPE_NAME.end() && (!(dim > -1) || model.dimension == dim)) { interactiveModels.push_back(model); } } } return interactiveModels; } const std::vector mitk::MonaiLabelTool::GetScribbleSegmentationModels(const int dim) const { std::vector scribbleModels; if (nullptr != m_InfoParameters) { for (mitk::MonaiModelInfo &model : m_InfoParameters->models) { if (m_SCRIBBLE_SEG_TYPE_NAME.find(model.type) != m_SCRIBBLE_SEG_TYPE_NAME.end() && (!(dim > -1) || model.dimension == dim)) { scribbleModels.push_back(model); } } } return scribbleModels; } mitk::MonaiModelInfo mitk::MonaiLabelTool::GetModelInfoFromName(const std::string modelName) const { if (nullptr == m_InfoParameters) { mitkThrow() << "No model information found."; } mitk::MonaiModelInfo retVal; for (mitk::MonaiModelInfo &model : m_InfoParameters->models) { if (model.name == modelName) { retVal = model; break; } } return retVal; } std::pair mitk::MonaiLabelTool::CreateTempDirs(const std::string &filePattern) const { std::string inDir, outDir, inputImagePath, outputImagePath; inDir = IOUtil::CreateTemporaryDirectory("monai-in-XXXXXX", this->GetTempDir()); std::ofstream tmpStream; inputImagePath = IOUtil::CreateTemporaryFile(tmpStream, filePattern, 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("monai-out-XXXXXX", this->GetTempDir()); outputImagePath = outDir + IOUtil::GetDirectorySeparator() + token + "_000.nii.gz"; return std::make_pair(inputImagePath, outputImagePath); } std::string mitk::MonaiLabelTool::ConvertPointsAsListString(const mitk::BaseGeometry *baseGeometry, const PointSet::Pointer pointSet) const { bool is3DMode = nullptr == this->GetWorkingPlaneGeometry(); MITK_INFO << "No.of points: " << pointSet->GetSize(); std::stringstream pointsAndLabels; pointsAndLabels << "["; mitk::PointSet::PointsConstIterator pointSetIter = pointSet->Begin(); const char COMMA = ','; while (pointSetIter != pointSet->End()) { mitk::Point3D point3d = pointSetIter.Value(); if (baseGeometry->IsInside(point3d)) { mitk::Point3D index3D; baseGeometry->WorldToIndex(point3d, index3D); pointsAndLabels << "["; pointsAndLabels << static_cast(index3D[0]) << COMMA << static_cast(index3D[1]) << COMMA; if (is3DMode) { pointsAndLabels << static_cast(index3D[2]); } else { pointsAndLabels << 0; } pointsAndLabels << "],"; } ++pointSetIter; } if (pointsAndLabels.tellp() > 1) { pointsAndLabels.seekp(-1, pointsAndLabels.end); // remove last added comma character } pointsAndLabels << "]"; return pointsAndLabels.str(); } const mitk::MonaiAppMetadata *mitk::MonaiLabelTool::GetInfoParameters() const { return m_InfoParameters.get(); } diff --git a/Modules/Segmentation/Interactions/mitkMonaiLabelTool.h b/Modules/Segmentation/Interactions/mitkMonaiLabelTool.h index 1bf4e4bc0b..b1f141a941 100644 --- a/Modules/Segmentation/Interactions/mitkMonaiLabelTool.h +++ b/Modules/Segmentation/Interactions/mitkMonaiLabelTool.h @@ -1,264 +1,268 @@ /*============================================================================ 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 mitkMonaiLabelTool_h #define mitkMonaiLabelTool_h #include "mitkSegWithPreviewTool.h" #include #include #include #include #include #include #include namespace us { class ModuleResource; } namespace mitk { /** * @brief Struct to hold featured models individual info * */ struct MonaiModelInfo { std::string name; std::string type; std::unordered_map labels; int dimension; std::string description; std::unordered_map config; inline bool operator==(const MonaiModelInfo &rhs) const { return (this->name == rhs.name && this->type == rhs.type); // Comparing only name and type, for now. } inline bool IsInteractive() const { return ("deepgrow" == type || "deepedit" == type); } inline bool Is2D() const { return dimension == 2; } }; /** * @brief Struct to store MonaiLabel server metadata including all model infos * */ struct MonaiAppMetadata { std::string name; std::string description; std::vector labels; std::string version; std::string hostName; int port; std::string origin; std::vector models; }; /** * @brief Request class to pack model and other necessary server information * from GUI. * */ struct MonaiLabelRequest { MonaiModelInfo model; std::string hostName; std::string requestLabel; int port; inline bool operator==(const MonaiLabelRequest &rhs) const { return (this->model == rhs.model && this->hostName == rhs.hostName && this->port == rhs.port && this->requestLabel == rhs.requestLabel); } }; /** \brief MonaiLabel segmentation tool base class. \ingroup Interaction \ingroup ToolManagerEtAl \warning Only to be instantiated by mitk::ToolManager. */ class MITKSEGMENTATION_EXPORT MonaiLabelTool : public SegWithPreviewTool { public: mitkClassMacro(MonaiLabelTool, SegWithPreviewTool); itkCloneMacro(Self); void Activated() override; void Deactivated() override; void UpdatePrepare() override; /** * @brief Method does the GET Rest call to fetch MonaiLabel * server metadata including all models' info. */ void FetchOverallInfo(const std::string &hostName, const int &port); /** * @brief Variable to set selected model's and other data needed * for the POST call. */ std::unique_ptr m_RequestParameters; /** * @brief Get the Auto Segmentation Models info for the given * dimension. */ const std::vector GetAutoSegmentationModels(const int dim = -1) const; /** * @brief Get the Interactive Segmentation Models info for the given * dimension. */ const std::vector GetInteractiveSegmentationModels(const int dim = -1) const; /** * @brief Get the Scribble Segmentation Models info for the given * dimension. */ const std::vector GetScribbleSegmentationModels(const int dim = -1) const; /** * @brief Helper function to get full model info object from model name. */ MonaiModelInfo GetModelInfoFromName(const std::string) const; itkSetMacro(ModelName, std::string); itkGetConstMacro(ModelName, std::string); itkSetMacro(URL, std::string); itkGetConstMacro(URL, std::string); itkSetMacro(TempDir, std::string); itkGetConstMacro(TempDir, std::string); + itkSetMacro(Timeout, unsigned int); + itkGetConstMacro(Timeout, unsigned int); + const MonaiAppMetadata *GetInfoParameters() const; /** * @brief Clears all picks and updates the preview. */ void ClearPicks(); /** * @brief Checks if any point exists in the either of the PointSetPositive * or PointSetNegative. */ bool HasPicks() const; Message1 MonaiStatusEvent; protected: MonaiLabelTool(); ~MonaiLabelTool(); void DoUpdatePreview(const Image* inputAtTimeStep, const Image* oldSegAtTimeStep, LabelSetImage* previewImage, TimeStepType timeStep) override; void ConnectActionsAndFunctions() override; /** * @brief Writes image to disk as the tool desires. * Default implementation does nothing. */ virtual void WriteImage(const Image *, const std::string &) const = 0; /* * @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 */ virtual void OnDelete(StateMachineAction *, InteractionEvent *); /* * @brief Clear all seed points and call UpdatePreview to reset the segmentation Preview */ void ClearSeeds(); /** * @brief Get the Points from given pointset as csv string. * */ virtual std::string ConvertPointsAsListString(const mitk::BaseGeometry *baseGeometry, const PointSet::Pointer pointSet) const; /** * @brief Writes back segmentation results in 3D or 2D shape to preview LabelSetImage. */ virtual void WriteBackResults(LabelSetImage *, LabelSetImage *, TimeStepType) const = 0; PointSet::Pointer m_PointSetPositive; PointSet::Pointer m_PointSetNegative; DataNode::Pointer m_PointSetNodePositive; DataNode::Pointer m_PointSetNodeNegative; int m_PointSetCount = 0; private: /** * @brief Holds all parameters of the server to serve the UI * */ std::unique_ptr m_InfoParameters; /** * @brief Helper function to create temp directory for writing/reading images * Returns input and output file names expected as a pair. */ std::pair CreateTempDirs(const std::string &filePattern) const; /** * @brief Checks if MonaiLabel server is alive. */ bool IsMonaiServerOn(const std::string &hostName, const int &port) const; /** * @brief Applies level window filter on the input image. Current Level window bounds * are captured from the tool manager. */ mitk::Image::Pointer ApplyLevelWindowEffect(const Image *inputAtTimeStep) const; /** * @brief Function to prepare the Rest request and does the POST call. * Writes the POST responses back to disk. */ void PostInferRequest(const std::string &hostName, const int &port, const std::string &filePath, const std::string &outFile, const mitk::BaseGeometry *baseGeometry); std::string m_TempDir; std::string m_ModelName; std::string m_URL; nlohmann::json m_ResultMetadata; + unsigned int m_Timeout = 60; const std::set m_AUTO_SEG_TYPE_NAME = {"segmentation"}; const std::set m_SCRIBBLE_SEG_TYPE_NAME = {"scribbles"}; const std::set m_INTERACTIVE_SEG_TYPE_NAME = {"deepgrow"}; // deepedit not supported yet const std::string m_TEMPLATE_FILENAME = "XXXXXX_000_0000.nii.gz"; const std::string m_SERVER_503_ERROR_TEXT = "A connection to MonaiLabel server cannot be established."; const Label::PixelType MASK_VALUE = 1; }; } #endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkMonaiLabelToolGUI.cpp b/Modules/SegmentationUI/Qmitk/QmitkMonaiLabelToolGUI.cpp index 256f783b59..5f4b6557ea 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkMonaiLabelToolGUI.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkMonaiLabelToolGUI.cpp @@ -1,335 +1,343 @@ /*============================================================================ 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 "QmitkMonaiLabelToolGUI.h" #include #include #include #include #include #include namespace { mitk::IPreferences *GetPreferences() { auto *preferencesService = mitk::CoreServices::GetPreferencesService(); return preferencesService->GetSystemPreferences()->Node("org.mitk.views.segmentation"); } const static QString CONFIRM_QUESTION_TEXT = "Data will be sent to the processing server devoid of any patient information. Are you sure you want continue?"; /* Pretested models in MITK and corresponding app */ const static QMap WHITELISTED_MODELS = { {"deepgrow_2d", "Radiology"}, {"deepgrow_3d", "Radiology"}, {"deepedit_seg", "Radiology"}, {"localization_vertebra", "Radiology"}, {"segmentation", "Radiology"}, {"segmentation_spleen", "Radiology"}, {"segmentation_vertebra", "Radiology"}, {"deepgrow_pipeline", "Radiology"}, {"vertebra_pipeline", "Radiology"}}; /* Strictly unsupported models in MITK and corresponding app */ const static QMap BLACKLISTED_MODELS = { {"deepedit", "Radiology"}, // interaction type not yet supported {"localization_spine", "Radiology"}}; // Metadata discrepancy with label names } QmitkMonaiLabelToolGUI::QmitkMonaiLabelToolGUI(int dimension) : QmitkMultiLabelSegWithPreviewToolGUIBase(), m_SuperclassEnableConfirmSegBtnFnc(m_EnableConfirmSegBtnFnc), m_Dimension(dimension) { m_EnableConfirmSegBtnFnc = [this](bool enabled) { return !m_FirstPreviewComputation ? m_SuperclassEnableConfirmSegBtnFnc(enabled) : false; }; m_Preferences = GetPreferences(); m_Preferences->OnPropertyChanged += mitk::MessageDelegate1( this, &QmitkMonaiLabelToolGUI::OnPreferenceChangedEvent); } QmitkMonaiLabelToolGUI::~QmitkMonaiLabelToolGUI() { auto tool = this->GetConnectedToolAs(); if (nullptr != tool) { tool->MonaiStatusEvent -= mitk::MessageDelegate1(this, &QmitkMonaiLabelToolGUI::StatusMessageListener); } m_Preferences->OnPropertyChanged -= mitk::MessageDelegate1( this, &QmitkMonaiLabelToolGUI::OnPreferenceChangedEvent); } void QmitkMonaiLabelToolGUI::ConnectNewTool(mitk::SegWithPreviewTool *newTool) { Superclass::ConnectNewTool(newTool); m_FirstPreviewComputation = true; } void QmitkMonaiLabelToolGUI::InitializeUI(QBoxLayout *mainLayout) { m_Controls.setupUi(this); mainLayout->addLayout(m_Controls.verticalLayout); connect(m_Controls.previewButton, SIGNAL(clicked()), this, SLOT(OnPreviewBtnClicked())); connect(m_Controls.fetchUrl, SIGNAL(clicked()), this, SLOT(OnFetchBtnClicked())); connect(m_Controls.modelBox, QOverload::of(&QComboBox::activated), [=](int index) { OnModelChanged(m_Controls.modelBox->itemText(index)); }); QIcon refreshIcon = QmitkStyleManager::ThemeIcon(QStringLiteral(":/org_mitk_icons/icons/awesome/scalable/actions/view-refresh.svg")); m_Controls.fetchUrl->setIcon(refreshIcon); m_Controls.previewButton->setEnabled(false); Superclass::InitializeUI(mainLayout); } void QmitkMonaiLabelToolGUI::EnableWidgets(bool enabled) { Superclass::EnableWidgets(enabled); } void QmitkMonaiLabelToolGUI::StatusMessageListener(const bool status) { if (!status) { return; } auto tool = this->GetConnectedToolAs(); if (nullptr == tool) { return; } this->SetLabelSetPreview(tool->GetPreviewSegmentation()); this->ActualizePreviewLabelVisibility(); m_FirstPreviewComputation = false; } void QmitkMonaiLabelToolGUI::DisplayWidgets(bool enabled) { Superclass::DisplayTransferWidgets(enabled); m_Controls.previewButton->setVisible(enabled); } void QmitkMonaiLabelToolGUI::OnModelChanged(const QString &modelName) { auto tool = this->GetConnectedToolAs(); if (nullptr == tool) { return; } m_Controls.labelListLabel->clear(); mitk::MonaiModelInfo model = tool->GetModelInfoFromName(modelName.toStdString()); if (model.IsInteractive()) { this->WriteStatusMessage("Interactive model selected. Please press SHIFT + click on the render windows.\n"); m_Controls.previewButton->setEnabled(false); this->DisplayWidgets(false); } else { this->WriteStatusMessage("Auto-segmentation model selected. Please click on Preview.\n"); m_Controls.previewButton->setEnabled(true); this->DisplayWidgets(true); } auto selectedModel = m_Controls.modelBox->currentText().toStdString(); for (const auto &modelObject : tool->GetInfoParameters()->models) { if (modelObject.name == selectedModel) { auto requestObject = std::make_unique(); requestObject->model = modelObject; requestObject->hostName = tool->GetInfoParameters()->hostName; requestObject->port = tool->GetInfoParameters()->port; if (modelObject.IsInteractive()) // set only if interactive model { tool->m_RequestParameters = std::move(requestObject); } QStringList supportedLabels; for (const auto &label : modelObject.labels) { supportedLabels << QString::fromStdString(label.first); } m_Controls.labelListLabel->setText(supportedLabels.join(QStringLiteral(", "))); break; } } tool->MonaiStatusEvent += mitk::MessageDelegate1(this, &QmitkMonaiLabelToolGUI::StatusMessageListener); } void QmitkMonaiLabelToolGUI::OnFetchBtnClicked() { m_Controls.previewButton->setEnabled(false); m_Controls.labelListLabel->clear(); auto reply = QMessageBox::question(this, "Confirm", ::CONFIRM_QUESTION_TEXT, QMessageBox::Yes | QMessageBox::No); if (reply == QMessageBox::No) { MITK_INFO << "Didn't went ahead with Monai Label inferencing"; return; } auto tool = this->GetConnectedToolAs(); if (nullptr == tool) { return; } QString urlString = m_Controls.urlBox->text(); QUrl url(urlString); if (url.isValid() && !url.isLocalFile() && !url.hasFragment() && !url.hasQuery()) // sanity check { std::string hostName = url.host().toStdString(); int port = url.port(); try { tool->FetchOverallInfo(hostName, port); bool allowAllModels = m_Preferences->GetBool("monailabel allow all models", false); this->PopulateUI(allowAllModels); + auto timeout_sec = std::make_unsigned_t(m_Preferences->GetInt("monailabel timeout", 180)); + tool->SetTimeout(timeout_sec); } catch (const mitk::Exception &e) { m_Controls.appBox->clear(); m_Controls.modelBox->clear(); MITK_ERROR << e.GetDescription(); this->WriteErrorMessage(e.GetDescription()); } } else { std::string invalidURLMessage = "Invalid URL entered: " + urlString.toStdString(); MITK_ERROR << invalidURLMessage; this->ShowErrorMessage(invalidURLMessage); } } void QmitkMonaiLabelToolGUI::OnPreviewBtnClicked() { auto tool = this->GetConnectedToolAs(); if (nullptr == tool) { return; } tool->ClearPicks(); // clear any interactive segmentation from before auto selectedModel = m_Controls.modelBox->currentText().toStdString(); for (const auto &modelObject : tool->GetInfoParameters()->models) { if (modelObject.name == selectedModel) { auto requestObject = std::make_unique(); requestObject->model = modelObject; requestObject->hostName = tool->GetInfoParameters()->hostName; requestObject->port = tool->GetInfoParameters()->port; tool->m_RequestParameters = std::move(requestObject); break; } } try { tool->UpdatePreview(); } catch (const std::exception &e) { std::stringstream errorMsg; errorMsg << "STATUS: Error while processing parameters for MONAI Label 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 MONAI Label segmentation."; this->ShowErrorMessage(errorMsg); m_Controls.previewButton->setEnabled(true); return; } } void QmitkMonaiLabelToolGUI::PopulateUI(bool allowAllModels) { auto tool = this->GetConnectedToolAs(); if (nullptr == tool) { return; } m_Controls.appBox->clear(); m_Controls.labelListLabel->clear(); if (nullptr != tool->GetInfoParameters()) { QString appName = QString::fromStdString(tool->GetInfoParameters()->name); auto autoModels = tool->GetAutoSegmentationModels(m_Dimension); auto interactiveModels = tool->GetInteractiveSegmentationModels(m_Dimension); autoModels.insert(autoModels.end(), interactiveModels.begin(), interactiveModels.end()); this->WriteStatusMessage(appName); m_Controls.appBox->addItem(appName); this->PopulateModelBox(appName, autoModels, allowAllModels); m_Controls.modelBox->setCurrentIndex(-1); } } void QmitkMonaiLabelToolGUI::PopulateModelBox(QString appName, std::vector models, bool allowAllModels) { m_Controls.modelBox->clear(); for (const auto &model : models) { QString modelName = QString::fromStdString(model.name); if (allowAllModels) { if (::BLACKLISTED_MODELS.contains(modelName) && appName.contains(::BLACKLISTED_MODELS[modelName])) { continue; } m_Controls.modelBox->addItem(modelName); } else { if (::WHITELISTED_MODELS.contains(modelName)) { m_Controls.modelBox->addItem(modelName); } } } } void QmitkMonaiLabelToolGUI::WriteStatusMessage(const QString &message) { m_Controls.responseNote->setText(message); m_Controls.responseNote->setStyleSheet("font-weight: bold; color: white"); qApp->processEvents(); } void QmitkMonaiLabelToolGUI::WriteErrorMessage(const QString &message) { m_Controls.responseNote->setText(message); m_Controls.responseNote->setStyleSheet("font-weight: bold; color: red"); qApp->processEvents(); } void QmitkMonaiLabelToolGUI::ShowErrorMessage(const std::string &message) { this->setCursor(Qt::ArrowCursor); QMessageBox::critical(nullptr, "MONAI Label", message.c_str()); MITK_WARN << message; } void QmitkMonaiLabelToolGUI::OnPreferenceChangedEvent(const mitk::IPreferences::ChangeEvent& event) { if (event.GetProperty().rfind("monai", 0) == 0) { bool allowAllModels = m_Preferences->GetBool("monailabel allow all models", false); this->PopulateUI(allowAllModels); + auto tool = this->GetConnectedToolAs(); + if (nullptr != tool) + { + auto timeout_sec = std::make_unsigned_t(m_Preferences->GetInt("monailabel timeout", 180)); + tool->SetTimeout(timeout_sec); + } } } diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/QmitkSegmentationPreferencePage.cpp b/Plugins/org.mitk.gui.qt.segmentation/src/QmitkSegmentationPreferencePage.cpp index e3f2571e73..fa7cd9ab87 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/src/QmitkSegmentationPreferencePage.cpp +++ b/Plugins/org.mitk.gui.qt.segmentation/src/QmitkSegmentationPreferencePage.cpp @@ -1,159 +1,162 @@ /*============================================================================ 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 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); connect(m_Ui->labelSetPresetToolButton, SIGNAL(clicked()), this, SLOT(OnLabelSetPresetButtonClicked())); connect(m_Ui->suggestionsToolButton, SIGNAL(clicked()), this, SLOT(OnSuggestionsButtonClicked())); 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->PutBool("monailabel allow all models", m_Ui->allowAllModelsCheckBox->isChecked()); + prefs->PutInt("monailabel timeout", std::stoi(m_Ui->monaiTimeoutEdit->text().toStdString())); 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->allowAllModelsCheckBox->setChecked(prefs->GetBool("monailabel allow all models", true)); + m_Ui->monaiTimeoutEdit->setText(QString::number(prefs->GetInt("monailabel timeout", 180))); + } 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); } diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/QmitkSegmentationPreferencePageControls.ui b/Plugins/org.mitk.gui.qt.segmentation/src/QmitkSegmentationPreferencePageControls.ui index f0ddeafdb7..578d389f25 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/src/QmitkSegmentationPreferencePageControls.ui +++ b/Plugins/org.mitk.gui.qt.segmentation/src/QmitkSegmentationPreferencePageControls.ui @@ -1,253 +1,277 @@ 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 overridden 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 MONAILabel + + If checked, all models are visible. Allow all models - - true - + + true + + + + + + 0 + 0 + + + + Time out (s): + + + + + + + 180 + + + + + Qt::Vertical 20 40