diff --git a/Modules/Segmentation/Interactions/mitkMonaiLabelTool.cpp b/Modules/Segmentation/Interactions/mitkMonaiLabelTool.cpp index 97431db60c..d1ebd520f0 100644 --- a/Modules/Segmentation/Interactions/mitkMonaiLabelTool.cpp +++ b/Modules/Segmentation/Interactions/mitkMonaiLabelTool.cpp @@ -1,377 +1,387 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ // MITK #include "mitkMonaiLabelTool.h" // us #include "mitkIOUtil.h" #include #include #include #include #include #include #include #include #include #include namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, MonaiLabelTool, "MonaiLabel"); } mitk::MonaiLabelTool::MonaiLabelTool() { this->SetMitkTempDir(IOUtil::CreateTemporaryDirectory("mitk-XXXXXX")); } mitk::MonaiLabelTool::~MonaiLabelTool() { itksys::SystemTools::RemoveADirectory(this->GetMitkTempDir()); } void mitk::MonaiLabelTool::Activated() { Superclass::Activated(); this->SetLabelTransferMode(LabelTransferMode::AllLabels); } const char **mitk::MonaiLabelTool::GetXPM() const { return nullptr; } us::ModuleResource mitk::MonaiLabelTool::GetIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); us::ModuleResource resource = module->GetResource("AI.svg"); return resource; } const char *mitk::MonaiLabelTool::GetName() const { return "MonaiLabel"; } bool mitk::MonaiLabelTool::IsMonaiServerOn(std::string &hostName, int &port) { httplib::Client cli(hostName, port); - while (cli.is_socket_open()); - if (auto response = cli.Get("/info/")) + while (cli.is_socket_open()) + ; + if (auto response = cli.Get("/info/")) { return true; } return false; } void mitk::MonaiLabelTool::DoUpdatePreview(const Image *inputAtTimeStep, const Image * /*oldSegAtTimeStep*/, LabelSetImage *previewImage, TimeStepType timeStep) { std::string &hostName = m_RequestParameters->hostName; int port = m_RequestParameters->port; - if (!IsMonaiServerOn(hostName, port)) - { - mitkThrow() << m_SERVER_503_ERROR_TEXT; - } + if (!m_TEST) + if (!IsMonaiServerOn(hostName, port)) + { + mitkThrow() << m_SERVER_503_ERROR_TEXT; + } std::string inDir, outDir, inputImagePath, outputImagePath; inDir = IOUtil::CreateTemporaryDirectory("monai-in-XXXXXX", this->GetMitkTempDir()); std::ofstream tmpStream; inputImagePath = IOUtil::CreateTemporaryFile(tmpStream, m_TEMPLATE_FILENAME, inDir + IOUtil::GetDirectorySeparator()); tmpStream.close(); std::size_t found = inputImagePath.find_last_of(IOUtil::GetDirectorySeparator()); std::string fileName = inputImagePath.substr(found + 1); std::string token = fileName.substr(0, fileName.find("_")); outDir = IOUtil::CreateTemporaryDirectory("monai-out-XXXXXX", this->GetMitkTempDir()); outputImagePath = outDir + IOUtil::GetDirectorySeparator() + token + "_000.nii.gz"; - try { + m_IsLastSuccess = false; if (!m_TEST) { IOUtil::Save(inputAtTimeStep, inputImagePath); PostInferRequest(hostName, port, inputImagePath, outputImagePath); } else { std::string metaData_test = "{\"label_names\":{\"spleen\": 1, \"right kidney\": 2, \"left kidney\": 3, \"liver\": 6, \"stomach\": 7, " "\"aorta\": 8, \"inferior " "vena cava\": 9, \"background\": 0}, \"latencies\": {\"pre\": 0.64, \"infer\": 1.13, \"invert\": 0.03, " "\"post\": 0.06, \"write\": 0.1, \"total\": 1.96, \"transform\": {\"pre\": {\"LoadImaged\": 0.1483, " "\"EnsureChannelFirstd\": 0.0, \"Orientationd\": 0.0, \"ScaleIntensityRanged\": 0.0403, \"Resized\": 0.0401, " "\"DiscardAddGuidanced\": 0.0297, \"EnsureTyped\": 0.3703}, \"post\": {\"EnsureTyped\": 0.0, \"Activationsd\": " "0.0, \"AsDiscreted\": 0.0, \"SqueezeDimd\": 0.0, \"ToNumpyd\": 0.0473, \"Restored\": 0.0001}}}}"; m_ResultMetadata = nlohmann::json::parse(metaData_test); outputImagePath = "C:/DKFZ/MONAI_work/monai_test_python/output.nii.gz"; } Image::Pointer outputImage = IOUtil::Load(outputImagePath); - auto m_OutputBuffer = mitk::LabelSetImage::New(); - m_OutputBuffer->InitializeByLabeledImage(outputImage); - m_OutputBuffer->SetGeometry(inputAtTimeStep->GetGeometry()); - MITK_INFO << m_OutputBuffer->GetNumberOfLabels(); + auto outputBuffer = mitk::LabelSetImage::New(); + outputBuffer->InitializeByLabeledImage(outputImage); + outputBuffer->SetGeometry(inputAtTimeStep->GetGeometry()); std::map labelMap = m_ResultMetadata["label_names"]; - std::map flippedLabelMap; - - for (auto const &[key, val] : labelMap) - { - flippedLabelMap[val] = key; - } - int labelId = 0; - for (auto const &[key, val] : flippedLabelMap) - { - mitk::Label *labelptr = m_OutputBuffer->GetLabel(labelId, 0); - if (nullptr != labelptr) - { - MITK_INFO << "Replacing label with name: " << labelptr->GetName() << " as " << val; - labelptr->SetName(val); - } - else - { - MITK_INFO << "nullptr found for " << val; - } - labelId++; - } - TransferLabelSetImageContent(m_OutputBuffer, previewImage, timeStep); + MapLabelsToSegmentation(outputBuffer, labelMap); + TransferLabelSetImageContent(outputBuffer, previewImage, timeStep); + this->SetIsLastSuccess(true); } catch (const mitk::Exception &e) { + m_IsLastSuccess = false; MITK_ERROR << e.GetDescription(); mitkThrow() << e.GetDescription(); } } +void mitk::MonaiLabelTool::MapLabelsToSegmentation(mitk::LabelSetImage::Pointer outputBuffer, + std::map &labelMap) +{ + std::map flippedLabelMap; + for (auto const &[key, val] : labelMap) + { + flippedLabelMap[val] = key; + } + int labelId = 0; + for (auto const &[key, val] : flippedLabelMap) + { + mitk::Label *labelptr = outputBuffer->GetLabel(labelId, 0); + if (nullptr != labelptr) + { + MITK_INFO << "Replacing label with name: " << labelptr->GetName() << " as " << val; + labelptr->SetName(val); + } + else + { + MITK_INFO << "nullptr found for " << val; + } + labelId++; + } +} + void mitk::MonaiLabelTool::GetOverallInfo(std::string &hostName, int &port) { MITK_INFO << "URL..." << hostName << ":" << port; if (!IsMonaiServerOn(hostName, port)) { Tool::ErrorMessage.Send(m_SERVER_503_ERROR_TEXT); mitkThrow() << m_SERVER_503_ERROR_TEXT; } - httplib::Client cli(hostName, port); // httplib::Client cli("localhost", 8000); + httplib::Client cli(hostName, port); // httplib::Client cli("localhost", 8000); 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 \"" << /* jsonPath.toStdString() << */ "\" as JSON object!"; return; } m_InfoParameters = DataMapper(jsonObj); if (nullptr != m_InfoParameters) { m_InfoParameters->hostName = hostName; m_InfoParameters->port = port; } } } else { - Tool::ErrorMessage.Send(httplib::to_string(response.error())+" error occured."); + Tool::ErrorMessage.Send(httplib::to_string(response.error()) + " error occured."); } } void mitk::MonaiLabelTool::PostInferRequest(std::string &hostName, - int &port, - std::string &filePath, - std::string &outFile) + int &port, + std::string &filePath, + std::string &outFile) { // std::string url = "http://localhost:8000/infer/deepedit_seg"; // + m_ModelName; std::string &modelName = m_RequestParameters->model.name; // Get this from args as well. - std::string postPath = "/infer/"; // make this separate class of constants + std::string postPath = "/infer/"; // make this separate class of constants postPath.append(modelName); // std::string filePath = "C:/DKFZ/MONAI_work/monai_test_python/la_030.nii.gz"; 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 = { {"file", buffer_lf_img.str(), "post_from_mitk.nii.gz", "application/octet-stream"}}; - // httplib::MultipartFormDataMap itemMap = {{"file", {"file", buffer_lf_img.str(), "spleen_58.nii.gz", "application/octet-stream"}}}; + // httplib::MultipartFormDataMap itemMap = {{"file", {"file", buffer_lf_img.str(), "spleen_58.nii.gz", + // "application/octet-stream"}}}; httplib::Client cli(hostName, port); - cli.set_read_timeout(60); - if (auto response = cli.Post(postPath, items)) //auto response = cli.Post("/infer/deepedit_seg", items); - { + cli.set_read_timeout(60); // arbitary 1 minute time-out to avoid corner cases. + if (auto response = cli.Post(postPath, items)) // cli.Post("/infer/deepedit_seg", items); + { if (response->status == 200) { // Find boundary 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, "--"); MITK_INFO << "boundary hash: " << boundaryString; // Parse metadata JSON std::string resBody = response->body; std::vector multiPartResponse = this->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_INFO << metaDataPart; auto jsonObj = nlohmann::json::parse(metaDataPart); if (jsonObj.is_discarded() || !jsonObj.is_object()) { MITK_ERROR << "Could not parse \"" << /* jsonPath.toStdString() << */ "\" as JSON object!"; return; } else // use the metadata { m_ResultMetadata = jsonObj; } // Parse response image string std::string imagePart = multiPartResponse[1]; std::string contentTypeStream = "Content-Type: application/octet-stream"; ctPos = imagePart.find(contentTypeStream) + contentTypeStream.length(); std::string imageDataPart = imagePart.substr(ctPos); MITK_INFO << imageDataPart.substr(0, 50); std::string whitespaces = " \n\r"; imageDataPart.erase(0, imageDataPart.find_first_not_of(whitespaces)); // clean up std::ofstream output(outFile, std::ios::out | std::ios::app | std::ios::binary); output << imageDataPart; output.unsetf(std::ios::skipws); output.flush(); output.close(); // necessary to close? RAII } 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."; } } } + std::vector mitk::MonaiLabelTool::getPartsBetweenBoundary(const std::string &body, const std::string &boundary) { std::vector retVal; std::string master = body; size_t found = master.find(boundary); size_t beginPos = 0; while (found != std::string::npos) { std::string part = master.substr(beginPos, found); if (!part.empty()) { retVal.push_back(part); } master.erase(beginPos, found + boundary.length()); found = master.find(boundary); } return retVal; } std::unique_ptr mitk::MonaiLabelTool::DataMapper(nlohmann::json &jsonObj) { auto object = std::make_unique(); object->name = jsonObj["name"].get(); object->description = jsonObj["description"].get(); object->labels = jsonObj["labels"].get>(); 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."; } 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 } } modelInfo.type = _jsonObj["type"].get(); modelInfo.dimension = _jsonObj["dimension"].get(); modelInfo.description = _jsonObj["description"].get(); object->models.push_back(modelInfo); } return object; } std::vector mitk::MonaiLabelTool::GetAutoSegmentationModels() { 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()) { autoModels.push_back(model); } } } return autoModels; } std::vector mitk::MonaiLabelTool::GetInteractiveSegmentationModels() { 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()) { interactiveModels.push_back(model); } } } return interactiveModels; } std::vector mitk::MonaiLabelTool::GetScribbleSegmentationModels() { 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()) { scribbleModels.push_back(model); } } } return scribbleModels; } diff --git a/Modules/Segmentation/Interactions/mitkMonaiLabelTool.h b/Modules/Segmentation/Interactions/mitkMonaiLabelTool.h index 3847eef8fb..cc61b0eeea 100644 --- a/Modules/Segmentation/Interactions/mitkMonaiLabelTool.h +++ b/Modules/Segmentation/Interactions/mitkMonaiLabelTool.h @@ -1,110 +1,134 @@ /*============================================================================ 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 namespace us { class ModuleResource; } namespace mitk { struct MonaiModelInfo { std::string name; std::string type; std::unordered_map labels; int dimension; std::string description; std::unordered_map config; //TODO: find the full extent + + inline bool operator==(const MonaiModelInfo &rhs) const + { + if (this->name == rhs.name && this->type == rhs.type) // Comparing only name and type, for now. + { + return true; + } + return false; + } }; struct MonaiAppMetadata { std::string hostName; int port; std::string origin; std::string name; std::string description; std::string version; std::vector labels; std::vector models; }; + /** + * Parameters that goes into infer POST + */ struct MonaiLabelRequest { - /*Parameters that goes into infer POST*/ MonaiModelInfo model; std::string hostName; int port; + + inline bool operator==(const MonaiLabelRequest &rhs) const + { + if (this->model == rhs.model && this->hostName == rhs.hostName && this->port == rhs.port) + { + return true; + } + return false; + } }; class MITKSEGMENTATION_EXPORT MonaiLabelTool : public SegWithPreviewTool { public: mitkClassMacro(MonaiLabelTool, SegWithPreviewTool); itkFactorylessNewMacro(Self); itkCloneMacro(Self); bool m_TEST = false; // cleanup later const char *GetName() const override; const char **GetXPM() const override; us::ModuleResource GetIconResource() const override; void Activated() override; void GetOverallInfo(std::string&, int&); std::unique_ptr m_InfoParameters; //contains all parameters from Server to serve the GUI std::unique_ptr m_RequestParameters; std::vector GetAutoSegmentationModels(); std::vector GetInteractiveSegmentationModels(); std::vector GetScribbleSegmentationModels(); void PostInferRequest(std::string &, int &, std::string &, std::string &); itkSetMacro(ModelName, std::string); itkGetConstMacro(ModelName, std::string); itkSetMacro(URL, std::string); itkGetConstMacro(URL, std::string); itkSetMacro(MitkTempDir, std::string); itkGetConstMacro(MitkTempDir, std::string); + itkSetMacro(IsLastSuccess, bool); + itkGetConstMacro(IsLastSuccess, bool); protected: MonaiLabelTool(); ~MonaiLabelTool(); void DoUpdatePreview(const Image* inputAtTimeStep, const Image* oldSegAtTimeStep, LabelSetImage* previewImage, TimeStepType timeStep) override; private: std::string m_MitkTempDir; std::vector getPartsBetweenBoundary(const std::string &, const std::string &); std::unique_ptr mitk::MonaiLabelTool::DataMapper(nlohmann::json&); + void MapLabelsToSegmentation(mitk::LabelSetImage::Pointer, std::map &); bool IsMonaiServerOn(std::string &, int &); + bool m_IsLastSuccess = false; std::string m_ModelName; std::string m_URL; nlohmann::json m_ResultMetadata; 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 = {"deepedit", "deepgrow"}; 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."; }; // class } // namespace #endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkMonaiLabelToolGUI.cpp b/Modules/SegmentationUI/Qmitk/QmitkMonaiLabelToolGUI.cpp index 092dd8f87e..c6d829b3fe 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkMonaiLabelToolGUI.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkMonaiLabelToolGUI.cpp @@ -1,147 +1,143 @@ #include "QmitkMonaiLabelToolGUI.h" #include "mitkMonaiLabelTool.h" #include "usServiceReference.h" #include #include #include #include #include #include MITK_TOOL_GUI_MACRO(MITKSEGMENTATIONUI_EXPORT, QmitkMonaiLabelToolGUI, "") QmitkMonaiLabelToolGUI::QmitkMonaiLabelToolGUI() : QmitkMultiLabelSegWithPreviewToolGUIBase(), m_SuperclassEnableConfirmSegBtnFnc(m_EnableConfirmSegBtnFnc) { m_EnableConfirmSegBtnFnc = [this](bool enabled) { return !m_FirstPreviewComputation ? m_SuperclassEnableConfirmSegBtnFnc(enabled) : false; }; } void QmitkMonaiLabelToolGUI::ConnectNewTool(mitk::SegWithPreviewTool *newTool) { Superclass::ConnectNewTool(newTool); newTool->IsTimePointChangeAwareOff(); 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())); QIcon refreshIcon = QmitkStyleManager::ThemeIcon(QStringLiteral(":/org_mitk_icons/icons/awesome/scalable/actions/view-refresh.svg")); m_Controls.fetchUrl->setIcon(refreshIcon); Superclass::InitializeUI(mainLayout); } void QmitkMonaiLabelToolGUI::EnableWidgets(bool enabled) { Superclass::EnableWidgets(enabled); } void QmitkMonaiLabelToolGUI::OnFetchBtnClicked() { auto tool = this->GetConnectedToolAs(); if (nullptr != tool) { tool->m_InfoParameters.reset(); m_Controls.modelBox->clear(); m_Controls.appBox->clear(); QString urlString = m_Controls.urlBox->text(); QUrl url(urlString); if (url.isValid() && !url.isLocalFile() && !url.hasFragment() && !url.hasQuery()) // sanity check { QString hostName = url.host(); int port = url.port(); try { tool->GetOverallInfo(hostName.toStdString(), port); // tool->GetOverallInfo("localhost",8000"); if (nullptr != tool->m_InfoParameters) { std::string response = tool->m_InfoParameters->name; std::vector autoModels = tool->GetAutoSegmentationModels(); m_Controls.responseNote->setText(QString::fromStdString(response)); m_Controls.appBox->addItem(QString::fromStdString(response)); for (auto &model : autoModels) { m_Controls.modelBox->addItem(QString::fromStdString(model.name)); } } } catch (const mitk::Exception &e) { MITK_ERROR << e.GetDescription(); // Add GUI msg box to show } } else { MITK_ERROR << "Invalid URL entered: " << urlString.toStdString(); // set as status message on GUI } } } void QmitkMonaiLabelToolGUI::OnPreviewBtnClicked() { auto tool = this->GetConnectedToolAs(); if (nullptr != tool) { - if (false) + bool test = false; + if (!test) { std::string selectedModel = m_Controls.modelBox->currentText().toStdString(); for (const mitk::MonaiModelInfo &modelObject : tool->m_InfoParameters->models) { if (modelObject.name == selectedModel) { - tool->m_RequestParameters = std::make_unique(); - tool->m_RequestParameters->model = modelObject; - tool->m_RequestParameters->hostName = tool->m_InfoParameters->hostName; - tool->m_RequestParameters->port = tool->m_InfoParameters->port; - MITK_INFO << "tool found" << selectedModel; + auto requestObject = std::make_unique(); + requestObject->model = modelObject; + requestObject->hostName = tool->m_InfoParameters->hostName; + requestObject->port = tool->m_InfoParameters->port; + if (!m_FirstPreviewComputation && tool->GetIsLastSuccess() && *requestObject == *(tool->m_RequestParameters)) + { + MITK_INFO << "won't do segmentation..."; + return; + } + else + { + tool->m_RequestParameters = std::move(requestObject); + } break; } } } else { MITK_INFO << " RUNNING ON TEST parameters..."; tool->m_RequestParameters = std::make_unique(); mitk::MonaiModelInfo modelObject; modelObject.name = "deepedit_seg"; tool->m_RequestParameters->model = modelObject; tool->m_RequestParameters->hostName = "localhost"; tool->m_RequestParameters->port = 8000; } try { tool->UpdatePreview(); + m_FirstPreviewComputation = false; + this->SetLabelSetPreview(tool->GetPreviewSegmentation()); + tool->IsTimePointChangeAwareOn(); + this->ActualizePreviewLabelVisibility(); } - catch (const mitk::Exception &e) + catch (...) { - MITK_ERROR << "Connection error"; // Add GUI msg box to show + MITK_ERROR << "Connection error"; //This catch is never reached when exception is thrown in UpdatePreview method } - mitk::LabelSetImage *temp = tool->GetPreviewSegmentation(); - for (int i = 0; i < temp->GetNumberOfLabels(); ++i) - { - mitk::Label *labelptr = temp->GetLabel(i, 0); - if (nullptr != labelptr) - { - MITK_INFO << "Label with name: " << labelptr->GetName(); - } - else - { - MITK_INFO << "nullptr found for " << i; - } - } - - this->SetLabelSetPreview(tool->GetPreviewSegmentation()); - tool->IsTimePointChangeAwareOn(); - this->ActualizePreviewLabelVisibility(); } }