diff --git a/Modules/SemanticRelations/include/mitkLesionManager.h b/Modules/SemanticRelations/include/mitkLesionManager.h index 976d8f43de..49182efdd3 100644 --- a/Modules/SemanticRelations/include/mitkLesionManager.h +++ b/Modules/SemanticRelations/include/mitkLesionManager.h @@ -1,75 +1,75 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef MITKLESIONMANAGER_H #define MITKLESIONMANAGER_H #include // semantic relations module #include "mitkLesionData.h" /* * @brief Provides helper functions that are needed to work with lesions. * * These functions help to generate new lesions, check for existing lesions or provide functionality * to generate new and find existing lesion class types. */ namespace mitk { typedef std::vector LesionClassVector; /** * @brief Generate a new lesion and lesion class with UIDs and the given string as lesion class type. * * @param lesionClassType The lesion class type as string. Default parameter is "". */ MITKSEMANTICRELATIONS_EXPORT SemanticTypes::Lesion GenerateNewLesion(const std::string& lesionClassType = ""); /** * @brief Generate a new lesion class with UID and the given string as lesion class type. * * @param lesionClassType The lesion class type as string. Default parameter is "". */ MITKSEMANTICRELATIONS_EXPORT SemanticTypes::LesionClass GenerateNewLesionClass(const std::string& lesionClassType = ""); /** * @brief Find and return a whole lesion including its lesion class given a specific lesion UID. * - * @param lesionUID The lesion UID as string. - * @param allLesions All currently known lesions of a specific case. + * @param caseID The current case identifier is defined by the given string. + * @param lesionUID The lesion UID as string. * * @return The lesion with its UID and the lesion class. */ - MITKSEMANTICRELATIONS_EXPORT SemanticTypes::Lesion GetLesionByUID(const SemanticTypes::ID& lesionUID, const std::vector& allLesions); + MITKSEMANTICRELATIONS_EXPORT SemanticTypes::Lesion GetLesionByUID(const SemanticTypes::CaseID& caseID, const SemanticTypes::ID& lesionUID); /** * @brief Find and return the whole lesion class including its UID given a specific lesion class type. * + * @param caseID The current case identifier is defined by the given string. * @param lesionClassType The lesion class type as string. - * @param allLesionClasses All currently known lesion classes of a specific case. * * @return The lesion class with its UID and the class type. */ - MITKSEMANTICRELATIONS_EXPORT SemanticTypes::LesionClass FindExistingLesionClass(const std::string& lesionClassType, const std::vector& allLesionClasses); + MITKSEMANTICRELATIONS_EXPORT SemanticTypes::LesionClass FindExistingLesionClass(const SemanticTypes::CaseID& caseID, const std::string& lesionClassType); /** * @brief Compute and store lesion presence for all available control points and information types. * * @param lesionData The lesion data that holds the lesion and will hold the additional lesion data. * @param caseID The current case ID. */ MITKSEMANTICRELATIONS_EXPORT void ComputeLesionPresence(LesionData& lesionData, const SemanticTypes::CaseID& caseID); } // namespace mitk #endif // MITKLESIONMANAGER_H diff --git a/Modules/SemanticRelations/src/mitkLesionManager.cpp b/Modules/SemanticRelations/src/mitkLesionManager.cpp index 2811d241ba..f2565c1e1c 100644 --- a/Modules/SemanticRelations/src/mitkLesionManager.cpp +++ b/Modules/SemanticRelations/src/mitkLesionManager.cpp @@ -1,102 +1,104 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ // semantic relations module #include "mitkLesionManager.h" #include "mitkRelationStorage.h" #include "mitkSemanticRelationException.h" #include "mitkSemanticRelationsInference.h" #include "mitkUIDGeneratorBoost.h" mitk::SemanticTypes::Lesion mitk::GenerateNewLesion(const std::string& lesionClassType/* = ""*/) { SemanticTypes::Lesion lesion; lesion.UID = UIDGeneratorBoost::GenerateUID(); lesion.name = "New lesion"; lesion.lesionClass = GenerateNewLesionClass(lesionClassType); return lesion; } mitk::SemanticTypes::LesionClass mitk::GenerateNewLesionClass(const std::string& lesionClassType/* = ""*/) { SemanticTypes::LesionClass lesionClass; lesionClass.UID = UIDGeneratorBoost::GenerateUID(); lesionClass.classType = lesionClassType; return lesionClass; } -mitk::SemanticTypes::Lesion mitk::GetLesionByUID(const SemanticTypes::ID& lesionUID, const std::vector& allLesions) +mitk::SemanticTypes::Lesion mitk::GetLesionByUID(const SemanticTypes::CaseID& caseID, const SemanticTypes::ID& lesionUID) { auto lambda = [&lesionUID](const SemanticTypes::Lesion& currentLesion) { return currentLesion.UID == lesionUID; }; + SemanticTypes::LesionVector allLesions = RelationStorage::GetAllLesionsOfCase(caseID); const auto existingLesion = std::find_if(allLesions.begin(), allLesions.end(), lambda); SemanticTypes::Lesion lesion; if (existingLesion != allLesions.end()) { lesion = *existingLesion; } return lesion; } -mitk::SemanticTypes::LesionClass mitk::FindExistingLesionClass(const std::string& lesionClassType, const std::vector& allLesionClasses) +mitk::SemanticTypes::LesionClass mitk::FindExistingLesionClass(const SemanticTypes::CaseID& caseID, const std::string& lesionClassType) { auto lambda = [&lesionClassType](const SemanticTypes::LesionClass& currentLesionClass) { return currentLesionClass.classType == lesionClassType; }; + SemanticTypes::LesionClassVector allLesionClasses = SemanticRelationsInference::GetAllLesionClassesOfCase(caseID); const auto existingLesionClass = std::find_if(allLesionClasses.begin(), allLesionClasses.end(), lambda); SemanticTypes::LesionClass lesionClass; if (existingLesionClass != allLesionClasses.end()) { lesionClass = *existingLesionClass; } return lesionClass; } void mitk::ComputeLesionPresence(LesionData& lesionData, const SemanticTypes::CaseID& caseID) { std::vector lesionPresence; auto lesion = lesionData.GetLesion(); bool presence = false; auto controlPoints = RelationStorage::GetAllControlPointsOfCase(caseID); // sort the vector of control points for the timeline std::sort(controlPoints.begin(), controlPoints.end()); for (const auto& controlPoint : controlPoints) { try { presence = SemanticRelationsInference::IsLesionPresentAtControlPoint(caseID, lesion, controlPoint); } catch (SemanticRelationException&) { presence = false; } lesionPresence.push_back(presence); } lesionData.SetLesionPresence(lesionPresence); } diff --git a/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkLesionInfoWidget.cpp b/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkLesionInfoWidget.cpp index 4a6872c152..7733d6f650 100644 --- a/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkLesionInfoWidget.cpp +++ b/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkLesionInfoWidget.cpp @@ -1,496 +1,496 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ // semantic relations plugin #include "QmitkLesionInfoWidget.h" #include "QmitkDataNodeAddToSemanticRelationsAction.h" #include "QmitkFocusOnLesionAction.h" #include "QmitkSemanticRelationsNodeSelectionDialog.h" // semantic relations UI module #include // semantic relations module #include #include #include #include #include // segmentation #include #include // qt #include #include #include #include #include QmitkLesionInfoWidget::QmitkLesionInfoWidget(mitk::DataStorage* dataStorage, berry::IWorkbenchPartSite::Pointer workbenchPartSite, QWidget* parent /*= nullptr*/) : QWidget(parent) , m_DataStorage(dataStorage) , m_WorkbenchPartSite(workbenchPartSite) , m_SemanticRelationsDataStorageAccess(std::make_unique(dataStorage)) , m_SemanticRelationsIntegration(std::make_unique()) { Initialize(); } void QmitkLesionInfoWidget::Initialize() { m_Controls.setupUi(this); m_Controls.lesionTreeView->setAlternatingRowColors(true); m_Controls.lesionTreeView->setSelectionMode(QAbstractItemView::SingleSelection); m_Controls.lesionTreeView->setSelectionBehavior(QAbstractItemView::SelectRows); m_Controls.lesionTreeView->setContextMenuPolicy(Qt::CustomContextMenu); m_StorageModel = new QmitkLesionTreeModel(m_Controls.lesionTreeView); if (m_DataStorage.IsExpired()) { return; } auto dataStorage = m_DataStorage.Lock(); m_StorageModel->SetDataStorage(dataStorage); m_Controls.lesionTreeView->setModel(m_StorageModel); SetUpConnections(); } void QmitkLesionInfoWidget::SetUpConnections() { connect(m_StorageModel, &QmitkLesionTreeModel::ModelUpdated, this, &QmitkLesionInfoWidget::OnModelUpdated); connect(m_Controls.addLesionPushButton, &QPushButton::clicked, this, &QmitkLesionInfoWidget::OnAddLesionButtonClicked); connect(m_Controls.lesionTreeView->selectionModel(), &QItemSelectionModel::currentChanged, this, &QmitkLesionInfoWidget::OnSelectionChanged); connect(m_Controls.lesionTreeView, &QTreeView::customContextMenuRequested, this, &QmitkLesionInfoWidget::OnLesionListContextMenuRequested); } void QmitkLesionInfoWidget::SetCaseID(const mitk::SemanticTypes::CaseID& caseID) { m_CaseID = caseID; m_StorageModel->SetCaseID(caseID); } void QmitkLesionInfoWidget::SetDataNodeSelection(const QList& dataNodeSelection) { m_StorageModel->SetDataNodeSelection(dataNodeSelection); } ////////////////////////////////////////////////////////////////////////// // Implementation of the QT_SLOTS ////////////////////////////////////////////////////////////////////////// void QmitkLesionInfoWidget::OnModelUpdated() { m_Controls.lesionTreeView->expandAll(); int columns = m_Controls.lesionTreeView->model()->columnCount(); for (int i = 0; i < columns; ++i) { m_Controls.lesionTreeView->resizeColumnToContents(i); } } void QmitkLesionInfoWidget::OnAddLesionButtonClicked() { if (m_CaseID.empty()) { QMessageBox msgBox(QMessageBox::Warning, "No case ID set.", "In order to add a lesion, please specify the current case / patient."); msgBox.exec(); return; } mitk::SemanticTypes::Lesion newLesion = mitk::GenerateNewLesion(); try { m_SemanticRelationsIntegration->AddLesion(m_CaseID, newLesion); } catch (mitk::SemanticRelationException& e) { MITK_INFO << "Could not add a new lesion. " << e; } } void QmitkLesionInfoWidget::OnSelectionChanged(const QModelIndex& current, const QModelIndex& /*previous*/) { // only the UID is needed to identify a representing lesion QVariant data = m_StorageModel->data(current, Qt::UserRole); if (!data.canConvert()) { return; } auto lesion = data.value()->GetData().GetLesion(); if (false == mitk::SemanticRelationsInference::InstanceExists(m_CaseID, lesion)) { // no UID of a existing lesion found; cannot create a lesion return; } // if selected data nodes are set, reset to empty list to // hide "selected data nodes presence background highlighting" in the model if (!m_StorageModel->GetSelectedDataNodes().isEmpty()) { m_StorageModel->SetDataNodeSelection(QList()); } emit LesionSelectionChanged(lesion); } void QmitkLesionInfoWidget::OnLesionListContextMenuRequested(const QPoint& pos) { if (nullptr == m_SemanticRelationsIntegration) { return; } if (m_CaseID.empty()) { QMessageBox msgBox(QMessageBox::Warning, "No case ID set.", "In order to access the context menu entries a case ID has to be set."); msgBox.exec(); return; } QModelIndex index = m_Controls.lesionTreeView->indexAt(pos); if (!index.isValid()) { // no item clicked; cannot retrieve the current lesion return; } QVariant data = m_StorageModel->data(index, Qt::UserRole); mitk::SemanticTypes::Lesion selectedLesion; if (data.canConvert()) { selectedLesion = data.value()->GetData().GetLesion(); } else { return; } QMenu* menu = new QMenu(m_Controls.lesionTreeView); QAction* linkToSegmentation = new QAction("Link to segmentation", this); linkToSegmentation->setEnabled(true); connect(linkToSegmentation, &QAction::triggered, [this, selectedLesion] { OnLinkToSegmentation(selectedLesion); }); menu->addAction(linkToSegmentation); QAction* setLesionName = new QAction("Set lesion name", this); setLesionName->setEnabled(true); connect(setLesionName, &QAction::triggered, [this, selectedLesion] { OnSetLesionName(selectedLesion); }); menu->addAction(setLesionName); QAction* setLesionClass = new QAction("Set lesion class", this); setLesionClass->setEnabled(true); connect(setLesionClass, &QAction::triggered, [this, selectedLesion] { OnSetLesionClass(selectedLesion); }); menu->addAction(setLesionClass); QAction* createNewSegmentation = new QAction("Create new lesion", this); createNewSegmentation->setEnabled(true); connect(createNewSegmentation, &QAction::triggered, [this, selectedLesion] { OnCreateNewSegmentation(selectedLesion); }); menu->addAction(createNewSegmentation); QAction* removeLesion = new QAction("Remove lesion", this); removeLesion->setEnabled(true); connect(removeLesion, &QAction::triggered, [this, selectedLesion] { OnRemoveLesion(selectedLesion); }); menu->addAction(removeLesion); if (!m_WorkbenchPartSite.Expired()) { QmitkFocusOnLesionAction* focusOnLesion = new QmitkFocusOnLesionAction(this, m_WorkbenchPartSite.Lock()); focusOnLesion->SetDataStorage(m_DataStorage.Lock()); focusOnLesion->SetSelectedLesion(selectedLesion); menu->addAction(focusOnLesion); } menu->popup(QCursor::pos()); } void QmitkLesionInfoWidget::OnLinkToSegmentation(mitk::SemanticTypes::Lesion selectedLesion) { if (m_DataStorage.IsExpired()) { return; } auto dataStorage = m_DataStorage.Lock(); QmitkSemanticRelationsNodeSelectionDialog* dialog = new QmitkSemanticRelationsNodeSelectionDialog(this, "Select segmentation to link to the selected lesion.", ""); dialog->setWindowTitle("Select segmentation node"); dialog->SetDataStorage(dataStorage); dialog->SetNodePredicate(mitk::NodePredicates::GetSegmentationPredicate()); dialog->SetSelectOnlyVisibleNodes(true); dialog->SetCaseID(m_CaseID); // set the last added segmentation node as pre-selected data node const mitk::DataNode* lastSegmentation = m_StorageModel->GetLastSegmentation(); QList selectedDataNodes; if (nullptr != lastSegmentation) { selectedDataNodes.push_back(const_cast(lastSegmentation)); dialog->SetCurrentSelection(selectedDataNodes); } int dialogReturnValue = dialog->exec(); if (QDialog::Rejected == dialogReturnValue) { return; } mitk::DataNode::Pointer selectedDataNode = nullptr; selectedDataNodes = dialog->GetSelectedNodes(); if (!selectedDataNodes.isEmpty()) { // only single selection allowed selectedDataNode = selectedDataNodes.front(); } if (nullptr == selectedDataNode || false == mitk::NodePredicates::GetSegmentationPredicate()->CheckNode(selectedDataNode)) { QMessageBox msgBox(QMessageBox::Warning, "No valid segmentation node selected.", "In order to link the selected lesion to a segmentation, please specify a valid segmentation node."); msgBox.exec(); return; } mitk::BaseData* baseData = selectedDataNode->GetData(); if (nullptr == baseData) { QMessageBox msgBox(QMessageBox::Warning, "No valid base data.", "In order to link the selected lesion to a segmentation, please specify a valid segmentation node."); msgBox.exec(); return; } LinkSegmentationToLesion(selectedDataNode, selectedLesion); } void QmitkLesionInfoWidget::OnSetLesionName(mitk::SemanticTypes::Lesion selectedLesion) { // use the lesion information to set the input text for the dialog QmitkLesionTextDialog* inputDialog = new QmitkLesionTextDialog(this); inputDialog->setWindowTitle("Set lesion name"); inputDialog->SetLineEditText(selectedLesion.name); int dialogReturnValue = inputDialog->exec(); if (QDialog::Rejected == dialogReturnValue) { return; } selectedLesion.name = inputDialog->GetLineEditText().toStdString(); m_SemanticRelationsIntegration->OverwriteLesion(m_CaseID, selectedLesion); } void QmitkLesionInfoWidget::OnSetLesionClass(mitk::SemanticTypes::Lesion selectedLesion) { // use the lesion information to set the input text for the dialog QmitkLesionTextDialog* inputDialog = new QmitkLesionTextDialog(this); inputDialog->setWindowTitle("Set lesion class"); inputDialog->SetLineEditText(selectedLesion.lesionClass.classType); // prepare the completer for the dialogs input text field mitk::LesionClassVector allLesionClasses = mitk::SemanticRelationsInference::GetAllLesionClassesOfCase(m_CaseID); QStringList wordList; for (const auto& lesionClass : allLesionClasses) { wordList << QString::fromStdString(lesionClass.classType); } QCompleter* completer = new QCompleter(wordList, this); completer->setCaseSensitivity(Qt::CaseInsensitive); inputDialog->GetLineEdit()->setCompleter(completer); int dialogReturnValue = inputDialog->exec(); if (QDialog::Rejected == dialogReturnValue) { return; } // retrieve the new input lesion class type and check for an already existing lesion class types std::string newLesionClassType = inputDialog->GetLineEditText().toStdString(); - mitk::SemanticTypes::LesionClass existingLesionClass = mitk::FindExistingLesionClass(newLesionClassType, allLesionClasses); + mitk::SemanticTypes::LesionClass existingLesionClass = mitk::FindExistingLesionClass(m_CaseID, newLesionClassType); if (existingLesionClass.UID.empty()) { // could not find lesion class information for the new lesion class type // create a new lesion class for the selected lesion existingLesionClass = mitk::GenerateNewLesionClass(newLesionClassType); } selectedLesion.lesionClass = existingLesionClass; m_SemanticRelationsIntegration->OverwriteLesion(m_CaseID, selectedLesion); } void QmitkLesionInfoWidget::OnCreateNewSegmentation(mitk::SemanticTypes::Lesion selectedLesion) { if (m_DataStorage.IsExpired()) { return; } auto dataStorage = m_DataStorage.Lock(); QmitkSemanticRelationsNodeSelectionDialog* dialog = new QmitkSemanticRelationsNodeSelectionDialog(this, "Select image to segment lesion on.", ""); dialog->setWindowTitle("Select image node"); dialog->SetDataStorage(dataStorage); dialog->SetNodePredicate(mitk::NodePredicates::GetImagePredicate()); dialog->SetSelectOnlyVisibleNodes(true); dialog->SetCaseID(m_CaseID); dialog->SetLesion(selectedLesion); int dialogReturnValue = dialog->exec(); if (QDialog::Rejected == dialogReturnValue) { return; } auto nodes = dialog->GetSelectedNodes(); mitk::DataNode::Pointer selectedDataNode = nullptr; if (!nodes.isEmpty()) { // only single selection allowed selectedDataNode = nodes.front(); } if (nullptr == selectedDataNode || false == mitk::NodePredicates::GetImagePredicate()->CheckNode(selectedDataNode)) { QMessageBox msgBox(QMessageBox::Warning, "No valid image node selected.", "In order to create a new segmentation, please specify a valid image node."); msgBox.exec(); return; } mitk::Image* selectedImage = dynamic_cast(selectedDataNode->GetData()); if (nullptr == selectedImage) { QMessageBox msgBox(QMessageBox::Warning, "No valid image.", "In order to create a new segmentation, please specify a valid image node."); msgBox.exec(); return; } mitk::LabelSetImage::Pointer segmentation = mitk::LabelSetImage::New(); try { segmentation->Initialize(selectedImage); } catch (mitk::Exception& e) { std::stringstream exceptionMessage; exceptionMessage << e; QMessageBox msgBox(QMessageBox::Warning, "Could not initialize segmentation.", "The segmentation could not be correctly initialized with the selected image geometry.\n" "Reason:\n" + QString::fromStdString(exceptionMessage.str())); msgBox.exec(); return; } auto segmentationDialog = new QmitkNewSegmentationDialog(this); segmentationDialog->setWindowTitle("New lesion segmentation"); dialogReturnValue = segmentationDialog->exec(); if (dialogReturnValue == QDialog::Rejected) { return; } QString segmentatioName = segmentationDialog->GetSegmentationName(); if (segmentatioName.isEmpty()) { segmentatioName = "Unnamed"; } segmentation->GetActiveLabelSet()->AddLabel(segmentatioName.toStdString(), segmentationDialog->GetColor()); mitk::DataNode::Pointer segmentationNode = mitk::DataNode::New(); segmentationNode->SetData(segmentation); segmentationNode->SetName(segmentatioName.toStdString()); dataStorage->Add(segmentationNode, selectedDataNode); LinkSegmentationToLesion(segmentationNode, selectedLesion); } void QmitkLesionInfoWidget::OnRemoveLesion(mitk::SemanticTypes::Lesion selectedLesion) { try { m_SemanticRelationsIntegration->RemoveLesion(m_CaseID, selectedLesion); } catch (const mitk::SemanticRelationException& e) { std::stringstream exceptionMessage; exceptionMessage << e; QMessageBox msgBox(QMessageBox::Warning, "Could not remove the selected lesion.", "The program wasn't able to correctly remove the selected lesion from the semantic relations model.\n" "Reason:\n" + QString::fromStdString(exceptionMessage.str())); msgBox.exec(); } } void QmitkLesionInfoWidget::LinkSegmentationToLesion(const mitk::DataNode* selectedDataNode, mitk::SemanticTypes::Lesion selectedLesion) { if (m_DataStorage.IsExpired()) { return; } auto dataStorage = m_DataStorage.Lock(); // if the segmentation is not contained in the semantic relations, add it if (!mitk::SemanticRelationsInference::InstanceExists(selectedDataNode)) { try { AddToSemanticRelationsAction::Run(dataStorage, selectedDataNode); } catch (const mitk::SemanticRelationException& e) { std::stringstream exceptionMessage; exceptionMessage << e; QMessageBox msgBox(QMessageBox::Warning, "Could not link the selected lesion.", "The program wasn't able to correctly link the selected lesion with the selected segmentation.\n" "Reason:\n" + QString::fromStdString(exceptionMessage.str() + "\n")); msgBox.exec(); } } // link the segmentation try { m_SemanticRelationsIntegration->LinkSegmentationToLesion(selectedDataNode, selectedLesion); } catch (const mitk::SemanticRelationException& e) { std::stringstream exceptionMessage; exceptionMessage << e; QMessageBox msgBox(QMessageBox::Warning, "Could not link the selected lesion.", "The program wasn't able to correctly link the selected lesion with the selected segmentation.\n" "Reason:\n" + QString::fromStdString(exceptionMessage.str())); msgBox.exec(); } }