diff --git a/Modules/QtWidgets/src/QmitkMultiWidgetConfigurationToolBar.cpp b/Modules/QtWidgets/src/QmitkMultiWidgetConfigurationToolBar.cpp index a078239b81..e5a695957e 100644 --- a/Modules/QtWidgets/src/QmitkMultiWidgetConfigurationToolBar.cpp +++ b/Modules/QtWidgets/src/QmitkMultiWidgetConfigurationToolBar.cpp @@ -1,82 +1,82 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical Image Computing. 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. ===================================================================*/ #include "QmitkMultiWidgetConfigurationToolBar.h" QmitkMultiWidgetConfigurationToolBar::QmitkMultiWidgetConfigurationToolBar() : QToolBar() { QToolBar::setOrientation(Qt::Vertical); QToolBar::setIconSize(QSize(17, 17)); InitializeToolBar(); } QmitkMultiWidgetConfigurationToolBar::~QmitkMultiWidgetConfigurationToolBar() { // nothing here } void QmitkMultiWidgetConfigurationToolBar::InitializeToolBar() { // create popup to show a widget to modify the multi widget layout m_LayoutSelectionPopup = new QmitkMultiWidgetLayoutSelectionWidget(this); m_LayoutSelectionPopup->hide(); AddButtons(); connect(m_LayoutSelectionPopup, &QmitkMultiWidgetLayoutSelectionWidget::LayoutSet, this, &QmitkMultiWidgetConfigurationToolBar::LayoutSet); } void QmitkMultiWidgetConfigurationToolBar::AddButtons() { QAction* setLayoutAction = new QAction(QIcon(":/Qmitk/mwLayout.png"), tr("Set multi widget layout"), this); connect(setLayoutAction, &QAction::triggered, this, &QmitkMultiWidgetConfigurationToolBar::OnSetLayout); QToolBar::addAction(setLayoutAction); m_SynchronizeAction = new QAction(QIcon(":/Qmitk/mwSynchronized.png"), tr("Desynchronize render windows"), this); m_SynchronizeAction->setCheckable(true); m_SynchronizeAction->setChecked(true); connect(m_SynchronizeAction, &QAction::triggered, this, &QmitkMultiWidgetConfigurationToolBar::OnSynchronize); QToolBar::addAction(m_SynchronizeAction); } void QmitkMultiWidgetConfigurationToolBar::OnSetLayout() { m_LayoutSelectionPopup->setWindowFlags(Qt::Popup); - m_LayoutSelectionPopup->move(this->cursor().pos()); + m_LayoutSelectionPopup->move(this->cursor().pos().x() - m_LayoutSelectionPopup->width(), this->cursor().pos().y()); m_LayoutSelectionPopup->show(); } void QmitkMultiWidgetConfigurationToolBar::OnSynchronize() { bool synchronized = m_SynchronizeAction->isChecked(); if (synchronized) { m_SynchronizeAction->setIcon(QIcon(":/Qmitk/mwSynchronized.png")); m_SynchronizeAction->setText(tr("Desynchronize render windows")); } else { m_SynchronizeAction->setIcon(QIcon(":/Qmitk/mwDesynchronized.png")); m_SynchronizeAction->setText(tr("Synchronize render windows")); } m_SynchronizeAction->setChecked(synchronized); emit Synchronized(synchronized); } diff --git a/Modules/QtWidgets/src/QmitkMultiWidgetLayoutSelectionWidget.cpp b/Modules/QtWidgets/src/QmitkMultiWidgetLayoutSelectionWidget.cpp index 687ad5a934..17081206c4 100644 --- a/Modules/QtWidgets/src/QmitkMultiWidgetLayoutSelectionWidget.cpp +++ b/Modules/QtWidgets/src/QmitkMultiWidgetLayoutSelectionWidget.cpp @@ -1,76 +1,80 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical Image Computing. 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. ===================================================================*/ #include "QmitkMultiWidgetLayoutSelectionWidget.h" QmitkMultiWidgetLayoutSelectionWidget::QmitkMultiWidgetLayoutSelectionWidget(QWidget* parent/* = 0*/) : QWidget(parent) { Init(); } void QmitkMultiWidgetLayoutSelectionWidget::Init() { ui.setupUi(this); + + auto stylesheet = "QTableWidget::item{background-color: white;}\nQTableWidget::item:selected{background-color: #1C97EA;}"; + ui.tableWidget->setStyleSheet(stylesheet); + connect(ui.tableWidget, &QTableWidget::itemSelectionChanged, this, &QmitkMultiWidgetLayoutSelectionWidget::OnTableItemSelectionChanged); connect(ui.setLayoutPushButton, &QPushButton::clicked, this, &QmitkMultiWidgetLayoutSelectionWidget::OnSetLayoutButtonClicked); } void QmitkMultiWidgetLayoutSelectionWidget::OnTableItemSelectionChanged() { QItemSelectionModel* selectionModel = ui.tableWidget->selectionModel(); int row = 0; int column = 0; QModelIndexList indices = selectionModel->selectedIndexes(); if (indices.size() > 0) { row = indices[0].row(); column = indices[0].column(); QModelIndex topLeft = ui.tableWidget->model()->index(0, 0, QModelIndex()); QModelIndex bottomRight = ui.tableWidget->model()->index(row, column, QModelIndex()); QItemSelection cellSelection; cellSelection.select(topLeft, bottomRight); selectionModel->select(cellSelection, QItemSelectionModel::Select); } } void QmitkMultiWidgetLayoutSelectionWidget::OnSetLayoutButtonClicked() { int row = 0; int column = 0; QModelIndexList indices = ui.tableWidget->selectionModel()->selectedIndexes(); if (indices.size() > 0) { // find largest row and column for (const QModelIndex modelIndex : indices) { if (modelIndex.row() > row) { row = modelIndex.row(); } if (modelIndex.column() > column) { column = modelIndex.column(); } } close(); emit LayoutSet(row+1, column+1); } } diff --git a/Modules/QtWidgets/src/QmitkMultiWidgetLayoutSelectionWidget.ui b/Modules/QtWidgets/src/QmitkMultiWidgetLayoutSelectionWidget.ui index 34d3e62dfd..5c2d9e4af1 100644 --- a/Modules/QtWidgets/src/QmitkMultiWidgetLayoutSelectionWidget.ui +++ b/Modules/QtWidgets/src/QmitkMultiWidgetLayoutSelectionWidget.ui @@ -1,72 +1,70 @@ QmitkMultiWidgetLayoutSelectionWidget 0 0 230 230 QAbstractItemView::NoEditTriggers false false 3 4 false 50 50 false 50 50 - - Set multi widget layout diff --git a/Modules/RenderWindowManager/include/mitkRenderWindowLayerUtilities.h b/Modules/RenderWindowManager/include/mitkRenderWindowLayerUtilities.h index 6e30a38584..23af38d3c3 100644 --- a/Modules/RenderWindowManager/include/mitkRenderWindowLayerUtilities.h +++ b/Modules/RenderWindowManager/include/mitkRenderWindowLayerUtilities.h @@ -1,71 +1,79 @@ /*=================================================================== 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 MITKRENDERWINDOWLAYERUTILITIES_H #define MITKRENDERWINDOWLAYERUTILITIES_H // render window manager module #include "MitkRenderWindowManagerExports.h" // mitk core #include #include #include #include /** * @brief Render window layer helper functions to retrieve the currently valid layer stack */ namespace mitk { namespace RenderWindowLayerUtilities { typedef std::vector RendererVector; typedef std::map> LayerStack; /** * The base data node of a renderer is supposed to be on layer 0 (zero), which should be the lowest layer in a render window. */ const int BASE_LAYER_INDEX = 0; /** * The top layer index, denoting that no valid (positive) layer index is given and therefore the index should be resolved into the topmost layer index. */ const int TOP_LAYER_INDEX = -1; /** * @brief Return the stack of layers of the given renderer as std::map, which guarantees ordering of the layers. * Stacked layers are only included if they have their "fixedLayer" property set to true and their "layer" property set. * * If "renderer" = nullptr: a layer stack won't be created and an empty "LayerStack" will be returned. * If "withBaseNode" = true: include the base node in the layer stack, if existing. * If "withBaseNode" = false: exclude the base node from the layer stack. * * @param dataStorage Pointer to a data storage instance whose data nodes should be checked and possibly be included. * @param renderer Pointer to the renderer instance for which the layer stack should be generated. * @param withBaseNode Boolean to decide whether the base node should be included in or excluded from the layer stack. */ MITKRENDERWINDOWMANAGER_EXPORT LayerStack GetLayerStack(const DataStorage* dataStorage, const BaseRenderer* renderer, bool withBaseNode); /** * @brief Helper function to get a node predicate that can be used to filter render window specific data nodes. * * The data nodes must not be 'helper objects'. The must have set a 'fixed layer' property for the given renderer. */ MITKRENDERWINDOWMANAGER_EXPORT NodePredicateAnd::Pointer GetRenderWindowPredicate(const BaseRenderer* renderer); - + /** + * @brief Set renderer-specific properties to mark a data node as 'managed by the specific renderer'. + * In order for a renderer to manage a data node, the 'fixedLayer' property has to be set for the given renderer. + * Additionally, the 'visible' and the 'layer' property are set and allow to individually render a set of nodes + * with a specific renderer. + * The last two mentioned properties are set so that they initially have the same value as the corresponding + * global property. + */ + MITKRENDERWINDOWMANAGER_EXPORT void SetRenderWindowProperties(mitk::DataNode* dataNode, const BaseRenderer* renderer); } // namespace RenderWindowLayerUtilities } // namespace mitk #endif // MITKRENDERWINDOWLAYERUTILITIES_H diff --git a/Modules/RenderWindowManager/src/mitkRenderWindowLayerController.cpp b/Modules/RenderWindowManager/src/mitkRenderWindowLayerController.cpp index 2ba1043c82..4ca3aa6af3 100644 --- a/Modules/RenderWindowManager/src/mitkRenderWindowLayerController.cpp +++ b/Modules/RenderWindowManager/src/mitkRenderWindowLayerController.cpp @@ -1,575 +1,567 @@ /*=================================================================== 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. ===================================================================*/ // render window manager module #include "mitkRenderWindowLayerController.h" mitk::RenderWindowLayerController::RenderWindowLayerController() : m_DataStorage(nullptr) { // nothing here } void mitk::RenderWindowLayerController::SetDataStorage(DataStorage::Pointer dataStorage) { if (m_DataStorage != dataStorage) { // set the new data storage m_DataStorage = dataStorage; } } void mitk::RenderWindowLayerController::SetControlledRenderer(RenderWindowLayerUtilities::RendererVector controlledRenderer) { if (m_ControlledRenderer != controlledRenderer) { // set the new set of controlled renderer m_ControlledRenderer = controlledRenderer; } } void mitk::RenderWindowLayerController::SetBaseDataNode(DataNode* dataNode, const BaseRenderer* renderer /*= nullptr*/) { if (nullptr == dataNode) { return; } if (nullptr == renderer) { // set the data node as base data node in all controlled renderer for (const auto& renderer : m_ControlledRenderer) { if (nullptr != renderer) { SetBaseDataNode(dataNode, renderer); } } } else { // get the layer stack with the base data node of the current renderer RenderWindowLayerUtilities::LayerStack stackedLayers = RenderWindowLayerUtilities::GetLayerStack(m_DataStorage, renderer, true); if (!stackedLayers.empty()) { // see if base layer exists RenderWindowLayerUtilities::LayerStack::iterator layerStackIterator = stackedLayers.find(RenderWindowLayerUtilities::BASE_LAYER_INDEX); if (layerStackIterator != stackedLayers.end()) { // remove the current base data node from the current renderer layerStackIterator->second->GetPropertyList(renderer)->DeleteProperty("layer"); layerStackIterator->second->SetBoolProperty("fixedLayer", false, renderer); layerStackIterator->second->SetVisibility(false, renderer); } } // "RenderWindowLayerUtilities::BASE_LAYER_INDEX" indicates the base data node --> set as new background dataNode->SetIntProperty("layer", RenderWindowLayerUtilities::BASE_LAYER_INDEX, renderer); dataNode->SetBoolProperty("fixedLayer", true, renderer); dataNode->SetVisibility(true, renderer); dataNode->Modified(); mitk::RenderingManager::GetInstance()->RequestUpdate(renderer->GetRenderWindow()); } } void mitk::RenderWindowLayerController::InsertLayerNode(DataNode* dataNode, int layer /*= RenderWindowLayerUtilities::TOP_LAYER_INDEX*/, const BaseRenderer* renderer /*= nullptr*/) { if (nullptr == dataNode) { return; } if (nullptr == renderer) { // insert data node in all controlled renderer for (const auto& renderer : m_ControlledRenderer) { if (nullptr != renderer) { InsertLayerNode(dataNode, layer, renderer); } } } else { if (RenderWindowLayerUtilities::BASE_LAYER_INDEX == layer) { // "RenderWindowLayerUtilities::BASE_LAYER_INDEX" indicates the base data node --> set as new background (overwrite current base node if needed) SetBaseDataNode(dataNode, renderer); } else { InsertLayerNodeInternal(dataNode, layer, renderer); } } } void mitk::RenderWindowLayerController::InsertLayerNodeInternal(DataNode* dataNode, int newLayer, const BaseRenderer* renderer /*= nullptr*/) { - dataNode->SetBoolProperty("fixedLayer", true, renderer); - // use visibility of existing renderer or common renderer - bool visible = false; - bool visibilityProperty = dataNode->GetVisibility(visible, renderer); - if (true == visibilityProperty) - { - // found a visibility property - dataNode->SetVisibility(visible, renderer); - } + RenderWindowLayerUtilities::SetRenderWindowProperties(dataNode, renderer); // get the layer stack without the base node of the current renderer RenderWindowLayerUtilities::LayerStack stackedLayers = RenderWindowLayerUtilities::GetLayerStack(m_DataStorage, renderer, false); if (stackedLayers.empty()) { // no layer stack for the current renderer if (RenderWindowLayerUtilities::TOP_LAYER_INDEX == newLayer) { // set given layer as first layer above base layer (= 1) newLayer = 1; // alternatively: no layer stack for the current renderer -> insert as background node //SetBaseDataNode(dataNode, renderer); } } else { if (RenderWindowLayerUtilities::TOP_LAYER_INDEX == newLayer) { // get the first value (highest int-key -> topmost layer) // + 1 indicates inserting the node above the topmost layer newLayer = stackedLayers.begin()->first + 1; } else { MoveNodeToPosition(dataNode, newLayer, renderer); return; } } // update data storage (the "data node model") dataNode->SetIntProperty("layer", newLayer, renderer); dataNode->Modified(); mitk::RenderingManager::GetInstance()->RequestUpdate(renderer->GetRenderWindow()); } void mitk::RenderWindowLayerController::RemoveLayerNode(DataNode* dataNode, const BaseRenderer* renderer /*= nullptr*/) { if (nullptr == dataNode) { return; } if (nullptr == renderer) { // remove data node from all controlled renderer for (const auto& renderer : m_ControlledRenderer) { if (nullptr != renderer) { RemoveLayerNode(dataNode, renderer); } } } else { // "remove" node from the renderer list dataNode->GetPropertyList(renderer)->DeleteProperty("layer"); dataNode->SetBoolProperty("fixedLayer", false, renderer); dataNode->SetVisibility(false, renderer); dataNode->Modified(); mitk::RenderingManager::GetInstance()->RequestUpdate(renderer->GetRenderWindow()); } } bool mitk::RenderWindowLayerController::MoveNodeToPosition(DataNode* dataNode, int newLayer, const BaseRenderer* renderer /*= nullptr*/) { if (nullptr == dataNode) { return false; } if (nullptr == renderer) { // move data node to position in all controlled renderer for (const auto& renderer : m_ControlledRenderer) { if (nullptr != renderer) { MoveNodeToPosition(dataNode, newLayer, renderer); // we don't store/need the returned boolean value return false; } } } else { // get the layer stack without the base node of the current renderer RenderWindowLayerUtilities::LayerStack stackedLayers = RenderWindowLayerUtilities::GetLayerStack(m_DataStorage, renderer, false); if (!stackedLayers.empty()) { // get the current layer value of the given data node int currentLayer; bool wasFound = dataNode->GetIntProperty("layer", currentLayer, renderer); if (wasFound && currentLayer != newLayer) { // move the given data node to the specified layer dataNode->SetIntProperty("layer", newLayer, renderer); int upperBound; int lowerBound; int step; if (currentLayer < newLayer) { // move node up upperBound = newLayer + 1; lowerBound = currentLayer + 1; step = -1; // move all other nodes one step down } else { upperBound = currentLayer; lowerBound = newLayer; step = 1; // move all other nodes one step up } // move all other data nodes between the upper and the lower bound for (auto& layer : stackedLayers) { if (layer.second != dataNode && layer.first < upperBound && layer.first >= lowerBound) { layer.second->SetIntProperty("layer", layer.first + step, renderer); } // else: current data node is the selected data node or // was previously already above the selected data node or // was previously already below the new layer position } dataNode->Modified(); mitk::RenderingManager::GetInstance()->RequestUpdate(renderer->GetRenderWindow()); return true; } // else: data node has no layer information or is already at the specified position } // else: do not work with empty layer stack } return false; } bool mitk::RenderWindowLayerController::MoveNodeToFront(DataNode* dataNode, const BaseRenderer* renderer /*= nullptr*/) { if (nullptr == dataNode) { return false; } if (nullptr == renderer) { // move data node to front in all controlled renderer for (const auto& renderer : m_ControlledRenderer) { if (nullptr != renderer) { MoveNodeToFront(dataNode, renderer); // we don't store/need the returned boolean value return false; } } } else { // get the layer stack without the base node of the current renderer RenderWindowLayerUtilities::LayerStack stackedLayers = RenderWindowLayerUtilities::GetLayerStack(m_DataStorage, renderer, false); if (!stackedLayers.empty()) { // get the first value (highest int-key -> topmost layer) int topmostLayer = stackedLayers.begin()->first; // get the current layer value of the given data node int currentLayer; bool wasFound = dataNode->GetIntProperty("layer", currentLayer, renderer); if (wasFound && currentLayer < topmostLayer) { // move the current data node above the current topmost layer dataNode->SetIntProperty("layer", topmostLayer+1, renderer); dataNode->Modified(); mitk::RenderingManager::GetInstance()->RequestUpdate(renderer->GetRenderWindow()); return true; } // else: data node has no layer information or is already the topmost layer node } // else: do not work with empty layer stack } return false; } bool mitk::RenderWindowLayerController::MoveNodeToBack(DataNode* dataNode, const BaseRenderer* renderer /*= nullptr*/) { if (nullptr == dataNode) { return false; } if (nullptr == renderer) { // move data node to back in all controlled renderer for (const auto& renderer : m_ControlledRenderer) { if (nullptr != renderer) { MoveNodeToBack(dataNode, renderer); // we don't store/need the returned boolean value return false; } } } else { // get the layer stack without the base node of the current renderer RenderWindowLayerUtilities::LayerStack stackedLayers = RenderWindowLayerUtilities::GetLayerStack(m_DataStorage, renderer, false); if (!stackedLayers.empty()) { // get the last value (lowest int-key) // cannot be the base layer as the base node was excluded by the 'GetLayerStack'-function int lowermostLayer = stackedLayers.rbegin()->first; // get the current layer value of the given data node int currentLayer; bool wasFound = dataNode->GetIntProperty("layer", currentLayer, renderer); if (wasFound && currentLayer > lowermostLayer) { // move the current data node to the current lowermost layer dataNode->SetIntProperty("layer", lowermostLayer, renderer); // move all other data nodes one layer up for (auto& layer : stackedLayers) { if (layer.second != dataNode && layer.first < currentLayer) { layer.second->SetIntProperty("layer", layer.first + 1, renderer); } // else: current data node is the selected data node or // was previously already above the selected data node } dataNode->Modified(); mitk::RenderingManager::GetInstance()->RequestUpdate(renderer->GetRenderWindow()); return true; } // else: data node has no layer information or is already the lowermost layer node } // else: do not work with empty layer stack } return false; } bool mitk::RenderWindowLayerController::MoveNodeUp(DataNode* dataNode, const BaseRenderer* renderer /*= nullptr*/) { if (nullptr == dataNode) { return false; } if (nullptr == renderer) { // move data node down in all controlled renderer for (const auto& renderer : m_ControlledRenderer) { if (nullptr != renderer) { MoveNodeUp(dataNode, renderer); // we don't store/need the returned boolean value return false; } } } else { // get the layer stack without the base node of the current renderer RenderWindowLayerUtilities::LayerStack stackedLayers = RenderWindowLayerUtilities::GetLayerStack(m_DataStorage, renderer, false); if (!stackedLayers.empty()) { // get the current layer value of the given data node int currentLayer; bool wasFound = dataNode->GetIntProperty("layer", currentLayer, renderer); if (wasFound) { // get the current layer in the map of stacked layers RenderWindowLayerUtilities::LayerStack::const_iterator layerStackIterator = stackedLayers.find(currentLayer); if (layerStackIterator != stackedLayers.end() && layerStackIterator != stackedLayers.begin()) { // found the element in the map, at different position than 'begin' -> // current node is not on the topmost layer and therefore can be moved one layer up // swap the layers of the dataNode and the dataNode on the next higher layer (previous map element) RenderWindowLayerUtilities::LayerStack::const_iterator prevLayerStackIterator = std::prev(layerStackIterator); dataNode->SetIntProperty("layer", prevLayerStackIterator->first, renderer); prevLayerStackIterator->second->SetIntProperty("layer", currentLayer, renderer); dataNode->Modified(); mitk::RenderingManager::GetInstance()->RequestUpdate(renderer->GetRenderWindow()); return true; } // else: layer stack does not contain a layer with the 'currentLayer'data node or // layer is already the topmost layer node } // else: data node has no layer information } // else: do not work with empty layer stack } return false; } bool mitk::RenderWindowLayerController::MoveNodeDown(DataNode* dataNode, const BaseRenderer* renderer /*= nullptr*/) { if (nullptr == dataNode) { return false; } if (nullptr == renderer) { // move data node up in all controlled renderer for (const auto& renderer : m_ControlledRenderer) { if (nullptr != renderer) { MoveNodeDown(dataNode, renderer); // we don't store/need the returned boolean value return false; } } } else { // get the layer stack without the base node of the current renderer RenderWindowLayerUtilities::LayerStack stackedLayers = RenderWindowLayerUtilities::GetLayerStack(m_DataStorage, renderer, false); if (!stackedLayers.empty()) { // get the current layer value of the given data node int currentLayer; bool wasFound = dataNode->GetIntProperty("layer", currentLayer, renderer); if (wasFound) { // get the current layer in the map of stacked layers RenderWindowLayerUtilities::LayerStack::const_iterator layerStackIterator = stackedLayers.find(currentLayer); if (layerStackIterator != stackedLayers.end()) { // found the element in the map ... RenderWindowLayerUtilities::LayerStack::const_iterator nextLayerStackIterator = std::next(layerStackIterator); if (nextLayerStackIterator != stackedLayers.end()) { // ... and found a successor -> // current node is not on the lowermost layer and therefore can be moved one layer down // swap the layers of the dataNode and the dataNode on the next lower layer (next map element) dataNode->SetIntProperty("layer", nextLayerStackIterator->first, renderer); nextLayerStackIterator->second->SetIntProperty("layer", currentLayer, renderer); dataNode->Modified(); mitk::RenderingManager::GetInstance()->RequestUpdate(renderer->GetRenderWindow()); return true; } // else: data node is already the lowermost layer node } // else: layer stack does not contain a layer with the 'currentLayer' } // else: data node has no layer information } // else: do not work with empty layer stack } return false; } void mitk::RenderWindowLayerController::SetVisibilityOfDataNode(bool visibility, DataNode* dataNode, const BaseRenderer* renderer /*=nullptr*/) { if (nullptr == dataNode) { return; } if (nullptr == renderer) { // set visibility of data node in all controlled renderer for (const auto& renderer : m_ControlledRenderer) { if (nullptr != renderer) { SetVisibilityOfDataNode(visibility, dataNode, renderer); } } } else { dataNode->SetVisibility(visibility, renderer); dataNode->Modified(); mitk::RenderingManager::GetInstance()->RequestUpdate(renderer->GetRenderWindow()); } } void mitk::RenderWindowLayerController::HideDataNodeInAllRenderer(const DataNode* dataNode) { if (nullptr == dataNode) { return; } for (const auto& renderer : m_ControlledRenderer) { if (nullptr != renderer) { dataNode->GetPropertyList(renderer)->SetBoolProperty("visible", false); } } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::RenderWindowLayerController::ResetRenderer(bool onlyVisibility /*= true*/, const BaseRenderer* renderer /*= nullptr*/) { if (nullptr == renderer) { // reset all controlled renderer for (const auto& renderer : m_ControlledRenderer) { if (nullptr != renderer) { ResetRenderer(onlyVisibility, renderer); } } } else { // get the layer stack with the base node of the current renderer RenderWindowLayerUtilities::LayerStack stackedLayers = RenderWindowLayerUtilities::GetLayerStack(m_DataStorage, renderer, true); if (!stackedLayers.empty()) { for (const auto& layer : stackedLayers) { int layerLevel; layer.second->GetIntProperty("layer", layerLevel, renderer); if (RenderWindowLayerUtilities::BASE_LAYER_INDEX == layerLevel) { // set base data node visibility to true layer.second->SetVisibility(true, renderer); } else { // set visibility of all other data nodes to false layer.second->SetVisibility(false, renderer); // modify layer node if (!onlyVisibility) { // clear mode: additionally remove layer node from current renderer layer.second->GetPropertyList(renderer)->DeleteProperty("layer"); layer.second->SetBoolProperty("fixedLayer", false, renderer); } } layer.second->Modified(); } mitk::RenderingManager::GetInstance()->RequestUpdate(renderer->GetRenderWindow()); } } } diff --git a/Modules/RenderWindowManager/src/mitkRenderWindowLayerUtilities.cpp b/Modules/RenderWindowManager/src/mitkRenderWindowLayerUtilities.cpp index b3fd2e8a12..fd34b0de43 100644 --- a/Modules/RenderWindowManager/src/mitkRenderWindowLayerUtilities.cpp +++ b/Modules/RenderWindowManager/src/mitkRenderWindowLayerUtilities.cpp @@ -1,68 +1,92 @@ /*=================================================================== 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. ===================================================================*/ // render window manager module #include "mitkRenderWindowLayerUtilities.h" // mitk core #include #include mitk::RenderWindowLayerUtilities::LayerStack mitk::RenderWindowLayerUtilities::GetLayerStack(const DataStorage* dataStorage, const BaseRenderer* renderer, bool withBaseNode) { LayerStack stackedLayers; if (nullptr == dataStorage || nullptr == renderer) { // no nodes to stack or no renderer selected return stackedLayers; } int layer = -1; NodePredicateAnd::Pointer combinedNodePredicate = GetRenderWindowPredicate(renderer); DataStorage::SetOfObjects::ConstPointer filteredDataNodes = dataStorage->GetSubset(combinedNodePredicate); for (DataStorage::SetOfObjects::ConstIterator it = filteredDataNodes->Begin(); it != filteredDataNodes->End(); ++it) { DataNode::Pointer dataNode = it->Value(); if (dataNode.IsNull()) { continue; } bool layerFound = dataNode->GetIntProperty("layer", layer, renderer); if (layerFound) { if (BASE_LAYER_INDEX != layer|| withBaseNode) { // data node is not on the base layer or the base layer should be included anyway stackedLayers.insert(std::make_pair(layer, dataNode)); } } } return stackedLayers; } mitk::NodePredicateAnd::Pointer mitk::RenderWindowLayerUtilities::GetRenderWindowPredicate(const BaseRenderer* renderer) { NodePredicateAnd::Pointer renderWindowPredicate = NodePredicateAnd::New(); NodePredicateProperty::Pointer helperObject = NodePredicateProperty::New("helper object", BoolProperty::New(true)); NodePredicateProperty::Pointer fixedLayer = NodePredicateProperty::New("fixedLayer", BoolProperty::New(true), renderer); renderWindowPredicate->AddPredicate(NodePredicateNot::New(helperObject)); renderWindowPredicate->AddPredicate(fixedLayer); return renderWindowPredicate; } + +void mitk::RenderWindowLayerUtilities::SetRenderWindowProperties(mitk::DataNode* dataNode, const BaseRenderer* renderer) +{ + dataNode->SetBoolProperty("fixedLayer", true, renderer); + // use visibility of existing renderer or common renderer + // common renderer is used if renderer-specific property does not exist + bool visible = false; + bool visibilityProperty = dataNode->GetVisibility(visible, renderer); + if (true == visibilityProperty) + { + // found a visibility property + dataNode->SetVisibility(visible, renderer); + } + + // use layer of existing renderer or common renderer + // common renderer is used if renderer-specific property does not exist + int layer = -1; + bool layerProperty = dataNode->GetIntProperty("layer", layer, renderer); + if (true == layerProperty) + { + // found a layer property + dataNode->SetIntProperty("layer", layer, renderer); + } +} diff --git a/Modules/RenderWindowManagerUI/files.cmake b/Modules/RenderWindowManagerUI/files.cmake index 1637b1a861..bb0d58e96c 100644 --- a/Modules/RenderWindowManagerUI/files.cmake +++ b/Modules/RenderWindowManagerUI/files.cmake @@ -1,18 +1,21 @@ set(H_FILES include/QmitkRenderWindowDataStorageInspector.h include/QmitkRenderWindowDataStorageListModel.h + include/QmitkRenderWindowDataStorageTreeModel.h ) set(CPP_FILES QmitkRenderWindowDataStorageInspector.cpp QmitkRenderWindowDataStorageListModel.cpp + QmitkRenderWindowDataStorageTreeModel.cpp ) set(MOC_H_FILES include/QmitkRenderWindowDataStorageInspector.h include/QmitkRenderWindowDataStorageListModel.h + include/QmitkRenderWindowDataStorageTreeModel.h ) set(UI_FILES src/QmitkRenderWindowDataStorageInspector.ui ) diff --git a/Modules/RenderWindowManagerUI/include/QmitkRenderWindowDataStorageInspector.h b/Modules/RenderWindowManagerUI/include/QmitkRenderWindowDataStorageInspector.h index d0078bb2dc..03e54c8a8e 100644 --- a/Modules/RenderWindowManagerUI/include/QmitkRenderWindowDataStorageInspector.h +++ b/Modules/RenderWindowManagerUI/include/QmitkRenderWindowDataStorageInspector.h @@ -1,94 +1,96 @@ /*=================================================================== 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 QMITKRENDERWINDOWDATASTORAGEINSPECTOR_H -#define QMITKRENDERWINDOWDATASTORAGEWINSPECTOR_H +#define QMITKRENDERWINDOWDATASTORAGEINSPECTOR_H // render window manager UI module #include "MitkRenderWindowManagerUIExports.h" #include "ui_QmitkRenderWindowDataStorageInspector.h" // render window manager module #include #include -#include +#include // qt widgets module #include /** * The 'QmitkRenderWindowDataStorageInspector' offers a GUI to manipulate the base renderer / render windows of the MITK workbench. * * In order to use this widget, a (e.g.) plugin has to set the controlled renderer, which will be forwarded to * a render window view direction controller. */ class MITKRENDERWINDOWMANAGERUI_EXPORT QmitkRenderWindowDataStorageInspector : public QmitkAbstractDataStorageInspector { Q_OBJECT public: QmitkRenderWindowDataStorageInspector(QWidget* parent = nullptr); // override from 'QmitkAbstractDataStorageInspector' /** * @brief See 'QmitkAbstractDataStorageInspector' */ virtual QAbstractItemView* GetView() override; /** * @brief See 'QmitkAbstractDataStorageInspector' */ virtual const QAbstractItemView* GetView() const override; /** * @brief See 'QmitkAbstractDataStorageInspector' */ virtual void SetSelectionMode(SelectionMode mode) override; /** * @brief See 'QmitkAbstractDataStorageInspector' */ virtual SelectionMode GetSelectionMode() const override; /** * @brief Set the controlled base renderer. */ void SetControlledRenderer(mitk::RenderWindowLayerUtilities::RendererVector controlledRenderer); /** * @brief Set the currently selected render window. * * @param renderWindowId the text inside the combo box */ void SetActiveRenderWindow(const QString& renderWindowId); private Q_SLOTS: + void ModelRowsInserted(const QModelIndex& parent, int start, int end); + void SetAsBaseLayer(); void ResetRenderer(); void ChangeViewDirection(const QString& viewDirection); private: virtual void Initialize() override; void SetUpConnections(); Ui::QmitkRenderWindowDataStorageInspector m_Controls; - std::unique_ptr m_StorageModel; + std::unique_ptr m_StorageModel; std::unique_ptr m_RenderWindowLayerController; std::unique_ptr m_RenderWindowViewDirectionController; }; #endif // QMITKRENDERWINDOWDATASTORAGEINSPECTOR_H diff --git a/Modules/RenderWindowManagerUI/include/QmitkRenderWindowDataStorageTreeModel.h b/Modules/RenderWindowManagerUI/include/QmitkRenderWindowDataStorageTreeModel.h new file mode 100644 index 0000000000..44a37fbf55 --- /dev/null +++ b/Modules/RenderWindowManagerUI/include/QmitkRenderWindowDataStorageTreeModel.h @@ -0,0 +1,136 @@ +/*=================================================================== + +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 QMITKDATASTORAGERENDERWINDOWTREEMODEL_H +#define QMITKDATASTORAGERENDERWINDOWTREEMODEL_H + +// render window manager UI model +#include "MitkRenderWindowManagerUIExports.h" + +// render window manager module +#include "mitkRenderWindowLayerController.h" +#include "mitkRenderWindowLayerUtilities.h" + +//mitk core +#include + +// qt widgets module +#include +#include + +/* +* @brief The 'QmitkRenderWindowDataStorageTreeModel' is a tree model derived from the 'QmitkAbstractDataStorageModel'. +*/ +class MITKRENDERWINDOWMANAGERUI_EXPORT QmitkRenderWindowDataStorageTreeModel : public QmitkAbstractDataStorageModel +{ + Q_OBJECT + +public: + + QmitkRenderWindowDataStorageTreeModel(QObject* parent = nullptr); + + // override from 'QmitkAbstractDataStorageModel' + /** + * @brief See 'QmitkAbstractDataStorageModel' + */ + void DataStorageChanged() override; + /** + * @brief See 'QmitkAbstractDataStorageModel' + */ + void NodePredicateChanged() override; + /** + * @brief See 'QmitkAbstractDataStorageModel' + */ + void NodeAdded(const mitk::DataNode* node) override; + /** + * @brief See 'QmitkAbstractDataStorageModel' + */ + void NodeChanged(const mitk::DataNode* node) override; + /** + * @brief See 'QmitkAbstractDataStorageModel' + */ + void NodeRemoved(const mitk::DataNode* node) override; + + // override from 'QAbstractItemModel' + QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex& parent) const override; + + int rowCount(const QModelIndex& parent = QModelIndex()) const override; + int columnCount(const QModelIndex& parent = QModelIndex()) const override; + + QVariant data(const QModelIndex& index, int role) const override; + bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override; + + Qt::ItemFlags flags(const QModelIndex& index) const override; + + Qt::DropActions supportedDropActions() const override; + Qt::DropActions supportedDragActions() const override; + QStringList mimeTypes() const override; + QMimeData* mimeData(const QModelIndexList& indexes) const override; + + bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) override; + + void SetControlledRenderer(mitk::RenderWindowLayerUtilities::RendererVector controlledRenderer); + + void SetCurrentRenderer(mitk::BaseRenderer* baseRenderer); + mitk::BaseRenderer* GetCurrentRenderer() const; + +private: + + void ResetTree(); + void UpdateModelData(); + /** + * @brief Adjust the layer property according to the current tree. + * The function will set the "layer" property of each underlying data node so that it fits the + * the actual hierarchy represented by the current tree. + */ + void AdjustLayerProperty(); + /** + * @brief Fill a vector of tree items in a depth-first order (child-first). + */ + void TreeToVector(QmitkDataStorageTreeModelInternalItem* parent, std::vector& treeAsVector) const; + /** + * @brief Add the given data node to the tree of the given renderer. + * The given renderer specifies the "layer"-property that is used for adding the new tree item + * to the tree. The "layer"-property may be different for each renderer resulting in a + * different tree for each renderer. + * + * @param dataNode The data node that should be added. + * @param renderer The base renderer to which the data node should be added. + */ + void AddNodeInternal(const mitk::DataNode* dataNode, const mitk::BaseRenderer* renderer); + /** + * @brief Remove the tree item that contains the given data node. Removing an item may + * leave the child items of the removed item without a parent. In this case + * the children have to be moved inside the tree so the tree has to be rebuild + * according to the current status of the data storage. + * + * @param dataNode The data node that should be removed. + */ + void RemoveNodeInternal(const mitk::DataNode* dataNode); + + mitk::DataNode* GetParentNode(const mitk::DataNode* node) const; + QmitkDataStorageTreeModelInternalItem* GetItemByIndex(const QModelIndex& index) const; + QModelIndex GetIndexByItem(QmitkDataStorageTreeModelInternalItem* item) const; + + std::unique_ptr m_RenderWindowLayerController; + mitk::RenderWindowLayerUtilities::RendererVector m_ControlledRenderer; + QmitkDataStorageTreeModelInternalItem* m_Root; + mitk::WeakPointer m_BaseRenderer; + +}; + +#endif // QMITKDATASTORAGERENDERWINDOWTREEMODEL_H diff --git a/Modules/RenderWindowManagerUI/src/QmitkRenderWindowDataStorageInspector.cpp b/Modules/RenderWindowManagerUI/src/QmitkRenderWindowDataStorageInspector.cpp index 018d3441f0..152497dc09 100644 --- a/Modules/RenderWindowManagerUI/src/QmitkRenderWindowDataStorageInspector.cpp +++ b/Modules/RenderWindowManagerUI/src/QmitkRenderWindowDataStorageInspector.cpp @@ -1,165 +1,173 @@ /*=================================================================== 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. ===================================================================*/ // render window manager UI module #include "QmitkRenderWindowDataStorageInspector.h" #include "QmitkCustomVariants.h" // mitk core #include // qt #include QmitkRenderWindowDataStorageInspector::QmitkRenderWindowDataStorageInspector(QWidget* parent /*=nullptr*/) : QmitkAbstractDataStorageInspector(parent) { m_Controls.setupUi(this); // initialize the render window layer controller and the render window view direction controller m_RenderWindowLayerController = std::make_unique(); m_RenderWindowViewDirectionController = std::make_unique(); - m_StorageModel = std::make_unique(this); - - m_Controls.renderWindowListView->setModel(m_StorageModel.get()); - m_Controls.renderWindowListView->setEditTriggers(QAbstractItemView::NoEditTriggers); - m_Controls.renderWindowListView->setSelectionBehavior(QAbstractItemView::SelectRows); - m_Controls.renderWindowListView->setSelectionMode(QAbstractItemView::ExtendedSelection); - m_Controls.renderWindowListView->setAlternatingRowColors(true); - m_Controls.renderWindowListView->setDragEnabled(true); - m_Controls.renderWindowListView->setDropIndicatorShown(true); - m_Controls.renderWindowListView->setAcceptDrops(true); - m_Controls.renderWindowListView->setContextMenuPolicy(Qt::CustomContextMenu); + m_StorageModel = std::make_unique(this); + + m_Controls.renderWindowTreeView->setModel(m_StorageModel.get()); + m_Controls.renderWindowTreeView->setHeaderHidden(true); + m_Controls.renderWindowTreeView->setEditTriggers(QAbstractItemView::NoEditTriggers); + m_Controls.renderWindowTreeView->setSelectionBehavior(QAbstractItemView::SelectRows); + m_Controls.renderWindowTreeView->setSelectionMode(QAbstractItemView::ExtendedSelection); + m_Controls.renderWindowTreeView->setAlternatingRowColors(true); + m_Controls.renderWindowTreeView->setDragEnabled(true); + m_Controls.renderWindowTreeView->setDropIndicatorShown(true); + m_Controls.renderWindowTreeView->setAcceptDrops(true); + m_Controls.renderWindowTreeView->setContextMenuPolicy(Qt::CustomContextMenu); SetUpConnections(); } QAbstractItemView* QmitkRenderWindowDataStorageInspector::GetView() { - return m_Controls.renderWindowListView; + return m_Controls.renderWindowTreeView; } const QAbstractItemView* QmitkRenderWindowDataStorageInspector::GetView() const { - return m_Controls.renderWindowListView; + return m_Controls.renderWindowTreeView; } void QmitkRenderWindowDataStorageInspector::SetSelectionMode(SelectionMode mode) { - m_Controls.renderWindowListView->setSelectionMode(mode); + m_Controls.renderWindowTreeView->setSelectionMode(mode); } QmitkRenderWindowDataStorageInspector::SelectionMode QmitkRenderWindowDataStorageInspector::GetSelectionMode() const { - return m_Controls.renderWindowListView->selectionMode(); + return m_Controls.renderWindowTreeView->selectionMode(); } void QmitkRenderWindowDataStorageInspector::Initialize() { if (m_DataStorage.IsExpired()) { return; } auto dataStorage = m_DataStorage.Lock(); m_StorageModel->SetDataStorage(dataStorage); m_StorageModel->SetNodePredicate(m_NodePredicate); m_RenderWindowLayerController->SetDataStorage(dataStorage); m_RenderWindowViewDirectionController->SetDataStorage(dataStorage); - m_Connector->SetView(m_Controls.renderWindowListView); + m_Connector->SetView(m_Controls.renderWindowTreeView); } void QmitkRenderWindowDataStorageInspector::SetUpConnections() { + connect(m_StorageModel.get(), &QAbstractItemModel::rowsInserted, this, &QmitkRenderWindowDataStorageInspector::ModelRowsInserted); + connect(m_Controls.pushButtonSetAsBaseLayer, &QPushButton::clicked, this, &QmitkRenderWindowDataStorageInspector::SetAsBaseLayer); connect(m_Controls.pushButtonResetRenderer, &QPushButton::clicked, this, &QmitkRenderWindowDataStorageInspector::ResetRenderer); QSignalMapper* changeViewDirectionSignalMapper = new QSignalMapper(this); changeViewDirectionSignalMapper->setMapping(m_Controls.radioButtonAxial, QString("axial")); changeViewDirectionSignalMapper->setMapping(m_Controls.radioButtonCoronal, QString("coronal")); changeViewDirectionSignalMapper->setMapping(m_Controls.radioButtonSagittal, QString("sagittal")); changeViewDirectionSignalMapper->setMapping(m_Controls.radioButton3D, QString("3D")); connect(changeViewDirectionSignalMapper, static_cast(&QSignalMapper::mapped), this, &QmitkRenderWindowDataStorageInspector::ChangeViewDirection); connect(m_Controls.radioButtonAxial, &QPushButton::clicked, changeViewDirectionSignalMapper, static_cast(&QSignalMapper::map)); connect(m_Controls.radioButtonCoronal, &QPushButton::clicked, changeViewDirectionSignalMapper, static_cast(&QSignalMapper::map)); connect(m_Controls.radioButtonSagittal, &QPushButton::clicked, changeViewDirectionSignalMapper, static_cast(&QSignalMapper::map)); connect(m_Controls.radioButton3D, &QPushButton::clicked, changeViewDirectionSignalMapper, static_cast(&QSignalMapper::map)); } void QmitkRenderWindowDataStorageInspector::SetControlledRenderer(mitk::RenderWindowLayerUtilities::RendererVector controlledRenderer) { m_StorageModel->SetControlledRenderer(controlledRenderer); m_RenderWindowLayerController->SetControlledRenderer(controlledRenderer); m_RenderWindowViewDirectionController->SetControlledRenderer(controlledRenderer); } void QmitkRenderWindowDataStorageInspector::SetActiveRenderWindow(const QString& renderWindowId) { mitk::BaseRenderer* selectedRenderer = mitk::BaseRenderer::GetByName(renderWindowId.toStdString()); if (nullptr == selectedRenderer) { return; } m_StorageModel->SetCurrentRenderer(selectedRenderer); mitk::SliceNavigationController::ViewDirection viewDirection = selectedRenderer->GetSliceNavigationController()->GetDefaultViewDirection(); switch (viewDirection) { case mitk::SliceNavigationController::Axial: m_Controls.radioButtonAxial->setChecked(true); break; case mitk::SliceNavigationController::Frontal: m_Controls.radioButtonCoronal->setChecked(true); break; case mitk::SliceNavigationController::Sagittal: m_Controls.radioButtonSagittal->setChecked(true); break; default: break; } } +void QmitkRenderWindowDataStorageInspector::ModelRowsInserted(const QModelIndex& parent, int start, int end) +{ + m_Controls.renderWindowTreeView->setExpanded(parent, true); +} + void QmitkRenderWindowDataStorageInspector::SetAsBaseLayer() { - QModelIndex selectedIndex = m_Controls.renderWindowListView->currentIndex(); + QModelIndex selectedIndex = m_Controls.renderWindowTreeView->currentIndex(); if (selectedIndex.isValid()) { QVariant qvariantDataNode = m_StorageModel->data(selectedIndex, Qt::UserRole); if (qvariantDataNode.canConvert()) { mitk::DataNode* dataNode = qvariantDataNode.value(); m_RenderWindowLayerController->SetBaseDataNode(dataNode, m_StorageModel->GetCurrentRenderer()); - m_Controls.renderWindowListView->clearSelection(); + m_Controls.renderWindowTreeView->clearSelection(); } } } void QmitkRenderWindowDataStorageInspector::ResetRenderer() { m_RenderWindowLayerController->ResetRenderer(true, m_StorageModel->GetCurrentRenderer()); - m_Controls.renderWindowListView->clearSelection(); + m_Controls.renderWindowTreeView->clearSelection(); } void QmitkRenderWindowDataStorageInspector::ChangeViewDirection(const QString& viewDirection) { m_RenderWindowViewDirectionController->SetViewDirectionOfRenderer(viewDirection.toStdString(), m_StorageModel->GetCurrentRenderer()); } diff --git a/Modules/RenderWindowManagerUI/src/QmitkRenderWindowDataStorageInspector.ui b/Modules/RenderWindowManagerUI/src/QmitkRenderWindowDataStorageInspector.ui index 64c507a340..785e20c50c 100644 --- a/Modules/RenderWindowManagerUI/src/QmitkRenderWindowDataStorageInspector.ui +++ b/Modules/RenderWindowManagerUI/src/QmitkRenderWindowDataStorageInspector.ui @@ -1,92 +1,92 @@ QmitkRenderWindowDataStorageInspector 0 0 340 246 0 0 0 0 Render window manager Render window overview - + Reset render window - + Set as base layer - + Axial true - + Coronal Sagittal 3D diff --git a/Modules/RenderWindowManagerUI/src/QmitkRenderWindowDataStorageTreeModel.cpp b/Modules/RenderWindowManagerUI/src/QmitkRenderWindowDataStorageTreeModel.cpp new file mode 100644 index 0000000000..87214ffa53 --- /dev/null +++ b/Modules/RenderWindowManagerUI/src/QmitkRenderWindowDataStorageTreeModel.cpp @@ -0,0 +1,605 @@ +/*=================================================================== + +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. + +===================================================================*/ + +// render window manager UI module +#include "QmitkRenderWindowDataStorageTreeModel.h" + +// mitk core +#include + +// qt widgets module +#include "QmitkCustomVariants.h" +#include "QmitkEnums.h" +#include "QmitkMimeTypes.h" +#include "QmitkNodeDescriptorManager.h" + +QmitkRenderWindowDataStorageTreeModel::QmitkRenderWindowDataStorageTreeModel(QObject* parent /*= nullptr*/) + : QmitkAbstractDataStorageModel(parent) + , m_Root(nullptr) +{ + m_RenderWindowLayerController = std::make_unique(); + ResetTree(); +} + +void QmitkRenderWindowDataStorageTreeModel::DataStorageChanged() +{ + m_RenderWindowLayerController->SetDataStorage(m_DataStorage.Lock()); + ResetTree(); + UpdateModelData(); +} + +void QmitkRenderWindowDataStorageTreeModel::NodePredicateChanged() +{ + ResetTree(); + UpdateModelData(); +} + +void QmitkRenderWindowDataStorageTreeModel::NodeAdded(const mitk::DataNode* node) +{ + for (const auto renderer : m_ControlledRenderer) + { + // add the node to each render window + mitk::RenderWindowLayerUtilities::SetRenderWindowProperties(const_cast(node), renderer); + } + + if (!m_BaseRenderer.IsExpired()) + { + auto baseRenderer = m_BaseRenderer.Lock(); + AddNodeInternal(node, baseRenderer); + } +} + +void QmitkRenderWindowDataStorageTreeModel::NodeChanged(const mitk::DataNode* node) +{ + auto item = m_Root->Find(node); + if (nullptr != item) + { + auto parentItem = item->GetParent(); + // as the root node should not be removed one should always have a parent item + if (nullptr == parentItem) + { + return; + } + + auto index = createIndex(item->GetIndex(), 0, item); + emit dataChanged(index, index); + } +} + +void QmitkRenderWindowDataStorageTreeModel::NodeRemoved(const mitk::DataNode* node) +{ + RemoveNodeInternal(node); +} + +QModelIndex QmitkRenderWindowDataStorageTreeModel::index(int row, int column, const QModelIndex& parent) const +{ + auto item = GetItemByIndex(parent); + if (nullptr != item) + { + item = item->GetChild(row); + } + + if (nullptr == item) + { + return QModelIndex(); + } + + return createIndex(row, column, item); +} + +QModelIndex QmitkRenderWindowDataStorageTreeModel::parent(const QModelIndex& parent) const +{ + auto item = GetItemByIndex(parent); + if (nullptr != item) + { + item = item->GetParent(); + } + + if(nullptr == item) + { + return QModelIndex(); + } + + if (item == m_Root) + { + return QModelIndex(); + } + + return createIndex(item->GetIndex(), 0, item); +} + +int QmitkRenderWindowDataStorageTreeModel::rowCount(const QModelIndex& parent /*= QModelIndex()*/) const +{ + auto item = GetItemByIndex(parent); + if (nullptr == item) + { + return 0; + } + + return item->GetChildCount(); +} + +int QmitkRenderWindowDataStorageTreeModel::columnCount(const QModelIndex&/* parent = QModelIndex()*/) const +{ + if (0 == m_Root->GetChildCount()) + { + // no items stored, no need to display columns + return 0; + } + + return 1; +} + +QVariant QmitkRenderWindowDataStorageTreeModel::data(const QModelIndex& index, int role) const +{ + if (m_BaseRenderer.IsExpired()) + { + return QVariant(); + } + + auto baseRenderer = m_BaseRenderer.Lock(); + + if (!index.isValid() || this != index.model()) + { + return QVariant(); + } + + auto item = GetItemByIndex(index); + if (nullptr == item) + { + return QVariant(); + } + + auto dataNode = item->GetDataNode(); + if (nullptr == dataNode) + { + return QVariant(); + } + + if (Qt::CheckStateRole == role) + { + bool visibility = false; + dataNode->GetVisibility(visibility, baseRenderer); + if (visibility) + { + return Qt::Checked; + } + else + { + return Qt::Unchecked; + } + } + else if (Qt::DisplayRole == role) + { + return QVariant(QString::fromStdString(dataNode->GetName())); + } + else if (Qt::ToolTipRole == role) + { + return QVariant("Name of the data node."); + } + else if (Qt::DecorationRole == role) + { + QmitkNodeDescriptor* nodeDescriptor = QmitkNodeDescriptorManager::GetInstance()->GetDescriptor(dataNode); + return nodeDescriptor->GetIcon(dataNode); + } + else if (Qt::UserRole == role || QmitkDataNodeRawPointerRole == role) + { + // user role always returns a reference to the data node, + // which can be used to modify the data node in the data storage + return QVariant::fromValue(dataNode); + } + else if (QmitkDataNodeRole == role) + { + return QVariant::fromValue(mitk::DataNode::Pointer(dataNode)); + } + + return QVariant(); +} + +bool QmitkRenderWindowDataStorageTreeModel::setData(const QModelIndex& index, const QVariant& value, int role /*= Qt::EditRole*/) +{ + if (m_BaseRenderer.IsExpired()) + { + return false; + } + + auto baseRenderer = m_BaseRenderer.Lock(); + + if (!index.isValid() || this != index.model()) + { + return false; + } + + auto item = GetItemByIndex(index); + if (nullptr == item) + { + return false; + } + + auto dataNode = item->GetDataNode(); + if (nullptr == dataNode) + { + return false; + } + + if (Qt::EditRole == role && !value.toString().isEmpty()) + { + dataNode->SetName(value.toString().toStdString().c_str()); + emit dataChanged(index, index); + return true; + } + if (Qt::CheckStateRole == role) + { + Qt::CheckState newCheckState = static_cast(value.toInt()); + bool isVisible = newCheckState; + dataNode->SetVisibility(isVisible, baseRenderer); + + emit dataChanged(index, index); + mitk::RenderingManager::GetInstance()->RequestUpdate(baseRenderer->GetRenderWindow()); + return true; + } + return false; +} + +Qt::ItemFlags QmitkRenderWindowDataStorageTreeModel::flags(const QModelIndex& index) const +{ + if (this != index.model()) + { + return Qt::NoItemFlags; + } + + if (!index.isValid()) + { + return Qt::ItemIsDropEnabled; + } + + auto item = GetItemByIndex(index); + if (nullptr == item) + { + return Qt::NoItemFlags; + } + + const auto dataNode = item->GetDataNode(); + if (m_NodePredicate.IsNull() || m_NodePredicate->CheckNode(dataNode)) + { + return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled; + } + + return Qt::NoItemFlags; +} + +Qt::DropActions QmitkRenderWindowDataStorageTreeModel::supportedDropActions() const +{ + return Qt::CopyAction | Qt::MoveAction; +} + +Qt::DropActions QmitkRenderWindowDataStorageTreeModel::supportedDragActions() const +{ + return Qt::CopyAction | Qt::MoveAction; +} + +QStringList QmitkRenderWindowDataStorageTreeModel::mimeTypes() const +{ + QStringList types = QAbstractItemModel::mimeTypes(); + types << QmitkMimeTypes::DataNodePtrs; + return types; +} + +QMimeData* QmitkRenderWindowDataStorageTreeModel::mimeData(const QModelIndexList& indexes) const +{ + QMimeData* mimeData = new QMimeData(); + QByteArray encodedData; + + QDataStream stream(&encodedData, QIODevice::WriteOnly); + + for (const auto& index : indexes) + { + if (index.isValid()) + { + auto dataNode = data(index, QmitkDataNodeRawPointerRole).value(); + stream << reinterpret_cast(dataNode); + } + } + + mimeData->setData(QmitkMimeTypes::DataNodePtrs, encodedData); + return mimeData; +} + +bool QmitkRenderWindowDataStorageTreeModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int /*row*/, int /*column*/, const QModelIndex& parent) +{ + if (m_BaseRenderer.IsExpired()) + { + return false; + } + + auto baseRenderer = m_BaseRenderer.Lock(); + + if (action == Qt::IgnoreAction) + { + return true; + } + + if (!data->hasFormat(QmitkMimeTypes::DataNodePtrs)) + { + return false; + } + + if (!parent.isValid()) + { + return false; + } + + int layer = -1; + auto dataNode = this->data(parent, QmitkDataNodeRawPointerRole).value(); + if (nullptr != dataNode) + { + dataNode->GetIntProperty("layer", layer, baseRenderer); + } + + auto dataNodeList = QmitkMimeTypes::ToDataNodePtrList(data); + for (const auto& dataNode : dataNodeList) + { + m_RenderWindowLayerController->MoveNodeToPosition(dataNode, layer, baseRenderer); + } + + ResetTree(); + UpdateModelData(); + AdjustLayerProperty(); + return true; +} + +void QmitkRenderWindowDataStorageTreeModel::SetControlledRenderer(mitk::RenderWindowLayerUtilities::RendererVector controlledRenderer) +{ + m_RenderWindowLayerController->SetControlledRenderer(controlledRenderer); + m_ControlledRenderer = controlledRenderer; + + ResetTree(); + if (m_DataStorage.IsExpired()) + { + return; + } + + auto dataStorage = m_DataStorage.Lock(); + + for (const auto& renderer : controlledRenderer) + { + if (nullptr == renderer) + { + continue; + } + + auto allDataNodes = dataStorage->GetAll(); + for (const auto& dataNode : *allDataNodes) + { + // add the node to each render window + mitk::RenderWindowLayerUtilities::SetRenderWindowProperties(dataNode, renderer); + } + } +} + +void QmitkRenderWindowDataStorageTreeModel::SetCurrentRenderer(mitk::BaseRenderer* baseRenderer) +{ + if (m_BaseRenderer == baseRenderer) + { + return; + } + + // base renderer changed + // reset tree to build a new renderer-specific item hierarchy + m_BaseRenderer = baseRenderer; + ResetTree(); + UpdateModelData(); +} + +mitk::BaseRenderer* QmitkRenderWindowDataStorageTreeModel::GetCurrentRenderer() const +{ + if (m_BaseRenderer.IsExpired()) + { + return nullptr; + } + + return m_BaseRenderer.Lock().GetPointer(); +} + +void QmitkRenderWindowDataStorageTreeModel::ResetTree() +{ + beginResetModel(); + if (nullptr != m_Root) + { + m_Root->Delete(); + } + endResetModel(); + + mitk::DataNode::Pointer rootDataNode = mitk::DataNode::New(); + rootDataNode->SetName("Data Storage"); + m_Root = new QmitkDataStorageTreeModelInternalItem(rootDataNode); +} + +void QmitkRenderWindowDataStorageTreeModel::UpdateModelData() +{ + if (!m_DataStorage.IsExpired()) + { + auto dataStorage = m_DataStorage.Lock(); + if (!m_BaseRenderer.IsExpired()) + { + auto baseRenderer = m_BaseRenderer.Lock(); + + mitk::NodePredicateAnd::Pointer combinedNodePredicate = mitk::RenderWindowLayerUtilities::GetRenderWindowPredicate(baseRenderer); + auto filteredDataNodes = dataStorage->GetSubset(combinedNodePredicate); + for (const auto& dataNode : *filteredDataNodes) + { + AddNodeInternal(dataNode, baseRenderer); + } + } + } +} + +void QmitkRenderWindowDataStorageTreeModel::AdjustLayerProperty() +{ + if (m_BaseRenderer.IsExpired()) + { + return; + } + + auto baseRenderer = m_BaseRenderer.Lock(); + + std::vector treeAsVector; + TreeToVector(m_Root, treeAsVector); + + int i = treeAsVector.size() - 1; + for (auto it = treeAsVector.begin(); it != treeAsVector.end(); ++it) + { + auto dataNode = (*it)->GetDataNode(); + dataNode->SetIntProperty("layer", i, baseRenderer); + --i; + } +} + +void QmitkRenderWindowDataStorageTreeModel::TreeToVector(QmitkDataStorageTreeModelInternalItem* parent, std::vector& treeAsVector) const +{ + QmitkDataStorageTreeModelInternalItem* item; + for (int i = 0; i < parent->GetChildCount(); ++i) + { + item = parent->GetChild(i); + TreeToVector(item, treeAsVector); + treeAsVector.push_back(item); + } +} + +void QmitkRenderWindowDataStorageTreeModel::AddNodeInternal(const mitk::DataNode* dataNode, const mitk::BaseRenderer* renderer) +{ + if (nullptr == dataNode + || m_DataStorage.IsExpired() + || nullptr != m_Root->Find(dataNode)) + { + return; + } + + // find out if we have a root node + auto parentItem = m_Root; + QModelIndex index; + auto parentDataNode = GetParentNode(dataNode); + + if (nullptr != parentDataNode) // no top level data node + { + parentItem = m_Root->Find(parentDataNode); + if (nullptr == parentItem) + { + // parent node not contained in the tree; add it + NodeAdded(parentDataNode); + parentItem = m_Root->Find(parentDataNode); + if (nullptr == parentItem) + { + // could not find and add the parent tree; abort + return; + } + } + + // get the index of this parent with the help of the grand parent + index = createIndex(parentItem->GetIndex(), 0, parentItem); + } + + int firstRowWithASiblingBelow = 0; + int nodeLayer = -1; + dataNode->GetIntProperty("layer", nodeLayer, renderer); + for (const auto& siblingItem : parentItem->GetChildren()) + { + int siblingLayer = -1; + auto siblingNode = siblingItem->GetDataNode(); + if (nullptr != siblingNode) + { + siblingNode->GetIntProperty("layer", siblingLayer, renderer); + } + if (nodeLayer > siblingLayer) + { + break; + } + ++firstRowWithASiblingBelow; + } + + beginInsertRows(index, firstRowWithASiblingBelow, firstRowWithASiblingBelow); + auto newNode = new QmitkDataStorageTreeModelInternalItem(const_cast(dataNode)); + parentItem->InsertChild(newNode, firstRowWithASiblingBelow); + endInsertRows(); +} + +void QmitkRenderWindowDataStorageTreeModel::RemoveNodeInternal(const mitk::DataNode* dataNode) +{ + if (nullptr == dataNode + || nullptr == m_Root) + { + return; + } + + auto item = m_Root->Find(dataNode); + if (nullptr == item) + { + return; + } + + auto parentItem = item->GetParent(); + auto parentIndex = GetIndexByItem(parentItem); + + auto children = item->GetChildren(); + beginRemoveRows(parentIndex, item->GetIndex(), item->GetIndex()); + parentItem->RemoveChild(item); + delete item; + endRemoveRows(); + + if (!children.empty()) + { + // rebuild tree because children could not be at the top level + ResetTree(); + UpdateModelData(); + } +} + +mitk::DataNode* QmitkRenderWindowDataStorageTreeModel::GetParentNode(const mitk::DataNode* node) const +{ + mitk::DataNode* dataNode = nullptr; + if (m_DataStorage.IsExpired()) + { + return dataNode; + } + + auto sources = m_DataStorage.Lock()->GetSources(node); + if (sources->empty()) + { + return dataNode; + } + + return sources->front(); +} + +QmitkDataStorageTreeModelInternalItem* QmitkRenderWindowDataStorageTreeModel::GetItemByIndex(const QModelIndex& index) const +{ + if (index.isValid()) + { + return static_cast(index.internalPointer()); + } + + return m_Root; +} + +QModelIndex QmitkRenderWindowDataStorageTreeModel::GetIndexByItem(QmitkDataStorageTreeModelInternalItem* item) const +{ + if (item == m_Root) + { + return QModelIndex(); + } + + return createIndex(item->GetIndex(), 0, item); +} diff --git a/Modules/SemanticRelationsUI/src/QmitkLesionTreeModel.cpp b/Modules/SemanticRelationsUI/src/QmitkLesionTreeModel.cpp index 261e378da1..44adbf74ab 100644 --- a/Modules/SemanticRelationsUI/src/QmitkLesionTreeModel.cpp +++ b/Modules/SemanticRelationsUI/src/QmitkLesionTreeModel.cpp @@ -1,297 +1,302 @@ /*=================================================================== 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 "QmitkLesionTreeModel.h" // semantic relations module #include #include #include #include #include #include // qt #include QmitkLesionTreeModel::QmitkLesionTreeModel(QObject* parent/* = nullptr*/) : QmitkAbstractSemanticRelationsStorageModel(parent) , m_LastSegmentation(nullptr) , m_RootItem(std::make_shared(mitk::LesionData())) { // nothing here } ////////////////////////////////////////////////////////////////////////// // overridden virtual functions from QAbstractItemModel ////////////////////////////////////////////////////////////////////////// QModelIndex QmitkLesionTreeModel::index(int row, int column, const QModelIndex& itemIndex) const { if (!hasIndex(row, column, itemIndex)) { return QModelIndex(); } auto childItem = GetItemByIndex(itemIndex)->GetChildInRow(row); if (nullptr == childItem) { return QModelIndex(); } return createIndex(row, column, childItem.get()); } QModelIndex QmitkLesionTreeModel::parent(const QModelIndex& itemIndex) const { if (!itemIndex.isValid()) { return QModelIndex(); } auto parentItem = GetItemByIndex(itemIndex)->GetParent(); if (parentItem.expired()) { return QModelIndex(); } auto sharedParent = parentItem.lock(); if (sharedParent == m_RootItem) { return QModelIndex(); } return createIndex(sharedParent->GetRow(), 0, sharedParent.get()); } int QmitkLesionTreeModel::rowCount(const QModelIndex& itemIndex/* = QModelIndex()*/) const { return GetItemByIndex(itemIndex)->ChildCount(); } int QmitkLesionTreeModel::columnCount(const QModelIndex&/* itemIndex = QModelIndex() */) const { if (0 == m_RootItem->ChildCount()) { // no lesion items stored, no need to display columns return 0; } return m_ControlPoints.size() + 1; } QVariant QmitkLesionTreeModel::data(const QModelIndex& index, int role) const { if (!index.isValid()) { return QVariant(); } if (index.column() < 0 || index.column() > static_cast(m_ControlPoints.size())) { return QVariant(); } QmitkLesionTreeItem* currentItem = GetItemByIndex(index); if (Qt::DisplayRole == role) { if (currentItem->GetParent().expired()) { return QVariant(); } auto parentItem = currentItem->GetParent().lock(); // parent exists and is the root item -> 1. item of a lesion entry if (m_RootItem == parentItem) { // display role fills the first columns with the lesion UID / name if (0 == index.column()) { std::string itemString = currentItem->GetData().GetLesionName(); if (itemString.empty()) { itemString = currentItem->GetData().GetLesionUID(); } return QString::fromStdString(itemString); } else { // display role fills other columns with the lesion presence info const auto lesionPresence = currentItem->GetData().GetLesionPresence(); if (index.column() - 1 > static_cast(lesionPresence.size())) { return "N/A"; } - return QVariant(lesionPresence.at(index.column() - 1)); + if (lesionPresence.at(index.column() - 1)) + { + return QString::fromStdString("present"); + } + + return QString::fromStdString("not present"); } } } if (Qt::BackgroundColorRole == role) { auto it = m_DataNodePresence.find(currentItem->GetData().GetLesion().UID); if (it != m_DataNodePresence.end()) { return it->second ? QVariant(QColor(Qt::darkGreen)) : QVariant(QColor(Qt::transparent)); } return QVariant(QColor(Qt::transparent)); } if (Qt::UserRole == role) { return QVariant::fromValue(currentItem); } return QVariant(); } QVariant QmitkLesionTreeModel::headerData(int section, Qt::Orientation orientation, int role) const { if (0 == m_RootItem->ChildCount()) { // no lesion items stored, no need to display the header return QVariant(); } if (Qt::Horizontal == orientation && Qt::DisplayRole == role) { if (0 == section) { return QVariant("Lesion"); } if (static_cast(m_ControlPoints.size()) >= section) { mitk::SemanticTypes::ControlPoint currentControlPoint = m_ControlPoints.at(section-1); return QVariant(QString::fromStdString(currentControlPoint.ToString())); } } return QVariant(); } const mitk::DataNode* QmitkLesionTreeModel::GetLastSegmentation() const { return m_LastSegmentation; } void QmitkLesionTreeModel::NodeAdded(const mitk::DataNode* dataNode) { if (mitk::NodePredicates::GetSegmentationPredicate()->CheckNode(dataNode)) { m_LastSegmentation = dataNode; } } void QmitkLesionTreeModel::SetData() { m_RootItem = std::make_shared(mitk::LesionData()); // get all control points of current case m_ControlPoints = mitk::RelationStorage::GetAllControlPointsOfCase(m_CaseID); // sort the vector of control points for the timeline std::sort(m_ControlPoints.begin(), m_ControlPoints.end()); SetLesionData(); SetSelectedDataNodesPresence(); } void QmitkLesionTreeModel::SetLesionData() { m_CurrentLesions = mitk::RelationStorage::GetAllLesionsOfCase(m_CaseID); for (auto& lesion : m_CurrentLesions) { AddLesion(lesion); } } void QmitkLesionTreeModel::AddLesion(const mitk::SemanticTypes::Lesion& lesion) { if (m_DataStorage.IsExpired()) { return; } auto dataStorage = m_DataStorage.Lock(); // create new lesion tree item data and modify it according to the control point data mitk::LesionData lesionData(lesion); mitk::ComputeLesionPresence(lesionData, m_CaseID); // add the top-level lesion item to the root item std::shared_ptr newLesionTreeItem = std::make_shared(lesionData); m_RootItem->AddChild(newLesionTreeItem); } void QmitkLesionTreeModel::SetSelectedDataNodesPresence() { m_DataNodePresence.clear(); for (const auto& dataNode : m_SelectedDataNodes) { if (!mitk::SemanticRelationsInference::InstanceExists(dataNode)) { continue; } for (const auto& lesion : m_CurrentLesions) { if (!mitk::SemanticRelationsInference::InstanceExists(m_CaseID, lesion)) { continue; } try { // set the lesion presence for the current node bool dataNodePresence = mitk::SemanticRelationsInference::IsLesionPresent(lesion, dataNode); SetDataNodePresenceOfLesion(&lesion, dataNodePresence); } catch (const mitk::SemanticRelationException&) { continue; } } } } void QmitkLesionTreeModel::SetDataNodePresenceOfLesion(const mitk::SemanticTypes::Lesion* lesion, bool dataNodePresence) { std::map::iterator iter = m_DataNodePresence.find(lesion->UID); if (iter != m_DataNodePresence.end()) { // key already existing, overwrite already stored bool value iter->second = dataNodePresence; } else { m_DataNodePresence.insert(std::make_pair(lesion->UID, dataNodePresence)); } } QmitkLesionTreeItem* QmitkLesionTreeModel::GetItemByIndex(const QModelIndex& index) const { if (index.isValid()) { auto item = static_cast(index.internalPointer()); if (nullptr != item) { return item; } } return m_RootItem.get(); } diff --git a/Modules/SemanticRelationsUI/src/QmitkPatientTableModel.cpp b/Modules/SemanticRelationsUI/src/QmitkPatientTableModel.cpp index 93a04931e8..04d9e0fc44 100644 --- a/Modules/SemanticRelationsUI/src/QmitkPatientTableModel.cpp +++ b/Modules/SemanticRelationsUI/src/QmitkPatientTableModel.cpp @@ -1,350 +1,350 @@ /*=================================================================== 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 "QmitkPatientTableModel.h" #include "QmitkPatientTableHeaderView.h" #include "QmitkSemanticRelationsUIHelper.h" // semantic relations module #include #include #include #include #include #include #include // qt #include // c++ #include #include QmitkPatientTableModel::QmitkPatientTableModel(QObject* parent /*= nullptr*/) : QmitkAbstractSemanticRelationsStorageModel(parent) , m_SelectedNodeType("Image") { m_HeaderModel = new QStandardItemModel(this); } QmitkPatientTableModel::~QmitkPatientTableModel() { // nothing here } QModelIndex QmitkPatientTableModel::index(int row, int column, const QModelIndex& parent/* = QModelIndex()*/) const { if (hasIndex(row, column, parent)) { return createIndex(row, column); } return QModelIndex(); } QModelIndex QmitkPatientTableModel::parent(const QModelIndex& /*child*/) const { return QModelIndex(); } int QmitkPatientTableModel::rowCount(const QModelIndex& parent/* = QModelIndex()*/) const { if (parent.isValid()) { return 0; } return m_InformationTypes.size(); } int QmitkPatientTableModel::columnCount(const QModelIndex& parent/* = QModelIndex()*/) const { if (parent.isValid()) { return 0; } return m_ExaminationPeriods.size(); } QVariant QmitkPatientTableModel::data(const QModelIndex& index, int role/* = Qt::DisplayRole*/) const { // special role for returning the horizontal header if (QmitkPatientTableHeaderView::HorizontalHeaderDataRole == role) { return QVariant::fromValue(m_HeaderModel); } if (!index.isValid()) { return QVariant(); } if (index.row() < 0 || index.row() >= static_cast(m_InformationTypes.size()) || index.column() < 0 || index.column() >= static_cast(m_ExaminationPeriods.size())) { return QVariant(); } mitk::DataNode* dataNode = GetCurrentDataNode(index); - if (nullptr == dataNode) - { - return QVariant(); - } if (Qt::DecorationRole == role) { auto it = m_PixmapMap.find(dataNode); if (it != m_PixmapMap.end()) { return QVariant(it->second); } - } - if (QmitkDataNodeRole == role) - { - return QVariant::fromValue(mitk::DataNode::Pointer(dataNode)); - } - - if (QmitkDataNodeRawPointerRole == role) - { - return QVariant::fromValue(dataNode); + auto emptyPixmap = QPixmap(120, 120); + emptyPixmap.fill(Qt::transparent); + return emptyPixmap; } if (Qt::BackgroundColorRole == role) { auto it = m_LesionPresence.find(dataNode); if (it != m_LesionPresence.end()) { return it->second ? QVariant(QColor(Qt::darkGreen)) : QVariant(QColor(Qt::transparent)); } return QVariant(QColor(Qt::transparent)); } + if (QmitkDataNodeRole == role) + { + return QVariant::fromValue(mitk::DataNode::Pointer(dataNode)); + } + + if (QmitkDataNodeRawPointerRole == role) + { + return QVariant::fromValue(dataNode); + } + return QVariant(); } QVariant QmitkPatientTableModel::headerData(int section, Qt::Orientation orientation, int role) const { if (Qt::Vertical == orientation && Qt::DisplayRole == role) { if (static_cast(m_InformationTypes.size()) > section) { mitk::SemanticTypes::InformationType currentInformationType = m_InformationTypes.at(section); return QVariant(QString::fromStdString(currentInformationType)); } } return QVariant(); } Qt::ItemFlags QmitkPatientTableModel::flags(const QModelIndex& index) const { Qt::ItemFlags flags; mitk::DataNode* dataNode = GetCurrentDataNode(index); if (nullptr != dataNode) { flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable; } return flags; } void QmitkPatientTableModel::SetNodeType(const std::string& nodeType) { m_SelectedNodeType = nodeType; UpdateModelData(); } void QmitkPatientTableModel::NodePredicateChanged() { UpdateModelData(); } void QmitkPatientTableModel::SetData() { // get all examination periods of current case m_ExaminationPeriods = mitk::RelationStorage::GetAllExaminationPeriodsOfCase(m_CaseID); // sort all examination periods for the timeline mitk::SortAllExaminationPeriods(m_CaseID, m_ExaminationPeriods); // rename examination periods according to their new order std::string examinationPeriodName = "Baseline"; for (int i = 0; i < m_ExaminationPeriods.size(); ++i) { auto& examinationPeriod = m_ExaminationPeriods.at(i); examinationPeriod.name = examinationPeriodName; mitk::RelationStorage::RenameExaminationPeriod(m_CaseID, examinationPeriod); examinationPeriodName = "Follow-up " + std::to_string(i); } // get all information types points of current case m_InformationTypes = mitk::RelationStorage::GetAllInformationTypesOfCase(m_CaseID); if ("Image" == m_SelectedNodeType) { m_CurrentDataNodes = m_SemanticRelationsDataStorageAccess->GetAllImagesOfCase(m_CaseID); } else if ("Segmentation" == m_SelectedNodeType) { m_CurrentDataNodes = m_SemanticRelationsDataStorageAccess->GetAllSegmentationsOfCase(m_CaseID); } SetHeaderModel(); SetPixmaps(); SetLesionPresences(); } void QmitkPatientTableModel::SetHeaderModel() { m_HeaderModel->clear(); QStandardItem* rootItem = new QStandardItem("Timeline"); QList standardItems; for (const auto& examinationPeriod : m_ExaminationPeriods) { QStandardItem* examinationPeriodItem = new QStandardItem(QString::fromStdString(examinationPeriod.name)); standardItems.push_back(examinationPeriodItem); rootItem->appendColumn(standardItems); standardItems.clear(); } m_HeaderModel->setItem(0, 0, rootItem); } void QmitkPatientTableModel::SetPixmaps() { m_PixmapMap.clear(); for (const auto& dataNode : m_CurrentDataNodes) { // set the pixmap for the current node QPixmap pixmapFromImage = QmitkSemanticRelationsUIHelper::GetPixmapFromImageNode(dataNode); SetPixmapOfNode(dataNode, &pixmapFromImage); } } void QmitkPatientTableModel::SetPixmapOfNode(const mitk::DataNode* dataNode, QPixmap* pixmapFromImage) { if (nullptr == dataNode) { return; } std::map::iterator iter = m_PixmapMap.find(dataNode); if (iter != m_PixmapMap.end()) { // key already existing if (nullptr != pixmapFromImage) { // overwrite already stored pixmap iter->second = pixmapFromImage->scaled(120, 120, Qt::IgnoreAspectRatio); } else { // remove key if no pixmap is given m_PixmapMap.erase(iter); } } else { m_PixmapMap.insert(std::make_pair(dataNode, pixmapFromImage->scaled(120, 120, Qt::IgnoreAspectRatio))); } } void QmitkPatientTableModel::SetLesionPresences() { m_LesionPresence.clear(); if (!mitk::SemanticRelationsInference::InstanceExists(m_CaseID, m_Lesion)) { return; } for (const auto& dataNode : m_CurrentDataNodes) { if (!mitk::SemanticRelationsInference::InstanceExists(dataNode)) { continue; } // set the lesion presence for the current node bool lesionPresence = mitk::SemanticRelationsInference::IsLesionPresent(m_Lesion, dataNode); SetLesionPresenceOfNode(dataNode, lesionPresence); } } void QmitkPatientTableModel::SetLesionPresenceOfNode(const mitk::DataNode* dataNode, bool lesionPresence) { std::map::iterator iter = m_LesionPresence.find(dataNode); if (iter != m_LesionPresence.end()) { // key already existing, overwrite already stored bool value iter->second = lesionPresence; } else { m_LesionPresence.insert(std::make_pair(dataNode, lesionPresence)); } } mitk::DataNode* QmitkPatientTableModel::GetCurrentDataNode(const QModelIndex& index) const { if (!index.isValid()) { return nullptr; } auto examinationPeriod = m_ExaminationPeriods.at(index.column()); auto currentInformationType = m_InformationTypes.at(index.row()); auto controlPointsOfExaminationPeriod = examinationPeriod.controlPointUIDs; for (const auto& controlPointUID : controlPointsOfExaminationPeriod) { auto currentControlPoint = mitk::GetControlPointByUID(m_CaseID, controlPointUID); try { std::vector filteredDataNodes; if ("Image" == m_SelectedNodeType) { filteredDataNodes = m_SemanticRelationsDataStorageAccess->GetAllSpecificImages(m_CaseID, currentControlPoint, currentInformationType); } else if ("Segmentation" == m_SelectedNodeType) { filteredDataNodes = m_SemanticRelationsDataStorageAccess->GetAllSpecificSegmentations(m_CaseID, currentControlPoint, currentInformationType); } if (filteredDataNodes.empty()) { // try next control point continue; } else { // found a specific image return filteredDataNodes.front(); } } catch (const mitk::SemanticRelationException&) { return nullptr; } } // could not find a specif image return nullptr; } diff --git a/Plugins/org.mitk.gui.qt.semanticrelations/CMakeLists.txt b/Plugins/org.mitk.gui.qt.semanticrelations/CMakeLists.txt index 2ffd63c2e3..970cb000dd 100644 --- a/Plugins/org.mitk.gui.qt.semanticrelations/CMakeLists.txt +++ b/Plugins/org.mitk.gui.qt.semanticrelations/CMakeLists.txt @@ -1,7 +1,7 @@ project(org_mitk_gui_qt_semanticrelations) mitk_create_plugin( EXPORT_DIRECTIVE MITK_GUI_SEMANTICRELATIONS_EXPORT EXPORTED_INCLUDE_SUFFIXES src - MODULE_DEPENDS MitkPersistence MitkSemanticRelationsUI MitkRenderWindowManager + MODULE_DEPENDS MitkPersistence MitkSemanticRelationsUI MitkRenderWindowManager MitkMultilabel MitkSegmentationUI ) 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 910b6cefaa..4a6872c152 100644 --- a/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkLesionInfoWidget.cpp +++ b/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkLesionInfoWidget.cpp @@ -1,461 +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 -// registration ontology module -//#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* propageLesionToImage = new QAction("Propagate lesion to image", this); - propageLesionToImage->setEnabled(true); - connect(propageLesionToImage, &QAction::triggered, [this, selectedLesion] { OnPropagateLesion(selectedLesion); }); - menu->addAction(propageLesionToImage); + 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; } - // 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(); - } + 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); 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::OnPropagateLesion(mitk::SemanticTypes::Lesion selectedLesion) +void QmitkLesionInfoWidget::OnCreateNewSegmentation(mitk::SemanticTypes::Lesion selectedLesion) { if (m_DataStorage.IsExpired()) { return; } auto dataStorage = m_DataStorage.Lock(); - QmitkSemanticRelationsNodeSelectionDialog* dialog = new QmitkSemanticRelationsNodeSelectionDialog(this, "Select data node to propagate the selected lesion.", ""); + 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 propagate the selected lesion to an image, please specify a valid image node."); + "In order to create a new segmentation, please specify a valid image node."); msgBox.exec(); return; } - mitk::BaseData* baseData = selectedDataNode->GetData(); - if (nullptr == baseData) + mitk::Image* selectedImage = dynamic_cast(selectedDataNode->GetData()); + if (nullptr == selectedImage) { QMessageBox msgBox(QMessageBox::Warning, - "No valid base data.", - "In order to propagate the selected lesion to an image, please specify a valid image node."); + "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 { - /* - auto allSegmentationsOfLesion = m_SemanticRelationsDataStorageAccess->GetAllSegmentationsOfLesion(m_CaseID, selectedLesion); - mitk::FindClosestSegmentationMask(); - */ + segmentation->Initialize(selectedImage); } - catch (const mitk::SemanticRelationException& e) + catch (mitk::Exception& e) { std::stringstream exceptionMessage; exceptionMessage << e; QMessageBox msgBox(QMessageBox::Warning, - "Could not propagate the selected lesion.", - "The program wasn't able to correctly propagate the selected lesion to the selected image.\n" + "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(); + } +} diff --git a/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkLesionInfoWidget.h b/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkLesionInfoWidget.h index 6864f1ca0f..f675f664fa 100644 --- a/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkLesionInfoWidget.h +++ b/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkLesionInfoWidget.h @@ -1,105 +1,107 @@ /*=================================================================== 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 QMITKLESIONINFOWIDGET_H #define QMITKLESIONINFOWIDGET_H // semantic relations plugin #include // semantic relations UI module #include // semantic relations module #include #include // mitk #include // berry #include // qt #include /* * @brief The QmitkLesionInfoWidget is a widget that shows and modifies the currently available lesion data of the semantic relations model. * * The widget provides a dialogs to add nodes from the data storage to the semantic relations model. * It provides functionality to create new lesions and link them with segmentation nodes. * * The QmitkLesionInfoWidget provides three QListWidgets, that show the lesion data and the referenced segmentation data, as * well as the connected image data, depending on the selected lesion. */ class QmitkLesionInfoWidget : public QWidget { Q_OBJECT public: static const QBrush DEFAULT_BACKGROUND_COLOR; static const QBrush SELECTED_BACKGROUND_COLOR; static const QBrush CONNECTED_BACKGROUND_COLOR; QmitkLesionInfoWidget(mitk::DataStorage* dataStorage, berry::IWorkbenchPartSite::Pointer workbenchPartSite, QWidget* parent = nullptr); void SetCaseID(const mitk::SemanticTypes::CaseID& caseID); void SetDataNodeSelection(const QList& dataNodeSelection); Q_SIGNALS: void LesionSelectionChanged(const mitk::SemanticTypes::Lesion&); private Q_SLOTS: void OnModelUpdated(); /* * @brief Generates a new, empty lesion to add to the semantic relations model for the current case ID. */ void OnAddLesionButtonClicked(); // slots for the mouse click events of tree view's selection model void OnSelectionChanged(const QModelIndex& current, const QModelIndex& previous); void OnLesionListContextMenuRequested(const QPoint&); // slots for the context menu actions of the lesion list widget void OnLinkToSegmentation(mitk::SemanticTypes::Lesion); void OnSetLesionName(mitk::SemanticTypes::Lesion); void OnSetLesionClass(mitk::SemanticTypes::Lesion); - void OnPropagateLesion(mitk::SemanticTypes::Lesion); + void OnCreateNewSegmentation(mitk::SemanticTypes::Lesion); void OnRemoveLesion(mitk::SemanticTypes::Lesion); private: void Initialize(); void SetUpConnections(); + void LinkSegmentationToLesion(const mitk::DataNode* selectedDataNode, mitk::SemanticTypes::Lesion selectedLesion); + Ui::QmitkLesionInfoWidgetControls m_Controls; QmitkLesionTreeModel* m_StorageModel; mitk::SemanticTypes::CaseID m_CaseID; mitk::WeakPointer m_DataStorage; berry::IWorkbenchPartSite::WeakPtr m_WorkbenchPartSite; std::unique_ptr m_SemanticRelationsDataStorageAccess; std::unique_ptr m_SemanticRelationsIntegration; }; #endif // QMITKLESIONINFOWIDGET_H