diff --git a/Modules/SemanticRelationsUI/include/QmitkPatientTableInspector.h b/Modules/SemanticRelationsUI/include/QmitkPatientTableInspector.h index bad2bf318a..61a13358cc 100644 --- a/Modules/SemanticRelationsUI/include/QmitkPatientTableInspector.h +++ b/Modules/SemanticRelationsUI/include/QmitkPatientTableInspector.h @@ -1,81 +1,86 @@ /*=================================================================== 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 QMITKPATIENTTABLEINSPECTOR_H #define QMITKPATIENTTABLEINSPECTOR_H // semantic relations UI module #include "MitkSemanticRelationsUIExports.h" #include #include #include "ui_QmitkPatientTableInspector.h" // qt widgets module #include "QmitkEnums.h" // qt #include /* * @brief The QmitkPatientTableInspector is a QmitkAbstractSemanticRelationsStorageInspector that shows the currently * available data of the semantic relations storage model in a control-point - information type matrix. * * The QmitkPatientTableInspector uses the QmitkSemanticRelationsStorageModel, a QmitkAbstractDataStorageModel that * presents the semantic relations data as a table, showing a QPixmap as thumbnail for the data nodes. */ class MITKSEMANTICRELATIONSUI_EXPORT QmitkPatientTableInspector : public QmitkAbstractSemanticRelationsStorageInspector { Q_OBJECT public: QmitkPatientTableInspector(QWidget* parent = nullptr); virtual QAbstractItemView* GetView() override; virtual const QAbstractItemView* GetView() const override; virtual void SetSelectionMode(SelectionMode mode) override; virtual SelectionMode GetSelectionMode() const override; virtual void SetCaseID(const mitk::SemanticTypes::CaseID& caseID) override; virtual void SetLesion(const mitk::SemanticTypes::Lesion& lesion) override; -protected: +Q_SIGNALS: - virtual void Initialize() override; + void DataNodeDoubleClicked(const mitk::DataNode::Pointer); private Q_SLOTS: void OnModelUpdated(); - void OnNodeButtonClicked(const QString& nodeType); + void OnNodeButtonClicked(const QString&); void OnTableViewContextMenuRequested(const QPoint&); void OnContextMenuSetInformationType(); void OnContextMenuSetControlPoint(); + void OnItemDoubleClicked(const QModelIndex&); + +protected: + + virtual void Initialize() override; private: void SetUpConnections(); Ui::QmitkPatientTableInspector m_Controls; QmitkPatientTableModel* m_StorageModel; QMenu* m_ContextMenu; mitk::DataNode* m_SelectedDataNode; }; #endif // QMITKPATIENTTABLEINSPECTOR_H diff --git a/Modules/SemanticRelationsUI/src/QmitkPatientTableInspector.cpp b/Modules/SemanticRelationsUI/src/QmitkPatientTableInspector.cpp index 184ad698b9..3625715ab2 100644 --- a/Modules/SemanticRelationsUI/src/QmitkPatientTableInspector.cpp +++ b/Modules/SemanticRelationsUI/src/QmitkPatientTableInspector.cpp @@ -1,255 +1,272 @@ /*=================================================================== 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 UI module #include "QmitkPatientTableInspector.h" #include "QmitkControlPointDialog.h" +// mitk qt widgets module #include "QmitkCustomVariants.h" +#include "QmitkEnums.h" // semantic relations module #include #include #include // qt #include #include QmitkPatientTableInspector::QmitkPatientTableInspector(QWidget* parent/* =nullptr*/) { m_Controls.setupUi(this); m_Controls.tableView->horizontalHeader()->setHighlightSections(false); m_Controls.tableView->verticalHeader()->setHighlightSections(false); m_Controls.tableView->setSelectionMode(QAbstractItemView::SingleSelection); m_Controls.tableView->setSelectionBehavior(QAbstractItemView::SelectItems); m_Controls.tableView->setContextMenuPolicy(Qt::CustomContextMenu); m_StorageModel = new QmitkPatientTableModel(this); m_Controls.tableView->setModel(m_StorageModel); m_ContextMenu = new QMenu(m_Controls.tableView); SetUpConnections(); } QAbstractItemView* QmitkPatientTableInspector::GetView() { return m_Controls.tableView; } const QAbstractItemView* QmitkPatientTableInspector::GetView() const { return m_Controls.tableView; } void QmitkPatientTableInspector::SetSelectionMode(SelectionMode mode) { m_Controls.tableView->setSelectionMode(mode); } QmitkPatientTableInspector::SelectionMode QmitkPatientTableInspector::GetSelectionMode() const { return m_Controls.tableView->selectionMode(); } void QmitkPatientTableInspector::SetCaseID(const mitk::SemanticTypes::CaseID& caseID) { m_StorageModel->SetCaseID(caseID); } void QmitkPatientTableInspector::SetLesion(const mitk::SemanticTypes::Lesion& lesion) { m_StorageModel->SetLesion(lesion); } void QmitkPatientTableInspector::Initialize() { m_StorageModel->SetDataStorage(m_DataStorage.Lock()); m_StorageModel->SetNodePredicate(m_NodePredicate); m_Connector->SetView(m_Controls.tableView); } void QmitkPatientTableInspector::OnModelUpdated() { m_Controls.tableView->resizeRowsToContents(); m_Controls.tableView->resizeColumnsToContents(); } void QmitkPatientTableInspector::OnNodeButtonClicked(const QString& nodeType) { m_StorageModel->SetNodeType(nodeType.toStdString()); } void QmitkPatientTableInspector::OnTableViewContextMenuRequested(const QPoint& pos) { QModelIndex selectedIndex = m_Controls.tableView->indexAt(pos); if (!selectedIndex.isValid()) { return; } QVariant qvariantDataNode = m_StorageModel->data(selectedIndex, QmitkDataNodeRawPointerRole); if (qvariantDataNode.canConvert()) { m_SelectedDataNode = qvariantDataNode.value(); m_ContextMenu->clear(); QAction* setInformationTypeAction = new QAction("Set information type", m_ContextMenu); m_ContextMenu->addAction(setInformationTypeAction); connect(setInformationTypeAction, &QAction::triggered, this, &QmitkPatientTableInspector::OnContextMenuSetInformationType); QAction* setControlPointAction = new QAction("Set control point", m_ContextMenu); m_ContextMenu->addAction(setControlPointAction); connect(setControlPointAction, &QAction::triggered, this, &QmitkPatientTableInspector::OnContextMenuSetControlPoint); m_ContextMenu->popup(QCursor::pos()); } } void QmitkPatientTableInspector::OnContextMenuSetInformationType() { bool ok = false; QString text = QInputDialog::getText(m_Controls.tableView, tr("Set information type of selected node"), tr("Information type:"), QLineEdit::Normal, "", &ok); if (ok && !text.isEmpty()) { m_StorageModel->GetSemanticRelations()->RemoveInformationTypeFromImage(m_SelectedDataNode); m_StorageModel->GetSemanticRelations()->AddInformationTypeToImage(m_SelectedDataNode, text.toStdString()); m_StorageModel->UpdateModelData(); } } void QmitkPatientTableInspector::OnContextMenuSetControlPoint() { QmitkControlPointDialog* inputDialog = new QmitkControlPointDialog(m_Controls.tableView); inputDialog->setWindowTitle("Set control point"); inputDialog->SetCurrentDate(mitk::GetDICOMDateFromDataNode(m_SelectedDataNode)); int dialogReturnValue = inputDialog->exec(); if (QDialog::Rejected == dialogReturnValue) { return; } // store the current control point to relink it, if anything goes wrong mitk::SemanticTypes::ControlPoint originalControlPoint = m_StorageModel->GetSemanticRelations()->GetControlPointOfData(m_SelectedDataNode); // unlink the data, that is about to receive a new date // this is needed in order to not extend a single control point, to which the selected node is currently linked m_StorageModel->GetSemanticRelations()->UnlinkDataFromControlPoint(m_SelectedDataNode); const QDate& userSelectedDate = inputDialog->GetCurrentDate(); mitk::SemanticTypes::Date date; date.UID = mitk::UIDGeneratorBoost::GenerateUID(); date.year = userSelectedDate.year(); date.month = userSelectedDate.month(); date.day = userSelectedDate.day(); std::vector allControlPoints = m_StorageModel->GetSemanticRelations()->GetAllControlPointsOfCase(m_StorageModel->GetCaseID()); if (!allControlPoints.empty()) { // need to check if an already existing control point fits/contains the user control point mitk::SemanticTypes::ControlPoint fittingControlPoint = mitk::FindFittingControlPoint(date, allControlPoints); if (!fittingControlPoint.UID.empty()) { try { // found a fitting control point m_StorageModel->GetSemanticRelations()->LinkDataToControlPoint(m_SelectedDataNode, fittingControlPoint, false); m_StorageModel->UpdateModelData(); } catch (const mitk::SemanticRelationException&) { MITK_INFO << "The data can not be linked to the fitting control point."; try { // link to the original control point m_StorageModel->GetSemanticRelations()->LinkDataToControlPoint(m_SelectedDataNode, originalControlPoint, false); } catch (const mitk::SemanticRelationException&) { MITK_INFO << "The data can not be linked to its original control point. Inconsistency in the semantic relations storage assumed."; } } return; } // did not find a fitting control point, although some control points already exist // need to check if a close control point can be found and extended mitk::SemanticTypes::ControlPoint extendedControlPoint = mitk::ExtendClosestControlPoint(date, allControlPoints); if (!extendedControlPoint.UID.empty()) { try { // found and extended a close control point m_StorageModel->GetSemanticRelations()->OverwriteControlPointAndLinkData(m_SelectedDataNode, extendedControlPoint, false); m_StorageModel->UpdateModelData(); } catch (const mitk::SemanticRelationException&) { MITK_INFO << "The extended control point can not be overwritten and the data can not be linked to this control point."; try { // link to the original control point m_StorageModel->GetSemanticRelations()->LinkDataToControlPoint(m_SelectedDataNode, originalControlPoint, false); } catch (const mitk::SemanticRelationException&) { MITK_INFO << "The data can not be linked to its original control point. Inconsistency in the semantic relations storage assumed."; } } return; } } // generate a control point from the user-given date mitk::SemanticTypes::ControlPoint controlPointFromUserDate = mitk::GenerateControlPoint(date); try { m_StorageModel->GetSemanticRelations()->AddControlPointAndLinkData(m_SelectedDataNode, controlPointFromUserDate, false); m_StorageModel->UpdateModelData(); } catch (const mitk::SemanticRelationException&) { MITK_INFO << "The control point can not be added and the data can not be linked to this control point."; try { // link to the original control point m_StorageModel->GetSemanticRelations()->LinkDataToControlPoint(m_SelectedDataNode, originalControlPoint, false); } catch (const mitk::SemanticRelationException&) { MITK_INFO << "The data can not be linked to its original control point. Inconsistency in the semantic relations storage assumed."; } } } +void QmitkPatientTableInspector::OnItemDoubleClicked(const QModelIndex& itemIndex) +{ + if (itemIndex.isValid()) + { + QVariant qvariantDataNode = m_StorageModel->data(itemIndex, QmitkDataNodeRole); + if (qvariantDataNode.canConvert()) + { + mitk::DataNode::Pointer dataNode = qvariantDataNode.value(); + emit DataNodeDoubleClicked(dataNode); + } + } +} + void QmitkPatientTableInspector::SetUpConnections() { - connect(m_StorageModel, SIGNAL(ModelUpdated()), SLOT(OnModelUpdated())); - connect(m_Controls.tableView, SIGNAL(customContextMenuRequested(const QPoint&)), SLOT(OnTableViewContextMenuRequested(const QPoint&))); + connect(m_StorageModel, &QmitkPatientTableModel::ModelUpdated, this, &QmitkPatientTableInspector::OnModelUpdated); + connect(m_Controls.tableView, &QTableView::customContextMenuRequested, this, &QmitkPatientTableInspector::OnTableViewContextMenuRequested); QSignalMapper* nodeButtonSignalMapper = new QSignalMapper(this); nodeButtonSignalMapper->setMapping(m_Controls.imageNodeButton, QString("Image")); nodeButtonSignalMapper->setMapping(m_Controls.segmentationNodeButton, QString("Segmentation")); - connect(nodeButtonSignalMapper, SIGNAL(mapped(const QString&)), this, SLOT(OnNodeButtonClicked(const QString&))); + connect(nodeButtonSignalMapper, static_cast(&QSignalMapper::mapped), this, &QmitkPatientTableInspector::OnNodeButtonClicked); connect(m_Controls.imageNodeButton, SIGNAL(clicked()), nodeButtonSignalMapper, SLOT(map())); connect(m_Controls.segmentationNodeButton, SIGNAL(clicked()), nodeButtonSignalMapper, SLOT(map())); m_Controls.imageNodeButton->setChecked(true); + + connect(m_Controls.tableView, &QTableView::doubleClicked, this, &QmitkPatientTableInspector::OnItemDoubleClicked); } 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 22e2450673..5513152be5 100644 --- a/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkLesionInfoWidget.cpp +++ b/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkLesionInfoWidget.cpp @@ -1,475 +1,472 @@ /*=================================================================== 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 "QmitkSemanticRelationsNodeSelectionDialog.h" // semantic relations UI module #include // semantic relations module #include #include #include #include #include "QmitkCustomVariants.h" -// multi label module -#include - // mitk core #include // qt #include #include #include #include #include const QBrush QmitkLesionInfoWidget::DEFAULT_BACKGROUND_COLOR = QBrush(Qt::transparent); const QBrush QmitkLesionInfoWidget::SELECTED_BACKGROUND_COLOR = QBrush(Qt::green); const QBrush QmitkLesionInfoWidget::CONNECTED_BACKGROUND_COLOR = QBrush(Qt::darkGreen); QmitkLesionInfoWidget::QmitkLesionInfoWidget(mitk::DataStorage* dataStorage, QWidget* parent /*= nullptr*/) : QWidget(parent) , m_DataStorage(dataStorage) , m_SemanticRelations(std::make_unique(dataStorage)) { Init(); } QmitkLesionInfoWidget::~QmitkLesionInfoWidget() { if (nullptr != m_SemanticRelations) { m_SemanticRelations->RemoveObserver(this); } } void QmitkLesionInfoWidget::Init() { // create GUI from the Qt Designer's .ui file m_Controls.setupUi(this); m_Controls.lesionListWidget->setContextMenuPolicy(Qt::CustomContextMenu); SetUpConnections(); m_SemanticRelations->AddObserver(this); } void QmitkLesionInfoWidget::SetUpConnections() { // connect buttons to modify semantic relations connect(m_Controls.addLesionPushButton, &QPushButton::clicked, this, &QmitkLesionInfoWidget::OnAddLesionButtonClicked); // connect each list widget with a custom slots connect(m_Controls.lesionListWidget, &QListWidget::currentItemChanged, this, &QmitkLesionInfoWidget::OnCurrentLesionItemChanged); connect(m_Controls.lesionListWidget, &QListWidget::itemDoubleClicked, this, &QmitkLesionInfoWidget::OnLesionItemDoubleClicked); // connect context menu entries connect(m_Controls.lesionListWidget, &QListWidget::customContextMenuRequested, this, &QmitkLesionInfoWidget::OnLesionListContextMenuRequested); } void QmitkLesionInfoWidget::SetCurrentCaseID(const mitk::SemanticTypes::CaseID& caseID) { m_CaseID = caseID; Update(m_CaseID); } void QmitkLesionInfoWidget::Update(const mitk::SemanticTypes::CaseID& caseID) { if (nullptr == m_SemanticRelations) { return; } // if the case ID of updated instance is equal to the currently active caseID if (caseID == m_CaseID) { ResetLesionListWidget(); } } ////////////////////////////////////////////////////////////////////////// // Implementation of the QT_SLOTS ////////////////////////////////////////////////////////////////////////// void QmitkLesionInfoWidget::OnAddLesionButtonClicked() { if (m_CaseID.empty()) { QMessageBox msgBox; msgBox.setWindowTitle("No case ID set."); msgBox.setText("In order to add a lesion, please specify the current case / patient."); msgBox.setIcon(QMessageBox::Warning); msgBox.exec(); return; } mitk::SemanticTypes::Lesion newLesion = mitk::GenerateNewLesion(); try { m_SemanticRelations->AddLesion(m_CaseID, newLesion); } catch (mitk::SemanticRelationException& e) { MITK_INFO << "Could not add a new lesion. " << e; } } void QmitkLesionInfoWidget::OnCurrentLesionItemChanged(QListWidgetItem* current, QListWidgetItem* /*previous*/) { if (nullptr == current || nullptr == m_SemanticRelations || nullptr == m_DataStorage) { return; } // only the UID is needed to identify a representing lesion m_CurrentLesion.UID = current->data(Qt::UserRole).toString().toStdString(); if (false == m_SemanticRelations->InstanceExists(m_CaseID, m_CurrentLesion)) { // no UID found; cannot create a lesion return; } if (SELECTED_BACKGROUND_COLOR == current->background() || CONNECTED_BACKGROUND_COLOR == current->background()) { DarkenBackgroundColors(); } else { ResetBackgroundColors(); } current->setBackground(SELECTED_BACKGROUND_COLOR); emit LesionChanged(m_CurrentLesion); } void QmitkLesionInfoWidget::OnLesionItemDoubleClicked(QListWidgetItem* clickedItem) { if (nullptr == clickedItem || nullptr == m_SemanticRelations) { return; } // only the UID is needed to identify a representing lesion m_CurrentLesion.UID = clickedItem->data(Qt::UserRole).toString().toStdString(); m_CurrentLesion.name = clickedItem->text().toStdString(); } void QmitkLesionInfoWidget::OnLesionListContextMenuRequested(const QPoint& pos) { QListWidgetItem* currentItem = m_Controls.lesionListWidget->itemAt(pos); if (nullptr == currentItem) { // no item clicked; cannot retrieve the current lesion return; } mitk::SemanticTypes::ID selectedLesionUID; selectedLesionUID = currentItem->data(Qt::UserRole).toString().toStdString(); if (selectedLesionUID.empty()) { // no UID found; cannot create a lesion return; } QMenu* menu = new QMenu(m_Controls.lesionListWidget); QAction* linkToSegmentation = new QAction("Link to segmentation", this); linkToSegmentation->setEnabled(true); connect(linkToSegmentation, &QAction::triggered, [this, selectedLesionUID] { OnLinkToSegmentation(selectedLesionUID); }); menu->addAction(linkToSegmentation); QAction* setLesionName = new QAction("Set lesion name", this); setLesionName->setEnabled(true); connect(setLesionName, &QAction::triggered, [this, selectedLesionUID] { OnSetLesionName(selectedLesionUID); }); menu->addAction(setLesionName); QAction* setLesionClass = new QAction("Set lesion class", this); setLesionClass->setEnabled(true); connect(setLesionClass, &QAction::triggered, [this, selectedLesionUID] { OnSetLesionClass(selectedLesionUID); }); menu->addAction(setLesionClass); QAction* removeLesion = new QAction("Remove lesion", this); removeLesion->setEnabled(true); connect(removeLesion, &QAction::triggered, [this, selectedLesionUID] { OnRemoveLesion(selectedLesionUID); }); menu->addAction(removeLesion); menu->popup(QCursor::pos()); } void QmitkLesionInfoWidget::OnLinkToSegmentation(const mitk::SemanticTypes::ID& selectedLesionUID) { if (m_CaseID.empty()) { QMessageBox msgBox; msgBox.setWindowTitle("No case ID set."); msgBox.setText("In order to link a lesion to a segmentation, please specify the current case / patient."); msgBox.setIcon(QMessageBox::Warning); msgBox.exec(); return; } // retrieve the full lesion with its lesion class using the given lesion UID mitk::SemanticTypes::Lesion selectedLesion = mitk::GetLesionByUID(selectedLesionUID, m_SemanticRelations->GetAllLesionsOfCase(m_CaseID)); if (selectedLesion.UID.empty()) { // could not find lesion information for the selected lesion return; } QmitkSemanticRelationsNodeSelectionDialog* dialog = new QmitkSemanticRelationsNodeSelectionDialog(this, "Select segmentation to link to the selected lesion.", ""); dialog->SetDataStorage(m_DataStorage); dialog->setWindowTitle("Select segmentation node"); dialog->SetNodePredicate(mitk::NodePredicates::GetSegmentationPredicate()); dialog->SetSelectOnlyVisibleNodes(true); dialog->SetSelectionMode(QAbstractItemView::SingleSelection); dialog->SetCaseID(m_CaseID); 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) { QMessageBox msgBox; msgBox.setWindowTitle("No valid segmentation node selected."); msgBox.setText("In order to link the selected lesion to a segmentation, please specify a valid segmentation node."); msgBox.setIcon(QMessageBox::Warning); msgBox.exec(); return; } if (false == mitk::NodePredicates::GetSegmentationPredicate()->CheckNode(selectedDataNode)) { QMessageBox msgBox; msgBox.setWindowTitle("No segmentation selected"); msgBox.setText("In order to link the selected lesion to a segmentation, please specify a valid segmentation node."); msgBox.setIcon(QMessageBox::Warning); msgBox.exec(); return; } mitk::BaseData* baseData = selectedDataNode->GetData(); if (nullptr == baseData) { QMessageBox msgBox; msgBox.setWindowTitle("No valid base data."); msgBox.setText("In order to link the selected lesion to a segmentation, please specify a valid segmentation node."); msgBox.setIcon(QMessageBox::Warning); msgBox.exec(); return; } try { m_SemanticRelations->LinkSegmentationToLesion(selectedDataNode, selectedLesion); } catch (const mitk::SemanticRelationException& e) { std::stringstream exceptionMessage; exceptionMessage << e; QMessageBox msgBox; msgBox.setWindowTitle("Could not link the selected lesion."); msgBox.setText("The program wasn't able to correctly link the selected lesion with the selected segmentation.\n" "Reason:\n" + QString::fromStdString(exceptionMessage.str())); msgBox.setIcon(QMessageBox::Warning); msgBox.exec(); } } void QmitkLesionInfoWidget::OnSetLesionName(const mitk::SemanticTypes::ID& selectedLesionUID) { // retrieve the full lesion with its lesion class using the given lesion UID mitk::SemanticTypes::Lesion selectedLesion = mitk::GetLesionByUID(selectedLesionUID, m_SemanticRelations->GetAllLesionsOfCase(m_CaseID)); if (selectedLesion.UID.empty()) { // could not find lesion information for the selected lesion return; } // 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; } std::string newLesionName = inputDialog->GetLineEditText().toStdString(); selectedLesion.name = newLesionName; m_SemanticRelations->OverwriteLesion(m_CaseID, selectedLesion); } void QmitkLesionInfoWidget::OnSetLesionClass(const mitk::SemanticTypes::ID& selectedLesionUID) { // retrieve the full lesion with its lesion class using the given lesion UID mitk::SemanticTypes::Lesion selectedLesion = mitk::GetLesionByUID(selectedLesionUID, m_SemanticRelations->GetAllLesionsOfCase(m_CaseID)); if (selectedLesion.UID.empty()) { // could not find lesion information for the selected lesion return; } // 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 = m_SemanticRelations->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); 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_SemanticRelations->OverwriteLesion(m_CaseID, selectedLesion); } void QmitkLesionInfoWidget::OnRemoveLesion(const mitk::SemanticTypes::ID& selectedLesionUID) { if (m_CaseID.empty()) { QMessageBox msgBox; msgBox.setWindowTitle("No case ID set."); msgBox.setText("In order to remove a lesion, please specify the current case / patient."); msgBox.setIcon(QMessageBox::Warning); msgBox.exec(); return; } // retrieve the full lesion with its lesion class using the given lesion UID mitk::SemanticTypes::Lesion selectedLesion = mitk::GetLesionByUID(selectedLesionUID, m_SemanticRelations->GetAllLesionsOfCase(m_CaseID)); if (selectedLesion.UID.empty()) { // could not find lesion information for the selected lesion return; } try { m_SemanticRelations->RemoveLesion(m_CaseID, selectedLesion); } catch (const mitk::SemanticRelationException& e) { std::stringstream exceptionMessage; exceptionMessage << e; QMessageBox msgBox; msgBox.setWindowTitle("Could not remove the selected lesion."); msgBox.setText("The program wasn't able to correctly remove the selected lesion from the semantic relations model.\n" "Reason:\n" + QString::fromStdString(exceptionMessage.str())); msgBox.setIcon(QMessageBox::Warning); msgBox.exec(); } } void QmitkLesionInfoWidget::ResetLesionListWidget() { m_Controls.lesionListWidget->clear(); m_CurrentLesion.UID = ""; m_CurrentLesion.name = ""; // create lesion list widget entries with the current lesions mitk::SemanticRelations::LesionVector allLesionsOfCase = m_SemanticRelations->GetAllLesionsOfCase(m_CaseID); if (allLesionsOfCase.empty()) { m_Controls.lesionListWidget->addItem("No lesions found"); } for (const auto& lesion : allLesionsOfCase) { // store the UID as 'UserRole' data in the widget item QListWidgetItem* lesionItem = new QListWidgetItem; lesionItem->setData(Qt::UserRole, QString::fromStdString(lesion.UID)); // use the lesion UID for the item text, if the lesion name is non-existent if (lesion.name.empty()) { lesionItem->setText(QString::fromStdString(lesion.UID)); } else { lesionItem->setText(QString::fromStdString(lesion.name)); } m_Controls.lesionListWidget->addItem(lesionItem); } } void QmitkLesionInfoWidget::ResetBackgroundColors() { // reset all lesion list widget items to original background color for (int i = 0; i < m_Controls.lesionListWidget->count(); ++i) { QListWidgetItem* item = m_Controls.lesionListWidget->item(i); item->setBackground(DEFAULT_BACKGROUND_COLOR); } } void QmitkLesionInfoWidget::DarkenBackgroundColors() { // reset all lesion list widget items to original background color for (int i = 0; i < m_Controls.lesionListWidget->count(); ++i) { QListWidgetItem* item = m_Controls.lesionListWidget->item(i); if (DEFAULT_BACKGROUND_COLOR != item->background()) { item->setBackground(CONNECTED_BACKGROUND_COLOR); } } } diff --git a/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkSemanticRelationsView.cpp b/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkSemanticRelationsView.cpp index 80fe8e3a5b..7144a0de1f 100644 --- a/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkSemanticRelationsView.cpp +++ b/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkSemanticRelationsView.cpp @@ -1,273 +1,335 @@ /*=================================================================== 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 "QmitkSemanticRelationsView.h" #include "QmitkSemanticRelationsNodeSelectionDialog.h" // semantic relations module #include #include #include // mitk qt widgets module #include +// mitk multi label module +#include + // blueberry #include #include // qt #include const std::string QmitkSemanticRelationsView::VIEW_ID = "org.mitk.views.semanticrelations"; void QmitkSemanticRelationsView::SetFocus() { // nothing here } void QmitkSemanticRelationsView::CreateQtPartControl(QWidget* parent) { // create GUI widgets m_Controls.setupUi(parent); // initialize the semantic relations m_SemanticRelations = std::make_unique(GetDataStorage()); m_LesionInfoWidget = new QmitkLesionInfoWidget(GetDataStorage(), parent); m_Controls.gridLayout->addWidget(m_LesionInfoWidget); m_PatientTableInspector = new QmitkPatientTableInspector(parent); m_PatientTableInspector->SetDataStorage(GetDataStorage()); m_Controls.gridLayout->addWidget(m_PatientTableInspector); QGridLayout* dndDataNodeWidgetLayout = new QGridLayout; dndDataNodeWidgetLayout->addWidget(m_PatientTableInspector, 0, 0); dndDataNodeWidgetLayout->setContentsMargins(0, 0, 0, 0); m_DnDDataNodeWidget = new QmitkDnDDataNodeWidget(parent); m_DnDDataNodeWidget->setLayout(dndDataNodeWidgetLayout); m_Controls.gridLayout->addWidget(m_DnDDataNodeWidget); SetUpConnections(); } void QmitkSemanticRelationsView::SetUpConnections() { connect(m_Controls.caseIDComboBox, static_cast(&QComboBox::currentIndexChanged), this, &QmitkSemanticRelationsView::OnCaseIDSelectionChanged); connect(m_LesionInfoWidget, &QmitkLesionInfoWidget::LesionChanged, this, &QmitkSemanticRelationsView::OnLesionChanged); - connect(m_DnDDataNodeWidget, &QmitkDnDDataNodeWidget::NodesDropped, this, &QmitkSemanticRelationsView::NodesAdded); + connect(m_PatientTableInspector, &QmitkPatientTableInspector::DataNodeDoubleClicked, this, &QmitkSemanticRelationsView::OnDataNodeDoubleClicked); + connect(m_DnDDataNodeWidget, &QmitkDnDDataNodeWidget::NodesDropped, this, &QmitkSemanticRelationsView::OnNodesAdded); } void QmitkSemanticRelationsView::NodeRemoved(const mitk::DataNode* dataNode) { if (nullptr == dataNode) { return; } if (mitk::NodePredicates::GetImagePredicate()->CheckNode(dataNode)) { RemoveImage(dataNode); } else if (mitk::NodePredicates::GetSegmentationPredicate()->CheckNode(dataNode)) { RemoveSegmentation(dataNode); } } -void QmitkSemanticRelationsView::NodesAdded(QmitkDnDDataNodeWidget* dnDDataNodeWidget, std::vector nodes) +void QmitkSemanticRelationsView::OnCaseIDSelectionChanged(const QString& caseID) +{ + m_LesionInfoWidget->SetCurrentCaseID(caseID.toStdString()); + m_PatientTableInspector->SetCaseID(caseID.toStdString()); +} + +void QmitkSemanticRelationsView::AddToComboBox(const mitk::SemanticTypes::CaseID& caseID) +{ + int foundIndex = m_Controls.caseIDComboBox->findText(QString::fromStdString(caseID)); + if (-1 == foundIndex) + { + // add the caseID to the combo box, as it is not already contained + m_Controls.caseIDComboBox->addItem(QString::fromStdString(caseID)); + } +} + +void QmitkSemanticRelationsView::OnLesionChanged(const mitk::SemanticTypes::Lesion& lesion) +{ + m_PatientTableInspector->SetLesion(lesion); +} + +void QmitkSemanticRelationsView::OnDataNodeDoubleClicked(const mitk::DataNode::Pointer dataNode) +{ + if (nullptr == dataNode) + { + return; + } + + if (mitk::NodePredicates::GetImagePredicate()->CheckNode(dataNode)) + { + OpenInEditor(dataNode); + } + else if (mitk::NodePredicates::GetSegmentationPredicate()->CheckNode(dataNode)) + { + JumpToPosition(dataNode); + } +} + +void QmitkSemanticRelationsView::OnNodesAdded(QmitkDnDDataNodeWidget* dnDDataNodeWidget, std::vector nodes) { for (mitk::DataNode* dataNode : nodes) { if (nullptr == dataNode) { continue; } if (mitk::NodePredicates::GetImagePredicate()->CheckNode(dataNode)) { AddImage(dataNode); } else if (mitk::NodePredicates::GetSegmentationPredicate()->CheckNode(dataNode)) { AddSegmentation(dataNode); } } } -void QmitkSemanticRelationsView::OnCaseIDSelectionChanged(const QString& caseID) +void QmitkSemanticRelationsView::RemoveFromComboBox(const mitk::SemanticTypes::CaseID& caseID) { - m_LesionInfoWidget->SetCurrentCaseID(caseID.toStdString()); - m_PatientTableInspector->SetCaseID(caseID.toStdString()); + std::vector allControlPoints = m_SemanticRelations->GetAllControlPointsOfCase(caseID); + int foundIndex = m_Controls.caseIDComboBox->findText(QString::fromStdString(caseID)); + if (allControlPoints.empty() && -1 != foundIndex) + { + // TODO: find new way to check for empty case id + // caseID does not contain any control points and therefore no data + // remove the caseID, if it is still contained + m_Controls.caseIDComboBox->removeItem(foundIndex); + } } -void QmitkSemanticRelationsView::OnJumpToPosition(const mitk::Point3D& position) +void QmitkSemanticRelationsView::OpenInEditor(const mitk::DataNode::Pointer dataNode) { - mitk::IRenderWindowPart* renderWindowPart = GetRenderWindowPart(); - if (nullptr != renderWindowPart) + auto renderWindowPart = GetRenderWindowPart(); + if (nullptr == renderWindowPart) { - renderWindowPart->SetSelectedPosition(position); - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); + renderWindowPart = GetRenderWindowPart(QmitkAbstractView::BRING_TO_FRONT | QmitkAbstractView::OPEN); + if (nullptr == renderWindowPart) + { + // no render window available + return; + } } -} -void QmitkSemanticRelationsView::OnLesionChanged(const mitk::SemanticTypes::Lesion& lesion) -{ - m_PatientTableInspector->SetLesion(lesion); + auto image = dynamic_cast(dataNode->GetData()); + if (nullptr != image) + { + mitk::RenderingManager::GetInstance()->InitializeViews(image->GetTimeGeometry(), mitk::RenderingManager::REQUEST_UPDATE_ALL, true); + } } -void QmitkSemanticRelationsView::AddToComboBox(const mitk::SemanticTypes::CaseID& caseID) +void QmitkSemanticRelationsView::JumpToPosition(const mitk::DataNode::Pointer dataNode) { - int foundIndex = m_Controls.caseIDComboBox->findText(QString::fromStdString(caseID)); - if (-1 == foundIndex) + if (nullptr == dataNode) { - // add the caseID to the combo box, as it is not already contained - m_Controls.caseIDComboBox->addItem(QString::fromStdString(caseID)); + return; } -} -void QmitkSemanticRelationsView::RemoveFromComboBox(const mitk::SemanticTypes::CaseID& caseID) -{ - std::vector allControlPoints = m_SemanticRelations->GetAllControlPointsOfCase(caseID); - int foundIndex = m_Controls.caseIDComboBox->findText(QString::fromStdString(caseID)); - if (allControlPoints.empty() && -1 != foundIndex) + mitk::LabelSetImage* labelSetImage = dynamic_cast(dataNode->GetData()); + if (nullptr == labelSetImage) { - // TODO: find new way to check for empty case id - // caseID does not contain any control points and therefore no data - // remove the caseID, if it is still contained - m_Controls.caseIDComboBox->removeItem(foundIndex); + return; + } + + int activeLayer = labelSetImage->GetActiveLayer(); + mitk::Label* activeLabel = labelSetImage->GetActiveLabel(activeLayer); + labelSetImage->UpdateCenterOfMass(activeLabel->GetValue(), activeLayer); + const mitk::Point3D& centerPosition = activeLabel->GetCenterOfMassCoordinates(); + if (centerPosition.GetVnlVector().max_value() > 0.0) + { + auto renderWindowPart = GetRenderWindowPart(); + if (nullptr == renderWindowPart) + { + renderWindowPart = GetRenderWindowPart(QmitkAbstractView::BRING_TO_FRONT | QmitkAbstractView::OPEN); + if (nullptr == renderWindowPart) + { + // no render window available + return; + } + } + + auto segmentation = dynamic_cast(dataNode->GetData()); + if (nullptr != segmentation) + { + renderWindowPart->SetSelectedPosition(centerPosition); + mitk::RenderingManager::GetInstance()->InitializeViews(segmentation->GetTimeGeometry(), mitk::RenderingManager::REQUEST_UPDATE_ALL, true); + } } } void QmitkSemanticRelationsView::AddImage(const mitk::DataNode* image) { if (nullptr == image) { return; } try { // add the image to the semantic relations storage m_SemanticRelations->AddImage(image); mitk::SemanticTypes::CaseID caseID = mitk::GetCaseIDFromDataNode(image); AddToComboBox(caseID); } catch (const mitk::SemanticRelationException& e) { std::stringstream exceptionMessage; exceptionMessage << e; QMessageBox msgBox; msgBox.setWindowTitle("Could not add the selected image."); msgBox.setText("The program wasn't able to correctly add the selected images.\n" "Reason:\n" + QString::fromStdString(exceptionMessage.str())); msgBox.setIcon(QMessageBox::Warning); msgBox.exec(); return; } } void QmitkSemanticRelationsView::AddSegmentation(const mitk::DataNode* segmentation) { if (nullptr == segmentation) { return; } mitk::BaseData* baseData = segmentation->GetData(); if (nullptr == baseData) { return; } // continue with valid segmentation data // get parent node of the current segmentation node with the node predicate mitk::DataStorage::SetOfObjects::ConstPointer parentNodes = GetDataStorage()->GetSources(segmentation, mitk::NodePredicates::GetImagePredicate(), false); // check for already existing, identifying base properties mitk::BaseProperty* caseIDProperty = baseData->GetProperty("DICOM.0010.0010"); mitk::BaseProperty* nodeIDProperty = baseData->GetProperty("DICOM.0020.000E"); if (nullptr == caseIDProperty || nullptr == nodeIDProperty) { MITK_INFO << "No DICOM tags for case and node identification found. Transferring DICOM tags from the parent node to the selected segmentation node."; mitk::SemanticTypes::CaseID caseID = mitk::GetCaseIDFromDataNode(parentNodes->front()); mitk::SemanticTypes::ID nodeID = mitk::GetIDFromDataNode(parentNodes->front()); // transfer DICOM tags to the segmentation node mitk::StringProperty::Pointer caseIDTag = mitk::StringProperty::New(caseID); baseData->SetProperty("DICOM.0010.0010", caseIDTag); // DICOM tag is "PatientName" // add UID to distinguish between different segmentations of the same parent node mitk::StringProperty::Pointer nodeIDTag = mitk::StringProperty::New(nodeID + mitk::UIDGeneratorBoost::GenerateUID()); baseData->SetProperty("DICOM.0020.000E", nodeIDTag); // DICOM tag is "SeriesInstanceUID" } try { m_SemanticRelations->AddSegmentation(segmentation, parentNodes->front()); } catch (const mitk::SemanticRelationException& e) { std::stringstream exceptionMessage; exceptionMessage << e; QMessageBox msgBox; msgBox.setWindowTitle("Could not add the selected segmentation."); msgBox.setText("The program wasn't able to correctly add the selected segmentation.\n" "Reason:\n" + QString::fromStdString(exceptionMessage.str())); msgBox.setIcon(QMessageBox::Warning); msgBox.exec(); return; } } void QmitkSemanticRelationsView::RemoveImage(const mitk::DataNode* image) { try { m_SemanticRelations->RemoveImage(image); mitk::SemanticTypes::CaseID caseID = mitk::GetCaseIDFromDataNode(image); RemoveFromComboBox(caseID); } catch (const mitk::SemanticRelationException& e) { std::stringstream exceptionMessage; exceptionMessage << e; - QMessageBox msgBox; - msgBox.setWindowTitle("Could not remove the selected image."); - msgBox.setText("The program wasn't able to correctly remove the selected image from the semantic relations model.\n" - "Reason:\n" + QString::fromStdString(exceptionMessage.str())); - msgBox.setIcon(QMessageBox::Warning); - msgBox.exec(); + MITK_WARN << "Could not correctly remove the selected image from the semantic relations model.\n" + << "Reason: " + exceptionMessage.str(); } } void QmitkSemanticRelationsView::RemoveSegmentation(const mitk::DataNode* segmentation) { try { m_SemanticRelations->RemoveSegmentation(segmentation); } catch (const mitk::SemanticRelationException& e) { std::stringstream exceptionMessage; exceptionMessage << e; - QMessageBox msgBox; - msgBox.setWindowTitle("Could not remove the selected segmentation."); - msgBox.setText("The program wasn't able to correctly remove the selected segmentation from the semantic relations model.\n" - "Reason:\n" + QString::fromStdString(exceptionMessage.str())); - msgBox.setIcon(QMessageBox::Warning); - msgBox.exec(); + MITK_WARN << "Could not correctly remove the selected segmentation from the semantic relations model.\n" + << "Reason: " + exceptionMessage.str(); } } diff --git a/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkSemanticRelationsView.h b/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkSemanticRelationsView.h index fee49e432a..01a2ab5832 100644 --- a/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkSemanticRelationsView.h +++ b/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkSemanticRelationsView.h @@ -1,94 +1,97 @@ /*=================================================================== 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 QMITKSEMANTICRELATIONSVIEW_H #define QMITKSEMANTICRELATIONSVIEW_H // semantic relations plugin #include "ui_QmitkSemanticRelationsControls.h" #include "QmitkLesionInfoWidget.h" // semantic relations module #include #include // semantic relations UI module #include // blueberry #include // mitk qt #include class QmitkDnDDataNodeWidget; /* * @brief The QmitkSemanticRelationsView is an MITK view to combine and show the widgets of the 'SemanticRelationsUI'-module and this semantic relations plugin. * * It allows the MITK user to see and modify the content of the SemanticRelations-session. * A combo box is used to select and show the current patient. * * SIDE NOTE: Modifying the control points and information types of data in the semantic relations model is currently done * by using the right-click context-menu of the "PatientTableWidget" in the "Select Patient Node"-Dialog. * This is a leftover from when the widget was not used as a selection widget. Those functionality will be moved * to this main GUI soon. */ class QmitkSemanticRelationsView : public QmitkAbstractView { Q_OBJECT public: static const std::string VIEW_ID; protected: virtual void SetFocus() override; virtual void CreateQtPartControl(QWidget* parent) override; private Q_SLOTS: void OnCaseIDSelectionChanged(const QString&); void AddToComboBox(const mitk::SemanticTypes::CaseID&); - void OnJumpToPosition(const mitk::Point3D&); void OnLesionChanged(const mitk::SemanticTypes::Lesion&); + void OnDataNodeDoubleClicked(const mitk::DataNode::Pointer); + void OnNodesAdded(QmitkDnDDataNodeWidget*, std::vector); private: void SetUpConnections(); virtual void NodeRemoved(const mitk::DataNode* dataNode) override; - void NodesAdded(QmitkDnDDataNodeWidget* dnDDataNodeWidget, std::vector nodes); void RemoveFromComboBox(const mitk::SemanticTypes::CaseID& caseID); + void OpenInEditor(const mitk::DataNode::Pointer dataNode); + void JumpToPosition(const mitk::DataNode::Pointer dataNode); + void AddImage(const mitk::DataNode* image); void AddSegmentation(const mitk::DataNode* segmentation); void RemoveImage(const mitk::DataNode* image); void RemoveSegmentation(const mitk::DataNode* segmentation); Ui::QmitkSemanticRelationsControls m_Controls; QmitkLesionInfoWidget* m_LesionInfoWidget; QmitkPatientTableInspector* m_PatientTableInspector; QmitkDnDDataNodeWidget* m_DnDDataNodeWidget; std::unique_ptr m_SemanticRelations; }; #endif // QMITKSEMANTICRELATIONSVIEW_H